DEV Community

Cover image for Adding a translation MCP server to my i18n workflow: setup, real responses, and gotchas
Ash
Ash

Posted on

Adding a translation MCP server to my i18n workflow: setup, real responses, and gotchas

TL;DR: two tools (smart_translate, list_languages), a remote MCP server at https://www.machinetranslation.com/mcp, no API key, about a minute to connect. Every response below is real output from my own runs.

Last sprint I shipped a settings page into eight locales. The strings lived in en.json. The workflow was: open a translation tab, paste a value, copy the result, paste it into de.json, repeat about forty times, then sit there wondering whether the German I just pasted said "Discard" or "Throw away."

The tab switching was not the problem. The problem was that every string went through one model, and I had no idea when that model was guessing. Short UI strings are where machine translation is weakest. "Save" with no context can come back as the verb for saving a file, saving money, or rescuing someone, and nothing in the output tells you which one you got.

So I moved translation into the assistant I already have open, using an MCP server.

Disclosure: I work on MachineTranslation.com, which builds the server below.

What the server does

MCP is the open standard that lets an assistant call external tools inside a conversation. This server exposes two:

smart_translate(text, source_language_code, target_language_code)
list_languages()
Enter fullscreen mode Exit fullscreen mode

smart_translate is the interesting one. Instead of routing your text to a single model, it runs it through 22 AI models in parallel and returns the translation they converge on. MachineTranslation.com calls this SMART consensus and puts the error reduction at roughly 90% against single-model output. Take the number as the vendor's, which it is.

The mechanism is the part I care about. On a short, context-free string, disagreement between models is the signal that the string is risky. Consensus acts on that signal without me prompting three assistants and eyeballing the difference myself.

Setup

No config file to hand-edit, no API key to paste. It is a remote server behind a login.

  1. Open your assistant's connector, integration, or MCP settings.
  2. Choose the custom or "add by URL" option.
  3. Paste https://www.machinetranslation.com/mcp.
  4. Log in with your MachineTranslation.com account when the OAuth prompt appears.

Authorization sticks across sessions, so this is a one-time thing. It works in Claude, ChatGPT, Cursor, and anything else that supports custom MCP connectors.

One caveat before you spend time on it: the MCP server is on paid plans only, including the 24-hour pass. If you want it for a single afternoon of locale work, the day pass covers it.

First call: the language list

I ran list_languages() first, because the codes decide whether this fits an existing locale structure or fights it.

{
  "languages": [
    { "code": "af", "name": "Afrikaans" },
    { "code": "es-419", "name": "Spanish (Latin America)" },
    { "code": "es-MX", "name": "Spanish (Mexico)" },
    { "code": "pt", "name": "Portuguese (Brazil)" },
    { "code": "pt-PT", "name": "Portuguese (Portugal)" },
    { "code": "sr-Latn", "name": "Serbian (Latin)" },
    { "code": "sr-Cyrl", "name": "Serbian (Cyrillic)" },
    { "code": "zh-TW", "name": "Chinese (Traditional)" }
  ],
  "count": 333
}
Enter fullscreen mode Exit fullscreen mode

333 codes, and the regional splits are the reason to read the list rather than assume.

pt and pt-PT are separate targets. So are es-419, es-MX, es-AR, es-CO, and es-ES. Serbian ships in both scripts, Somali in Latin and Arabic, Uzbek in Latin.

If your locale directories are already named pt-BR and pt-PT, you can map them one to one instead of translating once into "Portuguese" and hoping nobody in Lisbon files a bug.

The test that decided it

Machine translation eats interpolation placeholders. That is the most common way a translated string breaks a build or renders {count} as literal text to a user. So this was the first real string I sent:

smart_translate(
  text: "Delete {count} items from your workspace?",
  source_language_code: "en",
  target_language_code: "de"
)
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "translation": "{count} Elemente aus Ihrem Arbeitsbereich löschen?",
  "source_language_code": "en",
  "target_language_code": "de",
  "word_count": { "source": 6, "target": 6 },
  "credits_used": 30,
  "request_id": "b0cea79d-fa11-4d21-be6a-9f2b0ef79b3c"
}
Enter fullscreen mode Exit fullscreen mode

The placeholder survived, and it moved to the front, which is where German syntax wants it. That reordering is what a naive find-and-replace pipeline gets wrong.

Second test, a longer engineering sentence into Japanese:

{
  "translation": "デプロイメントが失敗しました。キャッシュが冷えていたためです。以前のビルドにロールバックしてください。",
  "source_language_code": "en",
  "target_language_code": "ja",
  "word_count": { "source": 15, "target": 51 },
  "credits_used": 30,
  "request_id": "3b577571-1ead-4e7a-950e-aae1e69eb3b6"
}
Enter fullscreen mode Exit fullscreen mode

It split one English sentence into three Japanese ones, which reads better than a single clause would have.

Read the response fields

Three of them do real work once you are doing this at any volume.

request_id is a traceable ID per translation. I paste it into the PR next to any string I did not review by hand, so there is a record of which lines came from a machine.

credits_used came back as 30 on both calls, one for a six-word string and one for a fifteen-word string. Budget per call, not per word, until you have measured your own mix.

word_count is counted the way the target language works. English to German came back 6 and 6. English to Japanese came back 15 and 51, because Japanese has no spaces and the count is character-based. If you log these for cost or capacity, source and target are not comparable across scripts.

How it fits the day

I paste a block of English strings into the assistant, name the locales I need, and it calls the tool once per string and hands back JSON shaped the way my locale file already is. I read the output, fix the two or three strings I disagree with, and commit. No tab, no clipboard, no losing my place in the file.

It works in the other direction too. When a translation arrives from somewhere else and I cannot read it, running the same string through consensus tells me fast whether it is in the neighborhood of correct.

What it does not do

It is not a TMS. Two tools, text in and text out. No translation memory, no glossary enforcement, no term base. If you need "Workspace" to render identically across 400 strings, you still enforce that yourself.

There is no batch or file tool in the MCP surface. You loop over strings in the conversation. Fine for a feature's worth of copy, wrong for a 4,000-key migration. For build-time or CI work, use the API instead.

Consensus is not review. It lowers the odds of a bad string, it does not certify one. Legal copy, medical text, and anything on a pricing page still gets a human.

Paid only. No free tier for the MCP server.

Try it

Server URL for your connector settings:

https://www.machinetranslation.com/mcp
Enter fullscreen mode Exit fullscreen mode

If you connect it, run list_languages() before you translate anything and check your locale codes against the list. The regional variants are the difference between a string that ships and a string that gets reported.

What locale codes does your project actually use, and has anyone here found a reliable placeholder-safety check.

Top comments (0)