Skip to main content
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 keyTranslates
_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.
{
  /** 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"
  }
}

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:
LexiconPlain keyFully-qualified key (@ form)Fully-qualified key (MDX form)
_HIERARCHIES_hierarchyNamehierarchyName@dimensionName[dimensionName].[hierarchyName]
_LEVELS_levelNamelevelName@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.
{
    "_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:
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.