Open source

Claude Translator

Static site localization — translate a built site into dozens of languages as real static pages, without re-rendering it and without breaking Core Web Vitals. Free, self-hosted, AGPL-3.0.
No account No API key from us Runs on a local model too

See all features

This is the pipeline that runs www.conveythis.com itself — a 238-page Astro site, live in 55 languages. We extracted it, documented every failure mode it cost us to find, and published the whole thing. It is not a trial and not a teaser: there is no key to buy, no quota, and no account.

Two design decisions

How it works

Two decisions define it. First, translations are spliced into the built HTML by byte offset and the document is never re-serialised from a DOM — so inlined critical CSS, the LCP element, asset hashes and the width/height attributes that hold CLS all carry over untouched. That is why locale pages match the source language on Core Web Vitals rather than merely resembling it. Second, the unit of translation is the whole block, not the text node, because roughly a fifth of a typical site's text nodes are split by inline markup and translating the fragments separately breaks grammar in any language that reorders or inflects.

The result is proved rather than asserted: eight gates and an exhaustive SEO audit over every page, including one gate that exists purely because coverage metrics are structurally blind to extraction bugs.

Verification

Does it actually hold?

Fair question, and it is checkable rather than something you have to take our word for. doctranslator.com/fr is a large Astro site whose French pages were built by this pipeline. Compare it against the English original and the markup is byte-identical:

2,743

tags, in identical sequence

2,031

class attributes, all matching

+0.74%

page weight — French is longer

Nothing structural moved, so there is nothing for the browser to lay out differently. Check it yourself — it takes about ten seconds:

shell
curl -s https://doctranslator.com/   | grep -o 'class="[^"]*"' > en.txt
curl -s https://doctranslator.com/fr | grep -o 'class="[^"]*"' > fr.txt
diff en.txt fr.txt && echo "markup identical — only the text changed"

And the scores

English sourceFrench, from this toolA11y / Best practices / SEO
Desktop100100100 / 100 / 100
Mobile9898100 / 100 / 100

PageSpeed Insights performance scores, measured 25 August 2026. Accessibility, best practices and SEO were 100 on every run of both pages, in both strategies.

These are medians, deliberately. Lighthouse scores move: five consecutive runs of the same English mobile page returned 98, 98, 88, 98, 98, with largest-contentful paint swinging between 1.8s and 3.2s. That is network and CDN variance rather than page quality. If you measure once and get 91, that is why — run it a few times and compare the two languages against each other rather than against a number on a marketing page.

And the language

Markup identity is measurable. Whether the Spanish is any good is a different question, and until 2.0 the pipeline had no answer to it — the tests checked request shapes and scaffolding, and nothing scored a translation.

shell
node scripts/i18n/tqa.mjs --lang es --dry      # sample size and cost, no API call
node scripts/i18n/tqa.mjs --lang es,fr,de     # scorecard + per-locale JSON
node scripts/i18n/tqa.mjs --lang es --repeat  # judge the same sample twice

It scores a stratified sample on the MQM error typology at its conventional weights — minor 1, major 5, critical 10 — and weights the sample by how often each string appears, so the header everyone reads counts for more than a one-off footnote. Three things keep the number from being decorative:

The judge is a different model from the translator
Models score their own output generously. If no second provider key is configured it uses the same one and says so, in the run and in the report.
The sample is seeded, and the variance is reported
A seed reproduces a score exactly; --repeat scores the same sample twice and prints the gap. A quality number nobody can re-derive, quoted without its noise, is a marketing number.
A unit the judge cannot assess is excluded, not counted as clean
An early version printed 100.00 out of 100 from a sample where every single unit had failed to parse. It now refuses to report a score at all in that case.

Read it as a comparison — between locales, between models, before and after a prompt change — not as a grade. It is one model’s assessment of another’s work rather than a human review, and the report says so on its face.

Decision three

Terminology is not a per-sentence choice

