lokit is a universal localization manager with AI-powered translation. It supports gettext PO, po4a documentation, i18next JSON, vue-i18n JSON, Android strings.xml, YAML, Markdown, Java .properties, Flutter ARB, JS-KV, desktop entries, and polkit policy files configured via lokit.yaml.
- Config-first workflow —
lokit.yamlis required and acts as the single source of truth - Multi-target support — one config can define many translation targets across different formats
- AI translation — 8 providers including GitHub Copilot, Gemini, OpenAI, Ollama, and more
- Translation statistics — per-target progress tracking
- Smart PO management — extract, merge, update with
xgettextandmsgmerge - Native auth flows — GitHub Copilot (device code), Gemini (browser), and OpenAI (browser OAuth or device code)
- Parallel translation — concurrent API requests with configurable chunking
| Format | Extension | Use case |
|---|---|---|
| gettext | .po / .pot |
Source code strings (shell, Python, C, Go) |
| po4a | .po + po4a.cfg |
Documentation / manpages |
| i18next | .json |
Web apps with i18next |
| vue-i18n | .json |
Vue apps with nested JSON locales |
| android | strings.xml |
Android resource files |
| yaml | .yaml / .yml |
YAML key-value translations |
| markdown | .md |
Markdown document translation |
| properties | .properties |
Java application translations |
| flutter | .arb |
Flutter Application Resource Bundle |
| js-kv | .js |
JavaScript assignment key-value translations |
| desktop | .desktop |
freedesktop desktop entry translations |
| polkit | .policy |
PolicyKit XML policy translations |
| Provider | Auth |
|---|---|
| GitHub Copilot | OAuth (device code) |
| Gemini Code Assist | OAuth (browser) |
| Google AI (Gemini) | API key |
| Groq | API key |
| OpenCode | API key (optional) |
| OpenAI | browser OAuth, device code, or API key |
| Ollama | none (local) |
| Custom OpenAI | API key |
curl -fsSL https://raw.githubusercontent.com/minios-linux/lokit/refs/heads/master/install.sh | bashgit clone https://github.com/minios-linux/lokit.git
cd lokit
make buildgo install github.com/minios-linux/lokit@latestlokit versionCreate lokit.yaml:
source_lang: en
languages: [ru, de]
targets:
- name: app
format: i18next
from: [public/translations/en.json]
to: public/translations/{lang}.json# Check configured targets and translation status
lokit status
# Create or update translation files for configured targets
lokit init
# Authenticate
lokit auth login --provider copilot
# Translate all languages
lokit translate --provider copilot --model MODEL_NAMECreate a lokit.yaml in the project root:
languages: [de, es, fr, ru]
source_lang: en
targets:
- name: app
format: gettext
from: [src/**/*.sh]
template: po/messages.pot
to: po/{lang}.po
- name: docs
format: po4a
root: manpages
from: [po4a.cfg]
- name: website
format: i18next
root: submodules/site
from: [public/translations/en.json]
to: public/translations/{lang}.json
languages: [de, es, fr, pt-BR, ru]
- name: mobile
format: flutter
from: [lib/l10n/app_en.arb]
to: lib/l10n/app_{lang}.arbThen:
lokit status # Shows all targets with stats
lokit translate --provider copilot --model MODEL_NAME # Translates everythinglokit.yaml is required. lokit uses it as the sole source of truth.
A JSON Schema is available for editor autocompletion and validation.
To enable it in VS Code, add to the top of your lokit.yaml:
# yaml-language-server: $schema=./lokit.schema.jsonReference:
# Default languages for all targets
languages: [de, es, fr, id, it, pt, pt-BR, ru]
# Source language (default: en)
source_lang: en
# Optional default provider for `lokit translate`
provider:
id: copilot
model: MODEL_NAME
# base_url is supported for custom-openai and ollama only
# base_url: http://localhost:11434
# prompt: "Translate to {{targetLang}} with concise style."
# settings:
# temperature: 0.3
# Translation targets
targets:
- name: my-app # Display name (required)
format: gettext # Format (required, see below)
root: . # Working directory relative to config (default: .)
# --- gettext options ---
# For gettext, use source globs plus a POT template and PO output path:
# from: [src/**/*.sh]
# template: po/messages.pot
# to: po/{lang}.po
# keywords: [_, N_, gettext] # xgettext keywords
# --- po4a options ---
# For po4a, point from at the po4a config file:
# from: [po4a.cfg]
# --- i18next / vue-i18n options ---
from: [public/translations/en/common.json]
to: public/translations/{lang}/common.json
# --- android options ---
# to: app/src/main/res # Android res/ directory
# --- yaml / properties / flutter / js-kv options ---
# from: [translations/locale_en.yaml]
# to: translations/locale_{lang}.yaml
# --- markdown options ---
# from: [docs/**/*.md]
# to: translations/{lang}/{path}
# --- overrides ---
languages: [de, es, fr] # Override global language list
source_lang: en # Override source language
prompt: "Custom prompt" # Override system prompt for AIgettext — For code translation (shell, Python, C, Go). Reads .pot templates, translates to .po files.
po4a — For documentation (manpages, AsciiDoc). Works with po4a.cfg and its PO directory.
i18next — Flat JSON key/value translations with optional _meta and translations sections.
vue-i18n — For Vue applications with nested JSON locale files (for example buttons.save: "Save" in JSON object form).
For source-backed key/value formats (vue-i18n, yaml, properties, flutter, js-kv, markdown),
lokit translates only keys that have non-empty source text. Keys with missing/empty source
values are skipped (for example optional fields like longDescription that are absent in source records).
android — For Android applications. Translates strings.xml resource files.
yaml — For YAML key-value translation files. Define from and to (example: to: translations/{lang}.yaml).
markdown — For Markdown document translation. Supports source globs with to: translations/{lang}/{path} and filename-suffix layouts such as to: README.{lang}.md.
properties — For Java .properties files. Define from and to (example: to: messages_{lang}.properties).
flutter — For Flutter ARB files. Define from and to (example: to: app_{lang}.arb).
js-kv — JavaScript key/value files (for example window.i18n = { ... }). Define from and to (example: to: {lang}.js).
desktop — freedesktop .desktop files with per-language fields in one file.
polkit — PolicyKit .policy XML files with per-language localized tags in one file.
For file-per-language targets (i18next, vue-i18n, yaml, properties, flutter, js-kv),
you normally define a source file in from and an output template in to.
fromis relative torootand points to the source-language filetois relative toroottomust contain{lang}- examples:
locales/{lang}.json,locales/{lang}/common.json,locale_{lang}.properties,app_{lang}.arb
targets:
- name: frontend
format: i18next
from: [frontend/src/i18n/en/common.json]
to: frontend/src/i18n/{lang}/common.json
- name: java
format: properties
from: [src/main/resources/i18n/messages_en.properties]
to: src/main/resources/i18n/messages_{lang}.propertiesFor catalog-style projects (one source index file + many per-record translation files),
from can be an object instead of a file list.
from.indexpoints to the source index filefrom.recordsselects the records array ($or$.field)from.keyprovides values for{id}intofrom.fieldslists translatable fields copied from each record
Example (catalog index mode):
targets:
- name: recipes
format: vue-i18n
root: web/public/data
to: recipe-translations/{lang}/{id}.json
languages: [de, es, fr]
from:
index: recipes.json
records: "$"
key: id
fields: [name, description, longDescription]In index mode, lokit writes flat JSON objects per record/language and skips missing or empty source fields.
Notes:
tomust include{id}in index mode.--target recipesselects all expanded records likerecipes/<id>.- You can still target a single record with
--target recipes/<id>.
Shows project info and translation statistics for targets defined in lokit.yaml.
lokit status # Current directory
lokit status --root ./my-project # Specific projectExtracts translatable strings and creates/updates translation files:
lokit init # All languages
lokit init --lang ru,de # Specific languages- Runs
xgettextfor gettext projects - Runs
po4a --no-translationsfor po4a projects - Creates missing language files for i18next, vue-i18n, yaml, properties, flutter, js-kv
- In index mode, creates per-record files from
from.indexfor each target language - Idempotent — safe to run repeatedly
Translates using AI:
lokit translate --provider copilot --model MODEL_NAME
# All flags:
--provider string AI provider (or set provider.id in lokit.yaml)
--model string Model name (or set provider.model in lokit.yaml)
--lang, -l string Languages (comma-separated, default: all untranslated)
--parallel[=N] Enable parallel translation (default workers: 3)
--chunk int Entries per API request (0 = all at once)
--all, -a Translate all entries, including already translated ones
--fuzzy Translate fuzzy entries (default: true)
--dry-run Show what would be translated
--force, -f Ignore lock file, re-translate all entries
--prompt string Custom system prompt ({{targetLang}}/{{sourceLang}} placeholders)
--proxy string HTTP/HTTPS proxy URL
--api-key string API key (or provider env var)
--base-url string Custom API endpoint (custom-openai, ollama)
--timeout duration Request timeout (0 = provider default)
--retries int Retries on rate limit (default: 3)
--delay duration Delay between translation requests
--verbose, -v Detailed loggingManage provider credentials:
# Interactive login
lokit auth login
# Specific provider
lokit auth login --provider copilot
lokit auth login --provider google
# List stored credentials
lokit auth list
# Remove credentials
lokit auth logout --provider copilot
lokit auth logout # Remove allShow version, commit hash, and build date.
# lokit.yaml
languages: [de, es, fr, id, it, pt, pt-BR, ru]
targets:
- name: scripts
format: gettext
from: [scripts/**/*.sh]
template: po/messages.pot
to: po/{lang}.po
- name: manpages
format: po4a
root: manpages
from: [po4a.cfg]
- name: cli-tool
format: gettext
root: submodules/my-tool
from: [src/**/*.py]
template: po/messages.pot
to: po/{lang}.polokit translate --provider copilot --model MODEL_NAME --parallel=10# lokit.yaml
languages: [de, es, fr, ru, ja, zh]
source_lang: en
targets:
- name: app
format: flutter
from: [lib/l10n/app_en.arb]
to: lib/l10n/app_{lang}.arblokit init
lokit translate --provider copilot --model MODEL_NAME# lokit.yaml
languages: [de, es, fr, ru]
source_lang: en
targets:
- name: app
format: properties
from: [src/main/resources/messages_en.properties]
to: src/main/resources/messages_{lang}.propertieslokit translate \
--provider copilot --model MODEL_NAME \
--parallel=10 --chunk 50 \
--proxy "http://proxy:8080"lokit translate --provider copilot --model MODEL_NAME --lang ru,delokit translate --provider ollama --model MODEL_NAMElokit auth login --provider openai
lokit auth login --provider openai --headless
lokit auth login --provider openai --auth-method oauth
lokit auth login --provider openai --auth-method device
lokit auth login --provider openai --auth-method api-key
lokit translate --provider openai --model MODEL_NAMEOAuth/device auth uses the ChatGPT Codex endpoint, so choose a model supported by your OpenAI OAuth session. Use API key auth for models that are not available via OAuth, or custom-openai for custom OpenAI-compatible endpoints.
lokit auth login --provider custom-openai
# or non-interactively:
# lokit auth login --provider custom-openai --api-key YOUR_KEY --base-url https://api.example.com/v1
lokit translate --provider custom-openai --model my-modellokit translate --provider copilot --model MODEL_NAME --dry-runAll user data is stored in ~/.local/share/lokit/ (respects $XDG_DATA_HOME):
auth.json— OAuth tokens and API keys (permissions:0600)
Lookup order (highest → lowest):
--api-keyflag- Provider-specific environment variable (
GOOGLE_API_KEY,GROQ_API_KEY,OPENAI_API_KEY,CUSTOM_OPENAI_API_KEY,OPENCODE_API_KEY) - Stored credentials in
auth.json
Each target format has a built-in system prompt optimized for its structure (gettext, po4a/docs, i18next, vue-i18n, android, yaml, markdown, properties, flutter, js-kv, desktop, polkit). Prompts can be customized in two ways:
- Per target — set
prompt:in the target config inlokit.yaml - Per run — use the
--promptflag on the command line
The --prompt flag takes priority over the lokit.yaml target prompt, which takes priority over the built-in default. Use {{targetLang}} as a placeholder for the target language name.
lokit tracks MD5 checksums of source strings in a lokit.lock file (stored next to lokit.yaml). On subsequent runs, only new or changed strings are sent to the AI provider, saving tokens and time.
- Automatic — no configuration needed. The lock file is created on the first
lokit translaterun and updated after each translation. - Lock commands — manage lock data explicitly when needed:
lokit lock status lokit lock init lokit lock clean --dry-run lokit lock reset --target ui --lang ru
lokit lock cleanremoves stale source-string checksums and orphan target namespaces that no longer exist inlokit.yaml. With--target, cleanup is limited to that target namespace. - Per-target tracking — checksums are stored per target and language, so changes in one target don't trigger re-translation of others.
- Force re-translation — use
--forceto ignore the lock file and re-translate all entries:lokit translate --provider copilot --model MODEL_NAME --force
- Safe to delete — removing
lokit.locksimply causes a full translation on the next run. - Commit to VCS — it's recommended to commit
lokit.lockto version control so that CI and teammates benefit from incremental translation.
You can control which keys are translated per target in lokit.yaml:
targets:
- name: ui
format: i18next
from: [translations/en.json]
to: translations/{lang}.json
source_lang: en
languages: [ru, de, fr]
# Keys excluded from translation entirely (never sent to AI)
ignored_keys:
- debug_label
- internal_test_string
# Hand-curated translations preserved as-is (skipped even with --all)
locked_keys:
- app_name
- copyright_notice
# Regex patterns — matching keys treated as locked
locked_patterns:
- "^brand_.*"
- "^legal_"Semantics:
| Field | Effect | Overridden by |
|---|---|---|
ignored_keys |
Key is completely skipped, as if it doesn't exist | — |
locked_keys |
Existing translation is preserved, key is not re-translated | --force |
locked_patterns |
Same as locked_keys but matches keys by regex |
--force |
ignored_keysare always skipped, even with--force.locked_keysandlocked_patternsare skipped during normal and--allruns. Only--forceoverrides them.- These settings work with all formats: gettext PO, po4a, i18next, vue-i18n, Android, YAML, Markdown, .properties, Flutter ARB, js-kv, desktop, and polkit.
Built with Go 1.23+:
MIT License — see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.