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 each dashboard’s embedded i18n block |
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 that carry an i18n block |
Units (ms, rpm, %) — abbreviated technical conventions |
Layer alias and aliases.* (the display labels we author) |
OAP scope enums (Service, ServiceInstance, Endpoint, Process) |
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 zh-CN, es, pt, ja, ko 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
User-maintained dashboards (saved via the admin Layer Dashboards or
Overview Templates editor) can carry an embedded i18n block inside
their configuration JSON:
{
"title": "My Custom Dashboard",
"widgets": [
{ "title": "Top 20 APIs", "tip": "Top endpoints by traffic" }
],
"i18n": {
"zh-CN": {
"title": "我的自定义仪表板",
"widgets": [
{ "title": "前 20 接口", "tip": "按流量排序的前 20 接口" }
]
}
}
}
Rules:
- The
i18n.<locale>block mirrors the English source’s structure; fill only the fields you want translated. - Anything missing falls back to English at render time.
- Keys in the overlay that have no matching field in the source are silently dropped — the source is the schema.
- A
Refill from shared phrasesaction (in the editor) populates the common widget vocabulary from the shared lexicon so you only need to translate your own prose.
Adding a new locale
Adding de, fr, or any other locale 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.
The validate CLI catches drift:
pnpm --filter @skywalking-horizon-ui/bff i18n:validate
It rejects catalog keys that no longer match the source template,
non-string values at translatable paths, and lexicon entries that
aren’t present in the source en.json lexicon registry.
When you add a new layer, generating its template’s translations is part of that workflow — see Adding a New Layer.