# How Plane's i18n System Supports Multiple Locales Using next-i18next and react-i18next

> Discover how Plane's i18n system uses next-i18next and react-i18next to seamlessly support multiple locales. Learn about automatic language switching and efficient translation management.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: how-to-guide
- Published: 2026-06-22

---

**TLDR:** Plane's internationalization layer leverages a centralized `@plane/i18n` package that combines next-i18next for server-side locale detection and react-i18next for client-side translation delivery, enabling automatic language switching through a provider pattern and namespace-organized JSON files.

The open-source project management platform Plane (makeplane/plane) implements a robust **i18n system** that supports multiple locales using next-i18next and react-i18next. This architecture centralizes translation logic within a dedicated monorepo package while enabling seamless server-side rendering and client-side language persistence across the entire application.

## Configuration Architecture with next-i18next

The foundation of Plane's internationalization resides in the global configuration file at [[`packages/i18n/next-i18next.config.js`](https://github.com/makeplane/plane/blob/main/packages/i18n/next-i18next.config.js)](https://github.com/makeplane/plane/blob/preview/packages/i18n/next-i18next.config.js). This file declares the supported locales, default language, and resource path structure.

```javascript
// packages/i18n/next-i18next.config.js
const path = require('path');

module.exports = {
  i18n: {
    defaultLocale: 'en-US',
    locales: ['en-US', 'zh-CN', 'zh-TW', 'vi-VN', 'ua'],
  },
  localePath: path.resolve('./packages/i18n/src/locales'),
  ns: ['common', 'workspace', 'project'],
  defaultNS: 'common',
};

```

This configuration establishes **en-US** as the fallback locale while supporting five distinct languages. The `localePath` directs next-i18next to the centralized translation directory, ensuring consistent resource loading across both server and client environments.

## Server-Side Locale Detection

When Next.js renders a page, next-i18next automatically detects the preferred locale from incoming request headers or URL parameters. The server pre-loads the appropriate translation resources and injects them into the page props as `initialI18nStore` and `initialLocale`. This hydration strategy ensures that the React application receives the correct translation data on initial render, preventing content flickering or locale mismatches between server and client.

## The I18nextProvider Wrapper

