Skip to main content
Glama

Shipi18n

CI npm MCP downloads License OpenSSF Scorecard Smithery

Catch broken translations before you ship them. Shipi18n is an open-source check for locale files: it fails CI when a translation — from a human, a TMS or an AI agent — drops or breaks a placeholder, plural or key, and shows the exact fix.

  • Detects: dropped or renamed placeholders ({name}, {{count}}, %{name}, %s, %1$s, %@), plurals whose separator was lost or that miss a form the language needs (Polish few/many, Arabic two — in ICU, i18next keys, Rails, Android and .xcstrings), invalid ICU MessageFormat, missing and orphaned keys, a key written twice in one JSON object, empty and untranslated strings.

  • Formats: JSON (i18next, vue-i18n, next-intl, flat or nested), YAML including Rails, Flutter ARB, Apple .xcstrings, Android strings.xml, gettext .po, XLIFF.

  • Uploads anything? No. The check runs offline, on your machine or CI runner.

  • Needs a key or account? No. Only the optional --semantic review and translate use your own LLM key.

npx @shipi18n/cli check ./locales -s en

Exit 0 clean, 1 findings (each with a one-line fix), 2 usage error. Output as text, JSON, SARIF (GitHub PR annotations) or JUnit.

shipi18n check finding a dropped placeholder, then an LLM catching a translation that says "will save" where the English says "delete"

A real run, not a mockup — docs/check-demo.tape reproduces it.

Choose your task

I want to…

Use

check locale files locally or in any CI

npx @shipi18n/cli check ./locales -s en

annotate GitHub pull requests

Shipi18n/shipi18n-github-action@v3 (setup)

run it without Node

Docker ghcr.io/shipi18n/cli, or the pre-commit hook

run it without npm

the single file shipi18n.mjs from GitHub releases, with a SHA-256 and a build-provenance attestation

use it from a Python, Ruby, Go or Java project

the same CLI via Docker, pre-commit or the single file. There is no Python or Ruby package to import

have my AI coding agent check its own translations

npx @shipi18n/cli init --agents, or the MCP server @shipi18n/mcp

check meaning, not only structure

check --semantic with your own Anthropic or OpenAI key

translate (optional)

shipi18n translate with your own key

What it doesn't do

  • It is not a hosted service or a TMS: no account, dashboard or pricing. (An earlier hosted translation API was retired.)

  • It is not a library you import from Python or Ruby; it is a CLI you run from any CI.

  • Without --semantic it checks structure, not whether a translation reads well.

Found in the wild. The same command on a real project — Hoppscotch's Afrikaans locale as it was when we ran it (structural rules only; the missing-key noise switched off so the placeholder findings stand out):

$ npx @shipi18n/cli check packages/hoppscotch-common/locales -s en \
    --severity 'missing-key=off,untranslated=off,orphan-key=off'

✗ af  coverage 100.0%  7 error(s), 5 warning(s)
    error  import.file_size_limit_exceeded_warning_multiple_files  placeholder-missing — dropped {sizeLimit}
    error  import.file_size_limit_exceeded_warning_single_file  placeholder-missing — dropped {sizeLimit}
    error  state.connected_to  placeholder-missing — dropped {name}
    warning  state.connected_to  placeholder-added — unexpected {naam}
    error  team.invited_to_team  placeholder-missing — dropped {workspace}
    warning  team.invited_to_team  placeholder-added — unexpected {team}
    …

{naam} is {name} translated; vue-i18n will never substitute it. We scan public repos, verify every finding by hand against the source language, and send the fix upstream. Running tally, one row per repo: shipi18n.com/oss — 95 repos scanned · 23 ship a verified broken string · 402 strings verified by hand · fixed upstream in 8 repos.

Repo

What happened

Solidus

13 dropped %{…} interpolations in pt-BR — PR merged the same day, three core approvals

Plane

Czech toasts lost {templateName}/{templateType} — PR merged in 3 hours

nocodb