Identical strings were always consistent — units are keyed by a hash of the source text, so a nav label translated once is reused on every page and across runs. What that cannot do is hold a term inside varying sentences: “Dashboard” in two different paragraphs is two hashes, two batches, two stateless requests, and nothing compared them.

glossary.json
[
  { "source": "Acme",      "rule": "keep",      "matchCase": true },
  { "source": "Dashboard", "rule": "translate",
    "targets": { "es": "Panel de control", "de": "Übersicht" } }
]

matchCase is the answer to a question we were asked directly: can it tell Apple the company from apple the fruit? A flat list of names cannot express that. Whole-word, case-sensitive rules can.

sourcespanishwhy
Apple announced a deviceApple anunció un dispositivocapital A — protected, and now verified
An apple a dayUna manzana al díalowercase — translated normally
Applesauce is on saleLa compota está en ofertawhole-word — never matches inside a longer word

The same release tells the model what a string is. It used to receive the text and nothing else, so a button label and a body paragraph were indistinguishable — which made the prompt’s own instruction to keep button labels short unenforceable. A <button>, a heading, a form label and a meta description now each say so.

It deliberately does not demand the imperative for buttons: German UI prefers a verbal noun and French the infinitive, so it asks for whatever construction that language actually puts on a button. Brand names you already list are folded in automatically as case-sensitive keep rules, which also brings them under a verification gate that checks they survived. Nothing checked that before.

Locale conventions

Numbers get localized. Prices never get converted.

Writing 1,234.56 to a German reader is one of the most visible marks of a machine translation. Models are unreliable at separator conventions, so the model is told to leave numbers alone and Intl reformats them afterwards, deterministically.

sourcebecomeswhere
1,234.561.234,56de
1,234.561 234,56fr
$55,00 $USfr — placement and spacing, not the amount
50%50 %fr — with a non-breaking space

It formats. It never converts.

no config option for it

A price is a commercial commitment, and converting one at a rate baked into a build — stale the day after it is written — is how a translation tool starts publishing wrong offers in markets nobody is watching. Every monetary amount found is written to a report instead, so a human prices each market deliberately.

A numeric-integrity gate backs that up by failing the build if a number’s value changes between source and translation. A model that quietly ships $39 where the source said $49 passes every other check: identical markup, matching placeholders, plausible length, fluent Spanish.

Deliberately left alone: version numbers, times, IP addresses, ISO dates, phone numbers and any ungrouped number — during development a greedy pattern turned 192.168.1.1 into 1.921.681,1, and that case is now a test. One result that looks like a bug and is not: Spanish does not group four-digit numbers, so 1,234.50 is correctly 1234,50 in es and 1.234,50 in de.

Honest routing

Which one do you actually need?

Static substitution and a runtime layer solve different problems. Picking the wrong one costs you a weekend, so here is the honest split.

You have a static build, content changes on a release cadence, and you want to own the HTML outright.

Claude Translator

Free, self-hosted, AGPL-3.0. Your own API key, your own files, no account.

View on GitHub →

Your content changes daily, lives in a CMS, is user-generated, or sits behind a login or checkout.

ConveyThis

A managed runtime layer. No build step to hook, no re-run on every edit, a visual editor and human review.

See pricing →

What you need translated is documents rather than pages — PDF, DOCX, XLSX, PPTX.

DocTranslator

Whole files, layout and tables preserved, 100+ languages.

Go to DocTranslator →

Quickstart

Install it

Repository

https://github.com/ConveyThis/claude-translator

AGPL-3.0 · Node ≥ 20 · one dependency (parse5) · open on GitHub →

You need

  • Node.js 20 or newer
  • A site that builds to static HTML — it reads your build output, not your source
  • An API key for whichever model you choose — or none at all, if you run one locally

As a Claude Code skill

Clone it into your skills directory, then ask Claude to localize the site. It follows the bundled instructions, including the failure modes that cost us real money to find — and the rules for when this is the wrong tool entirely.

