How GeoLibre Implements Internationalization with react-i18next and RTL Support

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. This file creates an i18next instance, loads locale resources, and wires up React integration.

// 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 in the same directory. This declaration file ensures autocompletion and compile-time validation for translation keys across the codebase.

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:

// 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 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:

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:

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:

<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:

Adding a language requires three steps: create the JSON file, import it in index.ts, and append its code to supportedLngs.

Summary

  • Bootstrap: index.ts initializes i18next with fallbackLng: 'en' and initReactI18next plugin
  • Type safety: 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 file serves as the canonical catalogue. All UI strings are defined there first, and the TypeScript augmentation in 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.

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 →