A Rsbuild plugin for extracting i18n translations using i18next-cli.
i18next-cli can extract i18n translations from your source code through extract.input. However some i18n translations will be bundled together with your code even if they are not used.
This plugin uses the Rspack module graph to override the extract.input configuration with imported modules, generating i18n translations based on usage.
npm add rsbuild-plugin-i18next-extractor i18next-cli --save-dev// rsbuild.config.ts
import { pluginI18nextExtractor } from 'rsbuild-plugin-i18next-extractor';
import { defineConfig } from '@rsbuild/core';
export default defineConfig({
plugins: [
pluginI18nextExtractor({
localesDir: './locales',
}),
],
});Your project should have a locales directory with JSON files:
locales/
en.json
zh.json
ja.json
NOTE: rsbuild-plugin-i18next-extractor only supports *.json files.
// ./src/i18n.js
import i18next from 'i18next';
import en from '../locales/en.json';
import zh from '../locales/zh.json';
const i18n = i18next.createInstance()
i18n.init({
lng: 'en',
resources: {
en: {
translation: en,
},
zh: {
translation: zh,
}
}
})
export { i18n }use i18n.t('key') to translate
// src/index.js
import { i18n } from './i18n';
console.log(i18n.t('hello'));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.
Exported APIs:
getI18nextExtractorWebpackPluginHooks(compilation)I18nextExtractorWebpackPluginHooksAfterExtractPayloadRenderExtractedTranslationsPayload
Called after translation keys have been extracted and locale payloads have been assembled for an entry.
This hook is useful when you want to:
- store extracted translations for a later build step
- inspect extracted keys for debugging or reporting
- feed extracted translations into another plugin
Payload shape:
type ExtractedTranslationValue = string | ExtractedTranslationsObject;
type ExtractedTranslationsObject = {
[key: string]: ExtractedTranslationValue;
};
type AfterExtractPayload = {
entryName: string;
locales: string[];
files: string[];
extractedKeysByLocale: Record<string, string[]>;
extractedTranslationsByLocale: Record<string, ExtractedTranslationsObject>;
};Called before extracted translations are prepended back into JS assets.
The default behavior is still:
const __I18N_EN_EXTRACTED_TRANSLATIONS__ = { ... };This hook is useful when you want to:
- customize the injected JS code
- skip JS injection for some or all locales
- redirect the extracted payload to another artifact pipeline
Payload shape:
type RenderExtractedTranslationsPayload = {
entryName: string;
locale: string;
variableName: string;
extractedKeys: string[];
extractedTranslations: Record<string, unknown>;
targetAssetNames: string[];
code: string;
skip?: boolean;
};import {
defineConfig,
type RsbuildPlugin,
type Rspack,
} from '@rsbuild/core';
import {
getI18nextExtractorWebpackPluginHooks,
pluginI18nextExtractor,
} from 'rsbuild-plugin-i18next-extractor';
function pluginObserveI18nExtraction(): RsbuildPlugin {
return {
name: 'example:observe-i18n-extraction',
setup(api) {
api.modifyBundlerChain((chain) => {
chain
.plugin('example:observe-i18n-extraction')
.use(
class ObserveI18nExtractionPlugin {
apply(compiler: Rspack.Compiler) {
compiler.hooks.compilation.tap(
'example:observe-i18n-extraction',
(compilation) => {
const hooks =
getI18nextExtractorWebpackPluginHooks(compilation);
hooks.afterExtract.tapPromise(
'example:observe-i18n-extraction',
async (payload) => {
console.log(payload.entryName);
console.log(payload.extractedTranslationsByLocale);
return payload;
},
);
hooks.renderExtractedTranslations.tapPromise(
'example:observe-i18n-extraction',
async (payload) => {
if (payload.locale === 'zh-CN') {
return {
...payload,
skip: true,
code: '',
};
}
return payload;
},
);
},
);
}
},
);
});
},
};
}
export default defineConfig({
plugins: [
pluginI18nextExtractor({
localesDir: './locales',
}),
pluginObserveI18nExtraction(),
],
});afterExtractis a waterfall hook and should return the payload it wants later consumers to receive.renderExtractedTranslationsis also a waterfall hook and should return the payload it wants the default emitter to use.- Setting
skip: trueor returning an emptycodestring prevents that locale payload from being injected into JS assets. targetAssetNamescontains all synchronous and async JS assets that would otherwise receive the injected definitions for the current entry.
- Type:
string - Required: Yes
The directory containing your locale JSON files.
Supports both relative and absolute paths:
- Relative path: Resolved relative to the project root directory (e.g.,
'./locales','src/locales') - Absolute path: Used as-is (e.g.,
'/absolute/path/to/locales')
pluginI18nextExtractor({
localesDir: './locales',
});- Type:
I18nextToolkitConfig - Required: No
The configuration for i18next-cli toolkit. This allows you to customize how translation keys are extracted from your code.
See i18next-cli configuration for available options.
pluginI18nextExtractor({
localesDir: './locales',
i18nextToolkitConfig: {
extract: {
// Custom extraction configuration
},
},
});- Type:
string | string[] | undefined - Required: No
You 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.
The i18nextToolkitConfig.extract.ignore option supports glob patterns and can be either a string or an array of strings:
NOTE: Unlike i18next-cli 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.
pluginI18nextExtractor({
localesDir: './locales',
i18nextToolkitConfig: {
extract: {
// Ignore a single pattern
ignore: 'node_modules/**',
},
},
});pluginI18nextExtractor({
localesDir: './locales',
i18nextToolkitConfig: {
extract: {
// Ignore multiple patterns
ignore: [
'node_modules/dayjs/**',
'packages/**',
],
},
},
});- Type:
(key: string, locale: string, localeFilePath: string, entryName: string) => void - Required: No
Custom callback function invoked when a translation key is not found in the locale file.
By default, a warning is logged to the console with the missing key and file information.
Parameters:
key- The translation key that was not foundlocale- The locale identifier (e.g.,'en','zh-CN')localeFilePath- The path to the locale fileentryName- The name of the current entry being processed
pluginI18nextExtractor({
localesDir: './locales',
onKeyNotFound: (key, locale, localeFilePath, entryName) => {
console.error(`Missing key: ${key} in ${locale}`);
},
});rsbuild-plugin-tailwindcss - Inspiration for this plugin