{"id":35562204,"url":"https://github.com/rstackjs/rsbuild-plugin-i18next-extractor","last_synced_at":"2026-04-24T08:01:04.984Z","repository":{"id":330855884,"uuid":"1122261287","full_name":"rstackjs/rsbuild-plugin-i18next-extractor","owner":"rstackjs","description":"A Rsbuild plugin for extracting i18n translations using i18next-cli.","archived":false,"fork":false,"pushed_at":"2026-04-20T03:31:25.000Z","size":59,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-20T05:38:18.957Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/rstackjs.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}},"created_at":"2025-12-24T11:28:52.000Z","updated_at":"2026-04-20T03:29:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rstackjs/rsbuild-plugin-i18next-extractor","commit_stats":null,"previous_names":["rspack-contrib/rsbuild-plugin-i18next-extractor","rstackjs/rsbuild-plugin-i18next-extractor"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/rstackjs/rsbuild-plugin-i18next-extractor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rstackjs%2Frsbuild-plugin-i18next-extractor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rstackjs%2Frsbuild-plugin-i18next-extractor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rstackjs%2Frsbuild-plugin-i18next-extractor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rstackjs%2Frsbuild-plugin-i18next-extractor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rstackjs","download_url":"https://codeload.github.com/rstackjs/rsbuild-plugin-i18next-extractor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rstackjs%2Frsbuild-plugin-i18next-extractor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32214420,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T03:15:14.334Z","status":"ssl_error","status_checked_at":"2026-04-24T03:15:11.608Z","response_time":64,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":[],"created_at":"2026-01-04T13:11:28.743Z","updated_at":"2026-04-24T08:01:04.973Z","avatar_url":"https://github.com/rstackjs.png","language":"TypeScript","readme":"# rsbuild-plugin-i18next-extractor\n\n\u003cp\u003e\n  \u003ca href=\"https://npmjs.com/package/rsbuild-plugin-i18next-extractor\"\u003e\n   \u003cimg src=\"https://img.shields.io/npm/v/rsbuild-plugin-i18next-extractor?style=flat-square\u0026colorA=564341\u0026colorB=EDED91\" alt=\"npm version\" /\u003e\n  \u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square\u0026colorA=564341\u0026colorB=EDED91\" alt=\"license\" /\u003e\n  \u003ca href=\"https://npmcharts.com/compare/rsbuild-plugin-i18next-extractor?minimal=true\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/rsbuild-plugin-i18next-extractor.svg?style=flat-square\u0026colorA=564341\u0026colorB=EDED91\" alt=\"downloads\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nA Rsbuild plugin for extracting i18n translations using [i18next-cli](https://github.com/i18next/i18next-cli). \n\n## Why\n\n`i18next-cli` can extract i18n translations from your source code through [`extract.input`](https://github.com/i18next/i18next-cli?tab=readme-ov-file#1-initialize-configuration). However some i18n translations will be bundled together with your code even if they are not used.\n\nThis plugin uses the Rspack module graph to override the `extract.input` configuration with imported modules, generating i18n translations based on usage.\n\n## Installation\n\n```bash\nnpm add rsbuild-plugin-i18next-extractor i18next-cli --save-dev\n```\n\n## Usage\n\n### Install\n\n```ts\n// rsbuild.config.ts\nimport { pluginI18nextExtractor } from 'rsbuild-plugin-i18next-extractor';\nimport { defineConfig } from '@rsbuild/core';\n\nexport default defineConfig({\n  plugins: [\n    pluginI18nextExtractor({\n      localesDir: './locales',\n    }),\n  ],\n});\n```\n\n### Directory Structure\n\nYour project should have a locales directory with JSON files:\n\n```\nlocales/\n  en.json\n  zh.json\n  ja.json\n```\n\n**NOTE:** `rsbuild-plugin-i18next-extractor` only supports `*.json` files.\n\n\n### Using i18n in your code\n\n```js\n// ./src/i18n.js\nimport i18next from 'i18next';\nimport en from '../locales/en.json';\nimport zh from '../locales/zh.json';\n\nconst i18n = i18next.createInstance()\n\ni18n.init({\n  lng: 'en',\n  resources: {\n    en: {\n      translation: en,\n    },\n    zh: {\n      translation: zh,\n    }\n  }\n})\n\nexport { i18n }\n```\n\nuse `i18n.t('key')` to translate\n\n```js\n// src/index.js\nimport { i18n } from './i18n';\n\nconsole.log(i18n.t('hello'));\n```\n\n## Hooks\n\n`rsbuild-plugin-i18next-extractor` now exposes compilation hooks so other build-time plugins can consume extracted translations or customize how the extracted payload is written back to JS assets.\n\nExported APIs:\n\n- `getI18nextExtractorWebpackPluginHooks(compilation)`\n- `I18nextExtractorWebpackPluginHooks`\n- `AfterExtractPayload`\n- `RenderExtractedTranslationsPayload`\n\n### `afterExtract`\n\nCalled after translation keys have been extracted and locale payloads have been assembled for an entry.\n\nThis hook is useful when you want to:\n\n- store extracted translations for a later build step\n- inspect extracted keys for debugging or reporting\n- feed extracted translations into another plugin\n\nPayload shape:\n\n```ts\ntype ExtractedTranslationValue = string | ExtractedTranslationsObject;\n\ntype ExtractedTranslationsObject = {\n  [key: string]: ExtractedTranslationValue;\n};\n\ntype AfterExtractPayload = {\n  entryName: string;\n  locales: string[];\n  files: string[];\n  extractedKeysByLocale: Record\u003cstring, string[]\u003e;\n  extractedTranslationsByLocale: Record\u003cstring, ExtractedTranslationsObject\u003e;\n};\n```\n\n### `renderExtractedTranslations`\n\nCalled before extracted translations are prepended back into JS assets.\n\nThe default behavior is still:\n\n```ts\nconst __I18N_EN_EXTRACTED_TRANSLATIONS__ = { ... };\n```\n\nThis hook is useful when you want to:\n\n- customize the injected JS code\n- skip JS injection for some or all locales\n- redirect the extracted payload to another artifact pipeline\n\nPayload shape:\n\n```ts\ntype RenderExtractedTranslationsPayload = {\n  entryName: string;\n  locale: string;\n  variableName: string;\n  extractedKeys: string[];\n  extractedTranslations: Record\u003cstring, unknown\u003e;\n  targetAssetNames: string[];\n  code: string;\n  skip?: boolean;\n};\n```\n\n### Hook Example\n\n```ts\nimport {\n  defineConfig,\n  type RsbuildPlugin,\n  type Rspack,\n} from '@rsbuild/core';\nimport {\n  getI18nextExtractorWebpackPluginHooks,\n  pluginI18nextExtractor,\n} from 'rsbuild-plugin-i18next-extractor';\n\nfunction pluginObserveI18nExtraction(): RsbuildPlugin {\n  return {\n    name: 'example:observe-i18n-extraction',\n    setup(api) {\n      api.modifyBundlerChain((chain) =\u003e {\n        chain\n          .plugin('example:observe-i18n-extraction')\n          .use(\n            class ObserveI18nExtractionPlugin {\n              apply(compiler: Rspack.Compiler) {\n                compiler.hooks.compilation.tap(\n                  'example:observe-i18n-extraction',\n                  (compilation) =\u003e {\n                    const hooks =\n                      getI18nextExtractorWebpackPluginHooks(compilation);\n\n                    hooks.afterExtract.tapPromise(\n                      'example:observe-i18n-extraction',\n                      async (payload) =\u003e {\n                        console.log(payload.entryName);\n                        console.log(payload.extractedTranslationsByLocale);\n                        return payload;\n                      },\n                    );\n\n                    hooks.renderExtractedTranslations.tapPromise(\n                      'example:observe-i18n-extraction',\n                      async (payload) =\u003e {\n                        if (payload.locale === 'zh-CN') {\n                          return {\n                            ...payload,\n                            skip: true,\n                            code: '',\n                          };\n                        }\n\n                        return payload;\n                      },\n                    );\n                  },\n                );\n              }\n            },\n          );\n      });\n    },\n  };\n}\n\nexport default defineConfig({\n  plugins: [\n    pluginI18nextExtractor({\n      localesDir: './locales',\n    }),\n    pluginObserveI18nExtraction(),\n  ],\n});\n```\n\n### Hook Semantics\n\n- `afterExtract` is a waterfall hook and should return the payload it wants later consumers to receive.\n- `renderExtractedTranslations` is also a waterfall hook and should return the payload it wants the default emitter to use.\n- Setting `skip: true` or returning an empty `code` string prevents that locale payload from being injected into JS assets.\n- `targetAssetNames` contains all synchronous and async JS assets that would otherwise receive the injected definitions for the current entry.\n\n## Options\n\n### `localesDir`\n\n- **Type:** `string`\n- **Required:** Yes\n\nThe directory containing your locale JSON files.\n\nSupports both relative and absolute paths:\n- Relative path: Resolved relative to the project root directory (e.g., `'./locales'`, `'src/locales'`)\n- Absolute path: Used as-is (e.g., `'/absolute/path/to/locales'`)\n\n```ts\npluginI18nextExtractor({\n  localesDir: './locales',\n});\n```\n\n### `i18nextToolkitConfig`\n\n- **Type:** `I18nextToolkitConfig`\n- **Required:** No\n\nThe configuration for i18next-cli toolkit. This allows you to customize how translation keys are extracted from your code.\n\nSee [i18next-cli configuration](https://github.com/i18next/i18next-cli) for available options.\n\n```ts\npluginI18nextExtractor({\n  localesDir: './locales',\n  i18nextToolkitConfig: {\n    extract: {\n      // Custom extraction configuration\n    },\n  },\n});\n```\n\n#### Ignoring Files\n\n- **Type:** `string | string[] | undefined`\n- **Required:** No\n\n\nYou can use the `extract.ignore` option to exclude certain files from translation extraction. This is useful for avoiding extraction from third-party code, or other files that shouldn't be scanned for translations.\n\nThe `i18nextToolkitConfig.extract.ignore` option supports **glob patterns** and can be either a string or an array of strings:\n\n**NOTE:** Unlike [i18next-cli](https://github.com/i18next/i18next-cli/blob/6c25f7a20febccf73cf20e22a927e7b1745a71a9/src/extractor/core/key-finder.ts#L130) which ignores `node_modules` by default, `rsbuild-plugin-i18next-extractor` scans all files including `node_modules` to ensure translations from third-party packages are properly extracted. If you want to exclude `node_modules`, use the `extract.ignore` option shown in the examples below. \n\n```ts\npluginI18nextExtractor({\n  localesDir: './locales',\n  i18nextToolkitConfig: {\n    extract: {\n      // Ignore a single pattern\n      ignore: 'node_modules/**',\n    },\n  },\n});\n```\n\n```ts\npluginI18nextExtractor({\n  localesDir: './locales',\n  i18nextToolkitConfig: {\n    extract: {\n      // Ignore multiple patterns\n      ignore: [\n        'node_modules/dayjs/**',\n        'packages/**',\n      ],\n    },\n  },\n});\n```\n\n### `onKeyNotFound`\n\n- **Type:** `(key: string, locale: string, localeFilePath: string, entryName: string) =\u003e void`\n- **Required:** No\n\nCustom callback function invoked when a translation key is not found in the locale file.\n\nBy default, a warning is logged to the console with the missing key and file information.\n\n**Parameters:**\n- `key` - The translation key that was not found\n- `locale` - The locale identifier (e.g., `'en'`, `'zh-CN'`)\n- `localeFilePath` - The path to the locale file\n- `entryName` - The name of the current entry being processed\n\n```ts\npluginI18nextExtractor({\n  localesDir: './locales',\n  onKeyNotFound: (key, locale, localeFilePath, entryName) =\u003e {\n    console.error(`Missing key: ${key} in ${locale}`);\n  },\n});\n```\n\n## Credits\n\n[rsbuild-plugin-tailwindcss](https://github.com/rspack-contrib/rsbuild-plugin-tailwindcss) - Inspiration for this plugin\n\n## License\n\n[MIT](./LICENSE)\n","funding_links":[],"categories":["Plugins"],"sub_categories":["Rsbuild Plugins"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frstackjs%2Frsbuild-plugin-i18next-extractor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frstackjs%2Frsbuild-plugin-i18next-extractor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frstackjs%2Frsbuild-plugin-i18next-extractor/lists"}