Continuous Integration

При использовании библиотеки Globalize процессы интернационализации быстро становятся критически важной частью сборки приложения. Ошибки в CLDR-данных, отсутствие переводов, несовместимость форматов дат и чисел, повреждённые JSON-файлы локалей — всё это должно обнаруживаться автоматически ещё до публикации приложения.

Continuous Integration (CI) позволяет:

  • автоматически проверять корректность локализации;
  • запускать тесты на нескольких локалях;
  • валидировать CLDR-данные;
  • собирать production-бандлы Globalize;
  • предотвращать регрессии при обновлении переводов;
  • выполнять автоматическую публикацию локализаций.

В экосистеме Javascript чаще всего используются:

  • GitHub Actions
  • GitLab CI/CD
  • Jenkins
  • CircleCI
  • Travis CI

Проверка локализационных данных в CI

Одной из главных задач CI становится проверка всех локализационных файлов до момента слияния изменений в основную ветку.

Типичный набор файлов:

locales/
├── en.json
├── ru.json
├── de.json
└── fr.json

Проблемы, которые необходимо выявлять автоматически:

  • отсутствующие ключи;
  • различия структуры JSON;
  • повреждённый синтаксис;
  • неверные ICU-шаблоны;
  • несовместимость с Globalize;
  • отсутствие CLDR-зависимостей.

Установка зависимостей для CI

Для полноценной интеграции обычно устанавливаются:

npm install globalize cldr-data cldrjs

Для тестирования:

npm install --save-dev jest

Для сборки production-версии:

npm install --save-dev globalize-compiler

Автоматическая проверка JSON-файлов

Простейшая задача CI — убедиться, что локализационные JSON-файлы корректны.

Скрипт проверки

const fs = require("fs");
const path = require("path");

const localesDir = "./locales";

const files = fs.readdirSync(localesDir);

for (const file of files) {
    const fullPath = path.join(localesDir, file);

    try {
        JSON.parse(fs.readFileSync(fullPath, "utf8"));
        console.log(`${file} OK`);
    } catch (e) {
        console.error(`${file} INVALID`);
        process.exit(1);
    }
}

Добавление в package.json:

{
  "scripts": {
    "validate:locales": "node scripts/validate-locales.js"
  }
}

Проверка одинаковой структуры переводов

Очень распространённая проблема — разные локали содержат разные ключи.

Например:

// en.json
{
  "title": "Home",
  "logout": "Logout"
}
// ru.json
{
  "title": "Главная"
}

В production это приведёт к ошибке отсутствующего сообщения.


Сравнение ключей локалей

const fs = require("fs");

const en = require("../locales/en.json");
const ru = require("../locales/ru.json");

function compareKeys(base, target, path = "") {
    for (const key in base) {
        const fullPath = path ? `${path}.${key}` : key;

        if (!(key in target)) {
            console.error(`Missing key: ${fullPath}`);
            process.exitCode = 1;
        }

        if (typeof base[key] === "object") {
            compareKeys(base[key], target[key] || {}, fullPath);
        }
    }
}

compareKeys(en, ru);

Интеграция с GitHub Actions

Минимальный workflow:

name: Localization CI

on:
  push:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm install

      - run: npm run validate:locales

      - run: npm test

CI автоматически запускается:

  • при push;
  • при pull request;
  • при изменениях переводов;
  • при обновлении CLDR.

Проверка работы Globalize в тестах

Очень важно проверять, что библиотека действительно может инициализироваться со всеми локалями.


Загрузка CLDR в тестовом окружении

const Globalize = require("globalize");
const cldrData = require("cldr-data");

Globalize.load(
    cldrData.entireSupplemental(),
    cldrData.entireMainFor("en", "ru")
);

Globalize.locale("ru");

Тестирование форматирования чисел

test("number formatting", () => {
    const formatter = Globalize.numberFormatter();

    expect(formatter(12345.67))
        .toBe("12 345,67");
});

