Status: Accepted, stated in the project's written engineering rules. This record covers how Cluster ERP chooses and loads interface languages. The language list is data: the web app reads a manifest, the desktop client lists the JSON files in its folder, and the language picker shows every entry. The web mechanism is explained on internationalisation and languages; the task is on add a language to the web app.
Context¶
The project's written engineering rules state that the application targets every country and that a new language must be a new JSON file without a code change. They replace a binary Turkish and English switch with an N-language picker. They also state the limit: metadata labels, written in Python with en and tr keys, fall back to English in a new language until their dictionaries gain that language. Further rationale is not determined from code.
Decision¶
- The web app reads
webapp/i18n/languages.json, an array ofcodeandnameentries, and offers exactly those languages (webapp/js/i18n.js:1-7,webapp/js/i18n.js:32-44). The manifest holds two entries today (webapp/i18n/languages.json). - Each language has one dictionary file
webapp/i18n/<code>.json, loaded on demand (webapp/js/i18n.js:74-78). - The desktop client lists every
*.jsonfile insrc/cluster_erp/desktop/i18n/and takes the display name from the key_meta.name(src/cluster_erp/desktop/i18n/translator.py:92-99,src/cluster_erp/desktop/i18n/en.json:2). - A metadata label without the current language falls back to English, in both clients (
webapp/js/meta.js:284-288,src/cluster_erp/desktop/i18n/translator.py:57-67).
The flowchart shows how the web app picks a language at start.
flowchart TD
boot["initI18n"] --> man["fetch i18n/languages.json"]
man -->|fails| fb["keep a code-only fallback list"]
man -->|ok| list["languages = manifest entries"]
list --> pick{"saved choice in the list?"}
fb --> pick
pick -->|yes| use["use saved language"]
pick -->|no| nav{"browser language in the list?"}
nav -->|yes| usenav["use browser language"]
nav -->|no| def["use the default, else the first entry"]
use --> load["setLang: fetch code.json, apply"]
usenav --> load
def --> loadIn prose: initI18n loads the manifest and keeps a fallback list when it cannot (webapp/js/i18n.js:32-44, webapp/js/i18n.js:54-62). pick prefers the saved choice, then the browser language, then the default, then the first entry (webapp/js/i18n.js:46-52). setLang loads the dictionary, stores the choice, sets the document language, applies the texts, and announces the change to background apps (webapp/js/i18n.js:74-87).
Consequences¶
- Adding a web language touches only
webapp/i18n/: a dictionary file and one manifest line. - Pickers call
availableLanguages()and never hold a fixed list (webapp/js/i18n.js:22-24).cycleLangsteps through any number of languages (webapp/js/i18n.js:91). - Texts outside the web dictionary, such as metadata labels, appear in English in a new language until their own dictionaries gain it.
- The language choice is a device preference in
localStorageunder one key and is synchronised across duplicated home tiles through thestorageevent (webapp/js/i18n.js:9,webapp/js/i18n.js:67-71).
Where the code enforces it¶
| Mechanism | What it checks | Source |
|---|---|---|
_e2e_lang.mjs |
The picker lists every manifest language and a choice persists; expectations come from the manifest | web-client/_e2e_lang.mjs:1-6, web-client/_e2e_lang.mjs:17-20 |
tests/test_store_catalog_assets.py |
Store screenshots carry captions in every language of the manifest | tests/test_store_catalog_assets.py:1-17 |
tests/test_metadata_driven_rules.py |
Generic web code contains no language branch and no label_tr or label_en column |
tests/test_metadata_driven_rules.py:102-111 |
Related pages¶
- Decision records
- Internationalisation and languages
- Add a language to the web app
- Languages, themes, and accessibility
Verified against commit 92f8051 on 2026-11-13.