i18next-cli

repository·main·Indexed 42 days ago

https://github.com/i18next/i18next-cli

A high-performance, unified CLI toolchain for the i18next ecosystem. Built with a Rust-based parser (SWC), it provides features for key extraction, TypeScript type generation, locale synchronization, and linting. It includes tools to detect hardcoded strings, automate localization workflows via the `localize` command, and instrument existing codebases to transform static text into i18next translation calls.

Tokens
27K
Snippets
88
Records
112
Agent score
47%

What's inside i18next-cli

  1. Avoid conflicts with existing i18n libraries

    main

    Before using i18next-cli to instrument your application, check for existing internationalization tools. You should stop and consult the user if any of the following are present, as they create competing translation pipelines:

    • @inlang/paraglide-js
    • next-intl
    • @lingui/*
    • react-intl
    • vue-i18n
    • @nuxtjs/i18n
    • @tolgee/*
    • typesafe-i18n

    If an existing i18next setup is found, you can proceed directly to instrument to wrap remaining hardcoded strings.

  2. Handle Dynamic Keys

    main

    Since v1.49, the extractor performs TypeScript type-aware resolution of finite dynamic keys automatically. This includes:

    • Template literals with unions/ternaries/nullish coalescing: t("state.${isDone ? 'done' : 'notDone'}.title") resolves to state.done.title and state.notDone.title.
    • as const maps and arrays: const KEYS = ['a', 'b'] as const; KEYS.map(k => t("item.${k}")) resolves to item.a and item.b.
    • Helper-function return types and satisfies constraints.

    Handling Truly Runtime-Dynamic Keys

    Keys that are built from API data (e.g., const key = "user.${role}.permission"; // role from server) cannot be statically resolved. To prevent them from being deleted by removeUnusedKeys, use preservePatterns in your configuration.

    export default defineConfig({
      extract: {
        preservePatterns: ['user.*.permission']
      }
    });
  3. Extending extraction to non-JS/TS files

    main
    By default, i18next-cli only extracts keys from .js, .jsx, .ts, and .tsx files. To extract keys from other file types (like .pug, .vue, or .svelte), you must use or create a plugin. Simply adding these extensions to the extract.input configuration is not sufficient; a plugin is required to handle the non-JS/TS formats.
  4. Supported i18next usage patterns for extraction

    main

    The extractor automatically detects various i18next usage patterns in your codebase, including:

    • Function Calls: Standard t() calls, including those with namespaces (ns:key), interpolation, plurals (count), context (context), and ordinal plurals (ordinal: true). It also handles key fallbacks via arrays.
    • React Components: The <Trans /> component and the useTranslation hook.
    • Complex Patterns: Aliased functions, destructured hooks, and getFixedT calls.
    • Selector API: Type-safe selector paths (e.g., t($ => $.key)).
  5. Extend i18next-cli with the Plugin System

    main

    The i18next-cli plugin system allows you to extend capabilities via a unified plugins array in your configuration. Plugins can hook into different stages of the CLI lifecycle, including extraction, linting, and instrumentation.

    Extraction Hooks

    • setup: Runs once during initialization.
    • onLoad: Runs for each file before parsing (useful for transpilation).
    • onVisitNode: Runs for every node in the AST (provides full parsing context, scope, and TS syntax like satisfies).
    • onKeySubmitted: Called synchronously for every key submitted, including duplicates, before deduplication.
    • extractKeysFromExpression: Runs during AST traversal to extract keys from specific expressions (ideal for custom syntax).
    • extractContextFromExpression: Runs during AST traversal to extract context values that cannot be statically analyzed.
    • onEnd: Runs after all JS/TS files are parsed but before final comparison with translation files (ideal for parsing .html, .vue, or .svelte).
    • afterSync: Runs after the extractor has compared keys and generated final results (ideal for reporting).

    Lint Plugin Hooks

    • lintSetup(context): Runs once before linting. Receives LintPluginContext (config, logger).
    • lintExtensions: Optional array of extensions (e.g., ['.vue']) used as a skip hint.
    • lintOnLoad(code, filePath): Pre-processes code. Return string to replace, undefined to pass through, or null to skip.
    • lintOnResult(filePath, issues): Post-processes issues. Return a new array to filter/augment, or undefined to keep as-is.

    Instrument Plugin Hooks

    • instrumentSetup(context): Runs once before instrumentation. Receives InstrumentPluginContext (config, logger).
    • instrumentExtensions: Optional array of extensions (e.g., ['.vue']) used as a skip hint.
    • instrumentOnLoad(code, filePath): Pre-processes code for scanning. Return string to replace, undefined to pass through, or null to skip.
    • instrumentOnResult(filePath, candidates): Post-processes detected strings. Return a new CandidateString[] to filter/augment, or undefined to keep as-is.
  6. Initialize i18next-cli configuration

    main

    You can set up your project configuration in two ways:

    1. Interactively: Run the init command to walk through a setup wizard.
    2. Manually: Create an i18next.config.ts file in your project root using the defineConfig helper.
    npx i18next-cli init
    import { defineConfig } from 'i18next-cli';
    
    export default defineConfig({
      locales: ['en', 'de'],
      extract: {
        input: ['src/**/*.{js,jsx,ts,tsx}'],
        output: 'public/locales/{{language}}/{{namespace}}.json',
      },
    });
  7. Recommended workflow for instrumenting a codebase

    main

    When adding i18next to an existing project, follow these steps:

    1. Preview changes: Run with --dry-run to see proposed transformations.
      npx i18next-cli instrument --dry-run
    2. Interactive migration: Use --interactive to manually approve each candidate.
      npx i18next-cli instrument --interactive
    3. Review and commit: Verify the changes and commit them to git.
    4. Extract keys: Run the extraction command to sync the new keys with your translation files.
      npx i18next-cli extract
    npx i18next-cli instrument --dry-run
    npx i18next-cli instrument --interactive
    npx i18next-cli extract
  8. Implement Code-Splitting for Translations

    main

    To avoid shipping all translations to the client, use namespaces as code-splitting boundaries. The i18next runtime loads translations per namespace, per language.

    The Recipe

    1. One namespace per route/feature: Scope keys via useTranslation('checkout') or t('checkout:title'). Configure the extractor to output one file per namespace:
    export default defineConfig({
      locales: ['en', 'de'],
      extract: {
        input: 'src/**/*.{ts,tsx}',
        output: 'src/locales/{{language}}/{{namespace}}.json',
      }
    });
    1. Lazy-load namespaces with dynamic imports: Use i18next-resources-to-backend to allow bundlers to create separate chunks for each namespace/language pair:
    import i18next from 'i18next';
    import resourcesToBackend from 'i18next-resources-to-backend';
    
    // Each (lng, ns) pair becomes a separate chunk
    i18next
      .use(resourcesToBackend((lng, ns) => import(`./locales/${lng}/${ns}.json`)))
      .init({
        fallbackLng: 'en',
        defaultNS: 'app',
        ns: ['app'], // Only the app-shell namespace loads upfront
      });
    1. Load on demand: Use useTranslation('checkout') (react-i18next) or i18next.loadNamespaces('checkout') to fetch the specific chunk when the route renders.
  9. Configure i18next-cli with defineConfig

    main

    The configuration file supports both TypeScript (.ts) and JavaScript (.js) formats. For TypeScript users, it is recommended to use the defineConfig helper to provide type safety and IntelliSense.

    If you prefer not to install i18next-cli as a local dependency, you can skip defineConfig and simply export a plain JavaScript object or JSON. The helper is purely for TypeScript support and does not change the underlying functionality.

    // i18next.config.ts
    import { defineConfig } from 'i18next-cli';
    
    export default defineConfig({
      locales: ['en', 'de', 'fr'],
      extract: {
        input: ['src/**/*.{ts,tsx,js,jsx}'],
        output: 'locales/{{language}}/{{namespace}}.json',
      },
    });
  10. Automate localization with the localize command

    main

    The localize command is a high-level workflow for apps with hardcoded strings. It detects your setup, wraps hardcoded strings in t() calls, extracts keys, and can connect to Locize for AI translation.

    If you are using an AI coding agent (like Claude Code or Cursor), you can use the --print-agent-prompt flag to get a runbook for the agent to follow.

    npx i18next-cli localize
    # Or for AI agents:
    npx i18next-cli localize --print-agent-prompt
  11. Use i18next-cli with AI coding agents

    main

    You can integrate i18next-cli into your AI agent's workflow in two ways:

    1. Skills Integration: Install the flow as a skill so your agent can pick it up automatically:

      npx skills add i18next/i18next-cli

      Then, simply ask the agent to "add i18n to this project".

    2. Manual Prompting: If you prefer to copy-paste instructions, generate a version-matched prompt for your agent (Claude Code, Cursor, etc.):

      npx i18next-cli localize --print-agent-prompt
    npx skills add i18next/i18next-cli
    
    npx i18next-cli localize --print-agent-prompt