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, P50P99. 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 phrases action (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:

  1. Add the locale to the Locale union and SUPPORTED_LOCALES list in both apps/ui/src/i18n/index.ts and apps/bff/src/i18n/types.ts.
  2. Drop in the catalog files:
    • apps/ui/src/i18n/locales/<locale>.json (UI chrome)
    • apps/bff/src/i18n/lexicon/<locale>.json (shared widget vocabulary)
  3. From apps/bff, run pnpm 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.