Mühendislik//9 dk okuma

CI/CD Pipeline'ınızda i18n'i Otomatikleştirme: Git Push'tan Canlı Çevirilere

Eray Gündoğmuş
Paylaş

CI/CD Pipeline'ınızda i18n'i Otomatikleştirme: Git Push'tan Canlı Çevirilere

"12 dili destekliyoruz." Harika. Ancak bir geliştirici yeni bir özellik yayımladığında, bu 12 dildeki kullanıcılar çevrilmiş içeriği ne zaman görür?

Çoğu ekip için cevap, uzun bir beklemeyi kapsar. Bir geliştirici string ekler, bunu Slack'te belirtir, bir PM bir elektronik tablo açar, çeviri firmasına e-posta gönderilir, firma dosyaları bir hafta sonra teslim eder, bir geliştirici JSON'ı kopyalar, PR açar, incelenir, birleştirilir, dağıtılır. O noktada özellik, İngilizce kullanıcılarına iki hafta önce gönderilmiştir.

Bu yazı, o beklemeyi tamamen ortadan kaldırmakla ilgili. Tek manuel adımın bir geliştiricinin kod yazması olduğu — ve isteğe bağlı olarak bir insan çevirmenden AI çıktısını gözden geçirmesinin olduğu — uçtan uca otomatik bir i18n pipeline'ı inşa edeceğiz. Diğer her şey otomatik olarak gerçekleşir.

Manuel i18n İş Akışı (Ve Neden Bozulur)

Tipik akış şu şekildedir:

  1. Geliştirici kaynak koda t('checkout.shipping.title', 'Shipping Address') ekler
  2. Geliştirici PM'e "yeni bir string var" der
  3. PM çeviri anahtarlarını bir elektronik tabloya aktarır
  4. PM elektronik tabloyu çeviri ajansına e-posta ile gönderir
  5. Ajans çevirir, elektronik tabloyu iade eder
  6. Geliştirici (veya PM) çevirileri manuel olarak JSON locale dosyalarına kopyalar
  7. Geliştirici locale dosyalarıyla bir PR açar
  8. PR incelenir, birleştirilir
  9. Uygulama yeni çevirilerle yeniden dağıtılır

Her el değiştirme gecikme yaratır. Dahası, her el değiştirme potansiyel bir başarısızlık noktasıdır: anahtarlar gözden kaçar, elektronik tablolardan kopyala-yapıştır sırasında formatlar bozulur, JSON sözdizimi hataları sızar ve hataları yakalamak için geri bildirim döngüsü dakikalar değil günler olarak ölçülür.

Otomasyonun amacı: "geliştirici t('key') yazar" ile "kullanıcı çevrilmiş içeriği görür" arasında sıfır insan adımı.

Otomatik Pipeline

İnşa ettiğimiz mimari şu şekildedir:

git push
  └─> CI: yeni/değişmiş/silinen anahtarları çıkar
        └─> değişiklik tespit edilirse: çeviri API'sini tetikle
              └─> AI ilk geçiş çevirisi
                    └─> (isteğe bağlı) insan inceleme kuyruğu
                          └─> kalite kapıları: sözlük, yer tutucular, uzunluk
                                └─> CDN'e yayımla
                                      └─> tüm kullanıcılar için canlı (yeniden dağıtım gerekmez)

Dosya kopyalama yok. Locale dosyaları için PR yok. Çeviri güncellemeleri için yeniden dağıtım yok. Her adımı inşa edelim.

Adım 1: Otomatik Anahtar Çıkarma

İlk CI işinin, commit'ler arasında hangi çeviri anahtarlarının değiştiğini belirlemesi gerekir.

AST tabanlı çıkarma kullanımı

i18next-parser ve @formatjs/cli gibi araçlar AST tabanlı çıkarma yapar — kaynak kodunuzu ayrıştırır ve dinamik import'lar ile karmaşık bileşen ağaçlarında bile çeviri fonksiyonunuza yapılan her çağrıyı çıkarır.

# Kur
npm install --save-dev i18next-parser

# Kaynaktan tüm anahtarları çıkar
npx i18next-parser --config i18next-parser.config.js

Temel bir i18next-parser.config.js:

module.exports = {
  input: ['src/**/*.{ts,tsx,js,jsx,vue,svelte}'],
  output: '.i18n-extracted/$NAMESPACE.json',
  locales: ['en'],
  defaultNamespace: 'common',
  keySeparator: '.',
  namespaceSeparator: ':',
};

CI'da değişiklikleri tespit etme

Temel içgörü: her push'ta her şeyi yeniden çevirmek istemezsiniz. Son çıkarmadan bu yana yeni, değişmiş ve silinmiş anahtarları tespit etmek istersiniz.