Тестирование дат

test("date formatting", () => {
    const formatter = Globalize.dateFormatter({
        datetime: "medium"
    });

    const result = formatter(new Date());

    expect(typeof result).toBe("string");
});

Проверка pluralization

Globalize использует CLDR plural rules, поэтому их необходимо тестировать отдельно.

test("plural rules", () => {
    const plural = Globalize.pluralGenerator();

    expect(plural(1)).toBe("one");
    expect(plural(5)).toBe("many");
});

CI для production compilation

Одно из важнейших преимуществ Globalize Compiler — возможность компилировать форматтеры заранее.

Это уменьшает:

  • размер бандла;
  • runtime overhead;
  • время инициализации;
  • объём загружаемых CLDR-данных.

Компиляция форматтеров

const GlobalizeCompiler = require("globalize-compiler");

GlobalizeCompiler.compile([
    {
        output: "dist/compiled.js",
        locales: ["en", "ru"],
        messages: "locales/{locale}.json"
    }
]);

Проверка успешной компиляции в CI

- run: node scripts/compile-globalize.js

Если:

  • отсутствует ключ;
  • повреждён JSON;
  • не загружен CLDR;
  • ошибка ICU MessageFormat;

CI завершится с ошибкой.


Кэширование зависимостей

В крупных проектах CLDR-файлы могут занимать значительный объём.

Для ускорения CI используется cache.

GitHub Actions cache

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}

Тестирование нескольких локалей

CI должен проверять приложение на всех поддерживаемых языках.


Matrix strategy

strategy:
  matrix:
    locale: [en, ru, de, fr]

Использование matrix

- run: npm run test:locale -- ${{ matrix.locale }}

Передача локали в тесты

const locale = process.argv[2];

Globalize.locale(locale);

Проверка отсутствующих переводов

Очень часто переводчики добавляют новые строки не во все языки.

Автоматическая проверка:

function findMissing(base, target) {
    return Object.keys(base)
        .filter(key => !(key in target));
}

Более сложный вариант:

function deepDiff(base, target, path = "") {
    const missing = [];

    for (const key in base) {
        const current = path
            ? `${path}.${key}`
            : key;

        if (!(key in target)) {
            missing.push(current);
            continue;
        }

        if (typeof base[key] === "object") {
            missing.push(
                ...deepDiff(
                    base[key],
                    target[key],
                    current
                )
            );
        }
    }

    return missing;
}

Проверка ICU MessageFormat

Globalize поддерживает ICU Message syntax.

Ошибочный шаблон:

{
  "items": "{count, plural, one{# item} other{# items}"
}

Проблема — отсутствует закрывающая скобка.


Валидация ICU

const Globalize = require("globalize");

try {
    Globalize.loadMessages({
        en: require("./locales/en.json")
    });

    Globalize.messageFormatter("items");

} catch (e) {
    console.error(e.message);
    process.exit(1);
}

Автоматическая генерация артефактов

После успешной сборки CI может:

  • публиковать compiled-бандлы;
  • создавать release;
  • выгружать локали в CDN;
  • обновлять npm-пакеты;
  • публиковать Docker-образы.

Пример release workflow

name: Release

on:
  push:
    tags:
      - "v*"

jobs:
  release:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci

      - run: npm test

      - run: npm run build

Проверка размера бандла

После подключения новых локалей размер приложения может резко увеличиться.

CI может автоматически контролировать размер.


Использование size-limit

Установка:

npm install --save-dev size-limit

Конфигурация:

{
  "size-limit": [
    {
      "path": "dist/app.js",
      "limit": "200 KB"
    }
  ]
}

Запуск:

- run: npx size-limit

Проверка производительности

Некоторые formatter’ы Globalize могут быть тяжёлыми при динамической генерации.

CI способен запускать benchmark-тесты.


Benchmark example

const benchmark = require("benchmark");

const suite = new benchmark.Suite();

