This guide adds a new interface language to the web app in webapp/i18n/. The language list is data, so the web app code does not change. The guide also lists the texts that do not come from the web dictionary and therefore stay in English until they are translated at their source. Background is on internationalisation and languages.
Prerequisites¶
- The language has a two-letter code that browsers report, for example
de.pick()compares the first two letters of the browser language with the manifest codes (webapp/js/i18n.js:46-52). - You can edit files in
webapp/i18n/and deploy the web app.
Steps¶
-
Copy
webapp/i18n/en.jsontowebapp/i18n/<code>.json. The file name must be the language code, becausesetLangloads../i18n/<code>.json(webapp/js/i18n.js:74-78). -
Translate every value and keep every key.
en.jsonhas 2,853 keys; a missing key shows the key itself on screen (webapp/js/i18n.js:101-106). -
Keep each
{name}placeholder unchanged inside the translated text.t()fills placeholders by name (webapp/js/i18n.js:107-108). -
For texts with plural forms, provide the plural categories your language uses as sub-keys (
one,two,few,many,other).tp()picks the category withIntl.PluralRulesand falls back toother(webapp/js/i18n.js:112-127). -
Translate error texts under
error.<code>. When a key is missing, the server's English message is shown instead (webapp/js/gen-api.js:16-27). -
Add one entry to
webapp/i18n/languages.jsonwith the code and the language's own name. Entries without a stringcodeandnameare ignored (webapp/js/i18n.js:32-44).
Illustrative manifest built from the existing entries in webapp/i18n/languages.json:1-4, with a placeholder for the new language:
json
[
{ "code": "tr", "name": "Türkçe" },
{ "code": "en", "name": "English" },
{ "code": "<code>", "name": "<native name>" }
]
- Deploy. The asset stamp is a digest over every file in
webapp/, so the new files get new URLs and browsers load them on the next page load (src/cluster_erp/core/asset_stamp.py:30-42). See cache and asset stamp.
Verify the result¶
- Settings › Language lists the new entry by its native name (
webapp/js/home.js:3670-3712). - The language button on the sign-in screen cycles through it (
webapp/js/main.js:152-159). - After choosing it, the root element's
langattribute is the new code and static texts change at once (webapp/js/i18n.js:79-82). - Dates on widgets follow the new locale through
Intl.DateTimeFormat(webapp/js/prefs.js:156-170).
Troubleshooting¶
| Symptom | Cause in code | Fix |
|---|---|---|
| The language is not offered | The manifest entry lacks a string code or name, or the manifest is not valid JSON (webapp/js/i18n.js:32-44) |
Correct the entry |
| Choosing the language changes nothing | setLang throws when <code>.json cannot be loaded (webapp/js/i18n.js:76-77) |
Check the file name and that the file is valid JSON |
| Keys appear on screen | The key is missing from the new file (webapp/js/i18n.js:106) |
Add the key |
| Labels from modules stay in English | Metadata labels are dictionaries in Python module metadata; resolveLabel falls back to English (webapp/js/meta.js:284-288) |
Add the language to the module metadata labels |
| Reference rows stay in English | langLabel reads label_<code> columns and falls back to label_en (webapp/js/meta.js:418-421) |
Provide the column data for the language |
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| language dictionary | <code>.json |
The file of translated texts for one language, keyed by dotted keys. | dil sözlüğü | webapp/js/i18n.js:76 |
Related pages¶
- Web app platform
- Internationalisation and languages
- Data-driven languages
- Reference data and core i18n
Verified against commit 92f8051 on 2026-11-13.