# How Internationalization (i18n) is Implemented in NextChat: A Technical Deep Dive

> Discover how NextChat implements internationalization i18n using a TypeScript system with locale registry, fallback merging, browser language detection, and localStorage.

- Repository: [NextChat/NextChat](https://github.com/ChatGPTNextWeb/NextChat)
- Tags: deep-dive
- Published: 2026-02-28

---

**NextChat implements internationalization through a lightweight, file-based TypeScript system that uses a central locale registry with automatic fallback merging, browser language detection, and localStorage persistence.**

The ChatGPTNextWeb/NextChat repository handles multilingual support without heavy external libraries, instead relying on a custom-built i18n layer that prioritizes type safety and runtime flexibility. This article examines the exact implementation details found in the source code, from locale definitions to UI integration.

## Locale Architecture and Type Safety

NextChat stores each language in a dedicated file under `app/locales/`, such as [`en.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/en.ts) for English or [`cn.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/cn.ts) for Chinese. Every file exports a constant that conforms to either the full `LocaleType` shape or a partial variant (`PartialLocaleType`).

The Chinese locale file ([`app/locales/cn.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/locales/cn.ts)) serves as the master type definition. TypeScript infers `LocaleType` from this file, ensuring all other translations implement the same structure:

```typescript
// app/locales/en.ts
const en: LocaleType = { /* full translation object */ };
export default en;

```

This approach guarantees compile-time verification while allowing partial translations during development.

## The Central Locale Registry

The [`app/locales/index.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/locales/index.ts) file functions as the single source of truth for all language operations. It imports every language module, constructs an `ALL_LANGS` mapping, and exposes helper functions for the rest of the application:

```typescript
import cn from "./cn";
import en from "./en";
// ... additional imports

const ALL_LANGS = { cn, en, tw, pt /* ... */ };
export type Lang = keyof typeof ALL_LANGS;
export const AllLangs = Object.keys(ALL_LANGS) as Lang[];

```

The registry also defines `ALL_LANG_OPTIONS`, a mapping of language codes to human-readable names used in the settings UI dropdown.

## Language Detection and Persistence

NextChat detects the user's preferred language through browser capabilities and persists the choice across sessions using `localStorage`.

The `getLanguage()` function utilizes `Intl.Locale` to parse `navigator.language`, extracting both region and language codes to match against supported locales:

```typescript
function getLanguage() {
  try {
    const locale = new Intl.Locale(navigator.language).maximize();
    const region = locale?.region?.toLowerCase();
    if (AllLangs.includes(region as Lang)) return region as Lang;
    if (AllLangs.includes(locale.language as Lang)) return locale.language as Lang;
    return DEFAULT_LANG;
  } catch { 
    return DEFAULT_LANG; 
  }
}

```

The system stores the selection under the `"lang"` key (`LANG_KEY`) in `localStorage`. The `getLang()` function checks this stored value first, falling back to `getLanguage()` if none exists. To switch languages programmatically, components call `changeLang()`, which updates storage and reloads the page to apply the new locale.

## Fallback Merging Strategy

To prevent missing translation keys from breaking the UI, NextChat implements a recursive merge strategy. The English locale (`en`) serves as the universal fallback.

The utility in [`app/utils/merge.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/utils/merge.ts) provides a `merge(fallbackLang, targetLang)` function that recursively copies missing fields from the fallback into the target language object:

```typescript
import { merge } from "../utils/merge";

// Merges fallback (en) into target, ensuring all keys exist
const mergedLocale = merge(fallbackLang, targetLang);

```

This guarantees that every UI string has a value, even when a translation is incomplete.

## Using Locales in React Components

Components consume the locale system by importing the default export from `app/locales`. This export resolves to the fully merged locale object for the currently active language:

```tsx
import Locale from "@/app/locales";

export function SendButton() {
  return (
    <button type="submit">
      {Locale.Chat.Send}
    </button>
  );
}

```

Because the imported `Locale` object already contains merged fallback values, components require no additional null checks or conditional logic. Complex strings with parameters, such as `Locale.Chat.SubTitle(3)`, work immediately.

For language selection interfaces, import the control functions directly:

```tsx
import { changeLang, getLang, ALL_LANG_OPTIONS } from "@/app/locales";

export function LanguageSelector() {
  const current = getLang();

  return (
    <select
      value={current}
      onChange={e => changeLang(e.target.value as any)}
    >
      {Object.entries(ALL_LANG_OPTIONS).map(([code, name]) => (
        <option key={code} value={code}>{name}</option>
      ))}
    </select>
  );
}

```

## Adding New Languages

The repository includes a contributor guide at [`docs/translation.md`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/docs/translation.md) that outlines the standardized process for adding support for new languages. The workflow involves creating a new file in `app/locales/`, exporting a `PartialLocaleType` object, and registering the language in the central index:

```typescript
// app/locales/ja.ts
import { PartialLocaleType } from "./index";

const ja: PartialLocaleType = {
  WIP: "開発中です...",
  Chat: {
    SubTitle: (count) => `${count} 件のメッセージ`,
    // Additional translations
  },
};

export default ja;

```

After creating the file, edit [`app/locales/index.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/locales/index.ts) to import the module and add entries to both `ALL_LANGS` and `ALL_LANG_OPTIONS`. The merge system automatically fills any untranslated keys from the English fallback.

## Summary

- NextChat uses a **file-based TypeScript architecture** where each language resides in `app/locales/` and implements type-safe interfaces derived from the Chinese base file.
- The **central registry** in [`app/locales/index.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/locales/index.ts) manages language imports, type exports, and UI option mappings.
- **Runtime detection** leverages `Intl.Locale` to parse browser preferences, with persistence handled through `localStorage` under the `"lang"` key.
- **Automatic fallback merging** via [`app/utils/merge.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/utils/merge.ts) ensures incomplete translations default to English strings without runtime errors.
- Components import a singleton `Locale` object that reflects the currently selected and merged language state.

## Frequently Asked Questions

### How does NextChat detect the user's language on first visit?

NextChat calls `getLanguage()` from [`app/locales/index.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/locales/index.ts), which instantiates `Intl.Locale(navigator.language).maximize()` to extract region and language codes. It checks these against the `AllLangs` array, prioritizing exact region matches (e.g., `zh-CN`) before falling back to language-only codes (e.g., `zh`) and finally defaulting to English if no match exists.

### What happens if a translation file is missing some keys?

The system imports the `merge` utility from [`app/utils/merge.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/utils/merge.ts) to recursively overlay the English locale onto the target language. Any undefined keys in the partial translation receive values from the fallback, ensuring the UI never displays undefined strings.

### Can users switch languages without restarting the application?

While the selection updates immediately in `localStorage` via `changeLang()`, the function triggers a full page reload to ensure all components re-render with the new locale imports. This design choice avoids complex state management while guaranteeing consistency across the application.

### Where is the source of truth for type definitions in the i18n system?

The [`app/locales/cn.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/locales/cn.ts) file defines the complete `LocaleType` interface implicitly through its export. All other locales either implement this full type or the `PartialLocaleType` subset, creating a single reference point for TypeScript checking across all supported languages.