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 lang="ar" dir="rtl"></html>A region can override them for its subtree — <section lang="de"> — and components inside follow the nearest one.
dirmirrors the layout. Components use logical CSS (margin-inline-start, notmargin-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 setsdirfor you.langpicks 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.
| Component | Formats |
|---|---|
hy-calendar | Month and weekday names, the first day of the week, the full date it reads |
hy-date-field | The order of day, month and year, the separators, the 12- or 24-hour clock |
hy-date-picker | Through 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-datagrid | The default number and date columns |
hy-number-input | The number shown (grouped) and typed: 1.234,5 and 1,5 in German |
hy-slider | The value readout, tick labels and what a screen reader hears |
hy-progress | The value shown and announced (45 %, ٤٥٪) |
hy-meter | The 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:
| Component | Attributes (English default) |
|---|---|
hy-accordion | expanded-label ("{label} expanded"), collapsed-label ("{label} collapsed") |
hy-alert | variant-label ("Information:", "Success:", "Warning:", "Error:"), dismiss-label ("Dismiss alert") |
hy-avatar | presence-label (the presence status in English) |
hy-avatar-group | overflow-label ("{count} more") |
hy-badge | count-label ("{count, plural, one {# item} other {# items}}") |
hy-bottombar | label ("Bottom navigation") |
hy-breadcrumb | label ("Breadcrumb") |
hy-button, hy-fab | loading-text ("Loading") |
hy-calendar | loading-text ("Loading calendar"), previous-month-label ("Previous month"), next-month-label ("Next month") |
hy-chart | default-label ("{type} chart with {count} series"), legend-label ("Series"), tooltip-text ("{series}: {value}") |
hy-checkbox-group | select-all-label ("Select all") |
hy-chip | remove-label ("Remove {label}") |
hy-color-picker | label ("Color") |
hy-combobox | clear-label ("Clear selection"), empty-text ("No results"), open-options-label ("Open options"), close-options-label ("Close options"), required-label ("required") |
hy-context-menu | label ("Context menu") |
hy-copy-button | copy-label ("Copy"), success-label ("Copied"), error-label ("Could not copy") |
hy-datagrid | empty-heading ("No data"), filter-placeholder ("Search…") |
hy-dialog | label ("Dialog"), close-label ("Close"), closed-label ("{label} dialog closed") |
hy-drawer | label ("Drawer"), close-label ("Close"), collapse-label ("Toggle sidebar"), opened-label ("{label} drawer opened"), closed-label ("{label} drawer closed") |
hy-dtcg-tree | label ("Design tokens"), empty-text ("No tokens"), no-results-text ("No tokens match"), copy-label ("Copy path") |
hy-empty-state | query-heading-text (No results for "{query}"), loading-label ("Loading…"), retry-label ("Retry") |
hy-gradient-picker | stop-label ("Stop {index} of {count}"), stop-value-text ("{name} at {position}"), stop-caption-text ("Stop {index}") |
hy-image-gallery | list-label ("Image gallery"), status-label ("Gallery"); empty-heading-text, error-heading-text, error-description-text, retry-label (hy-empty-state's English) |
hy-inline-edit | edit-label ("Edit"), editor-label ("Edit value"), save-label ("Save"), cancel-label ("Cancel"), saving-label ("Saving") |
hy-link | disabled-label ("(disabled)"), new-window-label ("(opens in new window)") |
hy-multi-combobox | clear-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-input | decrease-label ("Decrease {label}"), increase-label ("Increase {label}") |
hy-pagination | label ("Pagination") |
hy-popover | opened-label ("{label} opened"), closed-label ("{label} closed"), fallback-label ("Popover"), blocked-label ("Action blocked") |
hy-progress | indeterminate-label ("In progress"), value-label ("{value} of {max}") |
hy-rating | value-label ("{value} out of {count, plural, one {# star} other {# stars}}") |
hy-select | clear-label ("Clear selection"), required-label ("required") |
hy-sidebar | label ("Main navigation"), collapse-label ("Collapse"), expand-label ("Expand") |
hy-slider | start-label ("Minimum"), end-label ("Maximum") in range mode |
hy-spinner | label ("Loading") |
hy-split-button | menu-label ("More options"), loading-text ("Loading") |
hy-splitter-handle | label ("Resize") |
hy-stepper | label ("Progress") |
hy-tab | label ("Tab") |
hy-tab-group | loading-label ("Loading") |
hy-table | empty-text ("No data"), region-label ("Table"), named-region-label ("Table: {label}") |
hy-text-input | clear-label ("Clear"), show-password-label ("Show password"), hide-password-label ("Hide password") |
hy-toast | variant-label ("Information:", "Success:", "Warning:", "Error:"), dismiss-label ("Dismiss toast") |
hy-topbar | label ("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.
| Component | What strings covers |
|---|---|
hy-carousel | The slide names and positions ("Slide {index} of {count}"), the previous and next buttons, the slide markers |
hy-color-picker | The names of the square, the hue and opacity strips, the value and format fields, the preset colors |
hy-date-field | The segment names ("Month", "Day", "Year"…), their placeholders ("MM", "DD", "YYYY"), "Empty", the AM / PM fallback |
hy-date-picker | Its date fields' strings, the start and end dates, the calendar button, "Today", "Clear", the calendar's month buttons |
hy-datagrid | Its pagination's strings, row selection, column resizing, filter, columns, export, its no-results and error states |
hy-empty-state | Each variant's default heading and description ("Nothing here yet", "No results"…) |
hy-file-upload | The browse and drop prompts, the accepted types, the progress, the remove button, the error messages |
hy-markdown-editor | The 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-stepper | Each 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:
<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.