Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Decision records

Explanation

Data-driven languages

Decision record for the rule that a new Cluster ERP interface language is a new JSON file plus one manifest line, with no code change.

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 of code and name entries, 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 *.json file in src/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 --> load

In 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). cycleLang steps 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 localStorage under one key and is synchronised across duplicated home tiles through the storage event (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

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13