## Summary
Replaces the AWS Translate i18n pipeline with a DeepSeek (LLM) backend,
standardizes locale files to BCP-47 naming with an `en-US` base, adds
new languages, and backfills **every locale to 100%**.
## What changed
### Translation engine (DeepSeek)
- Swap AWS Translate → DeepSeek via its OpenAI-compatible API
(`scripts/translations/`). Auth via the `DEEPSEEK_API_KEY` secret;
per-request 60s timeout.
- **Batched** translation (50/call) with **per-string validation**:
interpolation placeholders (`{count}`, `%s`, `@:key`), vue-i18n
**pluralization pipes** (`{n} Day \| {n} Days`), and literal escapes
(`{'@'}`, `{'{'}`, `{'\|'}`) are preserved or the string is rejected and
retried.
- Robust recovery: index-aligned batches, split-on-mismatch to isolate a
single bad string, temperature-perturbed retries, and rejection of
empty/whitespace output (never blanks a label). Removed the old
`TRANSLATION_MAX_KEYS` safety cap.
### Locale naming + base language
- All 15 locale files renamed to **BCP-47 hyphen** form (`en-US.json`,
`zh-CN.json`, `zh-TW.json`, `fr-FR.json`, …) — matching
`navigator.language` / `Intl` / HTML `lang`.
- **English base switched `en-gb` → `en-us`** (app code,
`fallbackLocale`, BCP-47 map, selector, defaults). British-Pound
currency and date-formatting `en-GB` usages left untouched. Existing
`en-gb` cookies fall back cleanly to English — no migration needed.
### New languages
- **Russian (`ru`), Polish (`pl`), Vietnamese (`vi`)** — fully generated
and wired into `localeFileMap`, `APP_LOCALE_TO_BCP47`, and the language
selector.
- **Traditional Chinese (`zh-TW`)** now maintained by the translator
alongside Simplified (`zh-CN`).
- RTL languages (`ar`, `fa`) intentionally **deferred** until the app
has `dir="rtl"` support.
### Full backfill (one-time)
- Every pre-existing locale filled from ~5.9k up to the full **8,269
keys** (~26.9k strings). `.translation_state.json` rebuilt complete, so
future runs are incremental.
### Nav i18n bug fix
- The **Dashboards** and **Data** nav group labels were hardcoded
English (bypassing i18n) and stayed English in every locale. Now
resolved via `t()` (`menu.dashboard`, new `menu.data`), localized and
re-resolving on language change.
## Verification
- Full web unit suite: **39,035 passed / 414 skipped / 0 failed** (1,167
files).
- `type-check` clean (0 TS errors).
- Locale registry sync spec green; all 15 files = 8,269 leaves.
- Translation engine verified live against `deepseek-v4-flash`.
## Notes
- Since all locales are now 100%, the post-merge `update-translations`
workflow will find nothing pending (no follow-up translation PR).
- `ar`/`fa` to be added with RTL support in a follow-up.
Translation Management
Automated translation system for OpenObserve using DeepSeek (an LLM) via its OpenAI-compatible API.
Overview
This system automatically translates the English locale file (en-US.json) into multiple languages during the build process. It intelligently preserves existing translations and only translates new keys, making it safe to run repeatedly.
🚀 How It Works
Automatic workflow triggered by en-US.json changes:
- Developer updates
en-US.jsonand pushes to any branch (main, develop, feature branches) - GitHub Action detects the change and automatically triggers
- Translation script runs using DeepSeek to update all language files
- Changes are committed back to the same branch automatically
- Build workflows use updated files - all subsequent builds have fresh translations
This means translations are always up-to-date without any manual intervention!
Supported Languages
- 🇹🇷 Turkish (tr)
- 🇨🇳 Chinese (zh)
- 🇫🇷 French (fr)
- 🇪🇸 Spanish (es)
- 🇩🇪 German (de)
- 🇮🇹 Italian (it)
- 🇵🇹 Portuguese (pt)
- 🇯🇵 Japanese (ja)
- 🇰🇷 Korean (ko)
- 🇳🇱 Dutch (nl)
How It Works
- Source File: All translations originate from
web/src/locales/languages/en-US.json - Translation: New/changed keys are translated by DeepSeek, in batches, with
interpolation placeholders (
{count},%s,@:linked.key) validated per string - Preservation: Existing translations are never overwritten unless their English source changed
- Nested Support: Handles nested JSON structures correctly
Local Development
Prerequisites
- Python 3.11+
- DeepSeek API key exported as
DEEPSEEK_API_KEY
Setup
# From the web directory
npm run translate:setup
Or manually:
cd scripts/translations
pip3 install -r requirements.txt
Configure the API key for Local Development
export DEEPSEEK_API_KEY=your_api_key
# optional overrides:
export DEEPSEEK_MODEL=deepseek-v4-flash # model id
export DEEPSEEK_BASE_URL=https://api.deepseek.com
export TRANSLATION_BATCH_SIZE=50 # strings per API call
Running Translations
Translate all languages:
# From the web directory
npm run translate
# Or directly
cd scripts/translations
python3 main.py
Translate specific languages:
cd scripts/translations
python3 main.py fr-FR es-ES de-DE
GitHub Actions Workflow
Automatic Translation Updates
The workflow (.github/workflows/update-translations.yml) automatically runs when:
- Trigger: Any push that modifies
web/src/locales/languages/en-US.json - Branches: All branches (
**) - Action:
- Runs Python translation script
- Updates all language JSON files
- Commits changes back to the same branch
- Subsequent builds use the updated files
Setup Requirements
The workflow authenticates to DeepSeek with an API key stored as a repository secret.
GitHub Repository Setup
Add one repository secret:
| Secret | Value |
|---|---|
DEEPSEEK_API_KEY |
Your DeepSeek API key |
Set it under Settings → Secrets and variables → Actions → New repository secret
(or via gh secret set DEEPSEEK_API_KEY). The workflow fails fast with a clear
error if this secret is missing.
Workflow Behavior
graph TD
A[Push Code to Branch] --> B[Update Translations Workflow]
B --> C{en-US.json Changed?}
C -->|Yes| D[Run Translation Script]
C -->|No| E[Skip Translation]
D --> F[Commit Updated Languages]
E --> G[Mark Complete]
F --> G
G --> H[Build Workflows Start]
H --> I[Build with Latest Translations]
Workflow Execution Order:
- Push code →
update-translations.ymlruns - Checks if
en-US.jsonchanged- If YES: Translates and commits back to branch
- If NO: Skips (fast, ~10 seconds)
- If translations were committed → New push event
- Build workflows trigger on the new commit → Use updated translations
Key Features:
- ✅ Always runs - Checks every push for en-US.json changes
- ✅ Smart detection - Only translates if en-US.json actually changed
- ✅ Non-blocking - Quick skip if no translation needed
- ✅ Auto-commit - Updates committed back to same branch
- ✅ Natural flow - Translation commit triggers builds automatically
- ✅ Works everywhere - All branches (main, develop, feature branches)
Manual Workflow Trigger
You can also run translations manually:
- Go to Actions tab in GitHub
- Select Update Translations workflow
- Click Run workflow
- (Optional) Specify specific languages:
fr-FR es-ES de-DE - Translations will be committed to the current branch
File Structure
scripts/translations/
├── README.md # This file
├── main.py # Entry point for translation script
├── translator.py # Core translation logic
└── requirements.txt # Python dependencies
web/src/locales/languages/
├── en-US.json # Source file (English)
├── tr-TR.json # Turkish translations
├── zh-CN.json # Chinese translations
├── fr-FR.json # French translations
├── es-ES.json # Spanish translations
├── de-DE.json # German translations
├── it-IT.json # Italian translations
├── pt-PT.json # Portuguese translations
├── ja-JP.json # Japanese translations
├── ko-KR.json # Korean translations
└── nl-NL.json # Dutch translations
Adding New Languages
- Add language code to
get_supported_languages()intranslator.py - Update the README to reflect the new language
- Run the translation script
Troubleshooting
Workflow Not Detecting en-US.json Changes
Symptom: You changed en-US.json but workflow says "en-US.json not modified"
Solution 1 - Check workflow logs:
- Go to Actions → Update Translations → Click the run
- Look at "Check if en-US.json was modified" step
- It shows debug info: event type, before/after SHAs, and changed files
Solution 2 - Debug detection:
# Locally check what git sees
git show --name-only --pretty="" HEAD
# Should show web/src/locales/languages/en-US.json
# Check last push
git diff HEAD~1 --name-only
Root cause: The detection uses git diff to compare commits. If:
- Multiple commits in one push → Uses
github.event.beforeandgithub.sha - First commit on branch → Uses
git show HEAD - Manual trigger → Uses
git show HEAD
API Key Error
DEEPSEEK_API_KEY is not set — cannot reach the translation service.
Solution: Set the DEEPSEEK_API_KEY secret (CI) or environment variable (local).
Import Error
ModuleNotFoundError: No module named 'openai'
Solution: Run npm run translate:setup or pip3 install -r requirements.txt
Translation Quality Issues
- Machine translations are not perfect
- Review translations before merging PRs
- Consider manual review for critical UI text
- Native speakers should review translations
Build Has Old Translations
Symptom: Build doesn't have the latest translations
Solution: Check if translation workflow ran and committed:
- Go to Actions → Find the Update Translations run
- Check if it detected en-US.json changes and committed translations
- If translations were committed, the commit should trigger a new build
- The new build will have updated translations
Workflow Example
Scenario: Adding New UI Text
-
Developer adds new text to
en-US.json:{ "dashboard": { "newFeature": "This is a new feature" } } -
Merge the
en-US.jsonchange tomain:git add web/src/locales/languages/en-US.json git commit -m "feat: add new dashboard feature text" # open a PR and merge to main -
Workflow automatically (on
mainonly):- Triggers because
web/src/locales/languages/en-US.jsonchanged - Runs translation script
- Translates only the new or modified keys to all 10 languages
- Commits updated
fr-FR.json,es-ES.json, etc. plus.translation_state.json
- Triggers because
-
Build workflows:
- Use the newly updated translation files
- No additional steps needed
Why
mainonly? Running on every feature branch re-translated the same strings repeatedly (per branch, per rebase, again on merge). Gating tomaintranslates each string once, when it actually lands.
Change detection (.translation_state.json)
scripts/translations/.translation_state.json records, per locale, a hash of the
English source each translated value was derived from. On every run the script:
- Translates a key only when it is new, missing in a target file, or its English source text changed since the last run (so editing an existing label re-translates it).
- Keeps already-translated text whose source is unchanged — it is never re-sent to the API, and English is never "translated" to English.
- Prunes keys that were removed from
en-US.json. - Bootstraps safely: the first run after this file is introduced adopts existing translations as-is (no costly full re-translation, no overwriting manual fixes).
Commit .translation_state.json together with the translation files — it is the
source of truth that keeps subsequent runs incremental.
Best Practices
- Review Commits: Check auto-generated translation commits for accuracy
- Test in UI: Verify translations display correctly in the application
- Manual Fixes: Manual edits to a key are preserved until its English source changes
- Context Matters: Some terms may need manual translation for proper context
- Land on
main: Translations are generated whenen-US.jsonis merged tomain
Cost Considerations
Translation is billed per token by DeepSeek. The whole en-US.json is ~205k
characters (~8,300 strings); a full 10-language rebuild is a one-time cost, and
day-to-day runs only translate the handful of new/changed keys per en-US.json
merge.
Cost Optimization:
- ✅ Only new or modified keys are translated (unchanged text is never re-sent)
- ✅ Strings are sent in batches (
TRANSLATION_BATCH_SIZE, default 50) to cut request overhead - ✅ Runs on
mainonly, and only whenen-US.jsonchanges (no per-branch re-billing) - ✅ Superseded runs are cancelled (
concurrencywithcancel-in-progress) - ✅ Failed API calls / placeholder-mismatched outputs are retried next run, not silently kept
Alternative Translation Services
To use a different backend, modify translate_batch() in translator.py. It uses
the OpenAI-compatible chat-completions API, so any provider exposing that interface
(OpenAI, DeepSeek, or a self-hosted model) drops in by changing DEEPSEEK_BASE_URL
and DEEPSEEK_MODEL.
Support
For issues or questions:
- Check this README first
- Review existing GitHub issues
- Create a new issue with the
translationslabel