{"id":38653237,"url":"https://github.com/i18next/i18next-cli","last_synced_at":"2026-01-17T09:24:20.480Z","repository":{"id":316549391,"uuid":"1063375628","full_name":"i18next/i18next-cli","owner":"i18next","description":"A unified, high-performance i18next CLI.","archived":false,"fork":false,"pushed_at":"2026-01-12T19:51:40.000Z","size":1046,"stargazers_count":130,"open_issues_count":3,"forks_count":21,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-13T00:48:07.417Z","etag":null,"topics":["cli","i18n","i18next","parser","toolkit"],"latest_commit_sha":null,"homepage":"https://www.locize.com/blog/i18next-cli","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/i18next.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["jamuhl","adrai"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":"https://locize.com"}},"created_at":"2025-09-24T14:38:31.000Z","updated_at":"2026-01-12T19:51:42.000Z","dependencies_parsed_at":"2025-09-25T09:27:36.925Z","dependency_job_id":null,"html_url":"https://github.com/i18next/i18next-cli","commit_stats":null,"previous_names":["i18next/i18next-cli"],"tags_count":156,"template":false,"template_full_name":null,"purl":"pkg:github/i18next/i18next-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/i18next%2Fi18next-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/i18next%2Fi18next-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/i18next%2Fi18next-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/i18next%2Fi18next-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/i18next","download_url":"https://codeload.github.com/i18next/i18next-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/i18next%2Fi18next-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28505448,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T06:57:29.758Z","status":"ssl_error","status_checked_at":"2026-01-17T06:56:03.931Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","i18n","i18next","parser","toolkit"],"created_at":"2026-01-17T09:24:20.297Z","updated_at":"2026-01-17T09:24:20.437Z","avatar_url":"https://github.com/i18next.png","language":"TypeScript","funding_links":["https://github.com/sponsors/jamuhl","https://github.com/sponsors/adrai","https://locize.com"],"categories":[],"sub_categories":[],"readme":"# i18next-cli 🚀\n\nA unified, high-performance i18next CLI toolchain, powered by SWC.\n\n[![Tests](https://github.com/i18next/i18next-cli/workflows/node/badge.svg)](https://github.com/i18next/i18next-cli/actions?query=workflow%3Anode)\n[![npm version](https://img.shields.io/npm/v/i18next-cli.svg?style=flat-square)](https://www.npmjs.com/package/i18next-cli)\n\n---\n\n`i18next-cli` is a complete reimagining of the static analysis toolchain for the i18next ecosystem. It consolidates key extraction, type safety generation, locale syncing, linting, and cloud integrations into a single, cohesive, and blazing-fast CLI.\n\n\u003e ### 🚀 Try it Now - Zero Config!\n\u003e You can get an instant analysis of your existing i18next project **without any configuration**. Just run this command in your repository's root directory:\n\u003e\n\u003e ```bash\n\u003e npx i18next-cli status\n\u003e ```\n\u003e Or find hardcoded strings:\n\u003e\n\u003e ```bash\n\u003e npx i18next-cli lint\n\u003e ```\n\n## Why i18next-cli?\n\n`i18next-cli` is built from the ground up to meet the demands of modern web development.\n\n- **🚀 Performance:** By leveraging a native Rust-based parser (SWC), it delivers orders-of-magnitude faster performance than JavaScript-based parsers.\n- **🧠 Intelligence:** A stateful, scope-aware analyzer correctly understands complex patterns like `useTranslation('ns1', { keyPrefix: '...' })`, `getFixedT`, and aliased `t` functions, minimizing the need for manual workarounds.\n- **✅ Unified Workflow:** One tool, one configuration file, one integrated workflow. It replaces various syncing scripts.\n- **🔌 Extensibility:** A modern plugin architecture allows the tool to adapt to any framework or custom workflow.\n- **🧑‍💻 Developer Experience:** A fully-typed configuration file, live `--watch` modes, CLI output, and a migration from legacy tools.\n\n## Features\n\n- **Key Extraction**: Extract translation keys from JavaScript/TypeScript files with advanced AST analysis.\n- **Type Safety**: Generate TypeScript definitions for full autocomplete and type safety.\n- **Locale Synchronization**: Keep all language files in sync with your primary language.\n- **Accurate Code Linting**: Detect hardcoded strings with high precision and configurable rules.\n- **Translation Status**: Get a high-level overview or a detailed, key-by-key report of your project's translation completeness.\n- **Plugin System**: Extensible architecture for custom extraction patterns and file types (e.g., HTML, Handlebars).\n- **Legacy Migration**: Automatic migration from `i18next-parser` configurations.\n- **Cloud Integration**: Seamless integration with the [locize](https://locize.com) translation management platform.\n\n## Installation\n\n```bash\nnpm install --save-dev i18next-cli\n```\n\n## Quick Start\n\n### 1. Initialize Configuration\n\nCreate a configuration interactively:\n\n```bash\nnpx i18next-cli init\n```\n\nOr manually create `i18next.config.ts` in your project root:\n\n```typescript\nimport { defineConfig } from 'i18next-cli';\n\nexport default defineConfig({\n  locales: ['en', 'de'],\n  extract: {\n    input: ['src/**/*.{js,jsx,ts,tsx}'],\n    output: 'public/locales/{{language}}/{{namespace}}.json',\n  },\n});\n```\n\n### 2. Check your Translation Status\n\nGet an overview of your project's localization health:\n\n```bash\nnpx i18next-cli status\n```\n\n### 3. Extract Translation Keys\n\n```bash\nnpx i18next-cli extract\n```\n\n### 4. Generate Types (Optional)\n\n```bash\nnpx i18next-cli types\n```\n\n## Commands\n\n### `init`\nInteractive setup wizard to create your configuration file.\n\n```bash\nnpx i18next-cli init\n```\n\n### `extract`\nParses source files, extracts keys, and updates your JSON translation files.\n\n```bash\nnpx i18next-cli extract [options]\n```\n\n**Options:**\n- `--watch, -w`: Re-run automatically when files change\n- `--ci`: Exit with non-zero status if any files are updated (for CI/CD)\n- `--dry-run`: Does not change any files - useful in combination with `--ci` (for CI/CD)\n- `--sync-primary`: Sync primary language values with default values from code\n- `--sync-all`: Sync primary language values with default values from code AND clear synced keys in all other locales (implies `--sync-primary`)\n\n**Examples:**\n```bash\n# One-time extraction\nnpx i18next-cli extract\n\n# Watch mode for development\nnpx i18next-cli extract --watch\n\n# CI mode (fails if files changed)\nnpx i18next-cli extract --ci\n\n# Sync primary language with code defaults\nnpx i18next-cli extract --sync-primary\n\n# Sync primary and clear synced keys in all other locales\nnpx i18next-cli extract --sync-all\n\n# Combine options for optimal development workflow\nnpx i18next-cli extract --sync-primary --watch\n```\n\n### `status [locale]`\n\nDisplays a health check of your project's translation status. Can run without a config file. Exits with a non-zero status code when translations are missing.\n\n**Options:**\n- `--namespace \u003cns\u003e, -n \u003cns\u003e`: Filter the report by a specific namespace.\n\n**Usage Examples:**\n\n```bash\n# Get a high-level summary for all locales and namespaces\nnpx i18next-cli status\n\n# Get a detailed, key-by-key report for the 'de' locale\nnpx i18next-cli status de\n\n# Get a summary for only the 'common' namespace across all locales\nnpx i18next-cli status --namespace common\n\n# Get a detailed report for the 'de' locale, showing only the 'common' namespace\nnpx i18next-cli status de --namespace common\n```\n\nThe detailed view provides a rich, at-a-glance summary for each namespace, followed by a list of every key and its translation status.\n\n**Example Output (`npx i18next-cli status de`):**\n\n```bash\nKey Status for \"de\":\n\nOverall: [■■■■■■■■■■■■■■■■■■■■] 100% (12/12)\n\nNamespace: common\nNamespace Progress: [■■■■■■■■■■■■■■■■■■■■] 100% (4/4)\n  ✓ button.save\n  ✓ button.cancel\n  ✓ greeting\n  ✓ farewell\n\nNamespace: translation\nNamespace Progress: [■■■■■■■■■■■■■■■■□□□□] 80% (8/10)\n  ✓ app.title\n  ✓ app.welcome\n  ✗ app.description\n  ...\n```\n\n### `types`\nGenerates TypeScript definitions from your translation files for full type-safety and autocompletion.\n\n```bash\nnpx i18next-cli types [options]\n```\n\n**Options:**\n- `--watch, -w`: Re-run automatically when translation files change\n\n### `sync`\nSynchronizes secondary language files against your primary language file, adding missing keys and removing extraneous ones.\n\n```bash\nnpx i18next-cli sync\n```\n\n### `lint`\nAnalyzes your source code for internationalization issues like hardcoded strings. Can run without a config file.\n\n```bash\nnpx i18next-cli lint\n```\n\n### `migrate-config`\nAutomatically migrates a legacy `i18next-parser.config.js` file to the new `i18next.config.ts` format.\n\n```bash\nnpx i18next-cli migrate-config\n\n# Using custom path for old config\nnpx i18next-cli migrate-config i18next-parser.config.mjs\n```\n\n### `rename-key`\n\nSafely refactor translation keys across your entire codebase. This command updates both source files and translation files atomically.\n\n```bash\nnpx i18next-cli rename-key \u003coldKey\u003e \u003cnewKey\u003e [options]\n```\n\n**Options:**\n- `--dry-run`: Preview changes without modifying any files\n\n**Usage Examples:**\n\n```bash\n# Basic rename\nnpx i18next-cli rename-key \"old.key\" \"new.key\"\n\n# With namespace prefix\nnpx i18next-cli rename-key \"common:button.submit\" \"common:button.save\"\n\n# Preview changes without modifying files\nnpx i18next-cli rename-key \"old.key\" \"new.key\" --dry-run\n\n# Refactor from mnemonic ID to meaningful key\nnpx i18next-cli rename-key \"Invalid username or password\" \"login.form.invalid-credentials\"\n```\n\n### Locize Integration\n\n**Prerequisites:** The locize commands require `locize-cli` to be installed:\n\n```bash\n# Install globally (recommended)\nnpm install -g locize-cli\n```\n\nSync translations with the Locize translation management platform:\n\n```bash\n# Download translations from Locize\nnpx i18next-cli locize-download\n\n# Upload/sync translations to Locize  \nnpx i18next-cli locize-sync\n\n# Migrate local translations to Locize\nnpx i18next-cli locize-migrate\n```\n\n**Locize Command Options:**\n\nThe `locize-sync` command supports additional options:\n\n```bash\nnpx i18next-cli locize-sync [options]\n```\n\n**Options:**\n- `--update-values`: Update values of existing translations on locize\n- `--src-lng-only`: Check for changes in source language only\n- `--compare-mtime`: Compare modification times when syncing\n- `--dry-run`: Run the command without making any changes\n\n**Interactive Setup:** If your locize credentials are missing or invalid, the toolkit will guide you through an interactive setup process to configure your Project ID, API Key, and version.\n\n## Global Options\n\n- `-c, --config \u003cpath\u003e` — Override automatic config detection and use the specified config file (relative to cwd or absolute). This option is forwarded to commands that load or ensure a config (e.g. extract, status, types, sync, locize-*).\n\nExamples:\n```bash\n# Use a config file stored in a package subfolder (monorepo)\nnpx i18next-cli extract --config ./packages/my-package/config/i18next.config.ts\n\n# Short flag variant, for status\nnpx i18next-cli status de -c ./packages/my-package/config/i18next.config.ts\n```\n\n## Configuration\n\nThe configuration file supports both TypeScript (`.ts`) and JavaScript (`.js`) formats. Use the `defineConfig` helper for type safety and IntelliSense.\n\n\u003e **💡 No Installation Required?** If you don't want to install `i18next-cli` as a dependency, you can skip the `defineConfig` helper and return a plain JavaScript object or JSON instead. The `defineConfig` function is purely for TypeScript support and doesn't affect functionality.\n\n### Basic Configuration\n\n```typescript\n// i18next.config.ts\nimport { defineConfig } from 'i18next-cli';\n\nexport default defineConfig({\n  locales: ['en', 'de', 'fr'],\n  extract: {\n    input: ['src/**/*.{ts,tsx,js,jsx}'],\n    output: 'locales/{{language}}/{{namespace}}.json',\n  },\n});\n```\n\n**Alternative without local installation:**\n\n```javascript\n// i18next.config.js\nexport default {\n  locales: ['en', 'de', 'fr'],\n  extract: {\n    input: ['src/**/*.{ts,tsx,js,jsx}'],\n    output: 'locales/{{language}}/{{namespace}}.json',\n  },\n};\n```\n\n### Advanced Configuration\n\n```typescript\nimport { defineConfig } from 'i18next-cli';\n\nexport default defineConfig({\n  locales: ['en', 'de', 'fr'],\n  \n  // Key extraction settings\n  extract: {\n    input: ['src/**/*.{ts,tsx}'],\n    output: 'locales/{{language}}/{{namespace}}.json',\n\n    /** Glob pattern(s) for files to ignore during extraction */\n    ignore: ['node_modules/**'],\n\n    // Use '.ts' files with `export default` instead of '.json'\n    // Or use 'json5' to enable JSON5 features (comments, trailing commas, formatting are tried to be preserved)\n    // if the file ending is .json5 it automatically uses json5 format\n    outputFormat: 'ts',\n\n    // Combine all namespaces into a single file per language (e.g., locales/en.ts)\n    // Note: `output` path must not contain `{{namespace}}` when this is true.\n    mergeNamespaces: false, \n    \n    // Translation functions to detect. Defaults to ['t', '*.t'].\n    // Supports wildcards for suffixes.\n    functions: ['t', '*.t', 'i18next.t'],\n    \n    // React components to analyze\n    transComponents: ['Trans', 'Translation'],\n    \n    // HTML tags to preserve in Trans component default values\n    transKeepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'],\n    \n    // Hook-like functions that return a t function.\n    // Supports strings for default behavior or objects for custom argument positions.\n    useTranslationNames: [\n      'useTranslation', // Standard hook (ns: arg 0, keyPrefix: arg 1)\n      'getT',\n      'useT',\n      {\n        name: 'loadPageTranslations',\n        nsArg: 1,       // Namespace is the 2nd argument (index 1)\n        keyPrefixArg: 2 // Options with keyPrefix is the 3rd (index 2)\n      }\n    ],\n    \n    // Namespace and key configuration\n    defaultNS: 'translation', // If set to false it will not generate any namespace, useful if i.e. the output is a single language json with 1 namespace (and no nesting).\n    fallbackNS: 'fallback', // Namespace to use as fallback when a key is missing in the current namespace for a locale. (default undefined)\n    nsSeparator: ':',\n    keySeparator: '.', // Or `false` to disable nesting and use flat keys\n    contextSeparator: '_',\n    pluralSeparator: '_',\n    \n    // Preserve dynamic keys matching patterns\n    preservePatterns: [\n      // Key patterns\n      'dynamic.feature.*',      // Matches dynamic.feature.anything\n      'generated.*.key',        // Matches generated.anything.key\n      \n      // Namespace patterns\n      'assets:*',               // Preserves ALL keys in the 'assets' namespace\n      'common:button.*',        // Preserves keys like common:button.save, common:button.cancel\n      'errors:api.*',           // Preserves keys like errors:api.timeout, errors:api.server\n      \n      // Specific key preservation across namespaces\n      'dynamic:user.*.profile', // Matches dynamic:user.admin.profile, dynamic:user.guest.profile\n    ],\n    \n    /**\n     * When true, preserves all context variants of keys that use context parameters.\n     * For example, if 'friend' is used with context, all variants like 'friend_male',\n     * 'friend_female', etc. are preserved even if not explicitly found in source code.\n     * (default: false)\n     */\n    preserveContextVariants: false,\n\n    // Output formatting\n    sort: true, // can be also a sort function =\u003e i.e. (a, b) =\u003e a.key \u003e b.key ? -1 : a.key \u003c b.key ? 1 : 0, // sort in reverse order\n    indentation: 2, // can be also a string\n    \n    // Primary language settings\n    primaryLanguage: 'en', // Defaults to the first locale in the `locales` array\n    secondaryLanguages: ['de', 'fr'], // Defaults to all locales except primaryLanguage\n\n    // Default value for missing keys in secondary languages\n    // Can be a string, function, or object for flexible fallback strategies\n    defaultValue: '', // Simple string: all missing keys get this value\n    \n    // Or use a function for dynamic defaults:\n    // defaultValue: (key, namespace, language, value) =\u003e key, // i18next-parser style: use key as value\n    // defaultValue: (key, namespace, language, value) =\u003e `TODO: translate ${key}`, // Mark untranslated keys\n    // defaultValue: (key, namespace, language, value) =\u003e language === 'de' ? 'German TODO' : 'TODO', // Language-specific\n\n    /** If true, keys that are not found in the source code will be removed from translation files. (default: true) */\n    removeUnusedKeys: true,\n\n    // When true (default), the extractor also scans code comments for t(...) / Trans examples and will extract keys found there.\n    // Set to false to ignore translation-like patterns in comments (useful to avoid extracting example/documentation strings).\n    extractFromComments: true,\n\n    // Control whether base plural forms are generated when context is present\n    // When false, t('key', { context: 'male', count: 1 }) will only generate \n    // key_male_one, key_male_other but NOT key_one, key_other\n    generateBasePluralForms: true, // Default: true\n\n    // Completely disable plural generation, even when count is present\n    // When true, t('key', { count: 1 }) will only generate 'key' (no _one, _other suffixes)\n    // The count option can still be used for {{count}} interpolation in the translation value\n    disablePlurals: false, // Default: false\n\n    // Prefix for nested translations.\n    // Controls how nested $t(...) calls inside strings are detected.\n    // Example: '$t('\n    nestingPrefix: '$t(', // Default: '$t('\n    \n    // Suffix for nested translations.\n    // Example: ')'\n    nestingSuffix: ')', // Default: ')'\n\n    // Separator for nested translation options.\n    // Used to split key vs options inside $t(key, {...}).\n    nestingOptionsSeparator: ',', // Default: ','\n\n    // Interpolation prefix used in defaultValue templates and runtime interpolation.\n    // Example: '{{'\n    interpolationPrefix: '{{', // Default: '{{'\n    \n    // Interpolation suffix used in defaultValue templates and runtime interpolation.\n    // Example: '}}'\n    interpolationSuffix: '}}', // Default: '}}'\n  },\n\n  // options for linter\n  lint: {\n    /** Optional accept-list of JSX attribute names to exclusively lint (takes precedence over ignoredAttributes). */\n    acceptedAttributes: ['title'];\n\n    /** Optional accept-list of JSX tag names to exclusively lint (takes precedence over ignoredTags). */\n    acceptedTags: ['p'];\n\n    // Optional custom JSX attributes to ignore during linting\n    ignoredAttributes: ['data-testid', 'aria-label'],\n\n    // Optional JSX tag names whose content should be ignored when linting\n    ignoredTags: ['pre'],\n\n    /** Glob pattern(s) for files to ignore during lint (in addition to those defined during extract) */\n    ignore: ['additional/stuff/**'],\n  },\n  \n  // TypeScript type generation\n  types: {\n    input: ['locales/en/*.json'],\n    output: 'src/types/i18next.d.ts',\n    resourcesFile: 'src/types/resources.d.ts',\n    enableSelector: true, // Enable type-safe key selection\n  },\n  \n  // Locize integration\n  locize: {\n    projectId: 'your-project-id',\n    apiKey: process.env.LOCIZE_API_KEY, // Recommended: use environment variables\n    version: 'latest',\n    cdnType: 'standard' // or 'pro'\n  },\n  \n  // Plugin system\n  plugins: [\n    // Add custom plugins here\n  ],\n});\n```\n\n### Extending Recommended Lint Tags and Attributes\n\nYou can extend the built-in recommended lists for linting by importing and spreading them in your config:\n\n```typescript\nimport { defineConfig, recommendedAcceptedTags, recommendedAcceptedAttributes } from 'i18next-cli';\n\nexport default defineConfig({\n  locales: ['en', 'de'],\n  extract: {\n    input: ['src/**/*.{js,jsx,ts,tsx}'],\n    output: 'public/locales/{{language}}/{{namespace}}.json',\n  },\n  lint: {\n    acceptedTags: ['my-web-component', ...recommendedAcceptedTags],\n    acceptedAttributes: ['data-label', ...recommendedAcceptedAttributes]\n  }\n});\n```\n\n## Advanced Features\n\n### Plugin System\n\nCreate custom plugins to extend the capabilities of `i18next-cli`. The plugin system provides several hooks that allow you to tap into different stages of the extraction process, with full access to the AST parsing context and variable scope information.\n\n**Available Hooks:**\n\n- `setup`: Runs once when the CLI is initialized. Use it for any setup tasks.\n- `onLoad`: Runs for each file *before* it is parsed. You can use this to transform code (e.g., transpile a custom language to JavaScript).\n- `onVisitNode`: Runs for every node in the Abstract Syntax Tree (AST) of a parsed JavaScript/TypeScript file. This provides access to the full parsing context, including variable scope and TypeScript-specific syntax like `satisfies` and `as` operators.\n- `extractKeysFromExpression`: Runs for specific expressions during AST traversal to extract additional translation keys. This is ideal for handling custom syntax patterns or complex key generation logic without managing pluralization manually.\n- `extractContextFromExpression`: Runs for specific expressions to extract context values that can't be statically analyzed. Useful for dynamic context patterns or custom context resolution logic.\n- `onEnd`: Runs after all JS/TS files have been parsed but *before* the final keys are compared with existing translation files. This is the ideal hook for parsing non-JavaScript files (like `.html`, `.vue`, or `.svelte`) and adding their keys to the collection.\n- `afterSync`: Runs after the extractor has compared the found keys with your translation files and generated the final results. This is perfect for post-processing tasks, like generating a report of newly added keys.\n\n**Basic Plugin Example:**\n\n```typescript\nimport { glob } from 'glob';\nimport { readFile, writeFile } from 'node:fs/promises';\n\nexport const myCustomPlugin = () =\u003e ({\n  name: 'my-custom-plugin',\n  \n  // Handle custom file formats\n  async onEnd(keys) {\n    // Extract keys from .vue files\n    const vueFiles = await glob('src/**/*.vue');\n    for (const file of vueFiles) {\n      const content = await readFile(file, 'utf-8');\n      const keyMatches = content.matchAll(/\\{\\{\\s*\\$t\\(['\"]([^'\"]+)['\"]\\)/g);\n      for (const match of keyMatches) {\n        keys.set(`translation:${match[1]}`, {\n          key: match[1],\n          defaultValue: match[1],\n          ns: 'translation'\n        });\n      }\n    }\n  }\n});\n```\n\n**Advanced Plugin with Expression Parsing:**\n\n```typescript\nexport const advancedExtractionPlugin = () =\u003e ({\n  name: 'advanced-extraction-plugin',\n  \n  // Extract keys from TypeScript satisfies expressions\n  extractKeysFromExpression: (expression, config, logger) =\u003e {\n    const keys = [];\n    \n    // Handle template literals with variable substitutions\n    if (expression.type === 'TemplateLiteral') {\n      // Extract pattern: `user.${role}.permission`\n      const parts = expression.quasis.map(q =\u003e q.cooked);\n      const variables = expression.expressions.map(e =\u003e \n        e.type === 'Identifier' ? e.value : 'dynamic'\n      );\n      \n      if (variables.includes('role')) {\n        // Generate keys for known roles\n        keys.push('user.admin.permission', 'user.manager.permission', 'user.employee.permission');\n      }\n    }\n    \n    // Handle TypeScript satisfies expressions\n    if (expression.type === 'TsAsExpression' \u0026\u0026 \n        expression.typeAnnotation?.type === 'TsUnionType') {\n      const unionTypes = expression.typeAnnotation.types;\n      for (const unionType of unionTypes) {\n        if (unionType.type === 'TsLiteralType' \u0026\u0026 \n            unionType.literal?.type === 'StringLiteral') {\n          keys.push(`dynamic.${unionType.literal.value}.extracted`);\n        }\n      }\n    }\n    \n    return keys;\n  },\n  \n  // Extract context from conditional expressions\n  extractContextFromExpression: (expression, config, logger) =\u003e {\n    const contexts = [];\n    \n    // Handle ternary operators: isAdmin ? 'admin' : 'user'\n    if (expression.type === 'ConditionalExpression') {\n      if (expression.consequent.type === 'StringLiteral') {\n        contexts.push(expression.consequent.value);\n      }\n      if (expression.alternate.type === 'StringLiteral') {\n        contexts.push(expression.alternate.value);\n      }\n    }\n    \n    // Handle template literals: `${role}.${level}`\n    if (expression.type === 'TemplateLiteral') {\n      const parts = expression.expressions.map(expr =\u003e\n        expr.type === 'Identifier' ? expr.value : 'unknown'\n      );\n      if (parts.length \u003e 0) {\n        const joins = expression.quasis.map(quasi =\u003e quasi.cooked);\n        contexts.push(joins.reduce((acc, join, i) =\u003e \n          acc + (join || '') + (parts[i] || ''), ''\n        ));\n      }\n    }\n    \n    return contexts;\n  },\n  \n  // Handle complex AST patterns\n  onVisitNode: (node, context) =\u003e {\n    // Custom extraction for specific component patterns\n    if (node.type === 'JSXElement' \u0026\u0026 \n        node.opening.name.type === 'Identifier' \u0026\u0026 \n        node.opening.name.value === 'CustomTransComponent') {\n      \n      const keyAttr = node.opening.attributes?.find(attr =\u003e\n        attr.type === 'JSXAttribute' \u0026\u0026 \n        attr.name.value === 'translationKey'\n      );\n      \n      if (keyAttr?.value?.type === 'StringLiteral') {\n        context.addKey({\n          key: keyAttr.value.value,\n          defaultValue: 'Custom component translation',\n          ns: 'components'\n        });\n      }\n    }\n  }\n});\n```\n\n**Configuration:**\n\n```typescript\nimport { defineConfig } from 'i18next-cli';\nimport { myCustomPlugin, advancedExtractionPlugin } from './my-plugins.mjs';\n\nexport default defineConfig({\n  locales: ['en', 'de'],\n  extract: {\n    input: ['src/**/*.{ts,tsx,vue}'],\n    output: 'locales/{{language}}/{{namespace}}.json'\n  },\n  plugins: [\n    myCustomPlugin(),\n    advancedExtractionPlugin()\n  ]\n});\n```\n\n### Location Metadata Tracking\n\nTrack where each translation key is used in your codebase with a custom metadata plugin.\n\n**Example Plugin Implementation:**\n\n```typescript\nimport { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport type { Plugin } from 'i18next-cli';\n\ninterface LocationMetadataOptions {\n  /** Output path for the metadata file (default: 'locales/metadata.json') */\n  output?: string;\n  /** Include line and column numbers (default: true) */\n  includePosition?: boolean;\n}\n\nexport const locationMetadataPlugin = (options: LocationMetadataOptions = {}): Plugin =\u003e {\n  const {\n    output = 'locales/metadata.json',\n    includePosition = true,\n  } = options;\n\n  return {\n    name: 'location-metadata',\n\n    async onEnd(keys) {\n      const metadata: Record\u003cstring, any\u003e = {};\n\n      for (const [uniqueKey, extractedKey] of keys.entries()) {\n        const { key, ns, locations } = extractedKey;\n\n        // Skip keys without location data\n        if (!locations || locations.length === 0) {\n          continue;\n        }\n\n        // Format location data\n        const locationData = locations.map(loc =\u003e {\n          if (includePosition \u0026\u0026 loc.line !== undefined) {\n            return `${loc.file}:${loc.line}:${loc.column ?? 0}`;\n          }\n          return loc.file;\n        });\n\n        // Organize metadata\n        const namespace = ns || 'translation';\n        if (!metadata[namespace]) {\n          metadata[namespace] = {};\n        }\n        metadata[namespace][key] = locationData;\n      }\n\n      // Write metadata file\n      await mkdir(dirname(output), { recursive: true });\n      await writeFile(output, JSON.stringify(metadata, null, 2), 'utf-8');\n      \n      console.log(`📍 Location metadata written to ${output}`);\n    }\n  };\n};\n```\n\n**Configuration:**\n\n```typescript\n// i18next.config.ts\nimport { defineConfig } from 'i18next-cli';\nimport { locationMetadataPlugin } from './plugins/location-metadata.mjs';\n\nexport default defineConfig({\n  locales: ['en', 'de'],\n  extract: {\n    input: ['src/**/*.{ts,tsx}'],\n    output: 'locales/{{language}}/{{namespace}}.json',\n  },\n  plugins: [\n    locationMetadataPlugin({\n      output: 'locales/metadata.json'\n    })\n  ]\n});\n```\n\n**Example Output (`locales/metadata.json`):**\n\n```json\n{\n  \"translation\": {\n    \"app.title\": [\n      \"src/App.tsx:12:15\",\n      \"src/components/Header.tsx:8:22\"\n    ],\n    \"user.greeting\": [\n      \"src/pages/Profile.tsx:45:10\"\n    ]\n  },\n  \"common\": {\n    \"button.save\": [\n      \"src/components/SaveButton.tsx:18:7\",\n      \"src/forms/UserForm.tsx:92:5\"\n    ]\n  }\n}\n```\n\n### Dynamic Key Preservation\n\nUse `preservePatterns` to maintain dynamically generated keys:\n\n```typescript\n// Code like this:\nconst key = `user.${role}.permission`;\nt(key);\n\n// With this config:\nexport default defineConfig({\n  extract: {\n    preservePatterns: ['user.*.permission']\n  }\n});\n\n// Will preserve existing keys matching the pattern\n```\n\n### Comment-Based Extraction\n\nExtract keys from comments for documentation or edge cases:\n\n```javascript\n// t('welcome.message', 'Welcome to our app!')\n// t('user.greeting', { defaultValue: 'Hello!', ns: 'common' })\n```\n\n### JavaScript \u0026 TypeScript Translation Files\n\nFor projects that prefer to keep everything in a single module type, you can configure the CLI to output JavaScript or TypeScript files instead of JSON.\n\nConfiguration (`i18next.config.ts`):\n\n```typescript\nexport default defineConfig({\n  extract: {\n    output: 'src/locales/{{language}}/{{namespace}}.ts', // Note the .ts extension\n    outputFormat: 'ts', // Use TypeScript with ES Modules\n  }\n});\n```\n\nThis will generate files like `src/locales/en/translation.ts` with the following content:\n\n```typescript\nexport default {\n  \"myKey\": \"My value\"\n} as const;\n```\n\n### Merging Namespaces\n\nYou can also combine all namespaces into a single file per language. This is useful for reducing the number of network requests in some application setups.\n\nConfiguration (`i18next.config.ts`):\n\n```typescript\nexport default defineConfig({\n  extract: {\n    // Note: The `output` path no longer contains the {{namespace}} placeholder\n    output: 'src/locales/{{language}}.ts',\n    outputFormat: 'ts',\n    mergeNamespaces: true,\n  }\n});\n```\n\nThis will generate a single file per language, like `src/locales/en.ts`, with namespaces as top-level keys:\n\n```typescript\nexport default {\n  \"translation\": {\n    \"key1\": \"Value 1\"\n  },\n  \"common\": {\n    \"keyA\": \"Value A\"\n  }\n} as const;\n```\n\n## Migration from i18next-parser\n\nAutomatically migrate from legacy `i18next-parser.config.js`:\n\n```bash\nnpx i18next-cli migrate-config\n```\n\nThis will:\n- Convert your existing configuration to the new format\n- Map old options to new equivalents\n- Preserve custom settings where possible\n- Create a new `i18next.config.ts` file\n\nImportant: File Management Differences \u003cbr/\u003e\nUnlike `i18next-parser`, `i18next-cli` takes full ownership of translation files in the output directory. If you have manually managed translation files that should not be modified, place them in a separate directory or use different naming patterns to avoid conflicts.\n\n## CI/CD Integration\n\nUse the `--ci` flag to fail builds when translations are outdated:\n\n```yaml\n# GitHub Actions example\n- name: Check translations\n  run: npx i18next-cli extract --ci\n```\n\n## Watch Mode\n\nFor development, use watch mode to automatically update translations:\n\n```bash\nnpx i18next-cli extract --watch\nnpx i18next-cli lint --watch\n```\n\n## Type Safety\n\nGenerate TypeScript definitions for full type safety:\n\n```typescript\n// Generated types enable autocomplete and validation\nt('user.profile.name'); // ✅ Valid key\nt('invalid.key');       // ❌ TypeScript error\n```\n\n---\n\n## Supported Patterns\n\nThe toolkit automatically detects these i18next usage patterns:\n\n### Function Calls\n\n```javascript\n// Basic usage\nt('key')\nt('key', 'Default value')\nt('key', { defaultValue: 'Default' })\n\n// With namespaces\nt('ns:key')\nt('key', { ns: 'namespace' })\n\n// With interpolation\nt('key', { name: 'John' })\n\n// With plurals and context\nt('key', { count: 1 }); // Cardinal plural\nt('keyWithContext', { context: 'male' });\nt('keyWithDynContext', { context: isMale ? 'male' : 'female' });\n\n// With ordinal plurals\nt('place', { count: 1, ordinal: true });\nt('place', {\n  count: 2,\n  ordinal: true,\n  defaultValue_ordinal_one: '{{count}}st place',\n  defaultValue_ordinal_two: '{{count}}nd place',\n  defaultValue_ordinal_other: '{{count}}th place'\n});\n\n// With key fallbacks\nt(['key.primary', 'key.fallback']);\nt(['key.primary', 'key.fallback'], { defaultValue: 'The fallback value' });\n\n// With structured content (returnObjects)\nt('countries', { returnObjects: true });\n```\n\nThe extractor correctly handles **cardinal and ordinal plurals** (`count`), as well as context options, generating all necessary suffixed keys (e.g., `key_one`, `key_ordinal_one`, `keyWithContext_male`). It can even statically analyze ternary expressions in the `context` option to extract all possible variations.\n\n### React Components\n\n```jsx\n// Trans component\n\u003cTrans i18nKey=\"welcome\"\u003eWelcome {{name}}\u003c/Trans\u003e\n\u003cTrans ns=\"common\"\u003euser.greeting\u003c/Trans\u003e\n\u003cTrans count={num}\u003eYou have {{num}} message\u003c/Trans\u003e\n\u003cTrans context={isMale ? 'male' : 'female'}\u003eA friend\u003c/Trans\u003e\n\n// useTranslation hook\nconst { t } = useTranslation('namespace');\nconst { t } = useTranslation(['ns1', 'ns2']);\n```\n\n### Complex Patterns\n\n```javascript\n// Aliased functions\nconst translate = t;\ntranslate('key');\n\n// Destructured hooks\nconst { t: translate } = useTranslation();\n\n// getFixedT\nconst fixedT = getFixedT('en', 'namespace');\nfixedT('key');\n```\n\n## Programmatic Usage\n\nIn addition to the CLI commands, `i18next-cli` can be used programmatically in your build scripts, Gulp tasks, or any Node.js application:\n\n### Basic Programmatic Usage\n\n```typescript\nimport { runExtractor, runLinter, runSyncer, runStatus, runTypesGenerator } from 'i18next-cli';\nimport type { I18nextToolkitConfig } from 'i18next-cli';\n\nconst config: I18nextToolkitConfig = {\n  locales: ['en', 'de'],\n  extract: {\n    input: ['src/**/*.{ts,tsx,js,jsx}'],\n    output: 'locales/{{language}}/{{namespace}}.json',\n  },\n};\n\n// Run the complete extraction process\nconst wasUpdated = await runExtractor(config);\nconsole.log('Files updated:', wasUpdated);\n\n// Check translation status programmatically\nawait runStatus(config);\n\n// Run linting and get results\nconst { success, message, files } = await runLinter(config);\nif (!success) {\n  console.error(message);\n  for (const [filename, issues] of Object.entries(files)) {\n    console.error(`${issues.length} issues found in ${filename}.`);\n  }\n}\n\n// Sync translation files\nawait runSyncer(config);\n\n// types generattion\nawait runTypesGenerator(config);\n```\n\n### Build Tool Integration\n\n**Gulp Example:**\n\n```typescript\nimport gulp from 'gulp';\nimport { runExtractor } from 'i18next-cli';\n\ngulp.task('i18next-extract', async () =\u003e {\n  const config = {\n    locales: ['en', 'de', 'fr'],\n    extract: {\n      input: ['src/**/*.{ts,tsx,js,jsx}'],\n      output: 'public/locales/{{language}}/{{namespace}}.json',\n    },\n  };\n  \n  await runExtractor(config);\n});\n```\n\n**Webpack Plugin Example:**\n\n```typescript\nclass I18nextExtractionPlugin {\n  apply(compiler) {\n    compiler.hooks.afterEmit.tapAsync('I18nextExtractionPlugin', async (compilation, callback) =\u003e {\n      await runExtractor(config);\n      callback();\n    });\n  }\n}\n```\n\n### Available Functions\n\n- `runExtractor(config, options?)` - Complete extraction with file writing\n- `runLinter(config)` - Run linting analysis and return results\n- `runSyncer(config)` - Sync translation files\n- `runStatus(config, options?)` - Get translation status\n- `runTypesGenerator(config)` - Generate types\n\n### Advanced Usage\n\n#### `Linter` - Class that lints your codebase and emits events along the way\n\n**Example usage**\n\n```typescript\nimport { Linter } from 'i18next-cli';\nimport type { I18nextToolkitConfig } from 'i18next-cli';\n\nconst config: I18nextToolkitConfig = {\n  locales: ['en', 'de'],\n  extract: {\n    input: ['src/**/*.{ts,tsx,js,jsx}'],\n    output: 'locales/{{language}}/{{namespace}}.json',\n  },\n};\n\nconst linter = new Linter(config);\n\nlinter.addEventListener('progress', ({ message }) =\u003e console.log(message));\n\nawait linter.run();\n```\n\nThis programmatic API gives you the same power as the CLI but with full control over when and how it runs in your build process.\n\n## Known plugins\n\n- [i18next-cli-plugin-svelte](https://github.com/dreamscached/i18next-cli-plugin-svelte) \u0026mdash; a simple plugin to extract translation keys from Svelte components\n- [rsbuild-plugin-i18next-extractor](https://github.com/rspack-contrib/rsbuild-plugin-i18next-extractor) \u0026mdash; A Rsbuild plugin that leverages the Rspack module graph to extract only the i18n translations that are actually imported and used in your code, preventing unused translations from being bundled.\n\n---\n\n\u003ch3 align=\"center\"\u003eGold Sponsors\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.locize.com/\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/i18next/i18next/master/assets/locize_sponsor_240.gif\" width=\"240px\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n**From the creators of i18next: localization as a service - locize.com**\n\nA translation management system built around the i18next ecosystem - [locize.com](https://www.locize.com).\n\n![locize](https://www.locize.com/img/ads/github_locize.png)\n\nWith using [locize](https://locize.com/?utm_source=i18next_readme\u0026utm_medium=github) you directly support the future of i18next.\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fi18next%2Fi18next-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fi18next%2Fi18next-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fi18next%2Fi18next-cli/lists"}