Languages
Usage guide of i18n.text: translation tables, ICU messages with plurals, language choice, settings and defaults, service, adding a language by a mod, engines.
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 <kit id>.<key> (ui.menu.play, settings.graphics.field.vsync, hud.action.wave…): a project table overrides them.
Turn it on #
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:
{ "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}}"
} } }
idis the language (fr,de,pt-BR,zh-Hant) unlesslangsays it;nameis 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-BRtopt), then fromfallback, 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:
{ "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() |