New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

@shipi18n/cli

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@shipi18n/cli

Catch broken translations before you ship: dropped placeholders, missing keys, collapsed plurals and — with your own LLM key — mistranslations the structure checks cannot see. CI-ready (SARIF, JUnit, exit codes). Translates too.

Source
npmnpm
Version
2.8.1
Version published
Weekly downloads
189
-85.33%
Maintainers
1
Weekly downloads
 
Created
Source

@shipi18n/cli

Catch broken translations before you ship them — and translate with your own LLM key when you want to. Open-source, no account, no hosted API.

Quickstart — check, no API key

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

Reports missing and orphaned keys, dropped placeholders, collapsed plurals, empty values, untranslated copy and per-language coverage. Deterministic and offline — no LLM, no key, nothing leaves your machine. Exit code 1 when there are errors, so it works as a CI gate as-is.

Then, for the errors structure cannot see, bring a key. The judge needs a provider SDK next to the CLI, so install both:

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

An LLM reads each source/translation pair and reports mistranslations, omissions and additions — advisory by default, so it never fails your build unless you ask it to.

Note. --semantic only judges keys that pass the structural checks, so if a tree is full of missing keys and dropped placeholders you will see judged 0 and no model calls. That is by design — fix the structural errors first, then re-run for meaning.

Measured on a 228-pair corpus committed before the judge was written: 100% of planted errors caught, 7.1% false positives. Full harness in the repo under evals/semantic/ — run it against your own model.

Translating

npm i -g @shipi18n/cli @anthropic-ai/sdk   # or add `openai` for the OpenAI provider
export ANTHROPIC_API_KEY=sk-ant-...
shipi18n translate en.json --target es,fr,de

Writes es.json, fr.json, de.json into the output directory (default ./locales), preserving structure and placeholders.

Usage

shipi18n translate <input> --target <langs> [options]

Options:
  -t, --target <langs>     Comma-separated target language codes (default: es,fr)
  -s, --source <language>  Source language code (default: en)
  -o, --output <dir>       Output directory (default: ./locales)
  -p, --provider <name>    LLM provider: anthropic (default) or openai
      --api-key <key>      LLM API key (else ANTHROPIC_API_KEY / OPENAI_API_KEY env)
      --model <model>      Override the provider's default model
      --base-url <url>     OpenAI-compatible endpoint (Ollama, Gemini compat, ...); needs -p openai
  -i, --incremental        Reuse existing output files; only translate new/missing keys

Examples

# Anthropic (default), multiple languages
shipi18n translate locales/en.json -t es,fr,ja

# OpenAI provider
shipi18n translate en.json -p openai -t de --api-key $OPENAI_API_KEY

# Incremental — only translate keys not already in the target file
shipi18n translate en.json -t es --incremental

# Ollama — fully local, no API key at all (any OpenAI-compatible server works)
shipi18n translate en.json -p openai --base-url http://localhost:11434/v1 --model llama3.2 -t es

# Google Gemini, via its OpenAI-compatible endpoint
shipi18n translate en.json -p openai --base-url https://generativelanguage.googleapis.com/v1beta/openai/ \
  --model gemini-2.5-flash --api-key $GEMINI_API_KEY -t es

Check — validate translations in CI (no LLM, no key)

shipi18n check is a deterministic QA gate for translated locale files. It works on output from any translator — this CLI, another tool, an agent, or a human — and needs no API key, so it can run on every push.

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

It detects both common layouts (locales/en.json and locales/en/<ns>.json), plus Flutter ARB directories and Apple String Catalogs (shipi18n check Localizable.xcstrings).

What it catches: missing and orphaned keys · dropped or invented placeholders ({{name}}, {count}, %s, %1$s, %@, %lld, $t(...), %{name}, HTML tags) · collapsed vue-i18n pipe plurals · empty values · untranslated copy · stale .xcstrings states.

FlagDefaultMeaning
-s, --source <lang>enSource language
-r, --reporter <name>humanhuman | json | sarif | junit
-o, --output <file>stdoutWrite the report to a file
--ignore-keys <globs>—Silence keys: '*.copyright,home:mcp.badge'
--fail-on <level>errorerror | warning | none
--min-coverage <pct>—Fail any language below this coverage

Exit codes: 0 pass, 1 findings at the fail level, 2 usage error. Errors may fail CI; warnings never do by default — a warning that blocks PRs gets the tool uninstalled.

