# How Plane Handles Internationalization (i18n): Architecture and Implementation

> Discover how Plane handles internationalization (i18n) using a dedicated package, i18next, and type-safe translations with dynamic JSON loading for a seamless global user experience.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: architecture
- Published: 2026-08-22

---

**Plane implements internationalization through a dedicated `@plane/i18n` package built on the i18next ecosystem, utilizing a singleton instance with ICU formatting, a React context provider, and a custom hook that ensures type-safe translations with dynamic JSON loading.**

Plane, the open-source project management platform, delivers multi-language support through a robust internalization layer encapsulated in the `@plane/i18n` package. This architecture leverages the **i18next** library with ICU formatting capabilities to provide runtime language switching and namespaced translation management. The implementation ensures optimal performance through eager resource loading and strict runtime type guards.

## Core Architecture

Plane's internationalization system consists of three coordinated components that initialize once and provide translation capabilities throughout the React application tree.

### Singleton i18next Instance

The foundation resides in [`packages/i18n/src/core/instance.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/core/instance.ts), which creates and configures a singleton `i18nInstance`. This module initializes **i18next** with the ICU plugin for advanced formatting, wires it to `react-i18next`, and connects a dynamic backend that imports JSON files from `packages/i18n/src/locales/<lang>/<ns>.json`. The configuration eagerly loads all namespaces during initialization to prevent render-cascade reloads, and sets up fallback languages and supported locales.

### React Provider Component

The [`packages/i18n/src/provider/index.tsx`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/provider/index.tsx) file exports the `I18nProvider` component that wraps the application tree with `I18nextProvider`. This provider checks `i18nInstance.isInitialized` and waits for the `initPromise` to resolve before rendering children, ensuring the UI only appears after translation resources are ready.

### Custom useTranslation Hook

Located at [`packages/i18n/src/hooks/use-translation.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/hooks/use-translation.ts), this wrapper around `react-i18next`'s hook adds runtime safety checks and convenience methods. It verifies that translation results are strings (preventing crashes when ICU returns raw objects) and exposes a `setLanguage` helper that updates `localStorage` and invokes `i18n.changeLanguage`.

## Language and Namespace Configuration

Language support and organizational structure are defined in centralized constants files.

### Supported Languages and Fallbacks

The [`packages/i18n/src/constants/language.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/constants/language.ts) file declares supported languages, the default locale, and the fallback strategy. On initialization, the system checks `localStorage` for a stored language preference before defaulting to the configured primary language.

### Namespace Organization

Translation keys are organized into namespaces—such as `"common"`, `"auth"`, and `"settings"`—defined in [`packages/i18n/src/constants/namespaces.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/constants/namespaces.ts). The architecture automatically derives a fallback namespace, allowing components to call `t('key')` without specifying a namespace while preventing unnecessary re-renders.

## Runtime Initialization and Language Switching

The initialization sequence ensures translations load before the UI renders, while language switching updates the entire application state.

### Initialization Sequence

When the application mounts, the `I18nProvider` triggers `initPromise`, causing `i18nInstance` to load all JSON resources for the detected language. Components access these translations through the custom `useTranslation` hook, which guarantees string outputs and provides the current locale.

### Changing Languages at Runtime

The `setLanguage` method exposed by the custom hook persists the selection to `localStorage` and calls `i18n.changeLanguage`, triggering a re-render of all translation-aware components with the new locale strings.

## Implementation Examples

Wrap your application root with the provider:

```tsx
import { I18nProvider } from '@plane/i18n';

function App() {
  return (
    <I18nProvider>
      <YourAppComponents />
    </I18nProvider>
  );
}

```

Access translations in components using the default namespace:

```tsx
import { useTranslation } from '@plane/i18n';

function Greeting() {
  const { t } = useTranslation();
  return <h1>{t('welcome_message')}</h1>;
}

```

Implement a language switcher with persistence:

```tsx
import { useTranslation } from '@plane/i18n';

function LanguageSwitcher() {
  const { i18n, setLanguage } = useTranslation();

  const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setLanguage(e.target.value);
  };

  return (
    <select value={i18n.language} onChange={handleChange}>
      <option value="en">English</option>
      <option value="es">Español</option>
      <option value="fr">Français</option>
    </select>
  );
}

```

## Summary

- Plane's i18n system is encapsulated in the `@plane/i18n` package using the **i18next** ecosystem with ICU formatting support.
- The singleton instance in [`packages/i18n/src/core/instance.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/core/instance.ts) configures dynamic JSON loading and eager namespace initialization to prevent UI flicker.
- The `I18nProvider` in [`packages/i18n/src/provider/index.tsx`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/provider/index.tsx) blocks rendering until translation resources are fully loaded.
- The custom `useTranslation` hook in [`packages/i18n/src/hooks/use-translation.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/hooks/use-translation.ts) provides runtime type safety and a `setLanguage` helper that persists preferences to `localStorage`.
- Namespaces and supported languages are centrally configured in [`packages/i18n/src/constants/namespaces.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/constants/namespaces.ts) and [`packages/i18n/src/constants/language.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/constants/language.ts).

## Frequently Asked Questions

### How does Plane store user language preferences?

Plane persists the selected language to `localStorage` through the `setLanguage` method exposed by the custom `useTranslation` hook. When the application initializes, the i18next instance checks this storage key before falling back to the default language defined in [`packages/i18n/src/constants/language.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/constants/language.ts).

### What is the purpose of namespaces in Plane's i18n implementation?

Namespaces organize translation keys into logical groups such as `"common"`, `"auth"`, and `"settings"`, defined in [`packages/i18n/src/constants/namespaces.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/constants/namespaces.ts). This separation allows the system to load translations on demand and enables components to call `t('key')` without specifying a namespace, simplifying the API while maintaining performance.

### Why does Plane use a custom wrapper around react-i18next's useTranslation hook?

The custom hook in [`packages/i18n/src/hooks/use-translation.ts`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/hooks/use-translation.ts) adds runtime guards that ensure translation values are strings, preventing application crashes when ICU formatting returns raw objects. It also centralizes language switching logic by combining `localStorage` updates with `i18n.changeLanguage` into a single `setLanguage` helper.

### How does Plane prevent translation loading delays from causing UI flicker?

The `I18nProvider` component waits for the `initPromise` returned by the singleton instance to resolve before rendering children. Additionally, the i18next instance is configured to eagerly load all namespaces during initialization rather than loading them on demand, eliminating render-cascade reloads once the application becomes visible.