- AI Localization Tool
Translates all source strings in a reference (js,mjs,json,jsonc) file to all target languages using AI.
- Loads source/reference key/value pairs from a file
- Localizes using AI as needed, writing to a .json file per language
- App-level context can be specified [
appContextMessage] - Additional context can be specified per string [
--contextPrefix,--contextSuffix] - Supports Claude, Gemini, OpenAI [
--provider] - Two ways to reach a provider: its HTTP API with your own key, or its CLI tool with that tool's own auth [
--access] - Many keys per request, and several languages at once, where the access method supports it [
--batch-size,--concurrency] - User-modifications to output files are safe and will not be overwritten
- Removes keys from your target files once they leave the reference file [
alt prune] - Languages are specified using BCP47 tags
npm install -g @drone1/altThis will house your source strings via key/value pairs in your preferred language.
Here's an example reference.js:
export default {
'error-msg': `Sorry, we don't know how to do anything`,
'success-msg': `A massive "achievement"`,
'_context:success-msg': `This text is for a button when a user completes a task`
}Use whatever filename you prefer. js,mjs,json,jsonc extensions are supported.
For .js and .mjs files, you can specify the name of an exported variable instead of using default, via --reference-exported-var-name.
ANTHROPIC_API_KEY=<secret>
alt translate --reference-file ./reference.js --reference-language en --target-languages aa,bo,es-MX,hi,zh-Hans --provider anthropicThis command would iterate across all key/value pairs defined in ./reference.js and translate if needed.
Here are all supported providers and their required environment variables:
-p, --provider |
environment variable |
|---|---|
| anthropic | ANTHROPIC_API_KEY |
| GOOGLE_API_KEY | |
| openai | OPENAI_API_KEY |
--provider says whose model to use; --access says how to reach it.
-a, --access |
how it authenticates | providers |
|---|---|---|
api (default) |
the vendor's HTTP API, using the API key in the environment variable above | anthropic, google, openai |
harness |
the vendor's own CLI tool, using whatever that tool is already logged in as — no API key | anthropic (via Claude Code) |
alt translate --provider anthropic --access harnessWith harness, ALT shells out to the claude executable on your PATH, so a Claude subscription
covers the translation instead of API billing. The CLI has to be installed and signed in first.
harness also translates in batches: one call carries --batch-size keys (25 by default) rather than
one key, and --concurrency languages can be in flight at once (1 by default). Bigger batches amortize
the CLI's startup cost better, but a batch that comes back empty costs that much more rework. Whatever
fails is retried up to --max-retries times, and if two attempts in a row translate nothing at all ALT
drops to one key per call, so a single unanswerable string stops blocking the rest of its batch.
The example above would write aa.json, bo.json, etc., to the current working directory.
Sample output:
{
"error-msg": "དགོངས་དག་ང་ཚོས་ག་རེ་བྱེད་དགོས་མིན་ཤེས་ཀྱི་མི་འདུག",
"success-msg": "གྲུབ་འབྲས་\"ཆེན་པོ་\"ཞིག"
}Note that output files can be lower-cased if you pass the --normalize-output-filenames option, so fr-FR translations would write to fr-fr.json
[optional] For convenience, a config file is supported. You can use a config file in conjunction with command-line arguments.
Command-line arguments override config file values.
If no explicit config file is specified via --config-file, ALT will search for a file with the name alt.config.json in the current working directory.
If no config is loaded, you'll need to specify --reference-file and --provider, otherwise ALT will fail.
Here's an example alt.config.json file you might keep in your project's root directory:
{
"appContextMessage": "This is a description of my app",
"referenceLanguage": "ar",
"provider": "google",
"access": "api",
"lookForContextData": true,
"contextPrefix": "_context:",
"contextSuffix": "",
"outputDir": "localization",
"referenceFile": "localization/reference.js",
"normalizeOutputFilenames": false,
"batchSize": 25,
"concurrency": 1,
"trackTargetEdits": true,
"targetLanguages": [
"es-MX", "zh-SG"
]
}
Since in most cases these arguments will not change often, a config file is recommended.
Any of the above settings can be specified using command-line arguments, but note the mapping from camelCase to kebab-case (appContextMessage => --app-context-message, referenceLanguage => --reference-language, etc.).
Sometimes a string isn't enough to give context to the AI, and as a result, it may give an undesirable translation. ALT allows you to specify additional context for this reason.
A global, application description can be specified --app-context-message (or appContextMessage in a config).
For example, your config may include something like:
"appContextMessage": "Voided is a MMORPG game based on outer space."Context can be added for any reference key/value pairs by passing --look-for-context-data (or setting lookForContextData: true in a config).
For example, given the following reference key/value pair:
"editor-add-component": '+ Star',This may not translate as desired, so ALT allows you to specify additional context in the form of another key/value pair. For example:
"_context:editor-add-component": "This is text for a button the galaxy UI, where a user can create a star"_context: can be whatever you prefer here. It's specified via --context-prefix, or contextPrefix in a config.
A suffix can be specified instead of (or in conjunction with) a prefix, with --context-suffix, or contextSuffix in a config. Example:
"editor-add-component[context]": "This is text for a button the graph editor"In this case, [context] would be specified by passing --context-suffix '[context]' or setting "contextSuffix": "[context]" in a config.
Further examples can be found here.
ALT CLI itself has been localized so you can use it many languages. You can optionally set the display language with the ALT_LANGUAGE environment variable. Please feel free to submit
an issue if you do not see your preferred language.
Usage: alt [options] [command]
An AI-powered localization tool
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
translate [options]
list-models [options]
prune [options] Remove keys from target files that no longer exist in
the reference file
help [command] display help for command
Environment variables:
ANTHROPIC_API_KEY Your Anthropic API key (for --provider anthropic --access api)
OPENAI_API_KEY Your OpenAI API key
GOOGLE_API_KEY Your Google Gemini API key
ALT_LANGUAGE BCP47 language tag used for display
---
Usage: alt translate [options]
Options:
-c, --config-file <path> Path to config file; defaults to "alt.config.json" in the current working directory if not specified
-r, --reference-file <path> Path to reference file of source strings to be translated. This file can be in .js, .mjs, .json, or .jsonc formats and is presumed to be in the reference language specified by --reference-language; overrides any 'referenceFile' config setting
-o, --output-dir <path> Output directory for localized files; overrides any 'outputDir' config setting
-rl, --reference-language <language> The reference file's language; overrides any 'referenceLanguage' config setting
-tl, --target-languages <list> Comma-separated list of language codes; overrides any 'targetLanguages' config setting
-k, --keys <list> Comma-separated list of keys to process; if none are processed, all keys in the reference file will be processed
-R, --reference-exported-var-name <var name> For .js or .mjs reference files only, this will be the exported variable, e.g. for 'export default = {...}' you'd use 'default' here, or 'data' for 'export const data = { ... }'. For .json or .jsonc reference files, this value is ignored. (default: "default")
-m, --app-context-message <message> Description of your app, to be passed along to the AI, per translation request; overrides any 'appContextMessage' config setting
-f, --force Force regeneration of all keys; if no '--keys' argument is specified, all keys will be processed (default: false)
-rtw, --realtime-writes Write updates to disk immediately, rather than on shutdown (default: false)
-y, --tty Use tty/simple renderer; useful for CI (default: false)
-M, --model <name> LLM model name to use; defaults per provider/access: anthropic/api="claude-haiku-4-5-20251001", anthropic/harness="haiku", google/api="gemini-2.0-flash", openai/api="gpt-4.1-mini"; use the 'list-models' command to view all models
-x, --max-retries <integer> Maximum retries on failure (default: 3)
-bs, --batch-size <integer> When the access method supports batched translation (currently only 'harness'), translate this many keys per call. Larger batches amortize CLI cold-start better but lose more work on a failed batch. Defaults to 25; overrides any 'batchSize' config setting
-C, --concurrency <integer> When the access method supports batched translation, run this many language batches in parallel. Defaults to 1; overrides any 'concurrency' config setting
-n, --normalize-output-filenames Normalizes output filenames (to all lower-case); overrides any 'normalizeOutputFilenames' in config setting (default: false)
--no-track-target-edits Stop recording a hash of every translated string. That record exists only to avoid overwriting hand-edited target values, and is roughly half the cache by size; disable it if you never hand-edit target files
-N, --no-logo Suppress logo printout
-cp, --context-prefix <value> String to be prefixed to all keys to search for additional context, which are passed along to the AI for context
-cs, --context-suffix <value> String to be suffixed to all keys to search for additional context, which are passed along to the AI for context
-L, --look-for-context-data If specified, ALT will pass any context data specified in the reference file to the AI provider for translation. At least one of --contextPrefix or --contextSuffix must be specified (default: false)
-v, --verbose Enables verbose spew; forces --tty mode (default: false)
-d, --debug Enables debug spew; forces --tty mode (default: false)
-t, --trace Enables trace spew; forces --tty mode (default: false)
--dev Enable dev mode, which prints stack traces with errors (default: false)
-p, --provider <name> AI provider vendor to use for translations (anthropic, google, openai); overrides any 'provider' config setting
-a, --access <method> Access method for the provider. `api` uses the vendor's HTTP API with an API key (env: <PROVIDER>_API_KEY). `harness` uses the vendor's own CLI tool with that tool's own auth — currently only supported by anthropic via Claude Code. Per-provider support: anthropic: api|harness; google: api; openai: api. Defaults to "api"; overrides any 'access' config setting
-h, --help display help for command
---
Usage: alt prune [options]
Remove keys from target files that no longer exist in the reference file
Options:
-c, --config-file <path> Path to config file; defaults to "alt.config.json" in the current working directory if not specified
-r, --reference-file <path> Path to reference file of source strings. This file can be in .js, .mjs, .json, or .jsonc formats; overrides any 'referenceFile' config setting
-o, --output-dir <path> Output directory for localized files; overrides any 'outputDir' config setting
-tl, --target-languages <list> Comma-separated list of language codes; overrides any 'targetLanguages' config setting
-R, --reference-exported-var-name <var name> For .js or .mjs reference files only, this will be the exported variable, e.g. for 'export default = {...}' you'd use 'default' here, or 'data' for 'export const data = { ... }'. For .json or .jsonc reference files, this value is ignored. (default: "default")
-n, --normalize-output-filenames Normalizes output filenames (to all lower-case); overrides any 'normalizeOutputFilenames' in config setting (default: false)
--dry-run Show what would be removed without actually modifying files (default: false)
-N, --no-logo Suppress logo printout
-v, --verbose Enables verbose spew (default: false)
-d, --debug Enables debug spew (default: false)
-t, --trace Enables trace spew (default: false)
--dev Enable dev mode, which prints stack traces with errors (default: false)
-h, --help display help for command
---
Usage: alt list-models [options]
Options:
-p, --provider <name> AI provider vendor to use for translations (anthropic,
google, openai); overrides any 'provider' config
setting
-a, --access <method> Access method for the provider. `api` uses the
vendor's HTTP API with an API key (env:
<PROVIDER>_API_KEY). `harness` uses the vendor's own
CLI tool with that tool's own auth — currently only
supported by anthropic via Claude Code. Per-provider
support: anthropic: api|harness; google: api; openai:
api. Defaults to "api"; overrides any 'access' config
setting
-h, --help display help for command
The best example is probably the localization for ALT's own display strings.
See alt.config.json, the config used for the tool's own display strings, and the localization directory it writes to.
Generated with npm run localize-display-strings
alt translate --reference-file loc.js
--reference-exported-var-name data
--provider anthropic
--look-for-context-data
--context-prefix _context:- Imports reference strings from
loc.js - Looks for exported variable
data(ifloc.jshasexport data = { ... }) - Translates with Claude
- Looks for context keys starting with
_context: - Writes output files to the current working directory
alt translate --config-file ./localization-config.json
--reference-file loc.js
--output-dir localization
--provider openai
--look-for-context-data
--context-suffix "[context]"- Imports config from
./localization-config.json - Imports reference strings from
loc.js - Looks for exported
defaultvalue - Translates with ChatGPT
- Looks for context keys ending with
[context] - Writes files to
./localization
alt translate --config-file config.json
--reference-file localization/reference/source.js
--output-dir localization/output
--provider openai
--target-languages vi,aa
--keys error-msg,title-hero,button-text-send- Overrides any target languages specified in the config
- Only processes the specified keys (
error-msg, etc.)
If your reference values include formatting information like this:
"error-msg": "The server returned an error: %%details%%"or
"error-msg": "The server returned an error: {{details}}"...or whatever syntax your app may use for formatting, the AI's consistently smart enough not to translate %%details%% or {{details}} into the target language, and will leave it untouched.
Internally, there is currently nothing in the prompt about this. I've tested with %%var%% syntax, and it hasn't failed yet.
Please submit an issue if it causes you any trouble.
Under what conditions will ALT translate a given source string?
Translation will occur for a given target language & reference key/value if any of the following are true:
- The output file does not exist
- Example: You're translating to target language zh-Hans and
zh-Hans.jsondoesn't exist
- Example: You're translating to target language zh-Hans and
- The output file is missing the reference key
- Example: You're translating reference key error-msg to target language zh-Hant.jso and
zh-Hans.jsondoes not have a key/value pair for key error-msg
- Example: You're translating reference key error-msg to target language zh-Hant.jso and
- The reference value was modified
- Example: You change the value of some-key in your reference file
- A context value for the given target language/key is found and has been modified
- Example: You modify the value of context:error-msg in your reference file. error-msg will be re-translated for all target languages.
-for--forceare specified- The cache file (
.localization.cache.json) is not present
NOTE: Translation will not occur if ALT detects that the given value in the target language file has been manually modified. If you modify an output value manually and want it to be re-translated
later, you can just delete that key/value pair from the given file. That protection is what --no-track-target-edits turns off – see Hand-edited target values.
There is one more case where a key is deliberately left alone: if the provider hands back the source string unchanged for a value that plainly should have been translated – usually a model that doesn't speak that target language and just echoes the source – ALT records the pair as untranslatable and drops the key from the output file, so your app falls back to the source language rather than shipping an English string inside a non-English file. The record is keyed by the source string's hash, so editing the reference value asks the question again.
Deleting a key from your reference file doesn't remove it from the target files – nothing translates it
any more, but the old translation sits there. alt prune cleans that up:
alt prune --dry-run
alt prune--dry-run reports what it would remove without touching anything.
If you do not do this, you'll lose important state and need to re-translate everything.
It is a machine-readable file, not one to review by hand: it's written without indentation, and its
per-key reference hashes are stored once and shared across languages instead of repeated per language.
ALT also maintains it as it goes – a language you remove from targetLanguages in your config has its
entries dropped on the next run, rather than being carried (and re-committed) forever.
Eviction follows your config, not whatever --target-languages a single invocation passes. Narrowing
one run (alt translate --target-languages en, say, as a source-language parity check) translates only
that language but leaves every other language's cache entries intact.
Alongside the reference hashes, ALT records a hash of every value it writes. That record exists for one purpose: noticing that you changed a target value afterwards, so it never overwrites your edit. It is also roughly half the cache by size.
If you never hand-edit your target files – if they're purely generated – pass --no-track-target-edits
(or set "trackTargetEdits": false in your config) and ALT stops recording it, discarding anything a
previous tracked run left behind so the space is actually reclaimed. Everything else still
works: a key missing from a target file is still translated, and a changed reference value still triggers
a re-translation. The only thing you give up is the "don't clobber my manual edit" guarantee.
By default, ALT will not write to disk until the tool is shutting down (including SIGTERM – yes, Ctrl+C is safe).
This behavior is useful if your application is monitoring the output directory and you don't want your server constantly restarting, for example.
If you prefer to write updates to disk in real-time (anytime any output data changes, due to translation, etc), you can pass --realtime-writes.
You may want to use --tty for more useful output.
Commit the updated output files and .localization.cache.json as part of the job; without the cache, the
next run re-translates everything from scratch.
If you're running --access harness, the claude CLI has to be installed on the runner and already
authenticated there – ALT does not log it in for you.
Feel free to fix existing issues and submit a PR, or submit a new issue.
Feedback is more than welcome. Please feel free to create an issue.
