How to Configure Internationalization (i18n) in OmniRoute with 42 Locales Using next-intl

TLDR: OmniRoute provides a complete next-intl integration that reads 42 locales from config/i18n.json, exports them as typed constants in src/i18n/config.ts, and wires everything together via src/i18n/request.ts and src/app/layout.tsx with zero hard-coding.

OmniRoute ships with a production-ready internationalization setup powered by next-intl. This open-source routing solution manages 42 languages through a single JSON catalogue, eliminating the need to hard-code locale lists in TypeScript. By centralizing configuration in config/i18n.json, the system automatically generates type-safe constants and handles RTL detection, cookie persistence, and message loading.

Understanding the OmniRoute i18n Architecture

OmniRoute separates concerns between data (the JSON catalogue), types (the TypeScript config), and runtime (the request loader and provider). This separation allows you to add or remove languages without touching application code.

The Locale Catalogue at config/i18n.json

The source of truth lives in config/i18n.json. This file defines the default locale, an rtl array for right-to-left languages, and the full list of 42 supported locales. Each locale object includes metadata fields such as code, label, name, native, english, and flag.

OmniRoute never hard-codes the locale list; it reads this file at build time. When you modify the JSON array, the TypeScript definitions update automatically on the next build.

Type-Safe Configuration in src/i18n/config.ts

The src/i18n/config.ts file transforms the JSON catalogue into typed exports. The LOCALES constant is generated by mapping the JSON array (config.locales.map(l => l.code)), ensuring the array remains synchronized with the catalogue. This file also exports:

  • DEFAULT_LOCALE – derived from the JSON default value
  • RTL_LOCALES – filtered from the rtl array in the JSON
  • LOCALE_COOKIE – set to "NEXT_LOCALE" for cookie-based persistence

Because these values are built from config/i18n.json, importing LOCALES anywhere in the codebase guarantees type safety and completeness.

Configuring the Server-Side Request Loader

The src/i18n/request.ts file implements getRequestConfig from next-intl/server. This loader determines the active locale by checking the URL, the NEXT_LOCALE cookie, or headers, falling back to DEFAULT_LOCALE if no match is found.

// src/i18n/request.ts
import { getRequestConfig } from "next-intl/server";
import { LOCALES, DEFAULT_LOCALE } from "./config";

export default getRequestConfig({
  locales: LOCALES,
  defaultLocale: DEFAULT_LOCALE,
  // Messages are auto-imported from src/i18n/messages/*-locale.json
});

When a request hits the server, this configuration loads the corresponding translation file from src/i18n/messages/ based on the resolved locale.

Implementing the Client-Side Provider

In src/app/layout.tsx, the root layout wraps the application in <NextIntlClientProvider>. This component receives the locale and messages from async server functions, making translations available to all client components.

// src/app/layout.tsx
import { NextIntlClientProvider } from "next-intl";
import { getMessages, getLocale } from "next-intl/server";

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const locale = await getLocale();          // Respects LOCALE_COOKIE
  const messages = await getMessages();      // Loads the JSON for `locale`

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

This setup supplies the useTranslations and useLocale hooks to every component in the tree, including shared components like src/shared/components/Header.tsx.

Adding or Removing Locales in the 42-Language Setup

To add a new language—such as Korean—you only need to edit config/i18n.json. Append the new locale object to the locales array:

// config/i18n.json (excerpt)
{
  "default": "en",
  "rtl": [],
  "locales": [
    {
      "code": "ko",
      "label": "Korean",
      "name": "Korean",
      "native": "한국어",
      "english": "Korean",
      "flag": "🇰🇷"
    }
  ]
}

After saving the file and running npm run build, the new ko entry appears in the LOCALES array exported by src/i18n/config.ts (line 26). No TypeScript changes are required. To remove a locale, simply delete its entry from the JSON file.

Consuming Translations in Components

Components access the 42-locale catalogue and current translation context through next-intl hooks. Import LOCALES from src/i18n/config.ts to render language selectors or locale-aware navigation.

import { useLocale, useTranslations } from "next-intl";
import { LOCALES } from "@/i18n/config";

export default function LanguageSelector() {
  const t = useTranslations("language-selector");
  const locale = useLocale();

  return (
    <select value={locale}>
      {LOCALES.map((code) => (
        <option key={code} value={code}>
          {t(`locale.${code}`)}
        </option>
      ))}
    </select>
  );
}

The useLocale() hook returns the active locale code, while useTranslations() accesses namespaced message keys from the loaded JSON files.

Summary

  • OmniRoute stores the 42-locale catalogue in config/i18n.json, making it the single source of truth for all language metadata.
  • src/i18n/config.ts generates type-safe exports (LOCALES, DEFAULT_LOCALE, RTL_LOCALES) by reading the JSON file at build time.
  • The server-side loader in src/i18n/request.ts uses getRequestConfig to resolve locales and load messages automatically.
  • src/app/layout.tsx wraps the app in <NextIntlClientProvider> to supply translation hooks to client components.
  • Adding or removing languages requires only editing config/i18n.json; no TypeScript code changes are necessary.

Frequently Asked Questions

How many locales does OmniRoute support by default?

OmniRoute ships with 42 locales configured in config/i18n.json. This includes major world languages and regional variants, all defined with metadata like native names and flag emojis. You can expand or reduce this list by editing the JSON file without modifying TypeScript code.

Where does OmniRoute store translation messages?

Translation files live in src/i18n/messages/ as individual JSON files named by locale code (e.g., en.json, pt-BR.json). The getRequestConfig loader in src/i18n/request.ts automatically imports the correct file based on the resolved locale, while getMessages() in src/app/layout.tsx makes them available to the provider.

Do I need to modify TypeScript files when adding a new language?

No. Because src/i18n/config.ts generates the LOCALES array dynamically from config/i18n.json using config.locales.map(l => l.code), adding a new locale to the JSON file automatically updates the typed exports on the next build. This zero-code workflow prevents synchronization errors between configuration and implementation.

How does OmniRoute handle RTL (Right-to-Left) languages?

The rtl array in config/i18n.json defines which locales require right-to-left text direction. src/i18n/config.ts exports these as RTL_LOCALES, which components can use to conditionally apply CSS directionality or layout adjustments. The system supports Arabic, Hebrew, and other RTL languages out of the box when listed in the JSON configuration.

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 →