i18n-key-usage

中文文档
0.2.0发布于 2026/11/04MITTypeScriptES Module构建来源已验证

Find unused, missing and unresolved i18n message keys in TypeScript projects. Follows keys across modules, types and helpers; fast oxc parser.

原始使用文档
文档目录10 节
  1. Quick start
  2. GitHub Action
  3. What it reports
  4. Configuration
  5. Flags and environment variables
  6. Dynamic key families
  7. Programmatic API
  8. Adapters
  9. Development
  10. License

i18n-key-usage

Find unused, missing and unresolved i18n message keys in TypeScript projects.

Most key checkers grep each file for t("…") calls. That misses keys stored in constants, passed through helpers, typed as a key union or built from an enum, so they report many used keys as unused. i18n-key-usage evaluates how keys flow through your code instead:

  • Across modules: imports, re-exports and export *, plus tsconfig path aliases.
  • Through values: constants, objects, maps, arrays, spreads, ternaries, template literals, destructuring, function parameters and return values, Array.map callbacks and React state.
  • Through types: values typed as your key type (e.g. TMessageKey) count as key references wherever they are used.
  • Strict about dynamic keys: a key it cannot resolve to a finite set is reported, with its location, instead of silently counting a whole namespace as used. Families such as status.* are declared against the enum that lists their values, and stale declarations fail.

Parsing uses oxc. A 3,200-file Next.js app with 5,400 keys checks in about 25 seconds.

Supported libraries: next-intl. The analyzer core is library-agnostic; adapters for other libraries are welcome (see Adapters).

Quick start

npm install --save-dev i18n-key-usage

Create i18n-key-usage.config.json in the project root:

{
  "$schema": "./node_modules/i18n-key-usage/schema.json",
  "messages": "messages/en.json"
}

Run it:

npx i18n-key-usage
Unused message: home.subtitle (messages/en.json)
src/app/page.tsx:12: Unknown message key: home.titel
  t("titel")
Scanned 214 files: 380/381 keys referenced, 1 unused, 1 missing, 0 unresolved.

Exit codes: 0 clean, 1 problems found, 2 configuration or parse error.

GitHub Action

name: i18n
on: pull_request
jobs:
  keys:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with:
          node-version: 24
      - uses: saasykits/i18n-key-usage@v0
        with:
          working-directory: . # project root, e.g. apps/web in a monorepo

Findings appear as annotations on the pull request and a table in the job summary. The action runs the npm release matching its tag (@v0 runs the latest 0.x), so no install step is needed. Set version to pin an exact release.

Every configuration option is also an input, in kebab-case, so a project can skip the config file or override it per workflow. Inputs replace the config file's value; lists take one item per line or comma-separated items:

- uses: saasykits/i18n-key-usage@v0
  with:
    messages: messages/en
    include: |
      src/**/*.{ts,tsx}
    key-types: src/i18n/types.ts#TMessageKey
    families: |
      orders.status.{{EOrderStatus}}

What it reports

ProblemMeaning
UnusedA catalog key that no application code references.
MissingCode references a key that the source catalog does not define.
UnresolvedA dynamic key whose possible values cannot be determined, e.g. t(row.labelKey) where labelKey is an arbitrary string. Type it as your key type, narrow it, or declare a family.
Invalid declarationA family or retained key that is malformed, unused or stale.

Tests, fixtures, generated files and catalog imports (e.g. passing messages to a provider) do not count as usage.

Configuration

All paths are relative to the project root. Only messages is required, and the config file itself is optional when options come from flags or environment variables. The full schema ships as schema.json.

OptionDefaultDescription
messagesSource-locale catalog: a JSON file, or a directory with one JSON file per top-level namespace (messages/en/common.json holds common.*).
library"next-intl"Which adapter recognizes translators.
include["**/*.{ts,tsx}"]Sources to scan.
excludetests, fixtures, *.d.ts, node_modules, build outputGlobs removed from include.
catalogs["messages/*"]Catalog files or directories. Importing one is a catalog read, not usage.
tsconfig"tsconfig.json"Where import aliases (compilerOptions.paths) come from.
keyTypes[]Types whose values are message keys, e.g. { "file": "src/i18n/types.ts", "export": "TMessageKey" }.
translatorTypes[]Types whose values are translators, e.g. an ITranslator interface with a call signature.
messageLoaders[]Functions that return the whole catalog. Member reads on the result count as usage.
keyFunctions[]Functions whose argument at argument (zero-based) is a key, e.g. translateMessage(t, key).
keyProperties[]Object properties whose string values are keys, e.g. ["labelKey", "titleKey"].
validationLibraries[]Modules whose call arguments may be keys, e.g. ["zod"] for z.string().min(1, "errors.required").
literalKeyPrefixes[]Any string literal with one of these prefixes is a key, e.g. ["errors."] for error codes thrown on the server.
families[]Dynamic key families such as "orders.status.{{EOrderStatus}}", see below.
retained[]Keys to keep although nothing references them, e.g. for historical data.

