v1.0.0-beta.1 · MIT · no account
Localization you run yourself.
A free, open-source alternative to Lingo.dev. Edit your source strings, run rosetta push, and every locale gets the new and changed strings, translated with your own LLM key and validated before anything is written.
Paste it into Claude Code, Cursor, Codex, or opencode from your project root.
Why Rosetta
No daily token cap
Rosetta sends requests straight to your model provider, so the only limits are your provider's. Translate a whole site in one run.
No platform fee, no subscription
There's no base price, no per-engine charge, and no markup per million tokens. You pay your LLM provider for the tokens you use, and nothing else.
Any model, your key
OpenRouter by default, or any OpenAI-compatible endpoint: OpenAI, a gateway, or Ollama on your own machine. Switch models with one line of config.
Everything lives in your repo
Config, lockfile, brand voice, and glossary are plain files in .rosetta/. Translations are reviewable diffs with git history, not rows in a dashboard.
Built for coding agents
--json on every command, stable exit codes, no prompts in CI, and a skill your agent can install. "Add a German string for this" just works.
Open source, MIT
Read it, fork it, run it anywhere. No account to create, nothing to cancel, and no vendor between you and your translations.
The math
Lingo.dev's published plans next to Rosetta. With Rosetta you still pay for tokens, but directly to your model provider, with no cap and no markup.
| Lingo.dev Sandbox | Lingo.dev Production | Rosetta | |
|---|---|---|---|
| Monthly base price | $0 | $99 | $0 |
| Throughput | 100K tokens / day | 5M tokens / day | Your provider's limits |
| LLM tokens | Passed through | Passed through | Paid to your provider |
| Platform fee | $2 / million tokens | $2 / million tokens | None |
| Engines | $0.50 / engine / month | $0.50 / engine / month | Free (it's a config file) |
| Bring your own model key | Enterprise only | Enterprise only | Always |
| Where your config lives | Their platform | Their platform | Your repo |
Lingo.dev figures are from lingo.dev/pricing as of September 2026. Enterprise pricing is custom.
Get started
Requires Node 20+. Works with any JSON/JSONC locale files (next-intl, i18next, vue-i18n, …). Prefer to hand it off? Your AI agent can do all of this.
Install
Add Rosetta as a dev dependency. The package is
rosetta-i18n, and it installs arosettacommand.npm install -D rosetta-i18n@nextCreate the config
initfinds your source files (messages/en.json,locales/en/*.json,pages.en.json, …), infers target locales from the files next to them, and writes.rosetta/config.json. Coming from Lingo.dev? Use--from-lingo.npx rosetta init # or: npx rosetta init --pattern messages/en.json --target es,fr,de # or: npx rosetta init --from-lingoTune the engine
Pick a model and give it your brand voice and never-translate terms. Add
lockedKeysfor anything that must stay identical (URLs, prices), and acontextper file..rosetta/config.json{ "sourceLocale": "en", "targetLocales": ["es", "fr", "de", "ja", "pt-BR", "zh-CN"], "files": [ { "pattern": "messages/en.json", "context": "Acme web app UI" } ], "engine": { "model": "anthropic/claude-sonnet-4.5", "brandVoice": { "*": "Friendly, plain, and direct." }, "glossary": { "*": { "Acme": "Acme" } } } }Add your model key
Rosetta has no key of its own. Use your OpenRouter key, or point
engine.baseURLat any OpenAI-compatible endpoint and setengine.apiKeyEnvto its variable.export OPENROUTER_API_KEY=sk-or-...Preview, then push
statusshows what would happen, with no key and no cost. The firstpushadopts your existing translations for free and translates only what's missing, changed, or broken.npx rosetta status # es 3 to translate · 412 to adopt npx rosetta push # writes locale files + .rosetta/lock.jsonCommit, then keep it in sync
Commit
.rosetta/with your locale files. From then on it'sedit English → push → commit, run yourself, by an agent, or by the GitHub Action..github/workflows/i18n.yml# .github/workflows/i18n.yml on: pull_request: push: { branches: [main] } jobs: i18n: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ian/rosetta@v1.0.0-beta.1 with: # PRs: check (no key needed). main: translate + open a PR. mode: ${{ github.event_name == 'pull_request' && 'check' || 'pull-request' }} env: OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
Or let your AI agent set it up
This prompt has your agent find your locale files and current setup, configure Rosetta, adopt your existing translations, report any that are broken today, wire up CI, and open one PR. It checks with you before anything expensive.
Set up Rosetta (npm: rosetta-i18n) for translations in this repo. Follow https://raw.githubusercontent.com/ian/rosetta/main/skills/rosetta/SKILL.md step by step: discover our locale files and current translation setup and report back, configure .rosetta/config.json, adopt our existing translations with `rosetta status` + `rosetta push` (list any translations it flags as broken), add tests and CI, remove the old translation tooling, document the workflow in AGENTS.md, and open one PR. Ask me before running `push --force`, before any large translation run, and before choosing how translations land if it's unclear.
For everyday requests ("add a German string", "why is the i18n job red?"), install the Rosetta skill. For agents that can't fetch URLs, use the self-contained prompt.
How it works
Rosetta keeps a lockfile, .rosetta/lock.json, that records a fingerprint of the source string behind every translation. On each push, every key is translated, retranslated, kept, adopted, copied, or removed, and nothing else is touched.
Only changed strings cost tokens
The lockfile records the source text behind every translation. Edit one string and only that string is retranslated, in every locale.
Existing translations adopted free
Switching from another tool? The first push records what you already have without calling a model.
Broken translations caught
Dropped {placeholders}, mangled ICU plurals, and broken <tags> are flagged and retranslated, including ones already in your files.
Never a partial file
A locale with a key that still fails after retries isn't written, and the error is reported. Every other locale still lands.
Key controls
lockedKeys copy URLs and prices verbatim, preservedKeys protect reviewed copy, and ignoredKeys keep internal strings out of targets.
Brand voice, rules, glossary
Per-locale voice (inline or in Markdown files), linguistic rules, exact term renderings, and a never-translate list.
Questions
Is there a hosted version or an account?
No. Rosetta is a CLI and a library. It runs on your machine or CI runner and talks directly to your model provider. The only credential is that provider's API key.
What does it cost to run?
Only your model provider's tokens, and only for new, changed, or broken strings. Run rosetta push --estimate to see key counts and a token estimate before anything is sent.
Which file formats are supported?
JSON and JSONC today: next-intl, i18next, vue-i18n, and any key-value catalog. In JSONC files, comments become translator notes. Markdown/MDX is next (#1).
Which models work?
Any chat model behind an OpenAI-compatible endpoint. Rosetta asks for JSON output, strips code fences, and validates every value, so most instruction-following models do fine.
I'm on Lingo.dev. How do I move over?
Run rosetta init --from-lingo. It imports .lingo/config.json (or the legacy i18n.json), and the first push adopts your existing translations for free. The vocabulary is the same: lockedKeys, push --key, check, purge. See the migration guide.
How do translations get into my repo?
However you like: run push locally or from an agent, gate PRs with check, or let the GitHub Action commit or open a rolling PR after merges. See CI & delivery.
Is it production-ready?
It's in beta (v1.0.0-beta.1). The spec is stable, and it's covered by tests and a CI end-to-end run of the GitHub Action. Pin the version, and please open issues.
Try it on your project
One prompt, one PR. Or three commands, if you'd rather do it yourself.