Plane wraps the React application with a custom provider defined in [[`packages/i18n/src/provider/index.tsx`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/provider/index.tsx)](https://github.com/makeplane/plane/blob/preview/packages/i18n/src/provider/index.tsx). This component initializes the i18next instance using `initReactI18next` and connects the pre-loaded server data to the React context.

```tsx
// packages/i18n/src/provider/index.tsx
import i18next from 'i18next';
import { I18nextProvider } from 'react-i18next';
import { initReactI18next } from 'react-i18next';
import nextI18NextConfig from '../../next-i18next.config';

i18next.use(initReactI18next).init({
  ...nextI18NextConfig.i18n,
  ns: ['common', 'workspace', 'project'],
  fallbackLng: 'en-US',
  interpolation: { escapeValue: false },
  react: {
    useSuspense: false,
  },
});

export const I18nProvider = ({ children }: { children: React.ReactNode }) => (
  <I18nextProvider i18n={i18next}>{children}</I18nextProvider>
);

```

By instantiating i18next at the package level and exporting the provider, Plane ensures that all consuming applications in the monorepo share a single, consistent translation instance.

## Namespace-Based Translation Organization

Translation strings are organized into **namespaces** that mirror functional domains within the application. The directory structure follows the pattern `packages/i18n/src/locales/<locale>/<namespace>.json`.

For example, the Simplified Chinese workspace translations reside at [[`packages/i18n/src/locales/zh-CN/workspace.json`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/locales/zh-CN/workspace.json)](https://github.com/makeplane/plane/blob/preview/packages/i18n/src/locales/zh-CN/workspace.json). This modular approach allows different teams to manage translations for specific features without merge conflicts, and enables webpack to split translation bundles for code-splitting optimization.

## Client-Side Translation Hooks

Components consume translations through the `useTranslation` hook from react-i18next. The hook automatically resolves the correct namespace based on the component's context or an explicit `ns` parameter.

```tsx
import { useTranslation } from 'react-i18next';

export const WorkspaceHeader = () => {
  const { t } = useTranslation('workspace');
  
  return (
    <header>
      <h1>{t('workspace.title')}</h1>
      <p>{t('workspace.description')}</p>
    </header>
  );
};

```

Because the `I18nextProvider` already contains the initialized instance with loaded resources, the `t` function immediately returns the localized string for the active locale without additional asynchronous fetching.

## Dynamic Locale Switching

Plane implements language switching through the i18next core API. When users select a different language, the application calls `i18n.changeLanguage(newLocale)`, which updates the internal state and triggers a React re-render with the new translations.

```tsx
import i18n from 'i18next';

export const LanguageSwitcher = () => {
  const handleChange = (locale: string) => {
    i18n.changeLanguage(locale);
  };

  return (
    <select 
      onChange={(e) => handleChange(e.target.value)} 
      defaultValue={i18n.language}
    >
      <option value="en-US">English</option>
      <option value="zh-CN">中文（简体）</option>
      <option value="zh-TW">中文（繁體）</option>
      <option value="vi-VN">Tiếng Việt</option>
      <option value="ua">Українська</option>
    </select>
  );
};

```

In production deployments, next-i18next synchronizes the locale change with the Next.js router, updating the URL prefix (e.g., `/en-US/dashboard` to `/zh-CN/dashboard`) to ensure that subsequent page loads maintain the selected language through server-side rendering.

## Build-Time Optimization

During the build process, next-i18next validates translation keys against the codebase and extracts only the namespaces required for each page. This optimization reduces the client-side bundle size by ensuring that translation files are loaded on-demand rather than bundled with the initial JavaScript payload. The static extraction also surfaces missing translation keys early in the development cycle, preventing runtime errors in production.

## Summary

- **Centralized Configuration**: The [`next-i18next.config.js`](https://github.com/makeplane/plane/blob/main/next-i18next.config.js) in `packages/i18n` defines supported locales and resource paths, serving as the single source of truth for the entire monorepo.
- **Provider Pattern**: The `I18nProvider` in [`packages/i18n/src/provider/index.tsx`](https://github.com/makeplane/plane/blob/main/packages/i18n/src/provider/index.tsx) initializes i18next with `initReactI18next` and injects the instance into React's context.
- **Namespace Organization**: Translations are split into JSON files by locale and functional domain (e.g., [`workspace.json`](https://github.com/makeplane/plane/blob/main/workspace.json), [`common.json`](https://github.com/makeplane/plane/blob/main/common.json)), enabling modular maintenance and code-splitting.
- **Automatic Locale Detection**: next-i18next handles server-side locale detection from headers and URLs, pre-loading resources before React hydration.
- **Dynamic Switching**: The `i18n.changeLanguage()` method enables runtime locale switching with immediate UI updates and URL synchronization.

## Frequently Asked Questions

### How does Plane handle fallback languages when a translation key is missing?

When a key is missing in the active locale, i18next falls back to the `defaultLocale` defined in [`next-i18next.config.js`](https://github.com/makeplane/plane/blob/main/next-i18next.config.js) (typically **en-US**). The `fallbackLng` parameter in the provider initialization ensures that if a specific key does not exist in `zh-CN`, the system automatically retrieves the English equivalent from the `en-US` namespace, preventing broken UI elements while maintaining user experience.

### Where are translation files stored in the Plane repository?

Translation files are stored in the `packages/i18n/src/locales/` directory, with subdirectories named after each locale code (e.g., `en-US`, `zh-CN`). Each subdirectory contains JSON files representing namespaces such as [`common.json`](https://github.com/makeplane/plane/blob/main/common.json), [`workspace.json`](https://github.com/makeplane/plane/blob/main/workspace.json), and [`project.json`](https://github.com/makeplane/plane/blob/main/project.json). This structure allows the build system to load only the required locale data and enables contributors to add new languages by creating additional locale folders following the same naming convention.

### Can components use multiple translation namespaces simultaneously?

Yes, components can access multiple namespaces by passing an array to the `useTranslation` hook. For example, `useTranslation(['workspace', 'common'])` returns a `t` function that searches both namespaces, with the first namespace serving as the default. This is useful for complex UI components that display text from both feature-specific and shared translation sets, reducing the need to import separate hook instances.

### How does Plane ensure translations are available during server-side rendering?

The application leverages next-i18next's server-side utilities to preload translation resources during the Next.js data fetching phase. The server injects the `initialI18nStore` into the HTML response, which the `I18nextProvider` consumes during React hydration. This mechanism guarantees that the initial HTML render contains the correct localized content, eliminating client-side loading states and improving SEO for multilingual pages.