Aşağıda, mevcut commit ile son bilinen çıkarma anlık görüntüsü arasındaki çıkarılmış anahtarları karşılaştıran bir shell script'i bulunmaktadır:

#!/bin/bash
# scripts/detect-i18n-changes.sh

set -euo pipefail

SNAPSHOT_FILE=".i18n-snapshot/en.json"
EXTRACTED_FILE=".i18n-extracted/common.json"

if [ ! -f "$SNAPSHOT_FILE" ]; then
  echo "Anlık görüntü bulunamadı — tüm anahtarlar yeni kabul ediliyor"
  echo "new_keys=$(jq -r 'keys | length' "$EXTRACTED_FILE")"
  cp "$EXTRACTED_FILE" "$SNAPSHOT_FILE"
  exit 0
fi

# Çıkarılmış ama anlık görüntüde olmayan anahtarları bul (yeni anahtarlar)
NEW_KEYS=$(jq -n \
  --slurpfile current "$EXTRACTED_FILE" \
  --slurpfile snapshot "$SNAPSHOT_FILE" \
  '[$current[0] | keys[]] - [$snapshot[0] | keys[]]'
)

# Anlık görüntüde olan ama çıkarılmamış anahtarları bul (silinen anahtarlar)
DELETED_KEYS=$(jq -n \
  --slurpfile current "$EXTRACTED_FILE" \
  --slurpfile snapshot "$SNAPSHOT_FILE" \
  '[$snapshot[0] | keys[]] - [$current[0] | keys[]]'
)

NEW_COUNT=$(echo "$NEW_KEYS" | jq 'length')
DELETED_COUNT=$(echo "$DELETED_KEYS" | jq 'length')

echo "Yeni anahtarlar: $NEW_COUNT"
echo "Silinen anahtarlar: $DELETED_COUNT"

# GitHub Actions için dışa aktar
echo "new_keys=$NEW_COUNT" >> "$GITHUB_OUTPUT"
echo "deleted_keys=$DELETED_COUNT" >> "$GITHUB_OUTPUT"
echo "has_changes=$([ "$NEW_COUNT" -gt 0 ] || [ "$DELETED_COUNT" -gt 0 ] && echo true || echo false)" >> "$GITHUB_OUTPUT"

has_changes true ise, çeviriyi tetiklemeye devam ediyoruz.

Adım 2: Çeviri Tetikleme

Yeni anahtarlar tespit edildiğinde, bir Çeviri Yönetim Sistemi'ne (TMS) veya bir çeviri API'sine bildirim göndermemiz gerekir. Bu bir webhook çağrısıdır.

Çeviri tetiklemesi için GitHub Action

# .github/workflows/i18n-sync.yml (kısmi)
- name: Yeni anahtarlar için çeviriyi tetikle
  if: steps.detect-changes.outputs.has_changes == 'true'
  run: |
    curl -X POST "${{ secrets.TMS_WEBHOOK_URL }}" \
      -H "Authorization: Bearer ${{ secrets.TMS_API_KEY }}" \
      -H "Content-Type: application/json" \
      -d '{
        "project_id": "${{ vars.I18N_PROJECT_ID }}",
        "source_locale": "en",
        "target_locales": ["de", "fr", "es", "ja", "pt-BR"],
        "keys_file_url": "${{ steps.upload-keys.outputs.url }}"
      }'

Better i18n kullanan ekipler için platform bu adımı natively yönetir — bağlı deponuza yapılan bir push, anahtar değişikliklerini otomatik olarak tespit eder ve ayrı bir webhook kurulumuna gerek kalmadan bunları çeviri için kuyruğa alır.

Adım 3: AI Çevirisi + İnsan İncelemesi

AI çevirisi, çoğu içerik için uygulanabilir bir ilk geçiş haline gelecek kadar iyi hale geldi. İş akışı:

  1. AI anında çevirir (sıfır bekleme)
  2. Çeviriler kullanıcılara hemen AI kalitesiyle sunulur
  3. İnsan çevirmenler eş zamansız olarak inceler ve onaylar/düzenler
  4. Onaylanan çeviriler AI sürümlerinin yerini CDN güncellemesiyle alır

AI çıktısında kalite kapıları

AI çevirileri kabul edilmeden önce otomatik kontroller çalıştırın:

// scripts/validate-translations.ts
import { readFileSync } from 'fs';

interface TranslationFile {
  [key: string]: string;
}

interface ValidationResult {
  key: string;
  locale: string;
  error: string;
}

