Languages and Translations
Horizon UI ships with eight locales:
| Locale | Native name |
|---|---|
en |
English (source) |
de |
Deutsch |
es |
Español |
fr |
Français |
ja |
日本語 |
ko |
한국어 |
pt |
Português |
zh-CN |
中文(简体) |
In the picker English sits on its own line at the top; the other seven are listed alphabetically by code so additions slot in predictably.
English is the immovable default. Every translatable string is authored in English first; the other locales are catalog overlays. Missing keys fall back to English at the leaf, never at the file — a half-translated catalog renders strictly better than English-only.
Coverage by scope
All eight languages are first-class across the surfaces Horizon controls. The scope decides where the translation resolves:
| Scope | Resolves | Languages |
|---|---|---|
| UI chrome — buttons, labels, modals, login, topbar, sidebar | In the browser (vue-i18n) | All 8 |
| Bundled layer-dashboard templates | On the BFF, from sibling overlay catalogs | All 8 |
| Bundled overview templates | On the BFF | All 8 |
| User-maintained dashboards saved to OAP | On the BFF, from per-language translation overlays stored on OAP | English + whatever the author provides |
| OAP-supplied data — service / instance / endpoint / alarm names, tags, log lines, trace ops | not translated | rendered verbatim |
A missing key in any translated scope falls back to English at the leaf, so a partially-translated catalog renders better than English-only.
Picking a language
The locale picker lives in the topbar (next to the theme chip) and on
the login page. Picks are stored in your browser’s local storage; the
choice follows you across logout and back. A fresh device — one that
has never set the locale picker — starts in English. The browser’s
Accept-Language is not consulted; the project policy is “English by
default, opt in to other locales via the picker” so a user on a
non-English browser sees a known starting state and can switch
explicitly.
Switching language re-renders the chrome immediately and triggers a silent re-fetch of any server-side content that ships translatable text (sidebar entries, layer dashboards, overview dashboards) so the new locale takes effect without a page reload.
What is and isn’t translated
| Translated | Not translated |
|---|---|
| UI chrome (buttons, labels, modals, time picker, login page) | OAP-supplied data: service names, instance names, endpoint names, alarm rule names, tag values, log messages, trace span names |
| Bundled layer-dashboard templates (widget titles, KPI labels, group titles, tooltips) | Layer keys (GENERAL, MESH, K8S_SERVICE, …) |
| Bundled overview templates | Metric ids, MQE expressions |
| User-maintained templates (via per-language overlays on the Translations page) | Units (ms, rpm, %) — abbreviated technical conventions |
Layer alias and aliases.* (the display labels we author) |
OAP scope enums (Service, ServiceInstance, Endpoint, Process) |
A layer’s naming-rule dimension label — the caption the topology and 3D map print beside a cluster (namespace, tenant) |
The naming rule’s pattern and its capture-group names |
| A tab widget’s panel names | Widget ids — id names the widget a translation belongs to, so a value-map entry keyed exactly id is left in English |
Product and project names — SkyWalking, Kubernetes, Envoy, Istio, OAP,
MQE, eBPF, Zipkin, OpenTelemetry, gRPC, Apache — are kept in their
original form across every locale. Same for technical abbreviations
like RPM, SLA, Apdex, P50–P99. Operators read these terms
across docs, source, and other SkyWalking surfaces; they need to stay
recognizable.
AI-assisted seeds
Initial translations for the seven non-English locales — de, es, fr, ja, ko, pt, zh-CN — were seeded with AI assistance. Modern models handle SkyWalking’s technical
vocabulary competently, but native speakers may spot phrasings that
could read more naturally. Pull requests with corrections are
welcome — target the matching catalog file:
- UI chrome:
apps/ui/src/i18n/locales/<locale>.json - Shared widget vocabulary (the “lexicon”):
apps/bff/src/i18n/lexicon/<locale>.json - Bundled layer dashboards:
apps/bff/src/bundled_templates/layers/<key>.i18n.<locale>.json - Bundled overview dashboards:
apps/bff/src/bundled_templates/overviews/<id>.i18n.<locale>.json
Translating a custom dashboard
Translations are not embedded in the dashboard you author. The English source dashboard (layer or overview) stays single-language; each locale is a separate translation overlay stored on OAP and edited on the Translations page, one language at a time.
To translate a dashboard you’ve published:
- Open the Translations page, pick the template and the Target language. The fields start from what that language renders today — the version published to OAP, or your own staged local draft where you have one. If nothing is published yet the fields start empty, because that language renders English; Reset to → bundled loads the shipped catalog as a starting point you can review and publish.
- Fill the translated fields — you fill only the fields you want translated. Anything you leave blank falls back to English at the leaf.
- Check diff & push shows a side-by-side diff of your draft against the version live on OAP and publishes the overlay. A template you haven’t edited shows no pending change; anything with no matching field in the source is dropped — the source is the schema.
A translation is attached to its widget, not to the widget’s place in the grid. Reordering, inserting or deleting widgets in the English source leaves every other widget’s translation exactly where it was; the widget you deleted simply takes its translation with it, and a widget you added starts out English until you fill it in. The same holds for the widgets inside a tab panel. A handful of lists have no per-entry identity — a widget’s expression labels and table headers, an overview tile’s KPI rows, a tab widget’s panels — and those still follow their position, so reordering them moves the text with the slot.
The overlay never travels inside the dashboard JSON, so a dashboard exported to another OAP arrives with its English source only; move its translations across on the Translations page if you need them there. Because a translation names its widget, a file exported from one OAP still lands on the right widgets after an import even if the target’s dashboard has since been re-laid-out.
Import / Export
On the Translations page, Export downloads the in-use translation for the picked template and Target language — the version live on OAP, or the shipped seed — as a JSON file. Import reads such a file and loads it as a local draft for that language; review it in the preview, then Check diff & push to publish. Import never writes OAP directly.
Each language is its own file and its own row, so you export/import one language at a time. Source templates and their translations are edited on separate pages, so their import/export are separate too: a template’s export carries the English source only — move its translations across here, per language.
Adding a new locale
Adding a locale Horizon doesn’t ship yet — say Italian (it) — is three steps:
- Add the locale to the
Localeunion andSUPPORTED_LOCALESlist in bothapps/ui/src/i18n/index.tsandapps/bff/src/i18n/types.ts. - Drop in the catalog files:
apps/ui/src/i18n/locales/<locale>.json(UI chrome)apps/bff/src/i18n/lexicon/<locale>.json(shared widget vocabulary)
- From
apps/bff, runpnpm i18n:seed -- --locale <locale>to generate sibling overlays for every bundled layer / overview template. Fill the gaps that the lexicon couldn’t cover.
Two gates catch drift, and CI runs both. Run them together with:
pnpm lint:i18n
The template overlay half (pnpm --filter @skywalking-horizon-ui/bff i18n:validate) rejects catalog entries that no longer match the source template — a field the source has dropped, a widget it no longer has, two entries claiming the same widget — plus non-string values at translatable paths, and lexicon entries that aren’t present in the source en.json lexicon registry.
The UI catalog half checks apps/ui/src/i18n/locales/*.json and fails on four things: a string the UI asks for that en.json doesn’t carry; a Cluster Status module description that en.json doesn’t carry (those are authored on the server side, so no scan of the UI can find them); a key left in en.json that nothing renders any more, which every locale would otherwise carry forever — delete it from all eight catalogs; and any locale whose key set has drifted from en.json.
When you add a new layer, generating its template’s translations is part of that workflow — see Adding a New Layer.