v1.0.0-beta.3 · 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
Works with any JSON/JSONC locale files (next-intl, i18next, vue-i18n, …) and Node 20+. Hand it to your coding agent, or set it up yourself in a few minutes.
Paste this into Claude Code, Cursor, Codex, or opencode from your project root. Your agent reads the Rosetta skill and does the whole setup, then opens one PR for you to review.
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.
What your agent will do
- Discover. Finds your i18n runtime, locale files, locale list, and current translation setup, then reports back.
- Configure. Writes
.rosetta/config.jsonand carries over your brand voice, context, and do-not-translate terms. - Adopt. Records your existing translations for free, and lists any that are broken today.
- Guard. Adds tests that keep the config in sync with your app's locale list.
- Wire up CI. Keeps your existing flow, or proposes
check, commit, or PR mode. - Clean up. Removes the old tooling, documents the workflow in
AGENTS.md, and opens one PR.
It asks before anything expensive: push --force, large translation runs, or choosing how translations land.
Keep your agent fluent
Install the Rosetta skill so everyday requests ("add a German string", "why is the i18n job red?") just work. For agents that can't fetch URLs, use the self-contained prompt.
# Claude Code (other agents: see docs/agent-prompt.md)
mkdir -p .claude/skills/rosetta && curl -fsSL https://raw.githubusercontent.com/ian/rosetta/main/skills/rosetta/SKILL.md -o .claude/skills/rosetta/SKILL.mdInstall
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's edit English → push → commit, run yourself, by an agent, or by the GitHub Action..github/workflows/i18n.ymlon: pull_request: push: { branches: [main] } jobs: i18n: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ian/rosetta@v1.0.0-beta.3 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 }}
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.3). 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.