# How to Implement Internationalization with the Astryx i18n Module: A Complete Guide

> Learn to implement internationalization with the Astryx i18n module. Wrap your app with InternationalizationProvider and useTranslator for easy ICU-formatted translations.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-08-05

---

**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.

1. **`<InternationalizationProvider>`** – Creates a React context (`InternationalizationContext`) that stores the active `locale`, optional `messages` (additional catalogs), and `overrides`.
2. **`resolve()` function** – Located in [`packages/core/src/i18n/resolve.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/i18n/resolve.ts), this performs BCP-47 locale chain resolution. It checks `overrides` → shipped catalogs → English fallback, caching ICU parsers for performance.
3. **Hook layer** – `useTranslator()` and `useDirection()` 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`](https://github.com/facebook/astryx/blob/main/packages/core/src/i18n/InternationalizationProvider.tsx).

```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`](https://github.com/facebook/astryx/blob/main/packages/core/src/i18n/useTranslator.ts) returns a stable `t()` function bound to the provider's current locale.

### Render-Time Translation

```tsx
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:

```tsx
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`.

```tsx
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:

```json
// 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:

```tsx
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`](https://github.com/facebook/astryx/blob/main/packages/core/src/i18n/resolve.ts)):
- Most-specific locale match first (`es-MX` → `es`)
- `overrides` take 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.ts`](https://github.com/facebook/astryx/blob/main/resolve.ts) caches compiled MessageFormat instances per locale/key combination
- **Stable hook references** – `useTranslator` returns a memoized function that doesn't invalidate consumer dependencies

Type safety is enforced through [`packages/core/src/i18n/types.ts`](https://github.com/facebook/astryx/blob/main/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

- **`InternationalizationProvider`** initializes the locale context at your app's root
- **`useTranslator`** provides a `t()` function for ICU-formatted translations anywhere in your component tree
- **`useDirection`** exposes `ltr`/`rtl` for responsive bi-directional layouts
- **[`resolve.ts`](https://github.com/facebook/astryx/blob/main/resolve.ts)** implements 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`](https://github.com/facebook/astryx/blob/main/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`](https://github.com/facebook/astryx/blob/main/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.