function extractPlaceholders(str: string): string[] {
  return [...str.matchAll(/\{(\w+)\}/g)].map(m => m[1]);
}

function validateTranslations(
  source: TranslationFile,
  target: TranslationFile,
  locale: string
): ValidationResult[] {
  const errors: ValidationResult[] = [];

  for (const [key, sourceValue] of Object.entries(source)) {
    const targetValue = target[key];

    // Eksik anahtar
    if (!targetValue) {
      errors.push({ key, locale, error: 'Eksik çeviri' });
      continue;
    }

    // Yer tutucu uyuşmazlığı
    const sourcePlaceholders = extractPlaceholders(sourceValue);
    const targetPlaceholders = extractPlaceholders(targetValue);
    const missingPlaceholders = sourcePlaceholders.filter(
      p => !targetPlaceholders.includes(p)
    );

    if (missingPlaceholders.length > 0) {
      errors.push({
        key,
        locale,
        error: `Eksik yer tutucular: ${missingPlaceholders.join(', ')}`,
      });
    }

    // String uzunluk kontrolü (çeviri kaynaktan 3 kat uzunsa uyar)
    if (targetValue.length > sourceValue.length * 3) {
      errors.push({
        key,
        locale,
        error: `Şüpheli derecede uzun çeviri (${targetValue.length} ve ${sourceValue.length} karakter)`,
      });
    }
  }

  return errors;
}

// Doğrulamayı çalıştır
const locales = ['de', 'fr', 'es', 'ja', 'pt-BR'];
const source: TranslationFile = JSON.parse(
  readFileSync('.i18n-extracted/common.json', 'utf-8')
);

let hasErrors = false;

for (const locale of locales) {
  const target: TranslationFile = JSON.parse(
    readFileSync(`.i18n-translations/${locale}/common.json`, 'utf-8')
  );

  const errors = validateTranslations(source, target, locale);

  if (errors.length > 0) {
    console.error(`\n${locale} için doğrulama hataları:`);
    errors.forEach(e => console.error(`  [${e.key}] ${e.error}`));
    hasErrors = true;
  }
}

if (hasErrors) {
  process.exit(1);
}

console.log('Tüm çeviriler başarıyla doğrulandı');

Adım 4: CDN Yayımlama

Burası, modern i18n platformlarının oyunu değiştirdiği yerdir. Geleneksel yaklaşımlar, locale JSON dosyalarını deponuza koyar. Çeviriler güncellendiğinde, bir PR birleştirmeniz ve yeniden dağıtmanız gerekir. Bu yavaştır ve çeviri iş akışınızı dağıtım pipeline'ınıza bağlar.

CDN öncelikli teslimat bunları tamamen ayrıştırır:

  • Uygulamanız çalışma zamanında bir CDN URL'sinden çevirileri çeker
  • Bir çeviri onaylandığında CDN'e iletilir
  • Kullanıcılar bir sonraki sayfa yüklemelerinde güncellenmiş çevirileri görür — dağıtım gerekmez

İki yaklaşımı karşılaştırın:

Dosya tabanlıCDN tabanlı
Çevirileri güncellePR birleştir + yeniden dağıtCDN'e aktar
Canlıya geçme süresiDakikalar ila saatlerSaniyeler
Geri almaGit revert + yeniden dağıtCDN önbellek geçersizleştirme
Ayrı çeviri PR'larıEvetHayır
Feature flag'lerle çalışırKarmaşıkNative

Better i18n'in CDN teslimatı ile çeviriler sürümlenir ve global olarak edge node'lardan sunulur. SDK'nız uygulamanızın mevcut dağıtımı için doğru sürümü çeker, böylece çevirileri belirli uygulama sürümlerine de sabitleyebilirsiniz.

Adım 5: CI'da Kalite Kapıları

CI kapıları, sorunları dağıtımdan önce yakalar. Ancak sızıp geçen sorunları yakalamak için çalışma zamanı izlemeye de ihtiyacınız var — çeviri kapsamı olmadan eklenen anahtarlar, uygulama geliştikçe geride kalan locale'ler.

Eksik anahtar tespiti

Kaynak locale'de olan ancak hedef locale'lerde olmayan herhangi bir anahtar varsa build'i başarısız yap:

#!/bin/bash
# scripts/check-missing-keys.sh

set -euo pipefail

SOURCE_FILE=".i18n-extracted/common.json"
LOCALES=("de" "fr" "es" "ja" "pt-BR")
HAS_MISSING=false

SOURCE_KEYS=$(jq -r 'keys[]' "$SOURCE_FILE" | sort)

