Skip to content

Translating the console

The web console ships in English and French. Every text of the interface lives in JSON files, one folder per language; adding a language means adding a folder, nothing else. No API or .NET change is needed.

Where the texts are

web/src/locales/
  en/                     reference language: every key exists here first
    meta.json             { "meta": { "name": "English", "dir": "ltr" } }
    common.json           app, common, nav, errors, validation, login, tls, step-up, permissions, command palette
    dashboard.json        dashboard, watch list
    users.json            user list, profile, attributes, account status, manager, history, export, lifecycle, lockout
    groups.json           groups, membership, group picker
    roles-delegations.json roles, delegations, scopes, operators, simulator, deny reasons, tiers
    audit.json            audit log and audit action names
    config.json           configuration, diagnostics, branding, directory settings
    helpdesk.json         reset, unlock, ticket, caller verification, dictation, recent users, helpdesk settings
    sso.json              sign-in settings (SSO), security (MFA)
    setup.json            setup wizard
    onboarding.json       new user, templates
  fr/                     same files, same keys

The split by file is only for convenience: at start-up web/src/i18n/index.ts finds every locales/<code>/*.json file (Vite import.meta.glob), deep-merges the files of each language into one tree and registers the language. A top-level key must live in only one file of a language; the file names must be the same in every language.

Add a language

  1. Copy web/src/locales/en to web/src/locales/<code>, where <code> is a BCP 47 tag: de, it, es, pt-BR...
  2. Edit meta.json: name is the language's own name as its speakers write it (Deutsch, Italiano), shown in the language picker; dir is ltr, or rtl for right-to-left scripts (the page's dir attribute follows it).
  3. Translate the values, never the keys. Keep the JSON structure and the {{placeholders}} as they are.
  4. Run the tests in web/:
npx vitest run src/i18n          # parity: same keys as English, same placeholders, meta.name present, no empty text
npx vitest run                   # the whole suite

If a newly added file is not picked up, run npx vitest --clearCache once. 5. Build (npm run build). The language appears automatically in the top-bar picker and in the language buttons of the password dictation view. It is chosen at first visit when it matches the browser's preferred languages (de-CH matches de); otherwise the console starts in French. The operator's choice is remembered in the browser.

A partially translated language still works: missing texts fall back to English. The parity test, however, fails until every key is translated, so a release never ships a half-done language by accident.

Rules for translators

  • Placeholders {{name}}, {{count}}, {{dc}}... are replaced at run time. Keep each one, with the same name; you may move it within the sentence. The parity test checks this.
  • Plurals use the i18next suffixes _zero, _one, _two, _few, _many, _other (Unicode CLDR rules for the language). English and French use _one and _other; a language with more forms (Polish, Arabic...) adds the forms it needs, for example key_one, key_few, key_many, key_other, and drops nothing that English has (keep _one and _other). A plural form may leave out {{count}} ("within the next day").
  • No HTML: texts are rendered as plain text. Markup is shown literally.
  • Keep product and protocol names as they are: Active Directory, LDAPS, Teleport, OU, UPN, SID, TOTP.
  • Error codes: the API never sends sentences; it sends codes (not_found, step_up_required, name_taken...). The console translates them by key: errors.<code> for generic errors, and per-feature tables such as editErrors.*, roleErrors.*, operatorErrors.*, groupErrors.*, helpdeskErrors.*, onboarding.errors.*, dirSettings.errors.*, diag.codes.*, denyReasons.*, auditActions.*. An unknown code falls back to the generic errors.unexpected_error, or to the code itself.
  • Dictation and the message sent to the user after a password reset are in helpdesk.json: dictation.speech (the words spoken around the NATO letters, which stay the same in every language: "capital", "digit", the digit and symbol names) and reset.userMessage (the ready-to-send message). The operator can pick any available language for them, independently of the interface language.

What stays in English (or untranslated)

  • Server logs, audit records (action names and stored fields; the audit page translates action names for display), the exported AD delegation script and the configuration files.
  • Directory data: names, descriptions, OU paths, attribute values are shown as stored in AD.
  • Generated passphrases: the server has French and English word lists only; the French list is used when the interface is in French, the English list otherwise.
  • CSV exports use ; as separator when the interface is in French, , otherwise.