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

> Learn how Open Notebook manages translation keys using i18next for multi-language UI support. Discover centralized catalogs, language detection, and JSON namespacing.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-19

---

**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`](https://github.com/lfnovo/open-notebook/blob/main/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

```typescript
// 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.

```typescript
// 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.

```typescript
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`](https://github.com/lfnovo/open-notebook/blob/main/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:

```json
// frontend/src/lib/locales/en-US.json
{
  "common": {
    "title": "Open Notebook",
    "welcome": "Welcome"
  },
  "newFeature": {
    "label": "New Feature"
  }
}

```

2. Add corresponding translations to [`fr-FR.json`](https://github.com/lfnovo/open-notebook/blob/main/fr-FR.json), [`es-ES.json`](https://github.com/lfnovo/open-notebook/blob/main/es-ES.json), and other locale files.

3. 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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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.