# How GeoLibre Implements Internationalization with react-i18next and RTL Support

> Discover GeoLibre's internationalization strategy using react-i18next. Learn how it automatically detects RTL languages and adapts layouts with Tailwind logical utilities.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-03

---

**GeoLibre uses react-i18next for full-stack internationalization, automatically detecting right-to-left languages and applying `dir="rtl"` to the HTML root, with Tailwind logical utilities ensuring layouts adapt seamlessly.**

The open-source GeoLibre desktop application delivers a multilingual, RTL-ready user interface built on a robust **react-i18next** foundation. This article examines the complete implementation—from bootstrap configuration to dynamic language switching and bidirectional layout support—based on the actual source code in the `opengeos/GeoLibre` repository.

## i18n Bootstrap Configuration

The internationalization engine initializes in [`apps/geolibre-desktop/src/i18n/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/i18n/index.ts). This file creates an **i18next** instance, loads locale resources, and wires up React integration.

```typescript
// apps/geolibre-desktop/src/i18n/index.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './locales/en.json';
import fr from './locales/fr.json';
import ar from './locales/ar.json';
// …additional imports

i18n
  .use(initReactI18next)
  .init({
    resources: { en, fr, ar, /* … */ },
    fallbackLng: 'en',
    supportedLngs: ['en', 'fr', 'ar', /* … */],
    interpolation: { escapeValue: false },
  });

```

Key configuration options include:

- **`fallbackLng: 'en'`** — English serves as the default when a translation key is missing
- **`supportedLngs`** — Explicit whitelist of available locales derived from the `locales/` folder
- **`interpolation.escapeValue: false`** — Disabled because React's JSX already handles escaping
- **`initReactI18next`** — Plugin enabling the `useTranslation()` hook throughout component trees

## TypeScript Type Safety

GeoLibre augments i18next's default types via [`i18next.d.ts`](https://github.com/opengeos/GeoLibre/blob/main/i18next.d.ts) in the same directory. This declaration file ensures autocompletion and compile-time validation for translation keys across the codebase.

- File: [`apps/geolibre-desktop/src/i18n/i18next.d.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/i18n/i18next.d.ts)

The augmentation maps the JSON structure of locale files directly into TypeScript definitions, eliminating string-typo errors when calling `t('settings.title')` or similar keys.

## Component Integration with useTranslation

Components consume translations through the standard **react-i18next** hook. The following pattern appears throughout GeoLibre's UI layer:

```tsx
// apps/geolibre-desktop/src/components/layout/SettingsDialog.tsx
import { useTranslation } from 'react-i18next';

export function SettingsDialog() {
  const { t } = useTranslation();
  return <h2>{t('settings.title')}</h2>;
}

```

