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.
Use project-owned terminology catalogs to keep exact labels, brands, and embedded terms consistent across every target format:
terminology:
from:
- l10n/terminology.yamlExact source phrases are applied locally without an AI request. Embedded term constraints are scoped to response IDs and validated before output or lock updates. Existing terminology violations are corrected by an ordinary lokit translate run without --force, and lokit status reports them in a Terms column.
See the Terminology Guide for the file format, selectors, locale fallback, plural rules, and validation behavior.
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.