i18n di Production Scale — i18n

i18n di Production Scale Saat aplikasi mendukung 20+ bahasa dengan ribuan translation keys, performance menjadi concern utama. Loading semua terjemahan sekaligu

i18n di Production Scale

Saat aplikasi mendukung 20+ bahasa dengan ribuan translation keys, performance menjadi concern utama. Loading semua terjemahan sekaligus membunuh initial load time.

Lazy Loading Translations

// i18next: load bahasa on-demand
i18n.init({
  partialBundledLanguages: true,
  resources: {}, // Kosong — load via backend

  backend: {
    loadPath: "/locales/{{lng}}/{{ns}}.json",
  },

  // Hanya load bahasa aktif + fallback
  preload: ["en"], // fallback selalu tersedia
});

// Namespace splitting: load per route
// React.lazy + namespace loading
const Dashboard = React.lazy(async () => {
  // Load namespace sebelum render component
  await i18n.loadNamespaces("dashboard");
  return import("./pages/Dashboard");
});

// Route-based loading
function App() {
  return (
    <Routes>
      <Route path="/dashboard" element={
        <Suspense fallback={<Skeleton />}>
          <Dashboard />
        </Suspense>
      } />
    </Routes>
  );
}

CDN untuk Translation Files

// Serve translation files dari CDN
i18n.init({
  backend: {
    loadPath: "https://cdn.example.com/locales/{{lng}}/{{ns}}.json",
    // Cache-Control: public, max-age=31536000, immutable
    // Gunakan content hash di filename untuk cache busting
  },
});

// Build step: hash translation files
// locales/id/common.abc123.json
// manifest.json: { "id/common": "abc123" }

// Atau gunakan service worker untuk cache
self.addEventListener("fetch", (event) => {
  if (event.request.url.includes("/locales/")) {
    event.respondWith(
      caches.match(event.request).then(
        (cached) => cached || fetch(event.request).then((response) => {
          const cache = await caches.open("i18n-v1");
          cache.put(event.request, response.clone());
          return response;
        })
      )
    );
  }
});

Bundle Size Optimization

// Masalah: Intl polyfills bisa besar
// @formatjs/intl-pluralrules — 50KB+ dengan semua locale

// Solusi: hanya load locale yang dibutuhkan
import "@formatjs/intl-pluralrules/polyfill";
import "@formatjs/intl-pluralrules/locale-data/id"; // Hanya Indonesia
import "@formatjs/intl-pluralrules/locale-data/en"; // Hanya English

// Dynamic import berdasarkan locale
async function loadPolyfills(locale) {
  if (!Intl.PluralRules) {
    await import("@formatjs/intl-pluralrules/polyfill");
  }
  await import(`@formatjs/intl-pluralrules/locale-data/${locale}`);
}

Server-side Caching

// Laravel: cache terjemahan per locale
// Jangan load file terjemahan setiap request

// Artisan command
php artisan lang:cache    // Cache semua translation files
php artisan lang:clear    // Clear cache

// Custom caching untuk dynamic translations (dari database)
class TranslationService
{
    public function get(string $key, string $locale): string
    {
        return Cache::remember(
            "translations.{$locale}.{$key}",
            now()->addHours(24),
            fn () => Translation::where("key", $key)
                ->where("locale", $locale)
                ->value("value") ?? $key
        );
    }

    public function clearCache(string $locale): void
    {
        Cache::tags(["translations", $locale])->flush();
    }
}

Monitoring & Analytics

// Track missing translations di production
i18n.on("missingKey", (lngs, namespace, key) => {
  // Kirim ke error tracking (Sentry, etc)
  captureMessage("Missing i18n key", {
    extra: { languages: lngs, namespace, key },
    level: "warning",
  });
});

// Track language distribution
analytics.track("language_selected", {
  locale: i18n.language,
  source: "auto" | "manual",
});

// Monitor translation coverage
// Dashboard: % translated per locale
// Alert jika coverage < 90%

Performance Checklist

Yang akan kamu pelajari