{"_id":"@didrod2539/i18nlint","name":"@didrod2539/i18nlint","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@didrod2539/i18nlint","version":"0.1.0","publishConfig":{"access":"public"},"description":"Lint i18n/l10n translation files locally: missing keys, placeholder mismatches, CLDR-aware incomplete plural forms, HTML tag drift and untranslated leftovers. Deterministic CLI, JSON/Markdown reports, no API key, no server.","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"bin":{"i18nlint":"dist/cli.js"},"engines":{"node":">=18"},"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","lint":"tsc --noEmit","example":"node dist/cli.js scan examples/locales --reference en","prepublishOnly":"npm run build"},"keywords":["i18n","l10n","internationalization","localization","translation","locale","i18n-linter","translation-checker","missing-keys","icu","pluralization","cldr","i18next","react-intl","gettext"],"author":{"name":"didrod205","url":"https://github.com/didrod205"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/didrod205/i18nlint.git"},"bugs":{"url":"https://github.com/didrod205/i18nlint/issues"},"homepage":"https://github.com/didrod205/i18nlint#readme","dependencies":{"cac":"^6.7.14","picocolors":"^1.1.1"},"devDependencies":{"@types/node":"^22.10.0","tsup":"^8.3.5","typescript":"^5.7.2","vitest":"^2.1.8"},"gitHead":"33736a2759271af80de4d64ff1760c3305ffc6c2","_id":"@didrod2539/i18nlint@0.1.0","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-YrL3D9xQLfMPSuXcSLG9Iz1ijKFzklH3LQAKSQu8EEnLNSJgWY21VUdMMQDs4XcSJvYgAnevwhLSFuzf5W+NBg==","shasum":"adb6039e1cec0360018b59d7a50b4ef0d8bbbc95","tarball":"https://registry.npmjs.org/@didrod2539/i18nlint/-/i18nlint-0.1.0.tgz","fileCount":15,"unpackedSize":341341,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDh5BKyZyx1Sk9uQdrnHCh7POzNPUxlS65BiECJKpx6sAiEA94mYxMygGpPbOL5jgzQ2UeryZ/82aBHTUJySITKI4tc="}]},"_npmUser":{"name":"didrod2539","email":"ykc205@naver.com"},"directories":{},"maintainers":[{"name":"didrod2539","email":"ykc205@naver.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/i18nlint_0.1.0_1780269724991_0.5334647611485055"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-31T23:22:04.790Z","0.1.0":"2026-05-31T23:22:05.153Z","modified":"2026-05-31T23:22:05.855Z"},"maintainers":[{"name":"didrod2539","email":"ykc205@naver.com"}],"description":"Lint i18n/l10n translation files locally: missing keys, placeholder mismatches, CLDR-aware incomplete plural forms, HTML tag drift and untranslated leftovers. Deterministic CLI, JSON/Markdown reports, no API key, no server.","homepage":"https://github.com/didrod205/i18nlint#readme","keywords":["i18n","l10n","internationalization","localization","translation","locale","i18n-linter","translation-checker","missing-keys","icu","pluralization","cldr","i18next","react-intl","gettext"],"repository":{"type":"git","url":"git+https://github.com/didrod205/i18nlint.git"},"author":{"name":"didrod205","url":"https://github.com/didrod205"},"bugs":{"url":"https://github.com/didrod205/i18nlint/issues"},"license":"MIT","readme":"<div align=\"center\">\n\n# 🌍 i18nlint\n\n### Catch broken translations before your users do — locally, no API key.\n\n[![npm version](https://img.shields.io/npm/v/@didrod2539/i18nlint.svg?color=success)](https://www.npmjs.com/package/@didrod2539/i18nlint)\n[![CI](https://github.com/didrod205/i18nlint/actions/workflows/ci.yml/badge.svg)](https://github.com/didrod205/i18nlint/actions/workflows/ci.yml)\n[![node](https://img.shields.io/node/v/@didrod2539/i18nlint.svg)](https://www.npmjs.com/package/@didrod2539/i18nlint)\n[![license](https://img.shields.io/npm/l/@didrod2539/i18nlint.svg)](./LICENSE)\n\nA deterministic CLI that lints your i18n/l10n translation files against a\nreference locale — **missing keys**, **placeholder mismatches**, **incomplete\nplural forms (CLDR-aware)**, **HTML tag drift** and **untranslated leftovers** —\nwith a coverage score, A–F grade and JSON/Markdown reports.\n\n</div>\n\n---\n\n## One-line summary\n\n`i18nlint` is a zero-config command-line linter that compares all of your\nlocale files (`en.json`, `ko.json`, `pl.json`, …) against a reference locale and\nreports every structural problem that breaks a translated UI — runs 100%\nlocally, no API key, no server.\n\n## Why this project exists\n\nTranslation files are where localized apps quietly break:\n\n- A **missing key** renders a blank label or falls back to English.\n- A renamed variable — `{name}` → `{nom}` — means the value is **never\n  interpolated** at runtime; users see a literal `{nom}`.\n- Polish has **four** plural forms (`one/few/many/other`); ship only two and the\n  grammar is wrong for whole ranges of numbers. Arabic has **six**.\n- A dropped `<a>` tag **breaks a link**; an injected tag can break layout.\n- A value left identical to English is an **untranslated leftover**.\n\nThese are mechanical, high-stakes, and easy to miss in review across 20 files.\nThey're also exactly the kind of cross-file, deterministic check an LLM gets\nsubtly wrong on large inputs — you want a **repeatable** tool you can gate a\ndeploy on. That's `i18nlint`.\n\n## Key features\n\n- 🔑 **Missing & orphan keys** vs a reference locale.\n- 🧩 **Placeholder mismatch** across `{x}`, `{{x}}`, `%s`/`%d`, `%(x)s`, `:x`.\n- 🔢 **CLDR-aware plural completeness** — knows Polish needs `one/few/many/other`,\n  Korean needs only `other`, Arabic needs six.\n- 🏷️ **HTML tag drift** — flags lost or unexpected markup in translations.\n- 🈳 **Empty values** and **untranslated** (same-as-source) detection.\n- 📊 **Coverage score + A–F grade** per locale and overall.\n- 📄 **JSON & Markdown export**, colored console output, **CI gate** exit codes.\n- ⚙️ **Config file** to set the reference, ignore keys, tune severities.\n- 🧱 Works with nested (i18next/react-intl) or flat JSON. Zero network calls.\n\n## Install\n\n```bash\n# run without installing\nnpx @didrod2539/i18nlint scan ./locales\n\n# or install\nnpm install -g @didrod2539/i18nlint    # global CLI (provides `i18nlint`)\nnpm install -D @didrod2539/i18nlint    # project dev-dependency (for CI)\n```\n\nNode ≥ 18. ESM + CJS + TypeScript types.\n\n## Quick start\n\n```bash\ni18nlint scan ./locales\n```\n\n```\nfr  56/100 (F)  75% coverage · 6/8 keys · locales/fr.json\n  ✗ Missing key \"cta\"\n      → Add \"cta\" to fr.\n  ⚠ Empty value for \"cart.empty\"\n      → Provide a translation for \"cart.empty\", or remove the key.\n  ✗ Incomplete plural in \"cart.items\" — missing `many`\n      → Add the many plural branch(es) for fr.\n  ℹ \"app.logout\" looks untranslated (same as en)\n\npl  69/100 (D)  100% coverage · 8/8 keys · locales/pl.json\n  ⚠ Orphan key \"legacy.old\" not in reference\n  ✗ Placeholder mismatch in \"cart.total\" (missing `amount`, unexpected `kwota`)\n  ✗ Incomplete plural in \"cart.items\" — missing `few`, `many`\n\nOverall  75/100 (C)  ref en, 92% avg coverage, 4 error(s), 2 warning(s), 1 info\n```\n\n## CLI usage\n\n```bash\ni18nlint scan [...paths]      # lint locale files / directories\ni18nlint report <input.json>  # re-render a saved JSON report as Markdown\ni18nlint init                 # scaffold i18nlint.config.json\ni18nlint --help\ni18nlint --version\n```\n\n`scan` options:\n\n| Option | Description |\n| --- | --- |\n| `--config <file>` | Path to a config file (otherwise auto-detected) |\n| `--reference <locale>` | Reference locale code (default: auto / `en`) |\n| `--json <file>` | Write a JSON report |\n| `--md <file>` | Write a Markdown report |\n| `--min-coverage <n>` | Exit non-zero if avg coverage < n (CI gate) |\n| `--max-errors <n>` | Exit non-zero if total errors > n (CI gate) |\n| `--quiet` | Hide info-level issues in the console |\n\nLocale codes are taken from file names (`en.json` → `en`, `pt-BR.json` →\n`pt-BR`). Point `scan` at a directory and it finds every `*.json` recursively.\n\n## Example result\n\nA full report for the bundled sample locales lives in\n[`examples/sample-report.md`](./examples/sample-report.md) and\n[`examples/sample-report.json`](./examples/sample-report.json).\n\n> 📸 _Screenshot / demo GIF placeholder:_ `./docs/screenshot.png` — record the\n> terminal running `npx @didrod2539/i18nlint scan examples/locales`.\n\n## Configuration\n\nCreate `i18nlint.config.json` (or run `i18nlint init`):\n\n```json\n{\n  \"reference\": \"en\",\n  \"untranslated\": \"warning\",\n  \"minCoverage\": 90,\n  \"ignoreKeys\": [\"legacy.*\"],\n  \"allowUntranslated\": [\"app.title\"],\n  \"disableRules\": [],\n  \"ruleSeverity\": { \"extra-keys\": \"error\" }\n}\n```\n\n| Field | Meaning |\n| --- | --- |\n| `reference` | Reference locale code, or `null` to auto-detect (`en`, else most keys) |\n| `untranslated` | Severity for same-as-source values: `\"off\"`, `\"info\"`, `\"warning\"`, `\"error\"` |\n| `minCoverage` | CI gate threshold (overridable with `--min-coverage`) |\n| `ignoreKeys` | Keys to skip — exact, or trailing-`*` prefix wildcard |\n| `ignoreLocales` | Locale codes to skip |\n| `allowUntranslated` | Keys allowed to equal the source (brand names, etc.) |\n| `disableRules` | Rule ids to turn off entirely |\n| `ruleSeverity` | Override severity per rule id |\n\nRule ids: `missing-keys`, `extra-keys`, `empty-value`, `placeholder-mismatch`,\n`plural-incomplete`, `html-mismatch`, `untranslated`.\n\n## Real-world use cases\n\n1. **Block broken translations in CI.** Add\n   `i18nlint scan ./locales --min-coverage 95 --max-errors 0` to your pipeline.\n   A PR that drops a key or renames a `{variable}` fails before it merges.\n2. **Onboard a new language safely.** Drop in `pl.json`, run `i18nlint scan` and\n   instantly see the missing keys, the Polish plural forms you still owe, and any\n   placeholders you mistyped.\n3. **Audit a translation vendor's delivery.** Run `i18nlint scan ./delivery\n   --md audit.md` and hand back a precise, per-key Markdown report instead of\n   eyeballing diffs.\n\n## Programmatic API\n\n```ts\nimport { lint, loadLocales, toMarkdown } from \"@didrod2539/i18nlint\";\n\nconst report = lint(loadLocales([\"./locales\"]), { config });\nconsole.log(report.summary.coverage, report.summary.grade);\nawait fs.writeFile(\"report.md\", toMarkdown(report));\n```\n\n## Roadmap\n\n- YAML and `.properties` (Java/Android) locale formats.\n- Namespaced / directory-per-locale layouts (`locales/en/common.json`).\n- Source-code scan to detect keys used in code but missing from locales.\n- ICU `select` / `selectordinal` deep validation.\n- `--fix` to scaffold missing keys and plural branches.\n- A GitHub Action wrapper that comments coverage on PRs.\n\n## FAQ\n\n**Does it send my files anywhere?**\nNo. `i18nlint` runs entirely on your machine — no API key, no telemetry, no\nuploads. It makes zero network calls.\n\n**Which i18n libraries does it work with?**\nAny that store messages as JSON: i18next, react-intl/FormatJS, vue-i18n, LinguiJS,\nPolyglot, and plain JSON. It understands ICU `plural` and the common placeholder\nstyles. (YAML/.properties are on the roadmap.)\n\n**How does it know Polish needs four plural forms?**\nIt ships a curated subset of the Unicode **CLDR** cardinal plural rules mapping\neach language to its required categories. Unknown languages default to\n`one/other`; see `src/plural.ts`.\n\n**Won't the plural/HTML checks have false positives?**\nThe plural scan is a deterministic regex over ICU branches and the HTML check\ncompares tag-name sets — both documented and conservative. Anything you disagree\nwith can be silenced via `disableRules`, `ignoreKeys`, or `ruleSeverity`.\n\n**Is the \"coverage score\" official?**\nNo — it's a transparent metric (translated keys ÷ reference keys, minus\ncorrectness penalties) so you can track and gate it. The math lives in\n`src/score.ts`.\n\n## Contributing\n\nContributions welcome! Each check is a small, self-contained rule in\n`src/rules/`. See [CONTRIBUTING.md](./CONTRIBUTING.md) and the\n[Code of Conduct](./CODE_OF_CONDUCT.md).\n\n```bash\ngit clone https://github.com/didrod205/i18nlint.git\ncd i18nlint\nnpm install\nnpm test\nnpm run build\nnode dist/cli.js scan examples/locales\n```\n\n## License\n\n[MIT](./LICENSE) © i18nlint contributors\n\n## 💖 Sponsor\n\ni18nlint is free, MIT-licensed, and built in spare time. If it caught a bug\nbefore your users did, please consider supporting it:\n\n- ⭐ **Star this repo** — free, and it helps others find it.\n- 🍋 **[Sponsor via Lemon Squeezy](https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010)** — one-time or recurring.\n\n**Where your support goes:** YAML/.properties support, namespaced layouts,\nsource-code key scanning, ICU `select` validation, a `--fix` mode, a PR-commenting\nGitHub Action, and fast issue responses.\n","readmeFilename":"README.md","_rev":"1-184df8f78a21fa96d850891232de283d"}