# Craft Agents OSS i18n System Architecture: How to Add a New Locale

> Explore the Craft Agents OSS i18n system architecture. Learn how to add a new locale with a simple JSON file and registry entry for efficient internationalization.

- Repository: [Craft Ai Agents/craft-agents-oss](https://github.com/craft-ai-agents/craft-agents-oss)
- Tags: architecture
- Published: 2026-07-03

---

**Craft Agents OSS uses i18next with react-i18next for runtime translation and a derivative-free locale registry that automatically generates all supported language constants, requiring only a JSON file and registry entry to add a new locale.**

Craft Agents OSS implements a robust internationalization system built on i18next and react-i18next. The architecture centers on a single-source-of-truth locale registry in [`packages/shared/src/i18n/registry.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/i18n/registry.ts) that eliminates manual wiring by automatically deriving supported language codes, i18next resources, and date-fns locale mappings. This design ensures that adding support for a new language requires minimal changes while maintaining consistency across the Electron renderer and main processes.

## Understanding the i18n System Architecture

The internationalization stack follows a layered, derivative-free approach designed for maintainability. Once a locale entry is added to the registry, all derived constants generate automatically.

### Locale Files and Translation Keys

Translation files use **flat-dot-notation JSON** stored in `packages/shared/src/i18n/locales/{code}.json`. The English file ([`en.json`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/en.json)) serves as the master reference, with all other locales maintaining identical key structures to satisfy parity tests.

### The Locale Registry (Single Source of Truth)

The `LOCALE_REGISTRY` in [`packages/shared/src/i18n/registry.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/i18n/registry.ts) maps each language code to its native name, translation messages, and corresponding `date-fns` locale. This central map automatically populates `SUPPORTED_LANGUAGE_CODES` and `LANGUAGES` in [`packages/shared/src/i18n/languages.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/i18n/languages.ts) without manual updates.

### i18next Initialization

The [`setupI18n.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/setupI18n.ts) file initializes i18next using resources built dynamically from the registry, configures fallback languages, and integrates the language detector for the renderer process.

### React Integration and Non-React Usage

UI components under `packages/ui/src/**/*.tsx` access translations via the `useTranslation()` hook from react-i18next. Non-React code uses `i18n.t()` **inside functions only**, never at module load time, to avoid initialization race conditions.

### Cross-Process Persistence

The renderer stores the selected language in `localStorage` under the key `i18nextLng`. The main process hydrates this value from `preferences.uiLanguage` at startup, ensuring synchronization between the Electron main and renderer processes according to the implementation in [`apps/electron/src/main/index.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/apps/electron/src/main/index.ts) and [`packages/shared/src/config/preferences.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/config/preferences.ts).

## How to Add a New Locale

Adding a new language to Craft Agents OSS requires five steps:

1. **Create the locale JSON file** by copying the English reference and translating all keys while preserving the flat-dot structure:

   ```bash
   cp packages/shared/src/i18n/locales/en.json packages/shared/src/i18n/locales/<new-code>.json
   ```

2. **Import the JSON and its date-fns locale** in [`packages/shared/src/i18n/registry.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/i18n/registry.ts):

   ```typescript
   import frMessages from "./locales/fr.json";
   import { fr as frDateLocale } from "date-fns/locale/fr";
   ```

3. **Add a registry entry** in `LOCALE_REGISTRY` providing the native name, messages, and date locale:

   ```typescript
   export const LOCALE_REGISTRY = {
     // existing entries...
     fr: {
       nativeName: "Français",
       messages: frMessages,
       dateLocale: frDateLocale,
     },
   } satisfies Record<string, LocaleEntry>;
   ```

4. **Run the validation test suite** to verify key parity across all locales:

   ```bash
   bun run validate:ci
   ```

5. **Commit the changes**. The CI pipeline runs `lint:i18n:sorted` and `lint:i18n:coverage` to enforce key order and usage coverage.

## Implementation Examples

### Configuring the Locale Registry

When adding French support, the [`packages/shared/src/i18n/registry.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/i18n/registry.ts) file requires both the translation messages and the date-fns locale for date formatting:

```typescript
import frMessages from "./locales/fr.json";
import { fr as frDateLocale } from "date-fns/locale/fr";

export const LOCALE_REGISTRY = {
  // other locales...
  fr: {
    nativeName: "Français",
    messages: frMessages,
    dateLocale: frDateLocale,
  },
} satisfies Record<string, LocaleEntry>;

```

### Using Translations in React Components

Components access the `t` function via the react-i18next hook:

```tsx
import { useTranslation } from "react-i18next";

export function SettingsHeader() {
  const { t } = useTranslation();
  return <h1>{t("settings.title")}</h1>;
}

```

### Accessing i18n Outside React

For utility functions or main process code, call `i18n.t()` inside functions to avoid initialization issues:

```typescript
import { i18n } from "@craft-agent/shared/i18n";

export function formatError(errorKey: string) {
  // Must be called inside a function, not at module top-level
  return i18n.t(`errors.${errorKey}`);
}

```

## Critical Files for i18n Management

- [`packages/shared/src/i18n/registry.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/i18n/registry.ts) - Central locale registry where new languages are registered.
- [`packages/shared/src/i18n/setupI18n.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/i18n/setupI18n.ts) - i18next initialization and resource configuration.
- [`packages/shared/src/i18n/languages.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/i18n/languages.ts) - Auto-generated list of supported codes derived from registry keys.
- [`scripts/check-i18n-parity.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/scripts/check-i18n-parity.ts) - CI validation script ensuring all locales match the English key set.
- [`scripts/lint-i18n-staged.sh`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/scripts/lint-i18n-staged.sh) - Pre-commit hook enforcing sorted keys and coverage requirements.
- [`packages/shared/CLAUDE.md`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/CLAUDE.md) - Internal documentation covering i18n workflow rules and constraints.

## Summary

- Craft Agents OSS uses a **derivative-free i18n architecture** centered on a single registry in [`packages/shared/src/i18n/registry.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/i18n/registry.ts).
- Adding a new locale requires only creating a JSON translation file and registering it with its `date-fns` locale in the registry.
- The system automatically generates `SUPPORTED_LANGUAGE_CODES`, `LANGUAGES`, and i18next resources without manual updates.
- Always use `useTranslation()` in React components and `i18n.t()` inside functions only for non-React code.
- Run `bun run validate:ci` to ensure key parity across all locale files before committing.

## Frequently Asked Questions

### What happens if I forget to add a translation key to the new locale file?

The CI pipeline executes [`scripts/check-i18n-parity.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/scripts/check-i18n-parity.ts), which validates that every locale file contains the exact same keys as [`en.json`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/en.json). The test will fail if any keys are missing or extra, preventing incomplete translations from merging into the main branch.

### Can I use nested JSON objects instead of flat-dot-notation for translations?

No, the architecture requires flat-dot-notation keys (e.g., `"settings.title"` rather than nested objects) to maintain consistency with the i18next configuration and the automated parity checking scripts used in the build pipeline.

### How does the application handle language selection across the Electron main and renderer processes?

The renderer persists the selected language in `localStorage` under the key `i18nextLng`, while the main process hydrates this value from `preferences.uiLanguage` at startup, ensuring both processes remain synchronized according to the implementation in [`apps/electron/src/main/index.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/apps/electron/src/main/index.ts) and [`packages/shared/src/config/preferences.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/config/preferences.ts).

### Do I need to manually update the supported languages list when adding a new locale?

No, the `SUPPORTED_LANGUAGE_CODES` and `LANGUAGES` exports in [`packages/shared/src/i18n/languages.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/i18n/languages.ts) are auto-generated from the `LOCALE_REGISTRY` keys. Once you add the entry to the registry, all derived constants update automatically without additional manual wiring.