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.

Get started

Paste it into Claude Code, Cursor, Codex, or opencode from your project root.

~/acme

Why I built Rosetta

I run a lot of websites, and I want all of them in a dozen or more languages. Lingo.dev got the workflow right: edit your English, push, and every locale follows. I liked it enough to build Rosetta on the same ideas.

What I couldn't make work was the pricing. The free plan caps throughput at 100,000 tokens a day, and translating a full page of copy into a dozen languages goes through that quickly. The next step up is $99 a month before usage. For one big product that's reasonable. When you have a lot of sites, it isn't cost-effective.

The expensive part of translation is the model, and I already pay for one. So Rosetta is the same workflow, running in your own repo with your own key. You pay your model provider for the tokens you use, and nothing else. It's free and open source, so anyone else in the same spot can use it too.

— Ian

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 SandboxLingo.dev ProductionRosetta
Monthly base price$0$99$0
Throughput100K tokens / day5M tokens / dayYour provider's limits
LLM tokensPassed throughPassed throughPaid to your provider
Platform fee$2 / million tokens$2 / million tokensNone
Engines$0.50 / engine / month$0.50 / engine / monthFree (it's a config file)
Bring your own model keyEnterprise onlyEnterprise onlyAlways
Where your config livesTheir platformTheir platformYour 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.

  1. Install

    Add Rosetta as a dev dependency. The package is rosetta-i18n, and it installs arosetta command.

    npm install -D rosetta-i18n@next
  2. Create the config

    init finds 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-lingo
  3. Tune 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 a context per 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" } }
      }
    }
  4. Add your model key

    Rosetta has no key of its own. Use your OpenRouter key, or point engine.baseURL at any OpenAI-compatible endpoint and set engine.apiKeyEnv to its variable.

    export OPENROUTER_API_KEY=sk-or-...
  5. Preview, then push

    status shows what would happen, with no key and no cost. The first pushadopts 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.json
  6. Commit, 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.

View on GitHub