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

# Internationalization

Atoti translates discoverable content, including captions, descriptions, level members, and formatted
measure values, for the locale of the requesting user.

Without an explicit configuration, no translation is applied.

## Lexicons

A lexicon is a locale-scoped map of keys to translated values. A translation file is made up of
several named lexicons, each covering a different category of content.

| Top-level key    | Translates                                                                                           |
| ---------------- | ---------------------------------------------------------------------------------------------------- |
| `_GENERAL_`      | Fallback lexicon                                                                                     |
| `_CUBES_`        | Cube captions                                                                                        |
| `_DIMENSIONS_`   | Dimension captions                                                                                   |
| `_HIERARCHIES_`  | Hierarchy captions                                                                                   |
| `_LEVELS_`       | Level captions                                                                                       |
| `_MEASURES_`     | Measure captions                                                                                     |
| `_MEMBERS_`      | Calculated member captions                                                                           |
| `_GROUPS_`       | Measure group captions                                                                               |
| `_FOLDERS_`      | Folder captions                                                                                      |
| `_KPIS_`         | KPI captions                                                                                         |
| `_DRILLTHROUGH_` | Drillthrough column captions                                                                         |
| `_SETS_`         | Named set captions                                                                                   |
| `_DESCRIPTIONS_` | Element descriptions                                                                                 |
| `_FORMATTERS_`   | Per-measure or per-level value formatters (numeric format strings or references to a custom lexicon) |

`_GENERAL_` is the fallback consulted whenever a type-specific lexicon does not contain a key.

## Translation file format

Each locale requires one JSON file, named after its locale code (for example, `i18n/fr-FR`,
`i18n/zh-CN`, `i18n/ja-JP`). The top-level keys are lexicon names; the nested objects hold the
translation maps.

A level-specific formatter can be created using a dedicated lexicon for the level, then referenced
from the `_FORMATTERS_` lexicon by the level's MDX unique name.

<Accordion title="Example locale file (fr-FR)">
  ```json5 theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  {
    /** Measure captions lexicon */
    "_MEASURES_": {
      "pnl.SUM": "pnl.SOMME",
      "update.TIMESTAMP": "Dernière mise à jour",
      "contributors.COUNT": "Nombre de faits"
    },
    "_FORMATTERS_": {
      /** Measure formatters */
      "[Measures].[update.TIMESTAMP]": "DATE[HH:mm:ss]",
      "[Measures].[delta.SUM]": "DOUBLE[#,###.0000;-#,###.00]",
      "[Measures].[pnl.SUM]": "DOUBLE[#,###.00;-#,###.00]",
      /** Level member formatters */
      "[Time].[TimeBucket].[Value Date]": "DATE",
      /** Reference to a custom lexicon for member translation */
      "[Underlyings].[Products].[ProductType]": "_PRODUCT_TYPES_"
    },
    /** Lexicon used for Product types' members */
    "_PRODUCT_TYPES_": {
      "LISTED": "COTE",
      "OTC": "DE GRE A GRE"
    },
    "_DESCRIPTIONS_": {
      "The two elements of underlyings which have the highest value": "Les deux éléments sous-jacents de plus grande valeur.",
      "Dimension of underlyings": "Dimension des sous-jacents"
    },
    /**
     * The GENERAL lexicon, used for all other types (dimensions, levels, drillthrough...)
     */
    "_GENERAL_": {
      "Time": "Temps",
      "Measures": "Mesures",
      "Bookings": "Enregistrement",
      "Epoch": "Epoque"
    },
    /** Level captions: plain key applies to all levels named "Status" */
    "_LEVELS_": {
      "Status": "Statut",
      /** Fully qualified key using the @ form: renames "Status" only in TradeHierarchy > Bookings */
      "Status@TradeHierarchy@Bookings": "Statut de l'opération",
      /** Equivalent fully qualified key using the MDX unique-name form */
      "[Bookings].[TradeHierarchy].[Status]": "Statut MDX de l'opération"
    }
  }
  ```
</Accordion>

### How to target hierarchies and levels that share a name

By default, a key in `_HIERARCHIES_` or `_LEVELS_` is a plain name that applies to every element
with that name across the cube. When two hierarchies or levels share the same name, a fully qualified
key makes it possible to target one specific element.

Two fully qualified forms are accepted and will resolve to the same element:

| Lexicon         | Plain key       | Fully-qualified key (`@` form)          | Fully-qualified key (MDX form)                |
| --------------- | --------------- | --------------------------------------- | --------------------------------------------- |
| `_HIERARCHIES_` | `hierarchyName` | `hierarchyName@dimensionName`           | `[dimensionName].[hierarchyName]`             |
| `_LEVELS_`      | `levelName`     | `levelName@hierarchyName@dimensionName` | `[dimensionName].[hierarchyName].[levelName]` |

The MDX form uses the same bracket syntax already used in the `_FORMATTERS_` lexicon, so projects
that write MDX unique names there can use the same style here.

**Escaping (`@` form):** if a name legitimately contains `@`, replace each `@` with `/@@` in the key.
For example, a level named `foo@bar` is written as `foo/@@bar`. A literal `/` does not need to be
escaped.

**Escaping (MDX form):** if a name contains a literal `]`, double it to `]]`. A literal `[` does not
need to be escaped.

The following example shows two hierarchies inside an `Organization` dimension that both have levels
named `Level 1` and `Level 2`. The fully qualified keys rename only `BookHierarchy`'s levels, while
`LegalEntityHierarchy`'s levels keep their original names. Both fully qualified forms appear together
to show that they can coexist in the same file.

```json5 theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
{
    "_LEVELS_": {
        // Plain key: applies to every level named "Level 1" that has no more-specific entry
        "Level 1": "Division",
        "Level 2": "Team",

        // Fully-qualified using the @ form
        "Level 1@BookHierarchy@Organization": "Book Division",

        // Equivalent using the MDX unique-name form
        "[Organization].[BookHierarchy].[Level 2]": "Book Team"
    }
}
```

## How to install a translation

### Step 1: Place the translation file

Place the locale file in the `i18n/` directory of the application's classpath (for example,
`src/main/resources/i18n/fr-FR`) or in the `i18n/` directory of the Content Service. The file name
is the locale code. At startup, Atoti registers one `ICubeFormatterFactory` per file it finds, keyed
on the file name.

### Step 2: Bind a role to a locale

Translation is opt-in. A role must explicitly select a locale via its MDX context. Configure this on
the entitlements builder for the role:

```java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
return builder
    .withGlobalEntitlement()
    .forRole(ROLE_ADMIN)
    .withMdxContext()
    .withDefaultMember(FIRST_TARGET_CURRENCY_LEVEL, "ZAR")
    .withCubeFormatter("en-US")
    .end();
```

The string passed to `withCubeFormatter` is the plugin key under which the factory was registered in
step 1, that is, the locale code from the file name. Without this binding, the role's queries return
untranslated content.

## Custom translation source

If translations come from a source other than the standard `i18n/` directory (for example, a
database, a remote service, or a different file format), implement `ICubeFormatterFactory` directly
and register it with the Atoti `Registry` under a chosen plugin key. That key is then referenced from
a role's MDX context using `withCubeFormatter(key)`, exactly as in step 2 above.
