Internationalization
i18next + react-i18next
Out of the box. i18next@26.3.6, react-i18next@17.0.11.
i18next is the de-facto JS i18n core — resources, interpolation, plurals,
namespaces, backends — and react-i18next binds it to React with
useTranslation, <Trans> for translations containing markup, and
provider-driven re-render on language change.
The core has no DOM in it and react-i18next renders whatever elements you
hand it, so the whole stack runs unmodified: interpolation, count-based
plurals, <Trans>, and i18n.changeLanguage('de') re-rendering the mounted
tree in place. Node ships full ICU, so Intl.NumberFormat and
Intl.DateTimeFormat formatting work too.
Two react-x11-specific rules:
<Trans>component maps must use react-x11 elements.components={{ 1: <span/> }}throwsreact-x11: <span> is not allowed inside <text>— inside a<text>, only strings and nested<text>spans are legal. Use<text style={...}/>as the wrapper.- Turn off HTML escaping. i18next escapes interpolated values for an
HTML sink by default, and react-x11 draws raw glyphs, so
O'Brienrenders asO'Brien. Setinterpolation: { escapeValue: false }.
import React from 'react';
import i18next from 'i18next';
import {
I18nextProvider,
initReactI18next,
useTranslation,
Trans,
} from 'react-i18next';
const i18n = i18next.createInstance();
await i18n.use(initReactI18next).init({
lng: 'en',
resources: {
en: {
translation: {
greeting: 'Hello, {{name}}!',
items_one: '{{count}} item',
items_other: '{{count}} items',
rich: 'Press <1>Enter</1> to save',
},
},
},
interpolation: { escapeValue: false }, // no HTML sink to escape for
});
function Status() {
const { t } = useTranslation();
return (
<box style={{ flexDirection: 'column' }}>
<text>{t('greeting', { name: 'X11' })}</text>
<text>{t('items', { count: 2 })}</text>
<text>
<Trans
i18nKey="rich"
components={{ 1: <text style={{ color: '#e00' }} /> }}
/>
</text>
</box>
);
}
// mount under <I18nextProvider i18n={i18n}> inside your <window>;
// i18n.changeLanguage('de') re-renders live
For loading translations from disk, i18next-fs-backend is the natural
desktop choice; i18next-http-backend also works, since Node has fetch.
What is missing below the string
These are react-x11 gaps rather than i18next ones, and they matter more than the library choice:
- Layout is computed left-to-right unconditionally. react-x11 calls
yoga's
calculateLayoutwithDIRECTION_LTRand there is nodirectionstyle property, so an Arabic or Hebrew UI will mirror wrong even though every string translates correctly. Yoga supportsDIRECTION_RTL; wiring it up is renderer work that has not been done. - There is no X input method. Input is limited to what the keyboard path produces, with no XIM integration, so languages that need composition — CJK, dead keys — can be displayed but not typed.
<Trans>with the default<strong>- and<em>-style numbered tags in translations is fine as long as every referenced component is a<text>span. An accidental DOM tag fails loudly at render, which is the good case.