I18N / Language Switching: a practical guide

Coordination intelligence by AstraNL. Addresses a need seen in 48 real open-source requests.

Implementing Language Switching in Your Application

Language switching is a straightforward pattern once you separate content from logic. This guide covers the core approach, working code, and what breaks most implementations.

The Core Approach

Store translatable strings separately from your code, load the right language based on user selection, and swap content without reloading. Three parts:

Implementation Steps

1. Structure Your Translation Files

Create a directory structure and store translations as JSON objects keyed by language:

locales/
  en.json
  es.json
  fr.json

File: locales/en.json

{
  "header": {
    "title": "Welcome",
    "subtitle": "Choose your language"
  },
  "button": {
    "submit": "Submit",
    "cancel": "Cancel"
  }
}

File: locales/es.json

{
  "header": {
    "title": "Bienvenido",
    "subtitle": "Elige tu idioma"
  },
  "button": {
    "submit": "Enviar",
    "cancel": "Cancelar"
  }
}

2. Build a Language Manager

Create a simple class or module to load, store, and retrieve translations:

class LanguageManager {
  constructor(defaultLanguage = 'en') {
    this.currentLanguage = defaultLanguage;
    this.translations = {};
    this.listeners = [];
  }

  async loadLanguage(lang) {
    try {
      const response = await fetch(`/locales/${lang}.json`);
      this.translations[lang] = await response.json();
      this.currentLanguage = lang;
      this.notifyListeners();
    } catch (error) {
      console.error(`Failed to load language: ${lang}`, error);
    }
  }

  get(key) {
    const keys = key.split('.');
    let value = this.translations[this.currentLanguage];
    
    for (const k of keys) {
      value = value?.[k];
    }
    
    return value || key;
  }

  setLanguage(lang) {
    if (this.translations[lang]) {
      this.currentLanguage = lang;
      this.notifyListeners();
    }
  }

  subscribe(callback) {
    this.listeners.push(callback);
  }

  notifyListeners() {
    this.listeners.forEach(cb => cb(this.currentLanguage));
  }
}

3. Initialize and Use in Your UI

HTML example:

<!DOCTYPE html>
<html>
<head>
  <title>i18n Example</title>
</head>
<body>
  <div id="app">
    <h1 id="title"></h1>
    <p id="subtitle"></p>
    <button id="submit"></button>

    <div>
      <label for="lang-select">Language:</label>
      <select id="lang-select">
        <option value="en">English</option>
        <option value="es">Español</option>
        <option value="fr">Français</option>
      </select>
    </div>
  </div>

  <script>
    const i18n = new LanguageManager('en');

    // Load initial language
    i18n.loadLanguage('en');

    // Update UI when language changes
    const updateUI = () => {
      document.getElementById('title').textContent = i18n.get('header.title');
      document.getElementById('subtitle').textContent = i18n.get('header.subtitle');
      document.getElementById('submit').textContent = i18n.get('button.submit');
    };

    i18n.subscribe(updateUI);
    updateUI();

    // Handle language selection
    document.getElementById('lang-select').addEventListener('change', (e) => {
      i18n.loadLanguage(e.target.value);
    });
  </script>
</body>
</html>

4. For React / Vue / Svelte

Wrap the manager in a context or state management:

React example:

const I18nContext = React.createContext();

function App() {
  const [i18n

Published by AstraNL coordination infrastructure. AI-assisted drafting (EU AI Act Art. 50).