KAERIS i18n

io.github.kaeris-ioPublished by GitHub account kaeris-iov0.2.18Updated Nov 3, 2026

Translate app language files into 46 languages and check placeholders, plurals and missing keys.

VerifiedSTDIODesktop onlyDeveloper ToolsAI & ML

Overview

AI-generated overview

Local MCP server that audits a repo's locale files and translates them into 46 languages, keeping placeholders and plurals intact.

What you can do
  • Scan a repository to find locale files, base and target languages, and the i18n framework in use
  • Check each target locale for missing keys, extra keys, placeholder mismatches, number drift, lost tags and plural gaps
  • List the exact missing or broken keys for one locale, with their source text
  • List the supported target languages
  • Translate inline UI strings, with QA flags and optional back-translation
  • Translate a strings file on disk, or bootstrap a brand-new locale from the source file
When to use it
Useful when an assistant should audit or fix the internationalization of an app repository, add a new language, or translate locale files across formats such as JSON, YAML, .strings, .po, ARB, XLIFF or Fluent.
Requirements
Runs locally over stdio, installed from PyPI with uvx or pip (or via Docker). No key is needed for the free demo; optional KAERIS_API_KEY for higher limits and KAERIS_OPENROUTER_KEY for the BYOK plan. Translation tools need network access to the KAERIS API; scanning and status checks on JSON files work offline.
Before you install
Translation tools send your source strings to the KAERIS backend, and non-JSON formats are parsed by its /api/parse endpoint. Translation tools write files next to the source, including a kaeris.lock file. Paid plans are advertised; the free demo and free key have per-file and per-run limits. Keep the API keys in environment variables.

Installation

In SourceWeft

  1. Open KAERIS i18n in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

[KAERIS]

KAERIS MCP server

Lets Claude Desktop, Claude Code, Cursor or any MCP client check your app's locale files and translate them into 46 languages.

[PyPI] [license]

bash
pip install kaeris-mcp && claude mcp add kaeris-i18n kaeris-mcp
  • Three tools work offline with no key: find the locale files in a repo, report missing keys and broken placeholders per language, list the exact keys to fix.
  • Translation keeps placeholders and markup, translates only new keys with only_new, and with verify=True translates each line back to flag a changed meaning.
  • 13 formats: JSON, YAML, .strings, .xcstrings, .po, ARB, Android XML, CSV, XLIFF, .properties, .resx, Fluent .ftl, Markdown.
  • Same engine and kaeris.lock as the CLI, so an agent, a teammate and CI agree on what is already translated.

Plans. No key needed to try it. Free API key: 50k characters per file, 8 languages per run. Pro $19/month with 12M characters, Team/API $79/month with 60M, Lifetime $79 once with your own OpenRouter key. See kaeris.dev/pricing.

Docs · Pricing · Discussions · Status · Security

Tools

ToolWhat it doesCalls the API?
kaeris_scan_repoDiscover a repo's i18n setup: locale files found, base language, target languages, framework guess (i18next/next-intl/vue-i18n/Flutter/Android/iOS/gettext/generic)No
kaeris_statusCompleteness/health per target locale — missing keys, extra keys, placeholder mismatches, plus the quality detectors that gate a merge in CI: number drift, lost inline tags, broken entities/escapes, ICU/CLDR plural gaps (same verdict as kaeris check --json)No
kaeris_list_missing_keysThe exact missing/broken keys (with source text) for one target locale, so an agent knows exactly what to fixNo
kaeris_list_languagesList all supported target languagesYes, free (no quota)
kaeris_translateTranslate inline strings → per-language results, with QA (placeholder-loss & UI-overflow flags; verify=True back-translates to check meaning)Yes
kaeris_translate_fileTranslate a file on disk (JSON/YAML/.strings/.xcstrings/.po/ARB/XML/CSV/XLIFF/.properties/.resx/.ftl/Markdown), optional incremental — reproducible via kaeris.lockYes
kaeris_add_languageBootstrap a brand-new target locale by translating the whole source file into itYes

The first three tools are local-only (no network call, no cost), and kaeris_list_languages is one free API call that counts toward no limit — an agent can use all four freely to audit and understand a repo's i18n before deciding what (if anything) to translate.

Reproducible by design