Flags and environment variables

Every option can be set three ways. A flag replaces the environment variable, which replaces the config file:

Config fileFlagEnvironment variable
messages--messages messages/enI18N_KEY_USAGE_MESSAGES
include--include 'src/**/*.{ts,tsx}'I18N_KEY_USAGE_INCLUDE
catalogs--catalog 'messages/*'I18N_KEY_USAGE_CATALOGS
keyTypes--key-type src/i18n/types.ts#TMessageKeyI18N_KEY_USAGE_KEY_TYPES
keyFunctions--key-function src/i18n/t.ts#translate:1I18N_KEY_USAGE_KEY_FUNCTIONS
families--family 'orders.status.{{EOrderStatus}}'I18N_KEY_USAGE_FAMILIES
retained--retain legacy.titleI18N_KEY_USAGE_RETAINED

The rest follow the same scheme (--exclude, --key-property, --literal-key-prefix, …); i18n-key-usage --help lists them all. List flags repeat, and every list also accepts comma-separated or newline-separated values; commas inside glob braces such as {ts,tsx} are kept. Symbol references are written file#export, key functions file#export:argument.

--config, --root and --format read I18N_KEY_USAGE_CONFIG, I18N_KEY_USAGE_ROOT and I18N_KEY_USAGE_FORMAT.

Dynamic key families

When keys are built at runtime from a finite set of values, declare the family with a placeholder naming the export that lists the values:

{
  "families": ["orders.status.{{EOrderStatus}}", "orders.{{REGIONS}}.{{EOrderStatus}}"]
}

A placeholder names an exported enum, constant object, constant array or string-literal union type in the scanned sources. If several files export the name, write {{src/orders/status.ts#EOrderStatus}}. Several placeholders expand to every combination. Only those keys count as used, so a removed enum member makes its key unused, and a family that no code matches anymore is reported as stale.

Families are only needed when the analyzer cannot see the values. A template such as t(`status.${status}`) resolves on its own when status is typed as an enum or a union of string literals.

Prefer typed keys over families where you can. A value typed as one of your keyTypes is checked at every place it is produced, with no declaration needed:

import type { TMessageKey } from "@/i18n/types";

export const STATUS_LABELS: Record<EOrderStatus, TMessageKey> = {
  [EOrderStatus.Open]: "orders.status.open",
  [EOrderStatus.Closed]: "orders.status.closed",
};

Programmatic API

import { checkMessageUsage, createReport, loadConfig } from "i18n-key-usage";

const { options, keyFiles } = loadConfig(process.cwd());
const report = createReport(checkMessageUsage(options), keyFiles);

checkMessageUsage also accepts in-memory sources, which is how the test suite runs.

Adapters

An adapter tells the analyzer which imports create translators and catalogs (src/adapters/next-intl.ts):

export const NEXT_INTL_ADAPTER: II18nAdapter = {
  name: "next-intl",
  factories: { "next-intl": FACTORIES, "next-intl/server": FACTORIES },
  namespaceOption: "namespace",
  keyMethods: ["rich", "markup"],
  rawMethods: ["raw"],
  probeMethods: ["has"],
};

Libraries with the same model (a factory returns a translator scoped to a dot-separated namespace) fit by adding an adapter and registering it in src/adapters/index.ts. Libraries with other key syntax, such as i18next's ns:key or keyPrefix, need small core changes. Issues and pull requests are welcome.

Development

pnpm install
pnpm test
pnpm build

pnpm check lints and formats with Biome, pnpm typecheck runs TypeScript and pnpm schema regenerates schema.json after config changes.

License

MIT

版本与发布记录

i18n-key-usage 共发布 4 个版本,首次发布于 2026/11/04;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.2.0latest2026/11/041 天前
0.1.12026/11/041 天前
0.1.02026/11/041 天前
0.0.0-stage2026/11/041 天前

关于 i18n-key-usage 的常见问题

i18n-key-usage 是什么?

Find unused, missing and unresolved i18n message keys in TypeScript projects. Follows keys across modules, types and helpers; fast oxc parser.

如何安装 i18n-key-usage?

在项目目录运行 npm install i18n-key-usage,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 i18n-key-usage,可以用 npx i18n-key-usage 直接运行。

i18n-key-usage 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

i18n-key-usage 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

i18n-key-usage 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.19。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

i18n-key-usage 可以商用吗?

i18n-key-usage 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

i18n-key-usage 还在维护吗?

最新版本 0.2.0 发布于 2026/11/04(1 天前);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/11/04。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。