Every text the web app shows comes either from a language file in webapp/i18n/ or from a label dictionary in server metadata. This page explains the language manifest, how webapp/js/i18n.js picks and loads a language, how keys, parameters, and plurals resolve, and what happens on a language switch. Adding a language is a task on add a language to the web app.
In short¶
- The available languages are data:
webapp/i18n/languages.jsonliststr(Türkçe) anden(English) (webapp/i18n/languages.json:1-4). en.jsonandtr.jsoneach hold 2,853 keys under 68 top-level groups, and both files have the same key set (counted by flattening both files with a JSON parser and comparing the key sets).t(key, params)resolves a dotted key and fills{name}placeholders; a missing key returns the key itself (webapp/js/i18n.js:101-109).tp(key, n)picks the plural form fromIntl.PluralRules(webapp/js/i18n.js:112-127).- Metadata labels resolve through
resolveLabel, which falls back to English (webapp/js/meta.js:284-288).
How it works¶
Choosing the language¶
initI18n() loads the manifest, then calls setLang(pick()) (webapp/js/i18n.js:54-72). pick() uses, in order (webapp/js/i18n.js:46-52):
- the code stored in local storage under
cluster_lang, if it is in the manifest; - the first two letters of the browser language, if they are in the manifest;
tr(DEFAULT_LANG), if it is in the manifest;- the first manifest entry.
If the manifest cannot be loaded, the list falls back to tr alone so the app still boots (webapp/js/i18n.js:14-16, webapp/js/i18n.js:32-44). If the dictionary cannot be loaded, the app continues with the markup's own text (webapp/js/i18n.js:56-62).
Switching language¶
setLang(code) loads i18n/<code>.json, stores the code, sets lang on the root element, translates the visible markup with apply(), and dispatches cluster:lang-changed (webapp/js/i18n.js:74-87). Three places change the language:
| Place | Behaviour | Source |
|---|---|---|
| Sign-in screen language button | Cycles through every manifest entry | webapp/js/main.js:152-159, webapp/js/i18n.js:91-95 |
| Home status line language chip | A menu of every manifest entry | webapp/js/home.js:167-215 |
Settings › Language (common.language) |
A list with a check mark on the active entry | webapp/js/home.js:3670-3712 |
Listeners of cluster:lang-changed:
gen-api.jsclears every cached option list and label, because server-resolved names depend on the language (webapp/js/gen-api.js:366-371).home.jsmarks every running app as stale; a stale app is redrawn when the user returns to it (webapp/js/home.js:2334-2342,webapp/js/home.js:2501-2511).
Duplicated home tiles follow a change made in another tile through the storage event (webapp/js/i18n.js:63-71).
Applying text¶
apply(root) fills four attribute families from the dictionary (webapp/js/i18n.js:129-144):
| Attribute | Target |
|---|---|
data-i18n |
Text content |
data-i18n-ph |
placeholder |
data-i18n-aria |
aria-label |
data-i18n-title |
title |
Screens built in code call t() directly. apiErrorText translates server error codes through keys of the form error.<code> and uses the server's English message when no translation exists (webapp/js/gen-api.js:11-35).
Language sent to the server¶
Every API call carries Accept-Language with the current code, so names the server resolves (units, categories, reference labels) come back in the same language (webapp/js/api.js:18-22). See reference data and core i18n.
Metadata labels and reference rows¶
Labels in metadata are dictionaries such as {"en": "...", "tr": "..."}. resolveLabel returns the current language, then English (webapp/js/meta.js:283-288). Reference rows that carry one column per language use langLabel, which reads label_<lang>, then label_en, then code (webapp/js/meta.js:416-421). App manifest labels follow the same rule (webapp/js/home.js:520).
Dates and numbers¶
Dates in lists use a fixed YYYY-MM-DD pattern in the user's time zone (webapp/js/prefs.js:150-154). Short date and time on widgets use Intl.DateTimeFormat with the page language, so order and separators come from the browser's locale data (webapp/js/prefs.js:156-170). The status line clock uses toLocaleTimeString and toLocaleDateString with the current language (webapp/js/home.js:110-116).
Why it is built this way¶
- The project's written engineering rules state that language selection is data-driven: a new language is a new JSON file and one line in
languages.json, with no code change. The comment inwebapp/js/i18n.js:4-7states the same rule. - The comment in
webapp/js/api.js:18-21states that the language header was added because a Turkish screen received English unit names from the server. - The comment in
webapp/js/i18n.js:112-115states that plural selection must not testn === 1and takes the category from CLDR data.
Rules and boundaries¶
- The project's written engineering rules require that every user-visible text comes from an i18n key, that both language files are updated together, and that Turkish text uses correct Turkish characters.
- Generic web files must not read per-language columns or branch on a language code.
tests/test_metadata_driven_rules.py:85enforces this for eleven files. - Browser tests:
web-client/_e2e_lang.mjs,web-client/_e2e_lang_background.mjs,web-client/_e2e_intranet_notif_i18n.mjs,web-client/_e2e_ui_a11y.mjs(plurals).
Interaction with other parts¶
- The desktop client has its own translator and dictionaries; see the desktop section.
- Server-side translation helpers are described on reference data and core i18n.
Limits and open points¶
- The language choice is stored in the browser only (
webapp/js/i18n.js:80). It is not part of the user account; see user UI state. - Metadata label dictionaries are written in each module's Python metadata. A language that has a web dictionary but no metadata entries shows the English metadata labels (
webapp/js/meta.js:287).
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| language manifest | languages.json |
The list of language codes and native names the web app offers. | dil manifestosu | webapp/i18n/languages.json:1 |
| translation key | t |
A dotted path into the language file that names one text. | çeviri anahtarı | webapp/js/i18n.js:104 |
| plural form | tp |
The text variant chosen by the CLDR plural category of a number. | çoğul biçimi | webapp/js/i18n.js:116 |
Related pages¶
- Web app platform
- Add a language to the web app
- Data-driven languages
- Reference data and core i18n
- Metadata rendering
Verified against commit 92f8051 on 2026-11-13.