docs/TRANSLATING.md
Rama main · réplica de lectura del repositorio original.
# 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/<code>.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.