suite
.add("number formatter", () => {
    const formatter = Globalize.numberFormatter();

    formatter(1000000);
})
.on("complete", function() {
    console.log(this[0].hz);
})
.run();

Использование Docker в CI

Контейнеризация позволяет:

  • гарантировать одинаковое окружение;
  • фиксировать версии Node.js;
  • исключать platform-specific ошибки.

Dockerfile для Globalize

FROM node:20

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm test

Запуск в GitHub Actions

jobs:
  docker:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - run: docker build -t globalize-app .

Проверка CLDR-обновлений

Unicode CLDR регулярно обновляется.

После обновления могут измениться:

  • plural rules;
  • форматы валют;
  • временные зоны;
  • форматирование дат;
  • локальные символы.

CI помогает обнаруживать несовместимости сразу после обновления зависимостей.


Snapshot testing

test("currency snapshot", () => {
    const formatter =
        Globalize.currencyFormatter("USD");

    expect(formatter(100))
        .toMatchSnapshot();
});

Если CLDR изменит формат валюты, snapshot-тест покажет различия.


Линтинг локализационных файлов

Для JSON-файлов часто подключают:

  • ESLint
  • Prettier
  • JSON schema validation
  • custom localization linters

JSON Schema validation

Пример схемы:

{
  "type": "object",
  "properties": {
    "title": {
      "type": "string"
    }
  },
  "required": ["title"]
}

Проверка через ajv:

const Ajv = require("ajv");

const ajv = new Ajv();

const validate = ajv.compile(schema);

const valid = validate(data);

if (!valid) {
    console.error(validate.errors);
    process.exit(1);
}

Автоматическое тестирование браузеров

Иногда локализация работает по-разному:

  • в Chrome;
  • Firefox;
  • Safari;
  • мобильных браузерах.

Для проверки используют:

  • Playwright
  • Cypress
  • Selenium

Playwright и локали

test("russian locale", async ({ page }) => {
    await page.goto("http://localhost:3000");

    await page.selectOption("#locale", "ru");

    await expect(page.locator("h1"))
        .toContainText("Главная");
});

Проверка fallback locale

Важно тестировать fallback-механизм.

Globalize.locale("fr");

const formatter =
    Globalize.messageFormatter("title");

Если fr отсутствует:

  • используется fallback;
  • либо возникает ошибка.

Оба сценария должны проверяться в CI.


Fail-fast стратегия

При работе с локализацией важно останавливать pipeline немедленно после первой ошибки.

Пример:

strategy:
  fail-fast: true

Это уменьшает:

  • время CI;
  • нагрузку на runners;
  • объём логов.

Разделение pipeline

Крупные проекты разделяют CI на этапы:

  1. Validate
  2. Lint
  3. Unit Tests
  4. Localization Tests
  5. Build
  6. E2E
  7. Release

Пример полного pipeline

name: Full CI

on:
  push:
  pull_request:

jobs:

  validate:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run validate:locales

  test:
    needs: validate

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test

  build:
    needs: test

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build

Защита main-ветки

CI особенно важен при работе больших команд переводчиков и разработчиков.

Branch protection rules позволяют:

  • запрещать merge при упавших тестах;
  • требовать прохождения localization checks;
  • блокировать публикацию без build;
  • автоматически запускать review.

Практика immutable localization

В крупных системах переводы часто versioned отдельно.

Например:

locales/
├── v1/
├── v2/
└── v3/

CI может:

  • проверять совместимость версий;
  • обнаруживать breaking changes;
  • валидировать migration scripts.

Автоматическое обновление CLDR

Некоторые команды создают отдельный pipeline:

  1. обновление CLDR;
  2. запуск snapshot-тестов;
  3. сравнение output formatter’ов;
  4. публикация отчёта;
  5. автоматический pull request.

Это позволяет безопасно поддерживать актуальность локализационной инфраструктуры без ручной проверки каждого изменения Unicode-данных.