> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Translation

> Localize, interpolate, and pluralize messages from a single dictionary behind the Translation base class.

The `@ooneex/translation` component is an internationalization layer for multi-language applications. You extend the `Translation` base class, point it at a dictionary, and resolve localized strings with `trans(key, options)` — dot-notation keys, `{{ param }}` interpolation, and count-driven pluralization are built in. The dictionary is keyed by locale, with `en` as the always-present fallback, across 31 supported locale codes.

## Why this component

* **One dictionary, every locale.** Each leaf carries its translations keyed by locale code (`en`, `fr`, `es`, ...); `en` is the guaranteed fallback when a target locale is missing.
* **Dot-notation keys.** Access nested entries like `trans("user.profile.name")` regardless of how deep the dictionary nests.
* **Interpolation built in.** Fill `{{ param }}` placeholders through `params` without string concatenation.
* **Pluralization by count.** Sibling keys (`<key>`, `<key>_plural`, `<key>_zero`) are selected automatically from the `count` you pass.
* **Container-managed.** Register a translation class with a decorator and resolve it from the container.

## How it works

You extend `Translation` and implement two methods: `getName()` returns a stable identifier for the domain, and `getDict()` returns the dictionary. Lookups go through `has()` and `trans()`, which resolve the requested locale, fall back to `en`, interpolate, and pick the right plural form.

| Member                 | Purpose                                                                                               |
| ---------------------- | ----------------------------------------------------------------------------------------------------- |
| `trans(key, options?)` | Resolve a localized string for `key`; interpolates `params` and selects the plural form from `count`. |
| `has(key)`             | Whether `key` exists in the dictionary.                                                               |
| `getName()`            | Abstract — a stable identifier for the translation domain.                                            |
| `getDict()`            | Abstract — returns the `TranslationDictType` dictionary.                                              |

`trans()` accepts a `TransOptionsType`:

| Option   | Type              | Purpose                                                             |
| -------- | ----------------- | ------------------------------------------------------------------- |
| `lang`   | `LocaleType`      | Target locale. Defaults to `en`.                                    |
| `params` | `TransParamsType` | Values for `{{ param }}` placeholders.                              |
| `count`  | `number`          | Selects the pluralization sibling (singular / `_plural` / `_zero`). |

Resolution order: the requested `lang` is tried first, then the `en` fallback. A missing key throws with `KEY_NOT_FOUND`; a key present but absent in both the locale and the `en` fallback throws with `LOCALE_NOT_FOUND`. Pluralization picks `<key>` when `count === 1`, `<key>_plural` when `count > 1` or `count < 0`, and `<key>_zero` when `count === 0` (falling back to `_plural` if `_zero` is absent).

The supported locale codes are exported as `locales`:

```
ar bg cs da de el en eo es et eu fi fr hu hy it ja ko lt nl no pl pt ro ru sk sv th uk zh zh-tw
```

## Usage

Extend `Translation`, load a dictionary, and resolve keys through `trans()`.

```typescript theme={null}
import type { TranslationDictType } from "@ooneex/translation";
import { decorator, Translation } from "@ooneex/translation";
import dict from "./translations.yml";

@decorator.translation()
export class CheckoutTranslation extends Translation {
  public getName = (): string => "checkout";

  public getDict = (): TranslationDictType => dict as TranslationDictType;
}
```

Resolve it from the container and translate keys:

```typescript theme={null}
import { container } from "@ooneex/container";

const t = container.get(CheckoutTranslation);

// Simple lookup with fallback to "en"
t.trans("user.profile.name", { lang: "fr" }); // "Nom complet"

// Interpolation with params
t.trans("coach.session.welcome", { lang: "fr", params: { name: "Marie" } });
// "Bon retour, Marie !"

// Pluralization driven by count
t.trans("cart.items", { count: 0 }); // "No items"   (items_zero)
t.trans("cart.items", { count: 1 }); // "1 item"     (items)
t.trans("cart.items", { count: 5 }); // "5 items"    (items_plural)

// Existence check
t.has("user.profile.email"); // true
```

The dictionary is a tree of nested keys; each leaf is an object keyed by locale code. Interpolation uses `{{ param }}` placeholders, and pluralization uses sibling keys selected by `count`:

