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

> Discover how the WorldMonitor 21-language i18n system uses lazy-loaded bundles and dynamic imports for efficient locale loading plus automatic RTL support for Arabic.

- Repository: [Elie Habib/worldmonitor](https://github.com/koala73/worldmonitor)
- Tags: internals
- Published: 2026-03-09

---

**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`](https://github.com/koala73/worldmonitor/blob/main/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.

```typescript
// 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`.

```typescript
// 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.

```typescript
// 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`](https://github.com/koala73/worldmonitor/blob/main/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.

```typescript
// 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.

```typescript
// 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.

```typescript
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.

```typescript
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.

```typescript
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`](https://github.com/koala73/worldmonitor/blob/main/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`](https://github.com/koala73/worldmonitor/blob/main/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.