Skip to Content
Internationalization

Internationalization (i18n)

Numa uses next-intl  v4 for internationalization. The active locale is stored in a cookie and applied to every [lang] route.

Supported locales

Configured in packages/config/i18n/locales.ts:

export type TLocale = "en" | "rw"; export const LOCALE_OPTIONS = [ { name: "English", flag: "🇺🇸", value: "en" }, { name: "Ikinyarwanda", flag: "🇷🇼", value: "rw" }, ];

How locale is resolved

  1. CookieVAR_SELECTED_LOCALE (from @numa/config) stores the user’s choice
  2. Serverpackages/i18n/request.ts reads the cookie and falls back to the default locale
  3. ClientGLocaleSelector reads the same cookie and syncs the language selector

Package structure

packages/i18n/ ├── request.ts # getRequestConfig — loads messages for locale ├── server.ts # Re-exports getLocale, getMessages, setRequestLocale ├── next-intl.ts # Client wrapper for next-intl hooks ├── plugin.ts # Re-exports createNextIntlPlugin ├── load-messages.ts # Client-side locale change helper ├── energy-select-options.ts # Translated select options ├── messages/ │ └── en.json # English translation messages └── global.d.ts # TypeScript type declarations for messages

App wiring

Each app has a thin re-export at app/i18n/request.ts:

export { default } from "@numa/i18n/request";

The Next.js config uses the i18n plugin:

import createNextIntlPlugin from "@numa/i18n/plugin"; const withNextIntl = createNextIntlPlugin("./app/i18n/request.ts"); export default withNextIntl(nextConfig);

Layout integration

In [lang]/layout.tsx, locale and messages are passed to providers:

import { setRequestLocale, getMessages } from "@numa/i18n/server"; export default async function LangLayout({ children, params }) { const { lang } = await params; setRequestLocale(lang); const messages = await getMessages(); return ( <GProviders locale={lang} messages={messages}> {children} </GProviders> ); }

Using translations

Client components

import { useTranslations } from "next-intl"; function MyComponent() { const t = useTranslations("forms.login"); return <h1>{t("title")}</h1>; }

Server components

import { getTranslations } from "next-intl/server"; export default async function Page() { const t = await getTranslations("dashboard"); return <h1>{t("welcome")}</h1>; }

Message file structure

Messages live in packages/i18n/messages/<locale>.json:

{ "forms": { "login": { "title": "Login", "desc": "Welcome back" } }, "errors": { "noEssentials": "Unable to load user information" } }

Type safety

packages/i18n/global.d.ts provides TypeScript autocomplete for translation keys:

import en from "./messages/en.json"; type Messages = typeof en; declare module "next-intl" { interface AppConfig { Messages: Messages; } }

If you change the source locale, update the import in global.d.ts accordingly.

Adding a new language

  1. Add the locale code to TLocale in packages/config/i18n/locales.ts
  2. Add an entry to LOCALE_OPTIONS
  3. Create packages/i18n/messages/<locale>.json mirroring en.json structure
  4. Update packages/i18n/load-messages.ts to import the new locale
Last updated on