-
Notifications
You must be signed in to change notification settings - Fork 1k
I18N
OmniRoute supports 30 languages with full dashboard UI translation, translated documentation, and RTL support for Arabic and Hebrew.
🌐 Languages: 🇺🇸 English | 🇧🇷 Português (Brasil) | 🇪🇸 Español | 🇫🇷 Français | 🇩🇪 Deutsch | 🇮🇹 Italiano | 🇷🇺 Русский | 🇨🇳 中文 (简体) | 🇯🇵 日本語 | 🇰🇷 한국어 | 🇸🇦 العربية | 🇮🇳 हिन्दी | 🇹🇭 ไทย | 🇹🇷 Türkçe | 🇺🇦 Українська | 🇻🇳 Tiếng Việt | 🇧🇬 Български | 🇩🇰 Dansk | 🇫🇮 Suomi | 🇮🇱 עברית | 🇭🇺 Magyar | 🇮🇩 Bahasa Indonesia | 🇲🇾 Bahasa Melayu | 🇳🇱 Nederlands | 🇳🇴 Norsk | 🇵🇹 Português (Portugal) | 🇷🇴 Română | 🇵🇱 Polski | 🇸🇰 Slovenčina | 🇸🇪 Svenska | 🇵🇭 Filipino | 🇨🇿 Čeština
OmniRoute uses a hash-based incremental translator for docs, backed by an
OpenAI-compatible LLM endpoint (typically cx/gpt-5.4-mini through OmniRoute
Cloud):
# Run translations (incremental — only touches changed sources)
npm run i18n:run
# Limit to one locale
npm run i18n:run -- --locale=pt-BR
# Specific files (comma-separated, repo-relative paths)
npm run i18n:run -- --files=CLAUDE.md,docs/architecture/ARCHITECTURE.md
# Force retranslate everything (expensive)
npm run i18n:run -- --force
# Preview what would happen (no API calls, no writes)
npm run i18n:run:dry
# CI gate — exits non-zero if state is drifting
npm run i18n:checkSource of truth. config/i18n.json lists every locale (UI + docs) plus
the RTL set and the docsExcluded codes. The runtime config in
src/i18n/config.ts is a thin adapter over that JSON.
Backend. Configured via env (set in .env, never committed):
| Variable | Purpose |
|---|---|
OMNIROUTE_TRANSLATION_API_URL |
OpenAI-compatible base URL, e.g. …/v1
|
OMNIROUTE_TRANSLATION_API_KEY |
bearer token (kept out of logs) |
OMNIROUTE_TRANSLATION_MODEL |
model id, e.g. cx/gpt-5.4-mini
|
OMNIROUTE_TRANSLATION_TIMEOUT_MS |
optional, default 60000
|
OMNIROUTE_TRANSLATION_CONCURRENCY |
optional, default 4
|
State tracking. .i18n-state.json (committed) keeps SHA-256 hashes per
source + per locale. Drift detection is automatic and deterministic — no API
calls in i18n:check.
Output shape. Each translated file gets a top-level # <heading> (<native>) line, a 🌐 Languages: … bar, an --- separator, and the
translated body. That layout matches what scripts/check/check-docs-sync.mjs
already enforces for llm.txt and CHANGELOG.md mirrors.
The older Python script (scripts/i18n/i18n_autotranslate.py) and the
Google-Translate-backed generator (scripts/i18n/generate-multilang.mjs)
still exist with a deprecation banner. They will be removed in v3.10. The
messages and readme modes of generate-multilang.mjs (UI strings + root
README variants) are not yet handled by the new pipeline and are still used.
| Task | Command |
|---|---|
| Translate docs (LLM) |
npm run i18n:run (preferred — incremental, hash-based) |
| Translate UI strings | node scripts/i18n/generate-multilang.mjs messages |
| Check translation drift | npm run i18n:check |
| Validate a locale | python3 scripts/i18n/validate_translation.py quick -l cs |
| Check code keys | python3 scripts/i18n/check_translations.py |
| Generate QA report | node scripts/i18n/generate-qa-checklist.mjs |
| Visual QA (Playwright) | node scripts/i18n/run-visual-qa.mjs |
Source: diagrams/i18n-flow.mmd
-
UI strings:
src/i18n/messages/en.json(English source, ~2800 keys) -
Locale files:
src/i18n/messages/{locale}.json(30 translations) -
Framework:
next-intlwith cookie-based locale resolution -
Config:
src/i18n/config.ts— defines all 30 locales, language names, flags
- User selects language →
NEXT_LOCALEcookie set -
src/i18n/request.tsresolves locale: cookie →Accept-Languageheader → fallbacken - Dynamic import loads
messages/{locale}.json - Components use
useTranslations("namespace")andt("key")
| Code | Language | RTL | Google Translate Code |
|---|---|---|---|
ar |
العربية | Yes | ar |
bg |
Български | No | bg |
cs |
Čeština | No | cs |
da |
Dansk | No | da |
de |
Deutsch | No | de |
es |
Español | No | es |
fi |
Suomi | No | fi |
fr |
Français | No | fr |
he |
עברית | Yes | iw |
hi |
हिन्दी | No | hi |
hu |
Magyar | No | hu |
id |
Bahasa Indonesia | No | id |
it |
Italiano | No | it |
ja |
日本語 | No | ja |
ko |
한국어 | No | ko |
ms |
Bahasa Melayu | No | ms |
nl |
Nederlands | No | nl |
no |
Norsk | No | no |
phi |
Filipino | No | tl |
pl |
Polski | No | pl |
pt |
Português (Portugal) | No | pt |
pt-BR |
Português (Brasil) | No | pt |
ro |
Română | No | ro |
ru |
Русский | No | ru |
sk |
Slovenčina | No | sk |
sv |
Svenska | No | sv |
th |
ไทย | No | th |
tr |
Türkçe | No | tr |
uk-UA |
Українська | No | uk |
vi |
Tiếng Việt | No | vi |
zh-CN |
中文 (简体) | No | zh-CN |
Edit src/i18n/config.ts:
// Add to LOCALES array
"xx",
// Add to LANGUAGES array
{ code: "xx", label: "XX", name: "Language Name", flag: "🏳️" },Edit scripts/i18n/generate-multilang.mjs — add entry to LOCALE_SPECS:
{
code: "xx",
googleTl: "xx",
label: "XX",
flag: "🏳️",
languageName: "Language Name",
readmeName: "Language Name",
docsName: "Language Name",
},node scripts/i18n/generate-multilang.mjs messagesThis creates src/i18n/messages/xx.json auto-translated from en.json via Google Translate.
Auto-translations are a starting point. Review manually for:
- Technical accuracy
- Context-appropriate terminology
- Proper handling of placeholders (
{count},{value}, etc.)
python3 scripts/i18n/validate_translation.py quick -l xx
python3 scripts/i18n/validate_translation.py diff common -l xxnode scripts/i18n/generate-multilang.mjs docsPrimary auto-translation engine — uses Google Translate free API to generate translations for UI strings, READMEs, and documentation.
node scripts/i18n/generate-multilang.mjs [messages|readme|docs|all]| Mode | What it does |
|---|---|
messages |
Translates missing keys in src/i18n/messages/{locale}.json from en.json
|
readme |
Translates README.md into all locales as README.{code}.md in project root |
docs |
Translates DOC_SOURCE_FILES into docs/i18n/{locale}/{docName}
|
all |
Runs all three modes |
Features:
-
Text protection: Masks code blocks (
```), inline code (`), markdown links/images ([text](url)), HTML tags, tables, and ICU placeholders ({count},{value},{total}, etc.) before translation, then restores them -
Chunked batching: Joins multiple strings with
__OMNIROUTE_I18N_SEPARATOR__delimiters to minimize API calls (max 1800 chars per request) - In-memory cache: Avoids redundant API calls for repeated strings within a session
- Retry logic: Exponential backoff (up to 5 attempts with 300ms × attempt delay) for 429/5xx errors
- Timeout: 20 seconds per request
- Skip existing: If target file already exists, it is NOT overwritten
Important behaviors:
-
docs/i18n/README.mdis regenerated each run — it's an auto-generated index of all docs - Root
README.{code}.mdfiles are only created if they don't exist (skips locales inEXISTING_README_CODES) - Language bars (
🌐 **Languages:** ...) are automatically inserted/updated in all translated docs
Secondary translator — uses any OpenAI-compatible LLM API (including OmniRoute itself) to translate existing docs/i18n/ markdown files. Best for polishing or re-translating docs with better quality than Google Translate.
python3 scripts/i18n/i18n_autotranslate.py \
--api-url http://localhost:20128/v1 \
--api-key sk-your-key \
--model gpt-4oFeatures:
- Scans
docs/i18n/markdown files for English paragraphs - Skips code blocks, tables, and already-translated content
- Sends paragraphs to LLM with technical translation system prompt
- Supports all 30 languages
The omniroute CLI has its own i18n layer separate from the Next.js dashboard.
- Every user-facing string in CLI commands goes through
t("module.key", vars)frombin/cli/i18n.mjs. - Catalogs are JSON files in
bin/cli/locales/— 42 ship out-of-the-box. - Locale falls back to
enfor any missing key, so partial translations are valid. - The source of truth for available locales is
config/i18n.json(shared with the dashboard).
Detection order (first match wins):
| Priority | Source | Example |
|---|---|---|
| 1 |
--lang flag |
omniroute --lang de status |
| 2 |
OMNIROUTE_LANG env var |
OMNIROUTE_LANG=ja omniroute providers |
| 3 |
LC_ALL system env |
auto-detected from terminal locale |
| 4 |
LC_MESSAGES system env |
auto-detected from terminal locale |
| 5 |
LANG system env |
auto-detected from terminal locale |
| 6 | Fallback | en |
Locale codes with underscores (pt_BR) are normalized to hyphen form (pt-BR).
Locale codes are validated against /^[a-zA-Z0-9-]+$/ — path traversal is rejected.
# Set language and save to ~/.omniroute/.env (persists across sessions)
omniroute config lang set pt-BR
# View current language
omniroute config lang get
# List all 42 available languages
omniroute config lang list
# JSON output
omniroute config lang list --output jsonThe saved preference is written atomically to ~/.omniroute/.env and is loaded by the
CLI bootstrap before any command runs.
# Override for one command only (not persisted)
omniroute --lang de providers listNote: the --lang flag does NOT write to the env file — it only affects the current
invocation. Use config lang set to persist.
42 locale files ship in bin/cli/locales/. Full translations: en, pt-BR.
Scaffold-only (all keys fall back to en): bn, gu, he, in, mr, ms, phi, sw, ta, te, ur.
All other 29 locales have common + program keys translated.
- Add the locale entry to
config/i18n.json. - Run
node bin/cli/scripts/generate-locales.mjs— creates the locale file. - Translate the keys (or leave as
{}for en-fallback scaffold). - PRs must add strings to
en.jsonandpt-BR.json; other files are best-effort.
Translation validator — compares any locale JSON against en.json and reports issues.
# Quick check (counts only)
python3 scripts/i18n/validate_translation.py quick -l cs
# Output:
# Missing: 0
# Untranslated: 0
# Ignored (UNTRANSLATABLE_KEYS): 236
# Detailed diff by category
python3 scripts/i18n/validate_translation.py diff common -l cs
python3 scripts/i18n/validate_translation.py diff settings -l cs
# Export to CSV
python3 scripts/i18n/validate_translation.py csv -l cs > report.csv
# Export to Markdown
python3 scripts/i18n/validate_translation.py md -l cs > report.md
# Full report (default)
python3 scripts/i18n/validate_translation.py -l csDetects:
-
Missing keys — keys in
en.jsonbut not in locale file -
Extra keys — keys in locale file but not in
en.json - Untranslated keys — keys where locale value equals English source (excluding allowlist)
- Placeholder mismatches — ICU placeholders that don't match between source and translation
Exit codes:
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | Generic error |
| 2 | Missing strings (hard error) |
| 3 | Untranslated warning (soft) |
Environment: Set TRANSLATION_LANG=cs or use -l cs flag.
Code-to-JSON key checker — scans src/**/*.tsx and src/**/*.ts for useTranslations() calls and verifies all referenced keys exist in en.json.
# Basic check
python3 scripts/i18n/check_translations.py
# Verbose output
python3 scripts/i18n/check_translations.py --verbose
# Auto-fix (adds missing keys to en.json)
python3 scripts/i18n/check_translations.py --fixStatic analysis QA — scans Next.js page files for i18n risk metrics and generates a Markdown report.
node scripts/i18n/generate-qa-checklist.mjsChecks:
- Fixed-width class usage (overflow risk)
- Directional left/right classes (RTL risk)
- Clipping-prone patterns
- Locale parity (missing/extra keys vs
en.json) - README language selector bars in priority locales (
es,fr,de,ja,ar)
Output: docs/reports/i18n-qa-checklist-{date}.md
Visual QA via Playwright — takes screenshots of all dashboard routes in multiple locales and viewports, then evaluates page health.
# Default: es, fr, de, ja, ar on localhost:20128
node scripts/i18n/run-visual-qa.mjs
# Custom base URL and locales
QA_BASE_URL=http://staging.example.com QA_LOCALES=de,fr node scripts/i18n/run-visual-qa.mjs
# Custom routes
QA_ROUTES=/dashboard/settings,/dashboard/providers node scripts/i18n/run-visual-qa.mjsDetects:
- Text overflow
- Element clipping
- RTL layout mismatches
Output: docs/reports/i18n-visual-qa-{date}.md + JSON report
File: scripts/i18n/untranslatable-keys.json
Allowlist of keys that should remain identical to English source. Used by validate_translation.py to avoid false-positive "untranslated" warnings.
{
"description": "Keys that should remain untranslated...",
"keys": [
"common.model",
"common.oauth",
"health.cpu",
...
]
}What belongs here:
- Brand/product names:
landing.brandName,common.social-github - Technical terms/acronyms:
health.cpu,mcpDashboard.pid,settings.ai - ICU/format strings:
apiManager.modelsCount,health.millisecondsShort - Placeholder values:
providers.openaiBaseUrlPlaceholder,cliTools.baseUrlPlaceholder - Protocol names:
common.http,common.oauth,providers.oauth2Label - Navigation sections:
sidebar.primarySection,sidebar.cliSection
To add a key: Edit the keys array in scripts/i18n/untranslatable-keys.json and re-run validation.
The CI pipeline validates all locales on every push and PR:
-
i18n-matrixjob — dynamically discovers all locale files (excludingen.json) -
i18njob — runsvalidate_translation.py quick -l '<lang>'for each locale in parallel -
ci-summaryjob — aggregates results into a dashboard summary
# i18n-matrix: discovers languages
LANGS=$(ls src/i18n/messages/*.json | xargs -n1 basename | sed 's/.json$//' | grep -v '^en$')
# i18n: validates each language
python3 scripts/i18n/validate_translation.py quick -l '${{ matrix.lang }}'Dashboard output:
## 🌍 Translations
| Metric | Value |
|--------|------|
| Languages checked | 30 |
| Total untranslated | 0 |
✅ All translations complete
src/i18n/
├── config.ts # Locale definitions (30 locales, RTL config)
├── request.ts # Runtime locale resolution
└── messages/
├── en.json # Source of truth (~2800 keys)
├── cs.json # Czech translation
├── de.json # German translation
└── ... # 30 locale files total
scripts/
├── i18n/
│ ├── generate-multilang.mjs # Auto-translation engine (Google Translate, 888 lines)
│ ├── generate-qa-checklist.mjs # Static analysis QA
│ ├── run-visual-qa.mjs # Playwright visual QA
│ └── untranslatable-keys.json # Allowlist for validation (236 keys)
├── validate_translation.py # Translation validator
├── check_translations.py # Code-to-JSON key checker
└── i18n_autotranslate.py # LLM-based doc translator
.github/workflows/
└── ci.yml # i18n validation in CI matrix
docs/
├── I18N.md # This file — i18n toolchain documentation
├── i18n/
│ ├── README.md # Auto-generated language index
│ ├── cs/ # Czech docs
│ │ └── docs/
│ │ ├── I18N.md # Czech translation of this file
│ │ └── ...
│ ├── de/ # German docs
│ └── ... # 30 locale directories
└── reports/
├── i18n-qa-checklist-*.md # Static analysis reports
└── i18n-visual-qa-*.md # Visual QA reports
-
Always edit
en.jsonfirst — it's the source of truth -
Run
generate-multilang.mjs messagesto propagate new keys to all locales - Review auto-translations — Google Translate is a starting point, not final
-
Validate before committing —
python3 scripts/i18n/validate_translation.py quick -l <lang> -
Update
untranslatable-keys.jsonif a key should remain in English
- ICU placeholders (
{count},{value},{total},{seconds}) must be preserved exactly - Plural formats (
{count, plural, one {# model} other {# models}}) must maintain structure - The validator detects placeholder mismatches automatically
// Use namespaced keys
const t = useTranslations("settings");
t("cacheSettings"); // maps to settings.cacheSettings in JSON
// Run check_translations.py to verify keys exist
python3 scripts/i18n/check_translations.py --verbose- Arabic (
ar) and Hebrew (he) are RTL locales - Avoid hardcoded
left/rightCSS — usestart/endlogical properties - Visual QA catches RTL layout mismatches via
run-visual-qa.mjs
The generator originally used code: "in" (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 hi. This created an orphaned in.json duplicate of hi.json. Fixed by changing code: "in" to code: "hi" in generate-multilang.mjs and removing the orphaned file.
⚠️ Audit (2026-05-13): Thedocs/i18n/in/directory still exists on disk (full duplicate ofhi/). Translation generator no longer writes to it, but the historical tree was not pruned. Safe to delete withrm -rf docs/i18n/in/after confirming no external links reference the old path.
The docs/i18n/README.md file is completely regenerated by generate-multilang.mjs docs. Any manual edits will be lost. Use docs/guides/I18N.md (this file) for hand-written documentation that should persist.
The untranslatable-keys.json allowlist was moved from an inline Python set in validate_translation.py to an external JSON file for easier maintenance. The validator loads it at runtime.
The generator originally used code: "in" (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 hi. This was introduced in upstream commit 952b0b22c by diegosouzapw. Fixed by changing code: "in" to code: "hi" in the LOCALE_SPECS array and removing the orphaned in.json file.
The quick check now displays the count of ignored keys from untranslatable-keys.json:
Missing: 0
Untranslated: 0
Ignored (UNTRANSLATABLE_KEYS): <varies per release>
OmniRoute · Website · npm · Docker Hub
- Setup Guide
- User Guide
- Features
- Quick Start (Docker)
- Electron Desktop App
- Termux (Android)
- PWA Guide
- MCP Server
- A2A Server
- Agent Protocols
- OpenCode Plugin
- Webhooks
- Cloud Agents
- Skills
- Memory
- Evals
- Gamification
- Guardrails
- Compliance
- Error Sanitization
- Public Credentials
- Route Guard Tiers
- Stealth Guide
- CLI Token Auth