shell
git clone https://github.com/ConveyThis/claude-translator.git \
  ~/.claude/skills/claude-translator

In any project

One command. init copies the pipeline into scripts/i18n/, writes a config, declares parse5 and adds the derived i18n/ paths to your .gitignore. It never overwrites anything without --force, and it prints every file it touched.

shell
cd your-project
npx claude-translator init
npm install                       # parse5, the only dependency

The scripts land in your repository rather than staying in node_modules on purpose — they resolve paths from the project they sit in, they are short enough to read, and the licence exists so you can change them.

Installing without npx
shell
git clone https://github.com/ConveyThis/claude-translator.git
cp -r claude-translator/scripts  your-project/scripts/i18n
cp claude-translator/i18n.config.example.json your-project/i18n.config.json
cd your-project && npm install --save-dev parse5

Configure

The smallest config that runs. buildDir is where your generator writes HTML; baseUrl is your canonical origin, no trailing slash.

i18n.config.json
{
  "buildDir": "dist",
  "baseUrl": "https://example.com",
  "provider": "anthropic",
  "glossary": "glossary.json",
  "locales": [
    { "hreflang": "es", "pathCode": "es", "nativeLabel": "Español" },
    { "hreflang": "de", "pathCode": "de", "nativeLabel": "Deutsch" }
  ]
}
shell
echo "ANTHROPIC_API_KEY=your-key-here" >> .env   # and gitignore it

Run it

npm run buildyour normal build — source language only
node scripts/i18n/extract.mjsfind every translatable unit
node scripts/i18n/translate.mjs --lang es,frtranslate into the memory
node scripts/i18n/build-locales.mjs --lang allwrite the localized pages
node scripts/i18n/verify.mjs --lang alleight gates — exits non-zero on failure
node scripts/i18n/audit-seo.mjscanonicals, hreflang, JSON-LD, sitemaps
node scripts/i18n/tqa.mjs --lang es --dryoptional — MQM quality score, cost first

Then deploy the build directory exactly as you deploy it today — the localized pages are ordinary files sitting next to your existing ones. Full options, every failure mode, and the reasoning behind each design decision are in the README.

Models

Bring your own model — including a local one

The translation step talks to a model through a small adapter, and three ship with it: Claude (the default), Gemini, and any OpenAI-compatible endpoint. That last one covers most of the field — OpenAI, Azure, Groq, DeepSeek, Mistral, OpenRouter, Together, Fireworks — and it also covers Ollama, LM Studio and vLLM.

Which means three lines of config run the entire pipeline on your own hardware:

i18n.config.json
{
  "provider": "openai",
  "apiBaseUrl": "http://localhost:11434/v1",
  "model": "qwen2.5:14b"
}

No key, no quota, and nothing leaves the machine — which matters if the content is confidential. Anything not on that list is an adapter file with two functions in it.

Limits

Where the free tool stops

Six real limits. None of them is a crippled feature — they are the shape of the approach, and the scripts tell you when you hit one instead of failing quietly.

Client-side hydration
Islands and framework payloads re-render in the browser, over the substituted HTML. The extractor counts the affected pages so you find out in seconds, not after a full run.
Documents
Linked PDFs, DOCX and XLSX stay in the source language — the pipeline only ever touches HTML.
Churn
The memory is keyed by source hash, so it can tell you what share of your site changed since the last run. High churn means paying to re-translate, repeatedly.
Editing a translation
Find the hash in the memory file, edit the string, rebuild. There is no editor, no reviewer and no workflow.
Volume
You pay your own model provider directly, at their rate, with your own key — or nothing at all, if you run the model yourself.
Modified network use
AGPL-3.0 §13 obliges you to publish modifications if you run a changed copy as a service for other people. Running it unmodified, or modified internally, is unrestricted.