```yaml theme={null}
# translations.yml
user:
  profile:
    name:
      en: "Full name"
      fr: "Nom complet"

coach:
  session:
    welcome:
      en: "Welcome back, {{ name }}!"
      fr: "Bon retour, {{ name }} !"

cart:
  items:
    en: "{{ count }} item"
    fr: "{{ count }} article"
  items_plural:
    en: "{{ count }} items"
    fr: "{{ count }} articles"
  items_zero:
    en: "No items"
    fr: "Aucun article"
```

## Exceptions

The component throws `TranslationException` when a key cannot be resolved. It carries a machine-readable `key`, a human-readable `message`, and a `data` object (with the lookup `key` and `lang`).

| Key                | When                                                                           |
| ------------------ | ------------------------------------------------------------------------------ |
| `KEY_NOT_FOUND`    | The requested key does not exist in the dictionary.                            |
| `LOCALE_NOT_FOUND` | The key exists but has no value for the requested locale or the `en` fallback. |

```typescript theme={null}
import { TranslationException } from "@ooneex/translation";

try {
  return t.trans("user.profile.name", { lang: "fr" });
} catch (error) {
  if (error instanceof TranslationException) {
    logger.error(`Translation error [${error.key}]: ${error.message}`, error.data);
  } else {
    throw error;
  }
}
```

## Best practices

* **Always provide an `en` value.** It is the fallback for every locale; a key without `en` throws `LOCALE_NOT_FOUND` whenever the target locale is missing.
* **Group keys by domain.** Use stable dot-notation paths like `user.profile.name` so dictionaries stay navigable and `getName()` maps cleanly to a domain.
* **Keep placeholders verbatim.** `{{ name }}` and `{{ count }}` must appear identically across locales; only the surrounding words change.
* **Always pass `count` for pluralized keys.** Provide `<key>`, `<key>_plural`, and (optionally) `<key>_zero` siblings so the correct form is selected.
* **Translate meaning, not words.** Phrase each string the way a native speaker writing the product UI would, matching the locale's capitalization and punctuation conventions.
* **Never overwrite correct translations.** When completing locales, fill blanks only — keep keys stable and existing entries intact.

## CLI command

Scaffold a translation class, its test file, and a sibling `translations.yml` dictionary with the generator. It writes the class under `modules/<module>/src/translations/<Name>Translation.ts` and installs `@ooneex/translation` if it is missing.

```bash theme={null}
# Interactive: prompts for the name
ooneex translation:create

# Provide the name
ooneex translation:create --name=Checkout

# Target a module and overwrite
ooneex translation:create --name=Checkout --module=shop --override
```

| Option       | Description                                                                 | Default             |
| ------------ | --------------------------------------------------------------------------- | ------------------- |
| `--name`     | Translation class name. The `Translation` suffix is appended automatically. | Prompted if omitted |
| `--module`   | Target module the class is generated into.                                  | `shared`            |
| `--override` | Overwrite an existing class without prompting.                              | `false`             |

The generated class extends `Translation` and loads the sibling `translations.yml` as its dictionary, ready for you to fill the keys:

```typescript theme={null}
import type { TranslationDictType } from "@ooneex/translation";
import { decorator, Translation } from "@ooneex/translation";
import dict from "./translations.yml";

@decorator.translation()
export class CheckoutTranslation extends Translation {
  public getName = (): string => "checkout";

  public getDict = (): TranslationDictType => dict as TranslationDictType;
}
```

The sibling `translations.yml` is written once per folder, so translation classes in the same `translations/` directory share one dictionary.

See [translation:create](/cli/commands/translation-create) for the full command reference.

## Use with Claude and Codex

The generator ships matching `translation:create` and `translation:translate` skills. The first runs the scaffold and guides your AI agent through filling the dictionary; the second translates existing dictionaries meaning-for-meaning, completing every target locale from the `en` source. Initialize the skills once for your agent:

<Tabs>
  <Tab title="Claude">
    ```bash theme={null}
    ooneex claude:init
    ```

    Then ask Claude in natural language — it maps the request to the generator, runs it, and fills in the translations:

    ```text Prompt icon="terminal" wrap theme={null}
    Create translations for the checkout page in English and French.
    ```
  </Tab>

  <Tab title="Codex">
    ```bash theme={null}
    ooneex codex:init
    ```

    Then ask Codex in natural language — it maps the request to the generator, runs it, and fills in the translations:

    ```text Prompt icon="terminal" wrap theme={null}
    Create translations for the checkout page in English and French.
    ```
  </Tab>
</Tabs>

For example, the prompt above maps to `translation:create --name=Checkout`, then fills the `translations.yml` dictionary with the `en` and `fr` entries for each key.
