Skip to content

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
entries: Array<string>

An array of glob patterns for finding files within rootDir

ts
targetFile: string

The absolute path of the JSON file where translations are saved

ts
languages: Array<string>

An array of language codes, such as ['en', 'ru', 'de']

ts
baseLanguage: string

The source language of the original text. Its text is stored in defaultValue; a separate translation for this language is not created automatically. translate is called only for the other languages

ts
parseMessages: (filePath: string) => Array<{ 
  name: string; 
  defaultValue: string 
}>

A function for extracting messages from a file

ts
translate: (params: { 
  to: string; 
  text: string 
}) => Promise<string>

An asynchronous function for translating text from baseLanguage into a language specified in languages

ts
getKey?: (params: { 
  rootDir: string; 
  name: string 
}) => string

A function for generating a unique translation key. Defaults to the messages file path relative to rootDir

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

ts
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
USER: features/orders/OrderStatusBadge/messages.ts was created with content:
ts
import { createMessages } from 'translations/createMessages';

export const messages = createMessages('features/orders/OrderStatusBadge', {
  draft: 'Draft',
  paid: 'Paid',
});
ARFA Generator shows logs:
txt
[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
ARFA Generator generated files:
json
{
  "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

ts
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
USER: features/orders/messages.json was created with content:
json
{
  "title": "Orders",
  "empty": "No orders"
}
ARFA Generator shows logs:
txt
[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
ARFA Generator generated files:
json
{
  "features/orders/empty": {
    "defaultValue": "No orders",
    "ru": "[ru] No orders"
  },
  "features/orders/title": {
    "defaultValue": "Orders",
    "ru": "[ru] Orders"
  }
}

MIT License