Translation Workflow & CI — i18n

Translation Workflow & CI Di production, mengelola terjemahan secara manual tidak scalable. Translation Management Systems (TMS) dan CI/CD integration memastika

Translation Workflow & CI

Di production, mengelola terjemahan secara manual tidak scalable. Translation Management Systems (TMS) dan CI/CD integration memastikan terjemahan selalu sinkron dan berkualitas.

Translation Management Platforms

Workflow Tipikal

// 1. Developer menambah/mengubah source string
//    en/common.json: { "new_feature": "Try our new feature!" }

// 2. CI otomatis push source strings ke TMS
//    GitHub Action / CI pipeline

// 3. Translator menerjemahkan di TMS UI
//    id: "Coba fitur baru kami!"
//    ja: "新機能をお試しください!"

// 4. TMS membuat PR otomatis dengan terjemahan baru

// 5. CI validasi → merge → deploy

GitHub Action: Crowdin Integration

# .github/workflows/crowdin.yml
name: Crowdin Sync
on:
  push:
    branches: [main]
    paths: ["public/locales/en/**"]  # Hanya trigger saat source berubah

jobs:
  upload-sources:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Upload sources to Crowdin
        uses: crowdin/github-action@v2
        with:
          upload_sources: true
          upload_translations: false
          download_translations: false
        env:
          CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
          CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_TOKEN }}

  # Crowdin akan membuat PR otomatis saat terjemahan selesai

CI: Validasi Terjemahan

# .github/workflows/i18n-check.yml
name: i18n Validation
on: pull_request

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci

      # Cek missing keys
      - name: Check missing translations
        run: node scripts/check-translations.js

      # Cek format ICU valid
      - name: Validate ICU messages
        run: npx @formatjs/cli compile public/locales/id/common.json

      # Cek tidak ada hardcoded strings
      - name: Lint for literal strings
        run: npx eslint src/ --rule 'i18next/no-literal-string: error'

Script Check Missing Translations

// scripts/check-translations.js
const fs = require("fs");
const path = require("path");

const sourceLocale = "en";
const targetLocales = ["id", "ja", "ar"];
const localesDir = path.join(__dirname, "../public/locales");

function getAllKeys(obj, prefix = "") {
  return Object.entries(obj).flatMap(([key, val]) => {
    const fullKey = prefix ? `${prefix}.${key}` : key;
    return typeof val === "object" ? getAllKeys(val, fullKey) : [fullKey];
  });
}

let hasErrors = false;
const namespaces = fs.readdirSync(path.join(localesDir, sourceLocale))
  .filter(f => f.endsWith(".json"));

for (const ns of namespaces) {
  const sourceKeys = getAllKeys(
    JSON.parse(fs.readFileSync(path.join(localesDir, sourceLocale, ns)))
  );

  for (const locale of targetLocales) {
    const targetFile = path.join(localesDir, locale, ns);
    if (!fs.existsSync(targetFile)) {
      console.error(`Missing file: ${locale}/${ns}`);
      hasErrors = true;
      continue;
    }
    const targetKeys = getAllKeys(JSON.parse(fs.readFileSync(targetFile)));
    const missing = sourceKeys.filter(k => !targetKeys.includes(k));
    if (missing.length) {
      console.error(`[${locale}/${ns}] Missing: ${missing.join(", ")}`);
      hasErrors = true;
    }
  }
}

process.exit(hasErrors ? 1 : 0);

Yang akan kamu pelajari