The first four are the ones people actually hit, and they are all the same shape: something has to happen after the build, which a build-time tool cannot do. That is what the managed product is for, and the free plan is 5,000 words with no card, which is enough to find out whether it helps. You do not need it to use anything on this page.

Attribution

What it puts on your pages

Localized pages carry a <meta name="generator"> tag and one HTML comment naming ConveyThis — about 150 bytes, no request, no script and no link. It is the same mechanism Astro, Hugo and WordPress use. Two config keys remove it, and nothing in the repo checks whether you did.

If you would rather show a visible credit, set credit.visibleLink to true and place the slot wherever you want it on the page. Nothing is asked of you for it and nothing is given in return — it exists because some people want to credit the tools they use, and for no other reason.

The credit link is rel="nofollow", deliberately. Not because it is paid — it is not — but because it is a link a build script would otherwise add across every page of a site, and sitewide links that appear through tooling rather than editorial choice are the shape Google's link-scheme guidance is aimed at. It is worth referral traffic, not backlinks — anyone telling you otherwise is selling you a penalty.

Commercial licence

The code is AGPL-3.0. Localizing your own sites and shipping the output is unrestricted — the licence covers the software, not the HTML it writes. It only bites if you run a modified copy as a network service for other people. If that is you and you cannot publish your changes, write to [email protected].

Questions

The questions people actually ask

Is this really free? What is the catch?
It is the whole pipeline under AGPL-3.0, and there is no catch of the usual kind. You need no ConveyThis account, no key from us, and no quota. We built it to localize conveythis.com into 55 languages and published what we use. The honest cost is that you run it yourself: you pay your own model provider, you host the output, and there is no support desk behind it.
Do I need a ConveyThis account?
No. Nothing in the tool checks for one, phones home, or reports usage. The only trace of us is a generator tag and an HTML comment in the pages it writes, and two config keys remove those.
What does a run actually cost?
You pay your model provider directly. On the default (Claude Haiku 4.5) a mid-sized marketing site of roughly 150,000 unique source words costs in the region of $30 across 20 languages. Switching one config key to Gemini Flash Lite brings that to about $2.40. Pointing it at a model on your own machine makes it free. Deduplication matters more than the provider choice: repeated headers, footers and navigation collapse to one unit each, which typically cuts the bill by an order of magnitude.
Can I use my own model, or run it completely offline?
Yes. Adapters ship for Claude, Gemini, and any OpenAI-compatible endpoint — which covers OpenAI, Azure, Groq, DeepSeek, Mistral, OpenRouter, Together and Fireworks, and also Ollama, LM Studio and vLLM. Set apiBaseUrl to your local server and the whole pipeline runs on your hardware with no key and no request leaving the machine. That matters when the content is confidential. Anything not on that list is an adapter file with two functions in it.
Will the translated pages be slower than the originals?
No, and this is the design decision the whole project rests on. Each locale page is the source page with byte ranges spliced in; the document is never re-serialised from a DOM. Inlined critical CSS, the LCP element, asset hashes and the width and height attributes that hold layout shift all carry over untouched, so locale pages match the source language on Core Web Vitals rather than approximately resembling it. The attribution adds about 150 bytes and makes no network request.
Does it work with my framework?
Anything that emits static HTML: Astro, Next.js with output export, Hugo, Eleventy, Jekyll, Gatsby, or hand-written HTML. It reads your build output rather than your source, so the framework mostly does not matter. The per-generator notes in references/adapting-generators.md cover the exceptions, hydration being the main one.
What about React components or Astro islands that hydrate?
Those re-render in the browser, over the top of whatever was substituted into the HTML, so the text reverts to the source language. Static substitution cannot reach them. The extractor counts pages carrying a hydration payload and tells you before you spend anything, so you find out in seconds rather than after a full run. If most of your content lives inside hydrated components, a runtime layer is the right tool and ConveyThis is that.
Can it keep my terminology consistent, and tell a brand name from an ordinary word?
Yes, since 2.0.0. Identical strings were always consistent because units are keyed by a hash of the source text, but a term inside varying sentences had no mechanism at all. A glossary file now pins terms per locale, and rules can be case-sensitive and whole-word: Apple the company is protected while apple the fruit is translated, and neither matches inside Applesauce. Brand names you already list are folded in automatically, and a verification gate checks they actually survived the translation, which nothing did before. The model is also told what each string is, so a button label, a heading and a meta description are no longer indistinguishable to it.
I already use it. What changes when I upgrade to 2.0.0?
Nothing re-translates. Unit hashes are computed from the source text alone, so no existing translation memory is invalidated, and that was verified against a fixture before release. Two behaviour changes do bite, and both are deliberate in a major version: locale number formatting is on by default, so a rebuild reformats numbers in your output, and a new numeric-integrity gate fails the build if a number changed value between source and translation. One caveat worth stating plainly: the stages added in 2.0.0 have not had a live billed run at scale against a hosted provider. The pipeline underneath them runs conveythis.com in 55 languages; the additions were verified offline, with fixtures and a local model.
Can I fix a translation I do not like?
Yes, by hand. Find the string hash in i18n/tm/{lang}.json, edit the text, rebuild. There is no editor, no reviewer and no approval workflow in the free tool — that is a real limit rather than a crippled feature. If you need a visual editor or professional human review, that is what the paid product provides.
What happens when I edit a page or add a new one?
The translation memory is keyed by a hash of the source text, so a re-run translates only what changed and reuses everything else — usually cents. Commit i18n/tm/{lang}.json; it is the asset, and deleting it means paying for a full re-translation. If your content changes daily, the arithmetic stops favouring this approach, and the tool tells you the churn rate so you can see that coming.
Will the translated pages get indexed by Google?
They are real static pages, so yes. Read what it does as corrected rather than created, because that distinction is what keeps the markup byte-identical: it rewrites the values on tags your template already emits. Every locale page comes out with the right html lang and dir, a self-referencing canonical, x-default and source-language hreflang pointed back at the original, og:url and og:locale, per-locale JSON-LD with the right @id and inLanguage, and locale-prefixed internal links. Your own template supplies the full hreflang mesh and the sitemap; the SEO audit then checks both across every page rather than a sample, and exits non-zero so it can gate a deploy.
Can I use it commercially? What does AGPL-3.0 mean for me?
Localizing your own sites and shipping the output is unrestricted, including for clients and including commercially. The licence covers the software, not the HTML it writes. Section 13 only applies if you run a modified copy as a network service for other people, in which case you must publish your modifications or take a commercial licence. LICENSING.md explains which group you are in; almost everyone is in the unrestricted one.
Is the ConveyThis attribution required?
No. Every localized page carries a generator meta tag and an HTML comment — about 150 bytes, no request, no script and no link. It is the same mechanism Astro, Hugo and WordPress use. Setting credit.generatorTag and credit.htmlComment to false removes both, and nothing in the code checks whether you did. A visible credit is opt-in and separate, and earns you nothing — it is there for people who want to credit the tools they use.
How is this different from ConveyThis itself?
They solve different problems. This produces static files at build time, which needs a build to hook and content that changes on a release cadence. ConveyThis is a managed runtime layer: no build step, no re-run on every edit, a visual editor, human translation, and support for CMS, e-commerce, user-generated content and pages behind a login. If you have a static build and want to own the HTML, use this. If your content changes daily or there is no build to hook, use the product. Same company either way.
Ready to Get Started?

Translation, far more than just knowing languages, is a complex process.

By following our tips and using ConveyThis , your translated pages will resonate with your audience, feeling native to the target language.

While it demands effort, the result is rewarding. If you're translating a website, ConveyThis can save you hours with automated machine translation.

Start with ConveyThis on our free plan — no credit card required!