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
- Cookie —
VAR_SELECTED_LOCALE(from@numa/config) stores the user’s choice - Server —
packages/i18n/request.tsreads the cookie and falls back to the default locale - Client —
GLocaleSelectorreads 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 messagesApp 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
- Add the locale code to
TLocaleinpackages/config/i18n/locales.ts - Add an entry to
LOCALE_OPTIONS - Create
packages/i18n/messages/<locale>.jsonmirroringen.jsonstructure - Update
packages/i18n/load-messages.tsto import the new locale
Last updated on