How the WorldMonitor 21-Language i18n System Implements Lazy-Loaded Bundles and RTL Support

The WorldMonitor app implements a 21-language internationalization system by eagerly loading only English at startup, then dynamically importing other locale JSON files via import.meta.glob while automatically applying RTL document direction for Arabic.

The koala73/worldmonitor repository delivers a production-grade i18n architecture designed to minimize initial bundle size while supporting 21 languages. This system leverages Vite's code-splitting capabilities to isolate the i18next library and locale-specific JSON bundles into separate chunks, ensuring that users download only the language data they need.

Core i18n Service Architecture

The heart of the system resides in src/services/i18n.ts, which orchestrates language detection, lazy loading, and RTL handling.

Language Detection and Normalization

The service defines supported languages as a constant tuple exported as LANGUAGES for UI selectors. The normalizeLanguage() function converts any BCP-47 language tag (such as en-US or fr-CA) into a short, supported code (like en or fr) that matches the available JSON files.

// From src/services/i18n.ts L22-L28
function normalizeLanguage(lang: string): string {
  const shortCode = lang.split('-')[0];
  return LANGUAGES.includes(shortCode as any) ? shortCode : 'en';
}

Lazy Loading with import.meta.glob

To keep the initial bundle minimal, the system uses import.meta.glob to create a mapping of locale modules without including them in the main chunk. The localeModules map targets ../locales/*.json but explicitly excludes the English file, which is eagerly imported instead.

The ensureLanguageLoaded() function checks a loadedLanguages cache and, if the requested locale is missing, dynamically imports the JSON via the localeModules map. The loaded resources are then registered with i18next using addResourceBundle.

// Conceptual flow from src/services/i18n.ts L40-L61
async function ensureLanguageLoaded(lang: string) {
  if (loadedLanguages.has(lang)) return;
  
  const loader = localeModules[`../locales/${lang}.json`];
  if (!loader) return; // Falls back to English
  
  const resources = await loader();
  i18next.addResourceBundle(lang, 'translation', resources.default);
  loadedLanguages.add(lang);
}

RTL Support Implementation

Right-to-left support is handled through a dedicated RTL_LANGUAGES Set containing 'ar' for Arabic. The applyDocumentDirection() function updates the <html> element's lang attribute and conditionally sets dir="rtl" when the active language is detected as right-to-left.

// From src/services/i18n.ts L30-L37
function applyDocumentDirection(lang: string) {
  document.documentElement.lang = lang;
  document.documentElement.dir = RTL_LANGUAGES.has(lang) ? 'rtl' : 'ltr';
}

Vite Build Configuration for Code Splitting

The build pipeline in vite.config.ts is specifically tuned to support the i18n architecture through strategic code splitting.

Manual Chunks for i18next

Within rollupOptions.manualChunks, any module path containing /i18next is assigned to a dedicated chunk named 'i18n'. This isolates the i18next library from entry point chunks, preventing duplication across main, settings, and liveChannels entry points.

// From vite.config.ts L75-L82
manualChunks: (id) => {
  if (id.includes('/i18next')) {
    return 'i18n';
  }
  // ... locale handling
}

Locale Bundle Naming Strategy

A custom regex extracts the locale code from JSON file paths (/locales/<code>.json). For all languages except English, the build generates chunks named locale-<code>. This naming convention allows the service worker to easily exclude these language bundles from precaching, ensuring they are fetched only when requested by the user.

// From vite.config.ts L10-L17
const localeMatch = id.match(/\/locales\/([a-z]{2})\.json$/);
if (localeMatch) {
  const lang = localeMatch[1];
  if (lang !== 'en') {
    return `locale-${lang}`;
  }
}

Practical Implementation Examples

Translating UI Strings

Components import the t helper to resolve translation keys. The function is a thin wrapper around i18next.t that automatically uses the currently loaded language.

import { t } from '@/services/i18n';

const label = t('settings.title');
const formatted = t('items.count', { count: 5 });

Switching Languages

The changeLanguage function handles the complete transition workflow: loading the new locale bundle, updating i18next's active language, applying the correct document direction, and reloading the page to refresh the UI with new translations.

import { changeLanguage, LANGUAGES, getCurrentLanguage } from '@/services/i18n';

async function onLanguageSelect(code: string) {
  if (LANGUAGES.includes(code as any)) {
    await changeLanguage(code);
    // Page reloads automatically to apply changes
  }
}

Detecting RTL for Custom Layouts

For components requiring specific RTL adjustments beyond CSS logical properties, the isRTL helper provides runtime detection.

import { isRTL } from '@/services/i18n';

if (isRTL()) {
  // Apply mirror-specific calculations or classes
  element.classList.add('mirror-layout');
}

Summary

  • Eager Loading: Only English (en) is bundled with the main application chunk, ensuring minimal initial payload.
  • Lazy Loading: All other 20 languages are loaded on-demand via dynamic imports using import.meta.glob in src/services/i18n.ts.
  • Build Optimization: Vite's manualChunks isolates the i18next library into a dedicated 'i18n' chunk and generates named locale-<code> chunks for each language file.
  • RTL Support: Automatic detection of right-to-left languages (Arabic) via RTL_LANGUAGES Set, with dynamic application of dir="rtl" to the HTML document element.
  • API Surface: Simple helper functions (t, changeLanguage, isRTL, getCurrentLanguage) abstract i18next complexity for the rest of the application.

Frequently Asked Questions

How does the system handle unsupported browser languages?

When normalizeLanguage() receives a BCP-47 tag that does not match any entry in the LANGUAGES array, it automatically falls back to the 'en' short code. This ensures the application always has a valid translation key set, defaulting to English for any unsupported locale.

Why is English treated differently from other languages in the build process?

English serves as the fallback language and is therefore eagerly imported into the main application bundle. This guarantees that translation keys are always available even if a network request for another locale fails. All other languages are code-split into separate locale-<code> chunks that the browser fetches only when the user selects that specific language.

What happens when a user switches languages at runtime?

The changeLanguage() function in src/services/i18n.ts executes a complete transition sequence: it first calls ensureLanguageLoaded() to dynamically import the new locale's JSON bundle, then updates i18next's active language, invokes applyDocumentDirection() to set the correct text direction, and finally triggers a full page reload to ensure all UI components render with the new translations.

How is RTL support implemented beyond just setting the dir attribute?

While applyDocumentDirection() handles the semantic HTML direction by setting dir="rtl" on the document element, the CSS architecture relies on logical properties (flexbox and grid layouts) that automatically respect the direction attribute. For components requiring explicit RTL awareness, the isRTL() helper function provides runtime detection, allowing JavaScript logic to apply mirror-specific calculations or conditional classes when the active language is Arabic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →