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 missingsupportedLngs— Explicit whitelist of available locales derived from thelocales/folderinterpolation.escapeValue: false— Disabled because React's JSX already handles escapinginitReactI18next— Plugin enabling theuseTranslation()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:
- English (source):
apps/geolibre-desktop/src/i18n/locales/en.json - French:
apps/geolibre-desktop/src/i18n/locales/fr.json - Arabic (RTL):
apps/geolibre-desktop/src/i18n/locales/ar.json
Adding a language requires three steps: create the JSON file, import it in index.ts, and append its code to supportedLngs.
Summary
- Bootstrap:
index.tsinitializes i18next withfallbackLng: 'en'andinitReactI18nextplugin - Type safety:
i18next.d.tsaugments TypeScript definitions for compile-time key validation - Components:
useTranslation()hook provides thetfunction for string retrieval - RTL detection:
i18n.dir()identifies RTL languages and setsdir="rtl"on the HTML element - CSS strategy: Tailwind logical utilities (
ms-,me-,ps-,pe-,text-start) adapt layouts automatically - Runtime switching:
changeLanguage()updates translations whilesetAttribute('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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →