Skip to content

Languages and right-to-left ​

Harmony components take their language and their text direction from the page, through the standard lang and dir attributes. Your app translates its own words; the components follow the page.

Set lang and dir ​

Put both on <html>, as any multilingual site does:

html
<html lang="ar" dir="rtl"></html>

A region can override them for its subtree — <section lang="de"> — and components inside follow the nearest one.

  • dir mirrors the layout. Components use logical CSS (margin-inline-start, not margin-left), so spacing, borders and alignment flip on their own, and the parts that need more — a switch thumb, a slider fill, a drawer's slide-in, a submenu chevron — follow the inherited direction too. Harmony never sets dir for you.
  • lang picks how dates and times are written: month and weekday names, the order of day, month and year, the first day of the week, the 12- or 24-hour clock. It also drives hyphenation and the glyph shapes the browser picks for Chinese and Japanese text.

Use the platform attributes, not a data-* attribute of your own: the browser itself reads lang and dir, so assistive technology, hyphenation and native form controls follow them too.

Components that format dates, times and numbers ​

Each takes a locale attribute; without one it follows the nearest lang — on the page, a region, or the component that contains it — and falls back to the browser's language.

ComponentFormats
hy-calendarMonth and weekday names, the first day of the week, the full date it reads
hy-date-fieldThe order of day, month and year, the separators, the 12- or 24-hour clock
hy-date-pickerThrough its date field and calendar, which follow the same lang
hy-relative-time"3 days ago", and the date it shows past its threshold
hy-datagridThe default number and date columns
hy-number-inputThe number shown (grouped) and typed: 1.234,5 and 1,5 in German
hy-sliderThe value readout, tick labels and what a screen reader hears
hy-progressThe value shown and announced (45 %, ٤٥٪)
hy-meterThe percentage shown and announced, unless formatValue is set

Dates are always on the Gregorian calendar, in the language's words and digits — a page asking for another calendar (ar-SA-u-ca-islamic-umalqura) gets Gregorian dates written in Arabic. Digits follow the language: under ar-EG the calendar, the date field and number fields show ١٥, and accept digits typed in any script. The value a component holds and submits never changes — a date, a plain number.

A later change of lang — on <html> or on any element above the component — is picked up at once: switch the page's language and these components re-render in it.

Labels your app supplies ​

Every visible or announced word is yours to translate. Most components take their words from the attributes you already set (label, heading, placeholder, slot content). Some also ship an English default for text they write themselves; set these attributes in your language:

ComponentAttributes (English default)
hy-accordionexpanded-label ("{label} expanded"), collapsed-label ("{label} collapsed")
hy-alertvariant-label ("Information:", "Success:", "Warning:", "Error:"), dismiss-label ("Dismiss alert")
hy-avatarpresence-label (the presence status in English)
hy-avatar-groupoverflow-label ("{count} more")
hy-badgecount-label ("{count, plural, one {# item} other {# items}}")
hy-bottombarlabel ("Bottom navigation")
hy-breadcrumblabel ("Breadcrumb")
hy-button, hy-fabloading-text ("Loading")
hy-calendarloading-text ("Loading calendar"), previous-month-label ("Previous month"), next-month-label ("Next month")
hy-chartdefault-label ("{type} chart with {count} series"), legend-label ("Series"), tooltip-text ("{series}: {value}")
hy-checkbox-groupselect-all-label ("Select all")
hy-chipremove-label ("Remove {label}")
hy-color-pickerlabel ("Color")
hy-comboboxclear-label ("Clear selection"), empty-text ("No results"), open-options-label ("Open options"), close-options-label ("Close options"), required-label ("required")
hy-context-menulabel ("Context menu")
hy-copy-buttoncopy-label ("Copy"), success-label ("Copied"), error-label ("Could not copy")
hy-datagridempty-heading ("No data"), filter-placeholder ("Search…")
hy-dialoglabel ("Dialog"), close-label ("Close"), closed-label ("{label} dialog closed")
hy-drawerlabel ("Drawer"), close-label ("Close"), collapse-label ("Toggle sidebar"), opened-label ("{label} drawer opened"), closed-label ("{label} drawer closed")
hy-dtcg-treelabel ("Design tokens"), empty-text ("No tokens"), no-results-text ("No tokens match"), copy-label ("Copy path")
hy-empty-statequery-heading-text (No results for "{query}"), loading-label ("Loading…"), retry-label ("Retry")
hy-gradient-pickerstop-label ("Stop {index} of {count}"), stop-value-text ("{name} at {position}"), stop-caption-text ("Stop {index}")
hy-image-gallerylist-label ("Image gallery"), status-label ("Gallery"); empty-heading-text, error-heading-text, error-description-text, retry-label (hy-empty-state's English)
hy-inline-editedit-label ("Edit"), editor-label ("Edit value"), save-label ("Save"), cancel-label ("Cancel"), saving-label ("Saving")
hy-linkdisabled-label ("(disabled)"), new-window-label ("(opens in new window)")
hy-multi-comboboxclear-label ("Clear all selections"), empty-text ("No results"), chip-remove-label ("Remove {label}"), create-label ("Create “{value}”"), open-options-label ("Open options"), close-options-label ("Close options"), required-label ("required")
hy-number-inputdecrease-label ("Decrease {label}"), increase-label ("Increase {label}")
hy-paginationlabel ("Pagination")
hy-popoveropened-label ("{label} opened"), closed-label ("{label} closed"), fallback-label ("Popover"), blocked-label ("Action blocked")
hy-progressindeterminate-label ("In progress"), value-label ("{value} of {max}")
hy-ratingvalue-label ("{value} out of {count, plural, one {# star} other {# stars}}")
hy-selectclear-label ("Clear selection"), required-label ("required")
hy-sidebarlabel ("Main navigation"), collapse-label ("Collapse"), expand-label ("Expand")
hy-sliderstart-label ("Minimum"), end-label ("Maximum") in range mode
hy-spinnerlabel ("Loading")
hy-split-buttonmenu-label ("More options"), loading-text ("Loading")
hy-splitter-handlelabel ("Resize")
hy-stepperlabel ("Progress")
hy-tablabel ("Tab")
hy-tab-grouploading-label ("Loading")
hy-tableempty-text ("No data"), region-label ("Table"), named-region-label ("Table: {label}")
hy-text-inputclear-label ("Clear"), show-password-label ("Show password"), hide-password-label ("Hide password")
hy-toastvariant-label ("Information:", "Success:", "Warning:", "Error:"), dismiss-label ("Dismiss toast")
hy-topbarlabel ("Site header")

Components with many words take them in one strings object instead, set as a property (in React, the strings prop). Set only the keys you translate; the others keep their English default.

ComponentWhat strings covers
hy-carouselThe slide names and positions ("Slide {index} of {count}"), the previous and next buttons, the slide markers
hy-color-pickerThe names of the square, the hue and opacity strips, the value and format fields, the preset colors
hy-date-fieldThe segment names ("Month", "Day", "Year"…), their placeholders ("MM", "DD", "YYYY"), "Empty", the AM / PM fallback
hy-date-pickerIts date fields' strings, the start and end dates, the calendar button, "Today", "Clear", the calendar's month buttons
hy-datagridIts pagination's strings, row selection, column resizing, filter, columns, export, its no-results and error states
hy-empty-stateEach variant's default heading and description ("Nothing here yet", "No results"…)
hy-file-uploadThe browse and drop prompts, the accepted types, the progress, the remove button, the error messages
hy-markdown-editorThe toolbar buttons, the preview switch, the pane names, the image dialog
hy-pagination"Rows per page", the first, previous, next and last page buttons, the current page
hy-stepperEach step's position ("Step {index} of {total}: {label}"), its status words, "Optional"

Where a default names a placeholder ({count}, {value}), keep it in your translation: the component fills it in, and you place it where your language puts it.

A word that changes with a number is written as a plural choice, in the ICU MessageFormat shape translation tools read. The component picks the form from the number in the page's language (Intl.PluralRules), and # stands for the number as the language writes it:

html
<hy-badge number="3" count-label="{count, plural, one {# Element} other {# Elemente}}"></hy-badge>
<div lang="ru">
  <hy-badge
    number="3"
    count-label="{count, plural, one {# элемент} few {# элемента} many {# элементов} other {# элемента}}"
  ></hy-badge>
</div>

Each language has its own forms: English, German, French, Spanish and Italian one and other, Russian one, few, many and other, Arabic zero, one, two, few, many and other, Chinese other alone. =0 {…} matches one exact number before the forms, and other is required. A choice works on every number a template names: {count} on the badge, the chart and the avatar group, {value} and {count} on the rating, {total} on the pagination, {max} on the file upload's limit.

hy-slider, hy-number-input and hy-meter also take a formatValue function (set it as a property) when a value needs more than the language's number format — a unit, a scale.

Every other number a component writes follows the nearest lang the same way, with no attribute of its own: page numbers and ranges (hy-pagination), step numbers, badge counts, the +3 of an avatar group, slide numbers, a rating's value text, a splitter's position, file sizes (1,5 ko in French) and the chart's ticks, point names, tooltip and data table. In React, HyProgress and HyMeter are Server Components: they cannot see the page's lang, so pass locale (unset they write en-US).

Validation messages — "Please fill out this field.", "Value must be greater than or equal to 3." — are the browser's own, in the language of the user's browser, exactly as on a native field.

Right-to-left today ​

Layout, spacing and most components mirror under dir="rtl", and ← / → follow the reading direction everywhere they move between items: tab groups, radio and toggle groups, toolbars, steppers, avatar groups, the calendar's days, carousel slides, date-field segments, menubars, submenus (← opens one in right to left), trees (← expands), swatch groups and splitters (keys and drag). The color picker's square and strips and the color slider mirror too: saturation, hue and opacity grow from the inline start, and the pointer and ← / → follow. A chart's plot does not: its axes run left to right in every direction, as in most charting libraries, and ← / → go to the point drawn on that side; its legend, tooltip and data table follow the page.

Arrows and chevrons that point along the reading direction (chevron-left, arrow-right, undo, send…) mirror on their own: hy-icon draws them flipped under dir="rtl", in every component and in your own markup. Put data that keeps its order in every language, such as a URL's path, in a dir="ltr" element, and its arrows stay as drawn.

Still to do: placements named left / right (hy-tooltip, hy-popover, hy-copy-button's tooltip, hy-toast, hy-fab, hy-tab's vertical-left / vertical-right) stay on that side.

Letter spacing and Arabic. Letter spacing separates letters that must join. The default theme sets a small letter spacing on body, label and caption text; for an Arabic page, use a design system whose text styles have no letter spacing until Harmony resets it for joined scripts.

Built with Lit. Documented with VitePress.