i18n
How translation of the user interface works across all clients
Table of Contents
- Overview
- Structure of the translation tree
- Where the strings come from
- How a client loads the translations
- Using translations in code
- Adding a string to a client
- Localization Editor
- Product-level customization
Overview
Every piece of user-interface text in Pankosmia is looked up by a key in one shared translation tree, so all clients (dashboard, settings, content handlers…) follow the same language. A key has three parts joined by colons: <category>:<section>:<name>, for example pages:core-settings:title. The value behind a key holds one translation per language code (en, fr, es, id…). When a key is not found, the key itself is displayed.
Structure of the translation tree
| Category | Content |
|---|---|
pages | One section per client, named after the client id, filled from the i18n object of its pankosmia_metadata.json. Example: pages:core-settings:title. |
library | Strings of shared libraries such as the RCL. Example: library:pankosmia-rcl:required_field. |
components | Strings of shared interface parts (header, framework messages…). Example: components:framework:no_entry_if_offline. |
branding | Identity of the product. Example: branding:software:name. |
scripture | Names of books and versifications. |
munchers, crunchers, flavors, server, fonts | Other categories of the template, used by specific content handlers and server features. |
Where the strings come from
- The template: the base file
i18n.jsonofresource-core/templates. The desktop app build copies it tolib/templates/i18n.json. It holdslibrary,components,branding,scripture… - Each client: the
i18nobject of itspankosmia_metadata.json. A keytitlein the clientcore-settingsbecomespages:core-settings:title. - The product:
i18nPatch.jsonandi18n-overrides.jsonin theglobalBuildResourcesof the desktop app (see below).
How a client loads the translations
When a client starts, the SpSpa component of the RCL requests /api/i18n/flat from the server and keeps the result in the i18n context. The response is a flat map of key to text, for the language chosen by the user in the Settings client (stored through /api/settings/languages). Components read it with i18nRef.current and look up a key with doI18n.
Using translations in code
Both functions come from pankosmia-lib/i18n. doI18n(key, source) returns the text, or the key itself when it is missing. doInterpolatedI18n(key, values, source) fills the placeholders %1%, %2%… of a text with an array of strings.
import { useContext } from "react";
import { i18nContext } from "pankosmia-rcl";
import { doI18n, doInterpolatedI18n } from "pankosmia-lib/i18n";
const { i18nRef } = useContext(i18nContext);
doI18n("pages:core-settings:title", i18nRef.current);
// "greeting": { "en": "Hello %1%, you have %2% books" }
doInterpolatedI18n(
"pages:my-client:greeting",
["Ada", "3"],
i18nRef.current,
);
doInterpolatedI18n is stricter than doI18n: it throws when the key is missing, when a value is not a string, when the number of values differs from the number of placeholders, or when a placeholder is malformed.
Adding a string to a client
- Add the key to the
i18nobject of the client’spankosmia_metadata.json, with at least theentranslation. - Use it in the code as
pages:<client id>:<key>. - Rebuild the client. Other languages can be added in the file or with the Localization Editor.
{
"id": "my-client",
"i18n": {
"title": { "en": "My client", "fr": "Mon client" }
}
}
Localization Editor
The client core-client-i18n-editor (id i18n-editor) edits the whole translation tree. It reads it from /api/i18n/raw and saves it with a POST to /api/i18n. It only lets you edit text values and add or remove language codes. It requires debug mode (require.debug is true), so it is listed in the Debug section of the menu.
Product-level customization
A desktop app can adjust texts without touching the clients, with two files in its globalBuildResources:
i18nPatch.json: applied at build time bybuildCommon.json top oftemplates/i18n.json. Every entry must already exist in the template, otherwise the build fails. In practice it only holds the product name (branding.software.name.en), which theapp_setupscript keeps equal toAPP_NAME.i18n-overrides.json: copied toapp_resources/product/i18n-overrides.json. It has the same purpose, with the shape{ "pages": { } }(empty by default).