Conversation
…e and CLI
Every surface a person reads is now bilingual. Four language sources, each
chosen because it is the only thing that surface actually knows:
panel localStorage, else navigator.languages (per browser)
sub page Accept-Language
client bots Telegram language_code, stored per subscriber
admin bot a panel-wide setting (pushes have no update to read from)
CLI English only
Frontend uses react-i18next with typed dictionaries: ru.ts exports
`type Dict = typeof ru` and en.ts is declared `const en: Dict`, so a key in
one and not the other is a tsc error rather than a silent runtime fallback.
Backend sends a KEY plus arguments, never rendered prose — the panel's
language is a per-browser choice the server cannot see. Validation and API
errors travel as {error, code, args}: the panel words the code and falls back
to the message, so a build whose dictionaries lag the server still reads as a
sentence instead of "err.tokenRequired". Health checks, journal catalogs and
audit sections work the same way. Verbatim third-party text (an Xray config
error, an ACME failure) stays free-form, carried in args.detail.
Diagnostics that no localised surface shows — the updater, blocklist fetch,
provider API errors, the SSH install log — are English rather than translated.
They land in logs and in the external REST API, where there is no dictionary.
Guards, because a key is a string resolved at runtime and nothing else can see
a typo in one:
- PluralComplete<T> makes a missing plural form a compile error. English
selects only one/other while Russian needs one/few/many, and the first cut
of this shipped English counts rendering in Russian.
- TestEveryCallSiteKeyExists walks every i18n.T/TN literal key in the repo.
- internal/i18n/dictcheck resolves a dotted key through the dictionary
nesting, so a leaf under the wrong parent fails. Substring matching had
accepted `nodes.update` for `audit.sec.update`.
- Per-package parity tests for every code-carrying writer.
- TestAcceptLanguageAgreesWithNormalize pins the surfaces to one answer: a
German reader got English from the bots and Russian from the page.
Stock plan names and the node-0 display name become English (Free, Trial,
Standard, Master) — they are shipped defaults, and a migration renames them
only where the operator has not.
All code comments are English.
BREAKING CHANGE: response shapes that carried rendered Russian now carry
dictionary keys, so anything reading them has to word them itself.
* GET /api/health — HealthCheck.label → label_key, hint → hint_key, and
detail splits into detail_key + args (detail survives, but only for
verbatim third-party text such as an Xray config error).
* GET /api/settings/abuse — FileInfo.title → title_key.
* The event and webhook catalog endpoints return [{key}] instead of
[{key,label}]; the label is gone.
* AdminAudit.target for a settings row now holds an "audit.sec.*" key
instead of a Russian section name. Rows written before this release keep
their old text and are shown verbatim.
* Validation errors gain "code" and "args" alongside "error". The field is
additive, but many "error" strings changed wording or language.
* The three stock plans are renamed to Free/Trial/Standard and node 0 to
"Master", by migration, only where the operator has not renamed them.
Release-As: 2.0.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every surface a person reads is now bilingual. Four language sources, each
chosen because it is the only thing that surface actually knows:
panel localStorage, else navigator.languages (per browser)
sub page Accept-Language
client bots Telegram language_code, stored per subscriber
admin bot a panel-wide setting (pushes have no update to read from)
CLI English only
Frontend uses react-i18next with typed dictionaries: ru.ts exports
type Dict = typeof ruand en.ts is declaredconst en: Dict, so a key inone and not the other is a tsc error rather than a silent runtime fallback.
Backend sends a KEY plus arguments, never rendered prose — the panel's
language is a per-browser choice the server cannot see. Validation and API
errors travel as {error, code, args}: the panel words the code and falls back
to the message, so a build whose dictionaries lag the server still reads as a
sentence instead of "err.tokenRequired". Health checks, journal catalogs and
audit sections work the same way. Verbatim third-party text (an Xray config
error, an ACME failure) stays free-form, carried in args.detail.
Diagnostics that no localised surface shows — the updater, blocklist fetch,
provider API errors, the SSH install log — are English rather than translated.
They land in logs and in the external REST API, where there is no dictionary.
Guards, because a key is a string resolved at runtime and nothing else can see
a typo in one:
selects only one/other while Russian needs one/few/many, and the first cut
of this shipped English counts rendering in Russian.
nesting, so a leaf under the wrong parent fails. Substring matching had
accepted
nodes.updateforaudit.sec.update.German reader got English from the bots and Russian from the page.
Stock plan names and the node-0 display name become English (Free, Trial,
Standard, Master) — they are shipped defaults, and a migration renames them
only where the operator has not.
All code comments are English.
BREAKING CHANGE: response shapes that carried rendered Russian now carry
dictionary keys, so anything reading them has to word them itself.
detail splits into detail_key + args (detail survives, but only for
verbatim third-party text such as an Xray config error).
[{key,label}]; the label is gone.
instead of a Russian section name. Rows written before this release keep
their old text and are shown verbatim.
additive, but many "error" strings changed wording or language.
"Master", by migration, only where the operator has not renamed them.
Release-As: 2.0.0