# Translating CaddyUI CaddyUI's interface can be shown in other languages (since v2.62.0, issue #128). English is the source; every other language is a JSON file of translations. **Adding or updating a language is a data-only change — no Go, template or JavaScript edits.** ## Available languages | Code | Language | Maintainer | |---|---|---| | `en` | English (source) | — | | `zh-CN` | 简体中文 (Simplified Chinese) | @chongfenglaosiji (since v2.62.1) | ## The catalog files Catalogs live in [`web/i18n/`](../web/i18n/), one file per language, named by its [BCP 47](https://www.rfc-editor.org/info/bcp47) code: ``` web/i18n/en.json ← English: the source of truth web/i18n/zh-CN.json ← Simplified Chinese web/i18n/de.json ← German ``` Each file is a flat JSON object of **key → text**: ```json { "_meta.name": "简体中文", "nav.proxy_hosts": "代理主机", "page.proxy_hosts.heading": "代理主机", "profile.language.saved": "语言已保存。" } ``` - **`_meta.name`** is the language's own name, shown in the language picker. - **Keys** are stable dotted names. Copy them exactly from `en.json`; never invent new ones in a translation — the build test rejects keys English does not have. - **Missing keys fall back to English**, so a catalog can be partial. An empty string also counts as missing. Translate what you can; the rest stays English until it is done. - **Placeholders** look like `{count}` or `{name}`. Keep every placeholder English uses, spelled the same (you may move them within the sentence). The build test checks this. - Keys starting with `js.` are also available to the browser-side scripts. - Text is plain text — no HTML. It is escaped when rendered. ## Choosing the language - Each person can pick a language under **Profile → Language** — that always wins. - Otherwise, if an administrator set **Settings → General → Default language**, that applies. - Otherwise the browser's `Accept-Language` decides (`zh-TW` or `zh` will use `zh-CN` if that is the closest available), otherwise English. ## Checking your work ```bash go test ./internal/i18n/ ./internal/server/ -run 'Translation|Catalog|Language|Locale' ``` This loads every catalog and fails if a file is not valid JSON, uses a key English lacks, or changes a placeholder. To see it, run CaddyUI, pick the language on your profile and browse. ## What is translated so far The plumbing landed in v2.62.0 with English for the navigation, the top bar and its menus, the user menu, the list-page titles, the profile language picker and the default-language setting. v2.63.0 added about 250 keys: | Prefix | What it covers | |---|---| | `dashboard.*` | the whole Dashboard page | | `js.dashboard.*`, `js.health.*` | texts the Dashboard's script writes (health-dot tooltips, update notice) | | `profile.*`, `profile.msg.*` | the whole Profile page and its saved/error messages | | `settings.section.*` | the Settings navigation: each section's name and description | | `settings.*` (others) | the Settings heading and save buttons, and the whole **General** page | | `status.*` | shared status words — Online, Offline, Enabled, Disabled, … | More of the interface is moved onto keys release by release — the other Settings pages, then the list pages and forms, the large proxy-host form last. When new keys appear in `en.json`, a translation simply shows English for them until it is updated. ## Security settings The Security settings page uses `settings.security.*` for CAPTCHA provider labels and help, trusted proxies and client-IP headers, certificate directories, IP allowlists and blocklists, OIDC, session limits and TOTP policy. Shared status and save labels reuse existing keys. New keys fall back to English in partial catalogs; translations can be contributed separately without template changes. OIDC callback/group help and the TOTP explanation use complete messages with named placeholders. Preserve `{suffix}`, `{claim}` and `{profile}` when translating, but move them where the language requires. Technical form values, role identifiers and environment-variable names must stay unchanged. Regression checks: ```sh go test ./internal/i18n/ ./internal/server/ -run 'I18n|Translation|Catalog|Language|Locale' go build ./... ``` The Security rendering tests cover partial-catalog fallback, form contracts, stored-value escaping, help interpolation and conditional CAPTCHA states. They use a fake Caddy API and do not verify external identity or CAPTCHA providers. ## Sign-in page The sign-in page uses `login.*` keys for its title, labels, reset/invitation notices, sign-in errors and built-in SSO button. The email field reuses `profile.account.email`. Anonymous requests use the site default, then the browser language, then English; no account language is inferred from an email entered before authentication. Custom SSO button labels remain administrator-provided text and are escaped, not translated. The built-in fallback is translated only when no label is set. Authentication, generic credential errors, rate limiting and session behavior are unchanged. Forgot/reset-password pages and the TOTP verification page are separate areas and are not covered by this extraction. `TestI18nLogin*` checks locale selection, notices, partial-catalog fallback, SSO labels, failed/successful sessions and lockout rendering through the router. ## Sending a translation Open a pull request that only adds or edits `web/i18n/.json`. If you maintain a language, mention it in the PR so you can be pinged when new keys appear. The SSO allowed-domain help uses `{example}` for the example subdomain. Keep it in the complete sentence. The private-provider warning retains its strong emphasis.