kaeris_translate_file with only_new=True keeps a kaeris.lock next to your source file — the same lock the CLI writes, so an agent and a human sharing a repo stay in sync. It records a hash of every source string plus the settings that produced it: tone, glossary, app context, and the model. That means:

  • Edit one string — only that string is re-translated; everything else stays byte-for-byte.
  • Change tone, glossary or context — the whole locale is re-translated, never a mix of old and new.
  • Change plan — every tier runs the same model (GPT-6 Luna), so upgrading does not force a re-translation. The lock records the model regardless: the day we change it, the locale is rebuilt in full instead of quietly ending up the work of two models.

Commit kaeris.lock alongside your source file so the agent, your teammates and CI all agree on what is already done.

Install

bash
pip install kaeris-mcp

Or run it with Docker

bash
docker build -t kaeris-mcp .docker run -i --rm -v "$PWD:/work" -w /work kaeris-mcp

The server speaks JSON-RPC over stdin/stdout, so there is no port to expose — -i is what keeps the conversation open. Mount your project at /work and the repo-aware tools (scan_repo, status, list_missing_keys) read it directly; pass -e KAERIS_API_KEY=… to use your KAERIS key.

Configure your client

Claude Desktop — add to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\):

json
{  "mcpServers": {    "kaeris-i18n": {      "command": "kaeris-mcp",      "env": {        "KAERIS_API_KEY": "kaerisp_optional"      }    }  }}

Cursor — Settings → MCP → Add, or .cursor/mcp.json:

json
{ "mcpServers": { "kaeris-i18n": { "command": "kaeris-mcp" } } }

Claude Code — one command:

bash
claude mcp add kaeris-i18n kaeris-mcp

Restart the client; the KAERIS tools appear automatically.

Auth & tiers (all optional)

Env varPurpose
KAERIS_API_KEYYour KAERIS key, e.g. the free API key from kaeris.dev/docs/key (50k chars/file, no account) — without one, the free 10k-char demo is used
KAERIS_OPENROUTER_KEYOpenRouter key for Lifetime/BYOK — no monthly volume cap
KAERIS_API_URLOverride the API base URL
KAERIS_NO_UPDATE_CHECKSet to 1 to turn off the once-a-day "new version available" line (stderr only; already off in CI)
KAERIS_NO_CTASet to 1 to drop the pricing link from the error a tool returns when a run is refused on a plan limit

No key is required to try it — the free demo works out of the box.

Example prompts

  • "Translate the strings in locales/en.json into German, Ukrainian and Japanese."
  • "Add French and Spanish translations for these buttons: Save, Cancel, Delete."
  • "Only translate the new keys I added to en.json — don't redo the whole file."
  • "Check this repo's i18n and tell me what's missing or broken." (scans, then reports status — no API call)
  • "We don't have Ukrainian yet — add it." (bootstraps a new locale via translation)

License

MIT

Source: README.md at commit 4dd41a4

Tools

7

kaeris_list_languages

List every target language KAERIS can translate into. Returns a mapping of language code → English name (e.g. {"es": "Spanish", ...}). Use the codes with kaeris_translate / kaeris_translate_file.

Takes no parameters.

kaeris_scan_repo

Discover a repo's i18n setup: which locale files exist, what the base (source) language and target languages are, and which framework/convention is in use. Purely local — reads the filesystem, makes no API call. Use this FIRST when asked to "look at/audit/fix this repo's i18n" and you don't already know the source file — it tells you where to point kaeris_status / kaeris_list_missing_keys / kaeris_add_language. Supports i18next/react-i18next (locales/<lang>/*.json), next-intl (messages/<lang>.json), vue-i18n (src/locales/<lang>.json), Flutter ARB (lib/l10n/*.arb), Android (res/values[-<lang>]/strings.xml), iOS (<lang>.lproj/Localizable.strings), gettext (locale/<lang>/LC_MESSAGES/*.po), Xcode String Catalogs (Localizable.xcstrings — one file holding every language, read out of the catalog itself), XLIFF (.xliff/.xlf), Java/Spring .properties, .NET RESX, Mozilla Fluent (.ftl), Markdown/MDX, plus plain <lang>.json/.yaml/.csv files anywhere else. Skips node_modules, .git, build/dist output and other noise; bounded so it's safe on large repos. Args: path: directory to scan (default: current directory). Returns: { "root": "<abs path scanned>", "locale_files": ["locales/en.json", "locales/de.json", ...], "base_lang": "en", # best guess, or null if none found/ambiguous "target_langs": ["de", "fr"], # languages present other than base_lang "framework": "i18next", # best guess, or null if unrecognized "file_count": 2 }

