PluginTranslations
Collects messages from source files and creates a JSON file containing the translations in use. The plugin stores defaultValue, adds new keys, removes obsolete ones, and fills in missing translations through the translate function.
Existing translations are preserved, while defaultValue is updated when the source text changes.
translate is called concurrently (up to 5 calls at a time) and only for missing translations.
ts | An array of glob patterns for finding files within |
ts | The absolute path of the JSON file where translations are saved |
ts | An array of language codes, such as |
ts | The source language of the original text. Its text is stored in |
ts | A function for extracting messages from a file |
ts | An asynchronous function for translating text from |
ts | A function for generating a unique translation key. Defaults to the messages file path relative to |
Example: Messages alongside components
All messages.ts files in the project are collected into a single JSON file. When a message or an entire file is deleted, obsolete keys are removed automatically.
This setup uses only one language, so the resulting JSON contains only defaultValue — convenient for passing to an external translation system
new PluginTranslations([
{
entries: ['**/messages.ts'],
targetFile: path.resolve(rootDir, 'translations/usedTranslations.json'),
languages: ['en'],
baseLanguage: 'en',
getKey({ name }) {
return name;
},
parseMessages(filePath) {
const source = fs.readFileSync(filePath, 'utf8');
const content = source.replace(/^[\s\S]*?export\s+const\s+messages\s*=\s*/, 'return ');
if (content === source) throw new Error(`Messages export not found in: ${filePath}`);
// imported from the library
type TypeMessage = { name: string; defaultValue: string };
function createMessages(scope: string, messages: Record<string, string>) {
const result: Record<string, TypeMessage> = {};
for (const key in messages) {
result[key] = { name: `${scope}/${key}`, defaultValue: messages[key] };
}
return result;
}
const messages = new Function('createMessages', content)(createMessages) as Record<string, TypeMessage>;
return Object.values(messages);
},
async translate() {
return '';
},
},
]),
Run Sandbox
import { createMessages } from 'translations/createMessages';
export const messages = createMessages('features/orders/OrderStatusBadge', {
draft: 'Draft',
paid: 'Paid',
});
[ARFA-GEN] [write] translations/usedTranslations.json
[ARFA-GEN] [translations] new key: features/orders/OrderStatusBadge/draft
[ARFA-GEN] [translations] new key: features/orders/OrderStatusBadge/paid
[ARFA-GEN] finished in 0ms
{
"features/orders/OrderStatusBadge/draft": {
"defaultValue": "Draft"
},
"features/orders/OrderStatusBadge/paid": {
"defaultValue": "Paid"
}
}
Example: Message synchronization and translation
In this example, messages are read from JSON. The plugin fills in missing translations through translate, preserves existing translations, and updates defaultValue from the source files.
This example works for multiple languages without an external translation system, with translations handled semi-manually
new PluginTranslations([
{
entries: ['features/**/messages.json'],
targetFile: path.resolve(rootDir, 'translations/translations.json'),
languages: ['en', 'ru'],
baseLanguage: 'en',
parseMessages(filePath) {
const messages: Record<string, string> = JSON.parse(fs.readFileSync(filePath, 'utf8'));
return Object.entries(messages).map(([name, defaultValue]) => ({
name: path.resolve(path.dirname(filePath), name),
defaultValue,
}));
},
async translate({ to, text }) {
return `[${to}] ${text}`;
},
},
]),
Run Sandbox
{
"title": "Orders",
"empty": "No orders"
}
[ARFA-GEN] [write] translations/translations.json
[ARFA-GEN] [translations] new key: features/orders/title
[ARFA-GEN] [translations] new key: features/orders/empty
[ARFA-GEN] [translations] features/orders/title: translated to [ru]: "[ru] Orders"
[ARFA-GEN] [translations] features/orders/empty: translated to [ru]: "[ru] No orders"
[ARFA-GEN] finished in 0ms
{
"features/orders/empty": {
"defaultValue": "No orders",
"ru": "[ru] No orders"
},
"features/orders/title": {
"defaultValue": "Orders",
"ru": "[ru] Orders"
}
}