Skip to content

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.

For every injected string, the highest-priority source wins:

  1. A per-element attribute the server/author wrote — an existing aria-label, or a data-hc-* override. The server localizes per element.
  2. The global catalog, set with setMessages() — the app-wide locale.
  3. The built-in English default.

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();

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.

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.

KeyDefaultInjected by
combobox.emptyNo matchescombobox empty-results marker
combobox.loadingLoading…remote combobox loading row
combobox.errorCouldn’t load optionsremote combobox error row
combobox.createCreate “{value}”creatable combobox “create” option
multicombobox.emptyNo matchesmulti-combobox empty-results marker
multicombobox.createAdd “{value}”creatable multi-combobox “add” option
multicombobox.removeRemove {label}tag remove-button aria-label
calendar.labelCalendargrid aria-label (fallback)
calendar.prevMonthPrevious monthprevious-month button aria-label
calendar.nextMonthNext monthnext-month button aria-label
calendar.monthMonthmonth dropdown aria-label (data-nav="select")
calendar.yearYearyear dropdown aria-label (data-nav="select")
confirm.messageContinue?confirm dialog body (fallback)
confirm.titleConfirmconfirm dialog title (fallback)
confirm.confirmConfirmconfirm button (fallback)
confirm.cancelCancelcancel button (fallback)
fieldErrors.unknownInvalid valuefield-errors item with no resolvable key and no text
shell.toggleNavToggle navigationshell nav-toggle aria-label (fallback)
shell.collapseNavCollapse sidebarshell collapse-button aria-label (fallback)
splitter.resizeResize panelssplitter handle aria-label (fallback)
themeToggle.labelSwitch color themeicon-only theme toggle aria-label (fallback)
toast.labelNotificationstoast 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.

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:

  • confirmdata-hc-confirm, data-hc-confirm-title, data-hc-confirm-label, data-hc-cancel-label on the trigger.
  • combobox / multi-comboboxdata-hc-empty on the <ul role="listbox">.
  • calendar / shell / splitter / toast — an author-provided aria-label on 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.