The [`en.json`](https://github.com/opengeos/GeoLibre/blob/main/en.json) file acts as the **canonical catalogue**—all translation keys are defined there first, with other locales providing equivalent mappings.

## Right-to-Left (RTL) Language Support

GeoLibre's **RTL support** operates through two coordinated mechanisms: runtime direction detection and logical CSS utilities.

### HTML Direction Attribute

After resolving the active language, the bootstrap code sets the `dir` attribute on `document.documentElement`:

```typescript
const htmlEl = document.documentElement;
htmlEl.setAttribute('dir', i18n.dir(i18n.language));

```

The **`i18n.dir()`** method returns `'rtl'` for languages listed in i18next's internal `rtlLanguages` array (including Arabic `ar`, Hebrew `he`, Persian `fa`, and others). This single attribute change triggers the browser's bidirectional text algorithm and enables CSS logical properties.

### Dynamic Language Switching with Direction Update

When users select a new language, the application recomputes directionality:

```tsx
import { useTranslation } from 'react-i18next';

export function LanguageSelector() {
  const { i18n } = useTranslation();

  const change = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const lng = e.target.value;
    i18n.changeLanguage(lng).then(() => {
      document.documentElement.setAttribute('dir', i18n.dir(lng));
    });
  };

  return (
    <select onChange={change} defaultValue={i18n.language}>
      <option value="en">English</option>
      <option value="fr">Français</option>
      <option value="ar">العربية</option>
    </select>
  );
}

```

The `changeLanguage()` call updates the translation catalogue and triggers component re-renders; the subsequent `setAttribute` ensures the layout mirrors correctly for RTL languages.

### Tailwind Logical Utilities

GeoLibre styles its UI with **Tailwind CSS logical properties** rather than physical directional classes. This approach makes spacing and alignment automatically adapt when `dir` changes.

| Logical Utility | Physical Equivalent (LTR) | Physical Equivalent (RTL) |
|---------------|-------------------------|-------------------------|
| `ms-4` | `ml-4` (margin-left) | `mr-4` (margin-right) |
| `me-2` | `mr-2` (margin-right) | `ml-2` (margin-left) |
| `ps-4` | `pl-4` (padding-left) | `pr-4` (padding-right) |
| `pe-2` | `pr-2` (padding-right) | `pl-2` (padding-left) |
| `text-start` | `text-left` | `text-right` |
| `text-end` | `text-right` | `text-left` |

Example usage in components:

```tsx
<div className="flex gap-2 ps-4 pe-2">
  {/* ps-4 = padding-start (logical) */}
  <Button>{t('common.save')}</Button>
  <Button>{t('common.cancel')}</Button>
</div>

```

The Tailwind configuration resides in `tailwind.config.cjs` at the repository root, following the project's established conventions for maintainable, internationalized styling.

## Locale File Structure

Translation resources live in `apps/geolibre-desktop/src/i18n/locales/` as flat JSON files with key-value pairs:

- **English (source)**: [`apps/geolibre-desktop/src/i18n/locales/en.json`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/i18n/locales/en.json)
- **French**: [`apps/geolibre-desktop/src/i18n/locales/fr.json`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/i18n/locales/fr.json)
- **Arabic (RTL)**: [`apps/geolibre-desktop/src/i18n/locales/ar.json`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/i18n/locales/ar.json)

Adding a language requires three steps: create the JSON file, import it in [`index.ts`](https://github.com/opengeos/GeoLibre/blob/main/index.ts), and append its code to `supportedLngs`.

## Summary

- **Bootstrap**: [`index.ts`](https://github.com/opengeos/GeoLibre/blob/main/index.ts) initializes i18next with `fallbackLng: 'en'` and `initReactI18next` plugin
- **Type safety**: [`i18next.d.ts`](https://github.com/opengeos/GeoLibre/blob/main/i18next.d.ts) augments TypeScript definitions for compile-time key validation
- **Components**: `useTranslation()` hook provides the `t` function for string retrieval
- **RTL detection**: `i18n.dir()` identifies RTL languages and sets `dir="rtl"` on the HTML element
- **CSS strategy**: Tailwind logical utilities (`ms-`, `me-`, `ps-`, `pe-`, `text-start`) adapt layouts automatically
- **Runtime switching**: `changeLanguage()` updates translations while `setAttribute('dir', ...)` handles bidirectional layout

## Frequently Asked Questions

### How does GeoLibre detect RTL languages automatically?

GeoLibre relies on i18next's built-in **`i18n.dir()`** method, which checks the active language code against an internal list of RTL languages including Arabic, Hebrew, and Persian. After calling `changeLanguage()`, the bootstrap code executes `document.documentElement.setAttribute('dir', i18n.dir(lng))` to apply the correct text direction.

### What makes GeoLibre's CSS approach RTL-compatible without separate stylesheets?

The project uses **Tailwind CSS logical utilities**—classes like `ms-4` (margin-start), `pe-2` (padding-end), and `text-start` instead of physical `left`/`right` properties. These utilities map to the appropriate physical side based on the `dir` attribute, so a single codebase supports both LTR and RTL layouts without conditional styling.

### Where is the source of truth for translation keys in GeoLibre?

The **[`en.json`](https://github.com/opengeos/GeoLibre/blob/main/en.json) file** serves as the canonical catalogue. All UI strings are defined there first, and the TypeScript augmentation in [`i18next.d.ts`](https://github.com/opengeos/GeoLibre/blob/main/i18next.d.ts) types these keys for autocompletion. Other locale files mirror this structure with translated values for the same keys.

### Can users switch languages without reloading the application?

Yes. The **`i18n.changeLanguage()`** method updates the translation catalogue asynchronously and triggers React re-renders for all `useTranslation()` consumers. The direction attribute updates immediately after resolution, enabling seamless LTR-to-RTL transitions without page refresh.