Parameterspath

kaeris_status

Completeness/health check for every target locale against a source file — the "i18n firewall". Works for EVERY supported format: JSON is checked fully offline; other formats (.arb/.strings/.po/.xml/.ftl/…) are parsed by the backend /api/parse endpoint (no translation, no cost). For each target language reports missing keys, extra/stale keys, placeholder mismatches (e.g. "Hello {name}" -> "Bonjour" loses {name}, or "{name}" -> "{nom}" which crashes str.format()/ICU at runtime), and the same quality detectors the CLI firewall gates a merge on: number drift ("Delete 5 files" -> "Delete 50 files"), lost inline tags, broken entities or escapes, missing ICU/CLDR plural categories. Those come back under "faults" (RED — they set ok=false) and "warnings" (YELLOW — advisory). Use this to find out what's broken before deciding whether to call kaeris_list_missing_keys (existing locale, some keys missing/broken) or kaeris_add_language (locale doesn't exist yet at all). Args: source: path to the base-language locale file, e.g. "locales/en.json" or "lib/l10n/app_en.arb" (typically kaeris_scan_repo's base_lang file). langs: target language codes to check, e.g. ["de", "fr"]. If omitted, falls back to kaeris.json's "langs" if present, else auto-discovers sibling <lang> files next to the source. out: directory containing the target locale files (default: source's own directory; each target is expected at "<out>/<lang><source-ext>", e.g. de.json for en.json, de.arb for en.arb). Returns: { "ok": bool, "missing": {lang: [keys]}, "extra": {lang: [keys]}, "placeholder_issues": [{"lang","key","missing","added"}, ...], "faults": [{"lang","key","severity":"error","msg"}, ...], # RED — ok is false "warnings": [{"lang","key"?,"severity":"warn","msg"}, ...], # YELLOW — advisory "missi

Parametersoutlangssourcerequired

kaeris_list_missing_keys

The exact keys missing or placeholder-broken in ONE target locale, with their source-language text, so an agent knows exactly what to translate/fix. Every supported format works (JSON offline; others via the backend /api/parse, no cost) — use kaeris_add_language or kaeris_translate to actually fill them in. The answer is PAGED: "missing_count" is always the exact total, while "missing" / "missing_values" carry one page of it (default 200 entries, and never more than about 15k tokens' worth). A whole locale file returned in one tool result is not a long answer, it is the end of the agent's session — measured 16.08.2026: a 2 MB source file, the size the product accepts, produced 667 000 tokens in a single reply. When there is a next page, "page.next_offset" says where it starts. Args: source: path to the base-language locale file, e.g. "locales/en.json" or "app_en.arb". lang: the target language code to inspect, e.g. "de". out: directory containing the target locale file (default: source's own directory; expected at "<out>/<lang><source-ext>", e.g. de.json / de.arb). limit: how many missing keys to return in this call (default 200, max 2000). offset: where to start — pass "page.next_offset" from the previous call. Returns: { "lang": "de", "missing_file": bool, "missing_count": 1234, # ALWAYS the exact total "missing": ["<dotted.key>", ...], # one page of them "missing_values": {"<dotted.key>": "<source text>", ...}, # the same page "page": {"total", "returned", "offset", "complete", "next_offset"?, "note"?}, "extra": ["<dotted.key>", ...], "extra_count": 12, "placeholder_issues": [{"key","missing","added"}, ...], "placeholder_issue_count": 3 }

Parametersoutlangrequiredlimitoffsetsourcerequired

kaeris_add_language

Bootstrap a BRAND-NEW target locale by translating the entire source file into it and writing the result. Use this when kaeris_scan_repo/kaeris_status show a language with no locale file at all yet (in "missing_files"). This CALLS THE TRANSLATION API (same as kaeris_translate_file) — unlike kaeris_scan_repo/kaeris_status/kaeris_list_missing_keys, which are local-only and free. For an EXISTING locale that's just missing a few keys, prefer kaeris_translate_file(..., only_new=True) so you don't re-translate the whole file. Args: source: path to the base-language locale file, e.g. "locales/en.json". lang: the new target language code to create, e.g. "de". out: output directory (default: source file's own directory). tone: "formal" or "casual" to steer register; "" (default) is neutral. icu: True if the source uses ICU MessageFormat (plurals/select), so the model preserves the syntax instead of translating it. keep: optional list of terms to NEVER translate (brand/product names). context: one line about what the app IS, e.g. "a mobile bank for teenagers" or "a wildlife documentary app". The model uses it to pick the right sense of ambiguous strings — "Bank" becomes Ufer in the second and Bank in the first. You are reading the repo, so you know this: pass it. Max 300 chars. api_key / openrouter_key: optional auth overrides. PREFER the KAERIS_API_KEY / KAERIS_OPENROUTER_KEY environment variables: a key passed as a tool argument is written into the conversation transcript and into the MCP client's logs, where it outlives this task. job_id: leave empty to start a translation. If a previous call came back with {"status": "running", "job_id": ...}, pass that id (with the same source/out) to collect the finished result instead of paying for the work twice

Parametersicuoutkeeplangrequiredtonejob_idsourcerequiredapi_keycontextopenrouter_key

kaeris_translate

Translate a set of UI strings into one or more languages. Format-aware and placeholder-safe: values like "Hello, {name}" keep their placeholders intact. Non-string values are passed through unchanged. Includes Translation QA so you can trust the output: it flags translations that dropped a placeholder or that grew long enough to risk overflowing the UI. With verify=True it also back-translates each result into back_lang so you can check the meaning is right even in a language you can't read. Args: strings: key → source-text pairs, e.g. {"greeting": "Hello", "save": "Save"}. target_languages: language codes from kaeris_list_languages, e.g. ["es", "fr", "ja"]. keep: optional list of terms to NEVER translate — kept verbatim in every language (brand/product names), e.g. ["KAERIS", "GitHub"]. context: one line about what the app IS, e.g. "a mobile bank for teenagers" or "a wildlife documentary app". The model uses it to pick the right sense of ambiguous strings — "Bank" becomes Ufer in the second and Bank in the first. You are reading the repo, so you know this: pass it. Max 300 chars. verify: back-translation and meaning check for your review — back-translates into back_lang and flags lines whose meaning may have changed (uses more tokens). back_lang: language for the verify back-translation (default "en"). tone: "formal" or "casual" to steer register; "" (default) is neutral. icu: True if values may contain ICU MessageFormat (plurals/select) so the model preserves the syntax instead of translating it. reuse: optional {lang: {key: previous_translation}} translation-memory map — strings that are unchanged from a prior run are reused verbatim server-side and only new/changed strings are actually translated. api_key / openrouter_key: optional auth overr

Parametersicukeeptonereusejob_idverifyapi_keycontextstringsrequiredback_langopenrouter_keytarget_languagesrequired

kaeris_translate_file

Translate a strings file on disk and write the results next to it. Supports JSON, YAML, .strings, Xcode String Catalogs (.xcstrings), .po, ARB, Android XML, CSV, XLIFF 1.2, Java/Spring .properties, .NET RESX, Mozilla Fluent (.ftl) and Markdown/MDX — every format the API accepts. With only_new=True (JSON), translates just the keys missing from existing target files and merges. Args: file_path: path to the source file, e.g. "locales/en.json". target_languages: language codes, e.g. ["de", "uk", "zh"]. out_dir: where to write outputs (default: the source file's directory). only_new: reproducible incremental mode — translate only new AND edited keys, tracked via kaeris.lock (a tone/ICU/glossary change re-translates all); unchanged stays stable. JSON and ARB only — for any other format the whole file is translated (and charged), and the result says so in "note". keep: optional list of terms to NEVER translate — kept verbatim in every language (brand/product names), e.g. ["KAERIS", "GitHub"]. context: one line about what the app IS, e.g. "a mobile bank for teenagers" or "a wildlife documentary app". The model uses it to pick the right sense of ambiguous strings — "Bank" becomes Ufer in the second and Bank in the first. You are reading the repo, so you know this: pass it. Max 300 chars. verify: back-translation and meaning check for your review; with only_new the result carries them as "back_translations". back_lang: language for the verify back-translation (default "en"). tone: "formal" or "casual" to steer register; "" (default) is neutral. icu: True if values may contain ICU MessageFormat (plurals/select) so the model preserves the syntax instead of translating it. reuse: optional {lang: {key: previous_translation}} translatio

Parametersicukeeptonereusejob_idverifyapi_keycontextout_dironly_newback_langfile_pathrequired+2 more

Version history

2
  1. v0.2.18LatestNov 1, 2026
  2. v0.2.16Oct 29, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts