How to Implement Internationalization with the Astryx i18n Module: A Complete Guide
Wrap your React application with Astryx's <InternationalizationProvider>, call useTranslator() in components for ICU-formatted translations, and optionally provide custom messages or overrides to localize any component without external dependencies.
Astryx ships a lightweight, self-contained i18n module that enables full localization without the bloat of heavyweight frameworks. This guide walks through the architecture, core APIs, and practical implementation patterns based on the facebook/astryx source code.
How the Astryx i18n Architecture Works
The module follows a three-layer design: Provider → Context → Translator.
<InternationalizationProvider>– Creates a React context (InternationalizationContext) that stores the activelocale, optionalmessages(additional catalogs), andoverrides.resolve()function – Located inpackages/core/src/i18n/resolve.ts, this performs BCP-47 locale chain resolution. It checksoverrides→ shipped catalogs → English fallback, caching ICU parsers for performance.- Hook layer –
useTranslator()anduseDirection()consume the context and expose stable, callable functions.
All Astryx components read from this unified context, so localization propagates automatically across your application.
Setting Up the InternationalizationProvider
The entry point for any i18n implementation is wrapping your app tree with the provider component defined in packages/core/src/i18n/InternationalizationProvider.tsx.
import { InternationalizationProvider } from '@astryxdesign/core';
import fr from '@astryxdesign/core/locales/fr.json';
function App() {
return (
<InternationalizationProvider
locale="fr" // BCP-47 language tag
messages={{ fr }} // Optional: additional message catalogs
overrides={{
fr: { '@astryx.pagination.next': 'Suivant' }
}}
dir="rtl" // Optional: explicit text direction
>
<YourApp />
</InternationalizationProvider>
);
}
The provider automatically derives direction from the locale (e.g., ar → rtl) unless you override it with the dir prop. The merged configuration is stored in InternationalizationContext for child components to access.
Translating Content with useTranslator
The useTranslator hook in packages/core/src/i18n/useTranslator.ts returns a stable t() function bound to the provider's current locale.
Render-Time Translation
import { useTranslator } from '@astryxdesign/core';
function UserGreeting({ name }: { name: string }) {
const t = useTranslator();
return (
<h1>
{t('@astryx.user.hello', { name })}
{/* Output: "Bonjour, Marie" (when locale is fr) */}
</h1>
);
}
Event Handler Translation
Because useTranslator returns a stable function reference, you can safely call t() outside the render phase:
function SaveButton() {
const t = useTranslator();
const handleSave = async () => {
const successMessage = t('@astryx.save.success', { filename: 'report.pdf' });
showToast(successMessage);
};
return (
<button onClick={handleSave}>
{t('@astryx.save.button')}
</button>
);
}
The t() function internally calls resolve() with ICU MessageFormat support, enabling pluralization, number formatting, and date interpolation.
Handling Text Direction for RTL Layouts
For bi-directional layouts, access the current text direction with useDirection or its server-safe equivalent getLocaleDirection.
import { useDirection } from '@astryxdesign/core';
function Navigation() {
const direction = useDirection(); // 'ltr' | 'rtl'
return (
<nav style={{
paddingInlineStart: direction === 'rtl' ? '1rem' : '0',
paddingInlineEnd: direction === 'rtl' ? '0' : '1rem'
}}>
{/* Logical properties adapt automatically */}
</nav>
);
}
useDirection reads from InternationalizationContext.direction, which defaults to the locale-derived value unless explicitly overridden.
Extending with Custom Message Catalogs
You can add new languages without modifying Astryx core. Create a JSON catalog following the ICU MessageFormat structure:
// src/locales/es.json
{
"@astryx.pagination.next": {
"defaultMessage": "Siguiente",
"description": "Next page button label"
},
"@astryx.pagination.pageInfo": {
"defaultMessage": "Página {page} de {total}",
"description": "Current page indicator"
}
}
Then inject it through the provider:
import es from './locales/es.json';
<InternationalizationProvider
locale="es-MX"
messages={{ es }}
overrides={{
'es-MX': { '@astryx.pagination.next': 'Sig.' }
}}
>
<App />
</InternationalizationProvider>
Resolution order (as implemented in packages/core/src/i18n/resolve.ts):
- Most-specific locale match first (
es-MX→es) overridestake precedence over base catalogs- Missing keys fall back to English with a dev-only warning
Performance and Type Safety
The Astryx i18n module keeps runtime costs minimal through two optimizations:
- ICU parser caching –
resolve.tscaches compiled MessageFormat instances per locale/key combination - Stable hook references –
useTranslatorreturns a memoized function that doesn't invalidate consumer dependencies
Type safety is enforced through packages/core/src/i18n/types.ts, which exports Locale, Catalog, Messages, and InternationalizationContextValue definitions. Your IDE can autocomplete translation keys and validate ICU placeholder types at compile time.
Summary
InternationalizationProviderinitializes the locale context at your app's rootuseTranslatorprovides at()function for ICU-formatted translations anywhere in your component treeuseDirectionexposesltr/rtlfor responsive bi-directional layoutsresolve.tsimplements efficient BCP-47 locale chain resolution with caching and fallback logic- Custom catalogs and overrides integrate seamlessly without touching Astryx source files
Frequently Asked Questions
Does Astryx i18n require external libraries like react-intl or i18next?
No. The facebook/astryx i18n module is self-contained. It bundles its own ICU MessageFormat implementation in resolve.ts and provides React bindings through native context, eliminating peer dependencies entirely.
What happens if a translation key is missing?
The resolve() function falls back to the key itself as the display string and emits a warning in development mode (process.env.NODE_ENV !== 'production'). The English catalog serves as the ultimate fallback, so your UI remains functional even with incomplete translations.
Can I use Astryx i18n outside of React components?
Yes. The t() function returned by useTranslator() is a plain JavaScript function. You can call it from utility files, event handlers, or async callbacks. For server-side rendering, use getLocaleDirection() instead of useDirection() since it doesn't depend on React context.
How do I override only specific strings without replacing entire catalogs?
Pass an overrides object to InternationalizationProvider. The merge logic in resolve.ts checks overrides[locale][key] before consulting the base catalog, so you can patch individual messages while inherring the rest from the default English strings or your custom messages bundles.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →