Ctrl K
ComunidadGuías…

docs/TRANSLATING.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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.