Internationalization (i18n)
Almost all text in Hypermedia Components comes from your HTML, which the server renders in whatever language it likes. But a few strings are injected by behaviors — created DOM nodes (the combobox “No matches” marker, a multi-select tag’s remove button) and default ARIA labels (the shell’s navigation toggle, the splitter handle). Those can’t be authored in markup, so they have a small message catalog you can translate in one place.
Resolution order
Section titled “Resolution order”For every injected string, the highest-priority source wins:
- A per-element attribute the server/author wrote — an existing
aria-label, or adata-hc-*override. The server localizes per element. - The global catalog, set with
setMessages()— the app-wide locale. - The built-in English default.
Setting the locale
Section titled “Setting the locale”Import the named installers from the main entry, set the catalog once, then
install. setMessages() merges a flat { key: value } map:
import { setMessages, installCombobox, installConfirm } from '@hypermedia-components/core';
setMessages({ 'combobox.empty': '一致なし', 'confirm.confirm': 'OK', 'confirm.cancel': 'キャンセル', 'multicombobox.remove': '{label} を削除', // {label} is interpolated});
installCombobox();installConfirm();Locale packs
Section titled “Locale packs”You don’t have to translate the built-in strings by hand: official locale
packs ship with the package as importable modules, ready to pass straight
to setMessages(). Japanese is available today:
import { setMessages } from '@hypermedia-components/core/i18n';import ja from '@hypermedia-components/core/locales/ja';
setMessages(ja);
// App-specific wording layers on top — later merges win:setMessages({ 'confirm.confirm': 'OK' });Every pack covers every catalog key: the kit’s own CI fails when a behavior adds a key a shipped pack doesn’t translate, so packs can’t silently drift behind the catalog across upgrades.
Ordering with the auto-init bundle
Section titled “Ordering with the auto-init bundle”The zero-config /behaviors entry
installs everything as soon as it runs, so set the catalog before it
executes. Because ES module scripts run in document order, set messages from
the i18n module first, then load the bundle:
<script type="module"> import { setMessages } from '@hypermedia-components/core/i18n'; setMessages({ 'combobox.empty': '一致なし' });</script><script type="module" src="/path/to/hc.behaviors.js"></script>Importing setMessages from @hypermedia-components/core (the auto-init main
entry) would run the install first — so for the auto-init flow, import it from
the i18n submodule, which has no side effects.
The catalog
Section titled “The catalog”| Key | Default | Injected by |
|---|---|---|
combobox.empty | No matches | combobox empty-results marker |
combobox.loading | Loading… | remote combobox loading row |
combobox.error | Couldn’t load options | remote combobox error row |
combobox.create | Create “{value}” | creatable combobox “create” option |
multicombobox.empty | No matches | multi-combobox empty-results marker |
multicombobox.create | Add “{value}” | creatable multi-combobox “add” option |
multicombobox.remove | Remove {label} | tag remove-button aria-label |
calendar.label | Calendar | grid aria-label (fallback) |
calendar.prevMonth | Previous month | previous-month button aria-label |
calendar.nextMonth | Next month | next-month button aria-label |
calendar.month | Month | month dropdown aria-label (data-nav="select") |
calendar.year | Year | year dropdown aria-label (data-nav="select") |
confirm.message | Continue? | confirm dialog body (fallback) |
confirm.title | Confirm | confirm dialog title (fallback) |
confirm.confirm | Confirm | confirm button (fallback) |
confirm.cancel | Cancel | cancel button (fallback) |
fieldErrors.unknown | Invalid value | field-errors item with no resolvable key and no text |
shell.toggleNav | Toggle navigation | shell nav-toggle aria-label (fallback) |
shell.collapseNav | Collapse sidebar | shell collapse-button aria-label (fallback) |
splitter.resize | Resize panels | splitter handle aria-label (fallback) |
themeToggle.label | Switch color theme | icon-only theme toggle aria-label (fallback) |
toast.label | Notifications | toast region aria-label (fallback) |
This table is the complete list — every string a behavior can render comes
from DEFAULT_MESSAGES, so overriding these keys translates the whole kit.
The key inventory is part of the public API surface (see VERSIONING.md
in the repository): DEFAULT_MESSAGES is exported precisely so you can
enumerate the keys your installed version defines. If you maintain your
own catalog instead of using a locale pack, diff it against
Object.keys(DEFAULT_MESSAGES) in CI to catch keys a new version added —
the failure mode is an English fallback, which nobody notices by eye.
{name} placeholders are interpolated from the params the behavior passes —
{label} for multicombobox.remove, and {value} for the creatable
combobox.create / multicombobox.create options.
Worked example — a Japanese UI
Section titled “Worked example — a Japanese UI”Set the catalog once at startup from the Japanese locale pack — it covers every key, so the kit renders no English afterwards:
import { setMessages } from '@hypermedia-components/core/i18n';import ja from '@hypermedia-components/core/locales/ja';
setMessages(ja);A destructive action then needs no per-button attributes for the dialog chrome — only the message itself:
<button class="hc-button" data-variant="error" data-hc-confirm="ユーザー alice を無効化しますか?" data-hx-post="/users/alice/disable" data-hx-trigger="hc:confirmed"> ユーザーを無効化</button>The title (確認), the confirm button (実行), and the cancel button
(キャンセル) come from the catalog; data-hc-confirm-* attributes remain
available as per-element overrides.
The catalog also resolves your own keys: the
field-errors recipe looks up
each server-sent data-message-key (e.g. members.email.duplicate) in the
same catalog, with {field} / {code} — plus any values the server sends
as data-message-params — available for interpolation.
Per-element overrides (server-side localization)
Section titled “Per-element overrides (server-side localization)”When a string belongs to a specific element, set it in markup and it wins over the catalog — handy when the server already knows the language of each region:
- confirm —
data-hc-confirm,data-hc-confirm-title,data-hc-confirm-label,data-hc-cancel-labelon the trigger. - combobox / multi-combobox —
data-hc-emptyon the<ul role="listbox">. - calendar / shell / splitter / toast — an author-provided
aria-labelon the element is never overwritten.
<!-- This dialog stays Japanese regardless of the global catalog. --><button class="hc-button" data-hc-confirm="削除しますか?" data-hc-confirm-label="削除" data-hc-cancel-label="キャンセル"> 削除</button>
<!-- Per-listbox empty text. --><ul class="hc-combobox__listbox" role="listbox" data-hc-empty="該当なし"></ul>All exported from the main entry and the i18n submodule.
setMessages(overrides)— merge translations into the catalog. Returns a function that restores the previous state.resetMessages()— revert to the built-in English defaults.getMessages()— a snapshot of the current catalog (defaults + overrides).DEFAULT_MESSAGES— the frozen English defaults.
Missing translations degrade gracefully: an unknown key falls back to the English default, then to the key itself — never an empty string or a throw.