How Open Notebook Manages Translation Keys for Multi-Language UI Support

Open Notebook uses i18next with react-i18next to manage translation keys through a centralized locale catalog, automatic language detection, and flat-key namespacing in JSON files.

Open Notebook implements a robust internationalization (i18n) system using i18next and react-i18next to handle translation keys across its React frontend. This architecture stores all language strings in JSON files under frontend/src/lib/locales/, enabling seamless multi-language UI support with automatic fallback to English. By leveraging flat-key naming conventions and browser-based language detection, the application delivers translated content without requiring complex code changes when adding new languages.

i18next Configuration and Initialization

The translation system bootstrap resides in frontend/src/lib/i18n.ts. This file creates the i18next instance and configures the language detection strategy.

The configuration imports the locale catalog from ./locales and initializes LanguageDetector to read the user's preferred language from localStorage or the browser's navigator.language. Key settings include:

  • fallbackLng: 'en-US' – Ensures English displays when translation keys are missing
  • useSuspense: false – Prevents React suspense boundaries from blocking rendering
  • caches: ['localStorage'] – Persists language selection across sessions
// frontend/src/lib/i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import resources from './locales';

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources,
    fallbackLng: 'en-US',
    interpolation: { escapeValue: false },
    useSuspense: false,
    detection: {
      order: ['localStorage', 'navigator'],
      caches: ['localStorage'],
    },
  });

Locale Catalog Structure and Flat-Key Convention

All translation keys live in a single resources object generated from JSON files under frontend/src/lib/locales/. Each language (e.g., en-US, fr-FR, es-ES) serves as a top-level key containing namespaced translation tables.

The project follows the flat-key convention, grouping UI text by feature using dot notation:

  • common.title – Shared interface elements
  • sources.addUrl – Source management features
  • navigation.settings – Navigation labels

Adding a new language requires only creating a new JSON file in the locales folder and updating the import in the configuration. The bundler automatically incorporates new translations without modifying component code.

Using Translation Keys in React Components

Components access translation keys via the useTranslation hook from react-i18next. The hook returns a t function that resolves the current language automatically based on the detector configuration.

When a key is missing in the selected language, i18next falls back to the en-US entry. This ensures the UI never displays raw key strings to users.

// frontend/src/components/sources/steps/SourceTypeStep.tsx
import { useTranslation } from 'react-i18next';

export function SourceTypeStep() {
  const { t } = useTranslation();
  
  return (
    <>
      <label>{t('sources.title')}</label>
      <button>{t('sources.addUrl')}</button>
    </>
  );
}

The t function accepts the namespace-key string directly, eliminating the need to import translation files into individual components.

Language Detection and Runtime Switching

Open Notebook implements automatic language detection on first load through a detector chain that checks localStorage first, then falls back to navigator.language. If a stored language exists, it takes precedence; otherwise, the browser's locale determines the initial language.

Components can programmatically change languages using i18n.changeLanguage(newLng), which automatically persists the selection to localStorage due to the detector's cache configuration.

import { useTranslation } from 'react-i18next';

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

  const changeLanguage = (lng: string) => {
    i18n.changeLanguage(lng);
  };

  return (
    <select 
      onChange={(e) => changeLanguage(e.target.value)} 
      defaultValue={i18n.language}
    >
      <option value="en-US">English</option>
      <option value="fr-FR">Français</option>
      <option value="es-ES">Español</option>
    </select>
  );
}

The frontend/src/lib/i18n-events.ts file provides additional helpers for emitting language-change events throughout the UI when components need to react to locale updates.

Adding New Translation Keys and Languages

Extending the translation system requires no code changes beyond editing JSON files:

  1. Insert the key-value pair into every language's JSON file under the appropriate namespace:
// frontend/src/lib/locales/en-US.json
{
  "common": {
    "title": "Open Notebook",
    "welcome": "Welcome"
  },
  "newFeature": {
    "label": "New Feature"
  }
}
  1. Add corresponding translations to fr-FR.json, es-ES.json, and other locale files.

  2. Reference the key in components using t('newFeature.label').

The bundler automatically re-imports the catalog when files change, and i18next's type-safe patterns help identify missing keys during development.

Summary

  • i18next powers the translation system in frontend/src/lib/i18n.ts with fallbackLng: 'en-US' and browser-based detection
  • Translation keys follow a flat-key convention (namespace.key) stored in JSON files under frontend/src/lib/locales/
  • Components use the useTranslation hook to access the t function for resolving keys
  • Language detection prioritizes localStorage over navigator.language and persists user selection automatically
  • Adding languages requires only new JSON files; adding keys requires only JSON edits and t() references

Frequently Asked Questions

How does Open Notebook handle missing translation keys?

When a translation key is missing in the selected language, i18next automatically falls back to the en-US locale as configured in frontend/src/lib/i18n.ts. This ensures users always see English text rather than raw key strings or blank spaces.

Can I add a new language without modifying React components?

Yes. Adding a new language requires only creating a new JSON file in frontend/src/lib/locales/ with the appropriate translations and updating the resources import in i18n.ts. The useTranslation hook automatically recognizes new languages once they are added to the configuration.

Where does Open Notebook store the user's language preference?

The application stores the user's language preference in localStorage via the LanguageDetector configuration with caches: ['localStorage']. This setting ensures returning users see the interface in their previously selected language immediately on load.

What is the difference between useTranslation() and i18n.changeLanguage()?

The useTranslation() hook provides the t function for rendering translated text and accessing the current i18n instance, while i18n.changeLanguage() is a method to programmatically switch the active language. The latter triggers the storage cache update and re-renders all components using translation keys.

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 →