our issue → maintainer PR sweeping all 39 locales; later 182 plurals in 13 locales whose | machine translation had turned into <unk>, fixed the same day

Excalidraw

our issue → contributor fix + a placeholder-parity test suite

Hoppscotch

the af run above — PR merged, 7 strings

24pullrequests

20 dropped interpolations fixed in two PRs, then an interpolation check added to their own test suite

devise_invitable

%{email) typo fixed, then the check added to their CI

Every false positive the scan exposed in our own checker became a fixture in evals/placeholders/corpus.jsonl first.

If the check catches something in your project, consider starring the repo — stars are how the next person with a broken es.json finds this.

Then, when you want the meaning checked, bring your own key. The judge needs a provider SDK alongside the CLI:

npm i -D @shipi18n/cli @anthropic-ai/sdk       # or `openai`
export ANTHROPIC_API_KEY=sk-ant-...            # or OPENAI_API_KEY
npx @shipi18n/cli check ./locales -s en --semantic

An LLM reads each pair and reports mistranslations, omissions and additions:

⚠ es  coverage 100.0%  0 error(s), 1 warning(s)
    warning  delete  semantic-mistranslation — Translation says 'will save' (guardará)
                     instead of 'will delete' (eliminará/borrará)

Every placeholder is intact and every key is present, so structural checks pass this file. Only reading it catches the bug. Advisory by default — it warns, it does not fail your build.

Measured, not asserted. On a 228-pair corpus committed before the judge was written (60d699b) and with thresholds fixed first: 54/54 planted errors caught (100%) and 12/168 false positives on clean pairs (7.1%), with 6/6 glossary violations found. Reproduced on two independent runs (2026-08-16 and 2026-08-17) using claude-haiku-4-5, 3 passes, ~59k tokens in 156s. Label accuracy moved between runs (100% → 98.1%) — it is a model, so read these as a range, not a constant. The harness is evals/semantic/. Run it against your own model.

Nothing goes through our servers, because there are none. The only network call is from your machine to the provider you chose.

Packages

Package

Description

@shipi18n/core

The engine: translation checks, the semantic judge, placeholder validation — plus provider-agnostic, structure-preserving translation with incremental mode.

@shipi18n/cli

shipi18n check ./locales for CI, --semantic for meaning, lock to protect hand-edits, translate when you need it.

@shipi18n/mcp

MCP server — check, diff and review locale files from Claude Desktop, Cursor, or any MCP client. Validation needs no API key.

vite-plugin-shipi18n

Vite plugin that translates locale files at build time with your own LLM key, with caching.

shipi18n-github-action

Check locale files on every PR (no key, SARIF annotations); optional BYO-key retranslate.

Related MCP server: i18n-keeper

Why

Generating translations is a solved problem. Half a dozen good tools will fill your locale files, and an agent will do it for free. Nothing checks the result. Your CI lints your JavaScript, typechecks your types and runs your tests — and then ships a de.json that nobody has read, produced by a model nobody audited.

The checks that do exist are structural: they diff key sets and stop there. That catches the missing key. It does not catch the translation that has every key and every placeholder and still tells your German users the opposite of what you meant.

Shipi18n is that missing gate, in two layers:

  • Deterministic, offline, no key. Missing and orphaned keys, dropped or malformed placeholders ({{name}}, {count}, %s, %d, %1$s, $t(...), %{name}, HTML), collapsed plural forms, empty values, untranslated copy, coverage per language.

  • Semantic, with your own key. An LLM-as-judge pass over changed keys only, with multi-pass majority voting because single-pass judge scores are unstable. Reports mistranslation, omission and addition. Advisory by default — a QA tool that fails your build gets uninstalled.