for locale in "${LOCALES[@]}"; do
  TARGET_FILE=".i18n-translations/${locale}/common.json"

  if [ ! -f "$TARGET_FILE" ]; then
    echo "HATA: ${locale} için çeviri dosyası eksik"
    HAS_MISSING=true
    continue
  fi

  TARGET_KEYS=$(jq -r 'keys[]' "$TARGET_FILE" | sort)

  MISSING=$(comm -23 <(echo "$SOURCE_KEYS") <(echo "$TARGET_KEYS"))

  if [ -n "$MISSING" ]; then
    echo "HATA: ${locale} için eksik anahtarlar:"
    echo "$MISSING" | while read -r key; do
      echo "  - $key"
    done
    HAS_MISSING=true
  fi
done

if [ "$HAS_MISSING" = true ]; then
  echo ""
  echo "Build başarısız: eksik çeviriler tespit edildi"
  exit 1
fi

echo "Tüm locale'lerde tüm çeviri anahtarları mevcut"

CI'da yer tutucu doğrulaması

{placeholder} değişkenlerinin çeviriyi atlatıp atlatmadığını doğrulamak için özel bir adım ekleyin:

- name: Yer tutucu bütünlüğünü doğrula
  run: npx ts-node scripts/validate-translations.ts

Adım 6: Üretimde İzleme

CI kapıları, sorunları dağıtımdan önce yakalar. Ancak sızıp geçen sorunları yakalamak için çalışma zamanı izlemeye de ihtiyacınız var — çeviri kapsamı olmadan eklenen anahtarlar, uygulama geliştikçe geride kalan locale'ler.

Locale başına çeviri kapsamını takip edin

// api/i18n-coverage.ts
export async function getTranslationCoverage(): Promise<Record<string, number>> {
  const sourceKeys = await fetchSourceKeys(); // TMS'nizden veya CDN'den
  const coverage: Record<string, number> = {};

  for (const locale of SUPPORTED_LOCALES) {
    const translatedKeys = await fetchTranslatedKeys(locale);
    const translatedSet = new Set(translatedKeys);
    const covered = sourceKeys.filter(k => translatedSet.has(k)).length;
    coverage[locale] = Math.round((covered / sourceKeys.length) * 100);
  }

  return coverage;
}

Kullanıcılara ulaşan eksik anahtarlar için uyarı verin

Eksik anahtarları günlüğe kaydetmek için çeviri fonksiyonunuzu sarın:

// lib/t.ts
import { track } from './analytics';

export function t(key: string, fallback: string): string {
  const translation = getTranslation(key);

  if (!translation) {
    // İzleme için eksik anahtarı günlüğe kaydet
    track('i18n.missing_key', {
      key,
      locale: getCurrentLocale(),
      url: window.location.pathname,
    });
    return fallback;
  }

  return translation;
}

Bu olayları gözlemlenebilirlik platformunuza (Datadog, Grafana vb.) gönderin ve eksik anahtar oranları artışında uyarılar ayarlayın.

Eksiksiz GitHub Actions İş Akışı

İşte her şeyi bir araya getiren tam iş akışı dosyası:

# .github/workflows/i18n-pipeline.yml
name: i18n Otomasyon Pipeline'ı

on:
  push:
    branches: [main]
    paths:
      - 'src/**/*.{ts,tsx,js,jsx,vue,svelte}'

env:
  LOCALES: de,fr,es,ja,pt-BR
  SOURCE_LOCALE: en