GitHub Actions with PR annotations

- name: Check translations
  run: npx @shipi18n/cli check ./locales -s en --reporter sarif --output i18n.sarif

- name: Upload findings
  if: always()
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: i18n.sarif

Semantic QA — --semantic (the judge)

The structural check cannot see a translation that is fluent but wrong. --semantic adds an LLM-as-judge pass with your own key:

npx @shipi18n/cli check ./locales -s en --semantic          # advisory: warnings only
npx @shipi18n/cli check ./locales -s en --semantic --glossary glossary.json

Honest limitations, up front: the judge is probabilistic. Every key is judged across 3 passes and flagged only on a majority vote, unparseable passes are discarded, and semantic findings are warnings by default — they never fail CI unless you opt in with --semantic-fail. It augments review; it does not replace it. You pay your provider for the tokens; the verdict cache (.shipi18n/semantic-cache.json, safe to commit) makes unchanged re-runs free, and keys that already failed the structural check are never sent to the judge.

What it flags: semantic-mistranslation (says something different), semantic-omission (meaning dropped), semantic-addition (meaning invented).

Measured (committed 228-pair corpus, thresholds fixed before the judge was built, default judge claude-haiku-4-5, 3 passes). Two independent runs, 2026-08-16 and 2026-08-17:

2026-08-162026-08-17
planted errors caught54/54 (100%)54/54 (100%)
per-category recall100%100%
label accuracy100%53/54 (98.1%)
false positives on clean pairs12/168 (7.1%)12/168 (7.1%)
glossary violations6/6, 0 false6/6, 0 false
cost~62k tokens / 141s~59k tokens / 156s, 48 calls

The judge is a model, so treat these as a range, not a constant — label accuracy moved between runs while catch and false-positive rates held. On a real 478-pair production tree it flagged 3.6% of keys; the warm-cache rerun made zero model calls. Full harness: evals/semantic/ in the repo — run it against your own model and publish what you get.

Glossary (deterministic — no LLM)

{
  "Shipi18n": { "dnt": true },
  "dashboard": { "es": "panel", "de": "Dashboard", "ja": "ダッシュボード" }
}

"dnt" terms must survive verbatim; language entries are required translations. Violations are glossary-violation errors, caught by string matching at zero cost, and the glossary is also given to the judge as context.

Protect hand-edited translations — shipi18n lock

The oldest complaint about machine translation: you fix a string by hand, the tool runs again, and your fix is gone. Lock the translations a human has blessed, and check tells you when that happens.

# bless everything currently in the tree
npx @shipi18n/cli lock ./locales

# or just the strings you actually hand-edited
npx @shipi18n/cli lock ./locales --keys 'legal.*,checkout.cta'
npx @shipi18n/cli lock ./locales --lang de,ja      # narrow to some languages

This writes .shipi18n/locks.json — commit it, it is the record of which translations a person reviewed. It stores only hashes, never your strings.

Afterwards check reports two new findings:

FindingMeaning
manual-translation-clobberedthe locked translation's text changed — someone re-translated over a human edit
manual-translation-stalethe source changed underneath a locked translation, so the human edit may no longer be right

Both are warnings, never errors: this feature exists to protect people's work, not to block their pipeline. A lock that failed CI would just get deleted. Use --fail-on warning if you disagree, or --no-locks to ignore the lock file entirely.

# accept the current state as the new blessed baseline
npx @shipi18n/cli lock ./locales --relock

A missing or corrupt lock file is a cold start, not a crash.

Bring your own LLM

Set ANTHROPIC_API_KEY (default provider) or use -p openai with OPENAI_API_KEY. Your keys, your models — nothing is sent to a Shipi18n server. Built on @shipi18n/core.

--base-url points the OpenAI provider at any OpenAI-compatible endpoint: Ollama (http://localhost:11434/v1 — no key needed, fully offline), Gemini's compatibility endpoint, Groq, Mistral, LM Studio, vLLM, or a corporate gateway. It works for translate and for the check --semantic judge alike — pass --model for the model that server actually hosts. The judge's published accuracy numbers were measured on claude-haiku-4-5; before trusting a different judge model, run the eval harness in the repo (evals/semantic/run.mjs) against it.

License

Apache-2.0

Keywords

sarif

FAQs

Package last updated on 12 Sep 2026

Related posts