Plus the parts that make it usable day to day:

  • Formats beyond JSON. YAML (.yaml/.yml), Flutter .arb, Apple .xcstrings, Android strings.xml, gettext .po/.pot, and XLIFF 1.2/2.0 — including %@/%lld specifiers.

  • CI-native. Correct exit codes, --fail-on, --min-coverage, SARIF for PR annotations, JUnit.

  • Adopts on a messy catalog. --baseline accepts today's backlog and fails only on new findings (the Stylelint/RuboCop pattern); --severity tunes or silences any rule (error|warning|info|off).

  • Runs anywhere, Node or not. GitHub Action, a pre-commit hook, or the Docker image ghcr.io/shipi18n/cli for GitLab / Bitbucket / Jenkins / local — no Node toolchain required. See the CLI README.

  • WordPress .po ↔ JED sync. shipi18n wp-sync catches JS translation JSON that has drifted from the .po because nobody re-ran wp i18n make-json — a silent bug no other tool checks.

  • Privacy pre-flight. Before --semantic sends anything to an LLM, a scan withholds any string carrying a secret or PII (keys, cards, emails); --detect-secrets runs it standalone with no LLM.

  • Hand-edits are protected. shipi18n lock records the translations a human blessed and warns when anything overwrites them, or when the source moves underneath them.

  • Keyless from your editor. The MCP server's validators call no model at all.

  • It also translates. Provider-agnostic, structure-preserving, incremental — Anthropic and OpenAI in the box, and any object with a complete(prompt) method is a valid adapter.

Check from your editor — no API key

@shipi18n/mcp brings the checks to any MCP client. The validation tools call no model, so they need no key at all.

Claude Code — one line:

claude mcp add shipi18n -- npx -y @shipi18n/mcp

Claude Desktop / any MCP client — paste the config:

// claude_desktop_config.json
{
  "mcpServers": {
    "shipi18n": { "command": "npx", "args": ["-y", "@shipi18n/mcp"] }
  }
}

"Check ./locales against English and tell me what's broken in Spanish."

review_locales goes further without needing a key either: it hands your agent the translation pairs and the review criteria, and your agent reasons about meaning with the model it already runs.

Check in CI — no key needed

The checks work on translations from any source — a TMS, another tool, an agent, a human. Run it on every push:

npx @shipi18n/cli check ./locales -s en

Missing keys, dropped placeholders, collapsed plurals, empty values and untranslated copy — reported as human output, JSON, SARIF (GitHub PR annotations) or JUnit. Works on plain JSON trees, YAML, Flutter .arb, Apple .xcstrings, Android strings.xml, gettext .po/.pot and XLIFF (1.2/2.0). Deterministic and offline: no LLM, no API key.

Protect hand-edited translations

Fix a string by hand, lock it, and check warns you if anything ever overwrites it — or if the English moves underneath it:

npx @shipi18n/cli lock ./locales --keys 'legal.*'

.shipi18n/locks.json stores hashes only, is safe to commit, and these findings are warnings — protecting human work must never block a pipeline. Details in the CLI README.

It also translates

Checking works on translations from anywhere, but if you want Shipi18n to produce them too, it does — with your key, your model, and nothing in between.

npm i -D @shipi18n/cli @anthropic-ai/sdk       # or `openai`
export ANTHROPIC_API_KEY=sk-ant-...            # or OPENAI_API_KEY
npx @shipi18n/cli translate locales/en.json -t es,fr,de
✔ es → locales/es.json (2 translated, 0 reused)

Or from Node (same SDK requirement):

import { translateJSON } from '@shipi18n/core'

const { result, stats } = await translateJSON({
  content: { greeting: 'Hello {{name}}' },
  from: 'en',
  to: 'es',
  provider: 'anthropic',        // 'anthropic' | 'openai' | custom { complete } adapter
})
// result → { greeting: 'Hola {{name}}' }

Structure-preserving, placeholder-safe and incremental — only new or changed keys are sent to the model. Then check the result with the same tool.

Examples

Runnable projects in examples/ — react (Vite + react-i18next), nextjs (App Router), vue (vue-i18n), and nodejs (@shipi18n/core). cd into one, install, and run its check/translate scripts.

Development

This is a pnpm + turbo monorepo.

pnpm install
pnpm test          # all packages
pnpm --filter @shipi18n/core test

Changesets manage versioning: pnpm changeset to add one.

Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md. pnpm install && pnpm test runs 105 tests against a mock adapter, so you need no API key to work on this.

License

Apache-2.0 © Shipi18n. See NOTICE.

Available Tools

8 tools
check_glossaryCheck glossary complianceA

Enforce do-not-translate terms and locked per-language translations across a locale tree. Deterministic string matching — no API key and no model call required.

Inputs: path (required), glossaryPath (required), source.

check_localesCheck locale filesA

Validate translated locale files against the source language: missing/orphaned keys, dropped or invented placeholders, collapsed plurals, empty values, untranslated copy. Deterministic — no API key and no model call required.

Inputs: path (required), source, ignoreKeys, glossaryPath.

check_placeholdersCheck PlaceholdersA

Verify a translated string preserves all placeholders from the source (no LLM call).

Inputs: source (required), translation (required).

diff_localesDiff locales against the sourceA

Answer "what needs translating?": per language, the keys missing from the translation and the keys present that no longer exist in the source. No API key required.

Inputs: path (required), lang, source.

list_languagesList LanguagesA

List the language codes and names Shipi18n recognizes (any BCP-47 code works; these have friendly names).

Inputs: no named parameters.

review_localesGet translation pairs for semantic reviewA

Return source/translation pairs plus review criteria so YOU can judge translation quality with your own reasoning. The server performs no model call and needs no API key. Structurally broken keys are excluded — run check_locales for those.

Inputs: lang (required), path (required), limit, source.

translate_fileTranslate FileA

Read a JSON locale file from disk, translate it into one or more languages, and write .json files. Set incremental=true to reuse existing output files and only translate new/missing keys.

Inputs: to (required), path (required), from, model, +3 more.

translate_jsonTranslate JSONA

Translate an i18n locale JSON object to one or more target languages, preserving structure and placeholders. Requires your own LLM key (ANTHROPIC_API_KEY/OPENAI_API_KEY). For key-free work use the validation tools. Legacy: the MCP client's model via sampling.

Inputs: to (required), content (required), from, model, +1 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.2/5.0

    Scored across 8 tools

    Disambiguation4/5

    Tools are mostly distinct, but translate_json and translate_file overlap in purpose, and check_locales/diff_locales/review_locales cover related validation concerns that could occasionally be confused.

    Naming Consistency5/5

    All tools follow a clear verb_noun pattern (check_, diff_, review_, translate_, list_), with consistent and descriptive naming throughout.

    Tool Count5/5

    Eight tools is a reasonable, focused set for an i18n workflow, covering validation, diffing, translation, glossary, and language listing without feeling bloated.

    Completeness5/5

    The toolset covers the core i18n lifecycle: listing languages, translating JSON/files, validating structure, checking glossaries, identifying missing keys, and reviewing translation quality. No major gaps are apparent.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      B
      maintenance
      AI localization from your editor — translate an app's string files into 46 languages with placeholder-safe, reproducible output. Eleven formats (JSON, .arb, .po, .strings, Android XML and more), and every translation is read back and checked before it lands.
      7
      118 PyPI
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables deterministic linting of locale files across JSON, Laravel PHP, gettext, and YAML, with no LLM or network, verifying translations for missing keys, placeholders, plural forms, glossary consistency, and stale entries.
      10 npm
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents, developers, and CI pipelines to run deterministic, offline localization QA — pseudolocalization, locale linting for separators/dates/placeholders/RTL, and glossary enforcement across 20 locales — without any network access or API key. When an OpenAI-compatible endpoint is configured, it can also translate into multiple locales and lint the results, or otherwise emit structured translation jobs instead of fabricating translations.
      10 npm
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Lets a coding agent find, translate, validate and review an app's existing localization files (Android strings.xml, Apple String Catalogs, gettext) directly in the repo, supplying code usage, comments, glossary and voice notes as context. Placeholders, plural forms, escaping and markup are checked in code before any translation is written, and results land as reviewable drafts that never overwrite hand edits or approved work.
      36 npm
      1
      MIT