jobs:
  extract-and-sync:
    name: Anahtarları Çıkar ve Çevirileri Senkronize Et
    runs-on: ubuntu-latest

    steps:
      - name: Kodu kontrol et
        uses: actions/checkout@v4
        with:
          fetch-depth: 2 # Diff için önceki commit gerekli

      - name: Node.js kur
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Bağımlılıkları yükle
        run: npm ci

      # Adım 1: Kaynak koddan çeviri anahtarlarını çıkar
      - name: i18n anahtarlarını çıkar
        run: npx i18next-parser --config i18next-parser.config.js

      # Adım 2: Son anlık görüntüye göre değişiklikleri tespit et
      - name: Anahtar değişikliklerini tespit et
        id: detect-changes
        run: bash scripts/detect-i18n-changes.sh

      # Adım 3: Yeni anahtarları çeviri API'sine yükle
      - name: Çeviri için yeni anahtarları yükle
        if: steps.detect-changes.outputs.has_changes == 'true'
        run: |
          curl -X POST "${{ secrets.BETTER_I18N_API_URL }}/api/sync" \
            -H "Authorization: Bearer ${{ secrets.BETTER_I18N_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d @.i18n-extracted/common.json
        env:
          PROJECT_ID: ${{ vars.I18N_PROJECT_ID }}

      # Adım 4: AI çevirisi için kısaca bekle (veya hazır olana kadar sorgula)
      - name: AI çevirilerini bekle
        if: steps.detect-changes.outputs.has_changes == 'true'
        run: |
          # 5 dakikaya kadar çeviri durumunu sorgula
          for i in $(seq 1 30); do
            STATUS=$(curl -s \
              -H "Authorization: Bearer ${{ secrets.BETTER_I18N_API_KEY }}" \
              "${{ secrets.BETTER_I18N_API_URL }}/api/status/${{ vars.I18N_PROJECT_ID }}" \
              | jq -r '.status'
            )

            if [ "$STATUS" = "ready" ]; then
              echo "Çeviriler hazır"
              break
            fi

            echo "Çeviriler bekleniyor... ($i/30)"
            sleep 10
          done

      # Adım 5: Çevrilmiş dosyaları indir
      - name: Çevirileri indir
        run: |
          IFS=',' read -ra LOCALE_ARRAY <<< "$LOCALES"
          for locale in "${LOCALE_ARRAY[@]}"; do
            mkdir -p ".i18n-translations/${locale}"
            curl -s \
              -H "Authorization: Bearer ${{ secrets.BETTER_I18N_API_KEY }}" \
              "${{ secrets.BETTER_I18N_API_URL }}/api/export/${locale}" \
              -o ".i18n-translations/${locale}/common.json"
          done

      # Adım 6: Çevirileri doğrula
      - name: Yer tutucu bütünlüğünü doğrula
        run: npx ts-node scripts/validate-translations.ts

      # Adım 7: Eksik anahtarları kontrol et
      - name: Eksik anahtarları kontrol et
        run: bash scripts/check-missing-keys.sh

      # Adım 8: Sonraki çalışma için anlık görüntüyü güncelle
      - name: i18n anlık görüntüsünü güncelle
        if: steps.detect-changes.outputs.has_changes == 'true'
        run: |
          cp .i18n-extracted/common.json .i18n-snapshot/en.json
          git config user.email "ci@yourorg.com"
          git config user.name "CI Bot"
          git add .i18n-snapshot/
          git diff --staged --quiet || git commit -m "chore: i18n anlık görüntüsünü güncelle [skip ci]"
          git push

  quality-gate:
    name: i18n Kalite Kapısı
    runs-on: ubuntu-latest
    needs: extract-and-sync

    steps:
      - uses: actions/checkout@v4

      - name: Node.js kur
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Bağımlılıkları yükle
        run: npm ci

      # Son kontrol: herhangi bir locale'de eksik çeviri yok
      - name: Çeviri kapsamını doğrula
        run: |
          node -e "
            const { getTranslationCoverage } = require('./dist/api/i18n-coverage');
            getTranslationCoverage().then(coverage => {
              const failed = Object.entries(coverage)
                .filter(([, pct]) => pct < 95);
              if (failed.length > 0) {
                console.error('%95 altında kapsam:', failed);
                process.exit(1);
              }
              console.log('Kapsam OK:', coverage);
            });
          "

Sonuç

Hedef, "geliştirici t('key') yazar" ile "kullanıcı çevrilmiş içeriği görür" arasında sıfır insan adımıdır. AST tabanlı çıkarma ile anahtar değişikliklerini otomatik olarak tespit etmek, AI çevirisiyle anında ilk geçiş sağlamak, CDN teslimatıyla yeniden dağıtım olmadan çevirileri canlıya almak ve kalite kapılarıyla CI'da yer tutucu uyuşmazlıklarını ve eksik anahtarları yakalamak — bu hedef bugün gerçekleştirilebilir.

Bunu doğru yapan ekipler i18n'e diğer altyapılarla aynı şekilde davranır: otomatik, izlenen ve bireysel dağıtımlardan bağımsız. Çeviriler bir darboğaz olmaktan çıkar ve teslimat pipeline'ının yalnızca bir parçası haline gelir.

Anahtar çıkarma script'i ve eksik anahtar CI kontrolüyle başlayın — bu iki adım, manuel zahmetin büyük çoğunluğunu tek başına ortadan kaldırır. Ardından pipeline'ı tamamlamak için çeviri API entegrasyonunu ve CDN teslimatını ekleyin.

Better i18n, modern frontend ekipleri için tasarlanmış geliştirici odaklı bir yerelleştirme platformudur. Tip güvenli SDK'lar, Git tabanlı iş akışları, CDN teslimatı ve sözlük zorunluluğuyla AI çevirisi — deponuzda locale dosyaları olmadan.

Comments

Loading comments...