# Texts and languages (i18n.text) Every text of a game in the player's language: translation tables, plural and gender messages, formatted numbers and dates, and a Language tab in the options. Readable offline: `gripforge docs i18n-text`, or the MCP tool `gripforge_docs` (topic `i18n-text`). ## What it is for The kit holds one message table per language in the project data, resolves a key to a message with a language fallback, formats it with ICU MessageFormat (CLDR plural rules per language, select, ordinals, numbers, currencies, dates), and follows the system language or the player's choice. The message engine is one implementation ported line for line, so web, Godot, Unity and Unreal give the same results. It is installed by default in every new game, with `ui.menu`, `input.remap` and `settings.graphics`. The kits' own texts are already there in English and French under `.` (`ui.menu.play`, `settings.graphics.field.vsync`, `hud.action.wave`…): a project table overrides them. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "i18n.text" } gripforge_gamekit_configure { project_id: "gkp_…", id: "i18n.text", config: { "language": "auto", "fallback": "en", "languages": ["en", "fr", "de"] } } gripforge_game_project { project_id: "gkp_…", action: "data", collection: "translations", documents: [ … ] } ``` It requires only `game.engine`. The full reference is `gripforge kit api i18n.text`. ## Settings and defaults | Key | Default | Meaning | | --- | --- | --- | | `language` | `auto` | language at the first start; `auto` follows the system language | | `fallback` | `en` | language of messages missing in the player's language, and when the system language is not offered | | `languages` | `[]` | languages offered, in this order; empty means every language that has a table | | `currency` | `USD` | currency of `{x, number, currency}` (ISO 4217) | | `timeZone` | `local` | `local` or `utc` for dates and times | | `optionsTab` | `true` | show the Language tab in the Options screen of `ui.menu` | ## Tables A table is a document of the `translations` collection: ```json { "id": "de", "name": "Deutsch", "strings": { "ui": { "menu": { "play": "Spielen", "options": "Optionen" } }, "game": { "coins": "{count, plural, one {# Münze} other {# Münzen}}", "greet": "{gender, select, female {Willkommen, Heldin} male {Willkommen, Held} other {Willkommen}}" } } } ``` - `id` is the language (`fr`, `de`, `pt-BR`, `zh-Hant`) unless `lang` says it; `name` is shown in the Language tab. - Keys are hierarchical: nested objects or dotted keys (`"ui.menu.play"`) are the same. - Several documents may fill the same language; a later one overrides an earlier one, key by key. - Resolution: a message missing in the language comes from its base language (`pt-BR` to `pt`), then from `fallback`, then the key itself is shown. - `keys('en')` lists every key of the kits, `missing('de')` what a language still lacks, `check()` the messages that do not parse. To add a language, add a document with every key of `keys('en')` and of the game's own texts, with the plural categories of that language (Russian and Polish `one few many other`, Arabic `zero one two few many other`, Japanese `other` only). It appears in the Language tab at once. ## Messages (ICU MessageFormat) | Syntax | Example (en) | | --- | --- | | `{name}` | `Hello {name}` gives Hello Ada | | `{n, plural, =0 {…} one {…} few {…} many {…} other {…}}`, `#` is the number | `{n, plural, one {# coin} other {# coins}}` gives 1 coin, 1,000 coins | | `{n, plural, offset:1 …}` | `=0`, `=1` match the value; categories and `#` use value minus offset | | `{n, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}` | 21st, 112th | | `{g, select, female {…} male {…} other {…}}` | gender or any keyword | | `{n, number}`, `integer`, `percent`, `currency` | 1,234.5, 1,235, 25%, $1,234.50 | | `{n, number, ::currency/EUR}`, `::.00`, `::.##`, `::percent .0`, `::group-off` | EUR 1,234.50, 3.10, 3.1 | | `{d, date, short}` (also `medium`, `long`, `full`), `{d, time, short}` (`medium`) | milliseconds since 1970 UTC | | `''` and `'{literal}'` | an apostrophe, quoted syntax | Plural rules follow CLDR 48 for 37 languages (af, ar, bg, bn, ca, cs, da, de, el, en, es, et, fa, fi, fr, he, hi, hu, id, it, ja, ko, ms, nb, nl, no, pl, pt, ro, ru, sk, sv, th, tr, uk, vi, zh; `other` only for any other). Number, currency, percent, date and time data exist for en, fr, de, es, it, pt, nl, ru, uk, pl, cs, ar, ja, zh, ko and tr (Latin digits; another language formats like its fallback). A missing parameter stays `{name}`; a message that does not parse is shown as it is. ## Language of the player With `language: "auto"` the kit follows the system language; the player picks another in Options > Language (the `language` tab of `ui.menu`, which replaces its General tab). The choice is kept in the player settings, outside game saves, and applies at once: the event `i18n.language { language, preference, previous }`, the variables `i18n.language` and `i18n.preference`, and menus, options, HUDs, prompts and dialogues are drawn again. A game that wants one language sets `language` and lists only that one in `languages`. ## Service `t(key, params?, lang?)`, `has(key, lang?)`, `text(textOrKey, params?, lang?)` (a data text that may be a key: a dialogue line, an objective, an interaction label), `format(message, params?, lang?)`, `plural(n)`, `ordinal(n)`, `number(n, style?)`, `currency(n, code?)`, `date(ms, style?)`, `time(ms, style?)`, `language()`, `preference()`, `setLanguage(pref)`, `languages()` (`{ code, name }`), `systemLanguage()`, `setLocale(locale)`, `setTimeZoneOffset(minutes)`, `addStrings(lang, strings)`, `keys(lang?)`, `missing(lang)`, `check()`, `strings(lang?)`; options tab `language` (`optionFields(lang)`, `setOption(id, value)`). ## The other kits `ui.menu`, `input.remap`, `settings.graphics`, `hud.action`, `hud.open_world`, `npc.dialogue`, `mission.system` and `interaction.system` draw their texts through `i18n.text` when it is installed (their own English and French tables otherwise). Data texts may be keys: a dialogue line, a choice, a mission title or an interaction label equal to a key is shown translated through `text()`, so a game written with keys is translated by its tables alone, and saves keep the keys. ## Hooks and modding A service: it decides nothing of the game, so it declares no hook. A mod ships texts through the collection `translations`: ```json { "data": { "translations": { "add": [{ "id": "de", "name": "Deutsch", "strings": { "game": { "title": "Das Dorf" } } }, { "id": "mymod_fr", "lang": "fr", "strings": { "mymod.hello": "Bonjour" } }], "patch": [{ "id": "fr", "set": { "strings": { "game": { "title": "Le Bourg" } } } }] } } } ``` `add` needs a free document id; `patch` changes a document of the project (the kits' own default texts are not documents: override them with an added document of the same `lang`). A mod may set `language`, `fallback`, `languages` and `currency`. Not moddable: `timeZone` and `optionsTab`. The player's own choice always wins over a `language` a mod sets. ## Engines | Engine | How to use it | | --- | --- | | Web and native player | the service above | | Godot | node `GfI18nText` (`t()`, `text()`, `format()`, signal `language_changed`); every table is also registered with `TranslationServer`, so `tr("ui.menu.play")` works | | Unity | `GfI18nText` component; `GfI18n.T("game.coins", ("count", 3))` from any script; event `LanguageChanged`; no Localization package needed | | Unreal | `UGfI18nTextComponent`; Blueprint nodes `T`, `Text`, `Format`, `Set Language`, `Get Languages`, event `On Language Changed` | ## When it does not do what you expect | Symptom | Cause and fix | | --- | --- | | The key shows instead of the text | missing in the language, its base language and `fallback`: `missing('de')` lists them | | A language is not offered | `languages` lists others, or no table exists for it | | A plural is wrong | the language needs its CLDR categories (`few`, `many`…) in the message | | The game starts in the wrong language | the player's saved choice wins over `language`; clear it in Options > Language | | A message is shown raw | it does not parse: run `check()` |