Internationalization (i18n)
Dictionary System
The app supports two locales: us and uk. Translations are stored as flat JSON files:
src/dictionaries/us.jsonsrc/dictionaries/uk.json
Each dictionary is a flat Record<string, string> (no nesting). Keys include things like app store URLs, locale-specific copy, and feature labels.
Loading Dictionaries
Dictionaries are loaded server-side via getDictionary() in src/app/[locale]/dictionaries.ts:
import "server-only";
const dictionaries = {
us: () => import("@/dictionaries/us.json").then((module) => module.default),
uk: () => import("@/dictionaries/uk.json").then((module) => module.default),
};
export const getDictionary = async (locale: "us" | "uk") =>
dictionaries[locale]();The "server-only" import ensures this module cannot be imported from client components, preventing the dictionary JSON from being bundled into client-side JavaScript.
How Dictionaries Flow Through Components
src/app/[locale]/layout.tsxloads the dictionary and passes it toSmartHeaderLayout:
const dictionary = await getDictionary(validatedLocale);
return <SmartHeaderLayout dictionary={dictionary}>{children}</SmartHeaderLayout>;SmartHeaderLayoutpasses the dictionary toHeaderLayoutHeaderLayoutpasses it toNavigation,FooterLayout, and anymainSection/customCtathat accepts it as a render prop:
mainSection: ({ dictionary }: { dictionary?: Record<string, string> }) => {
return <HomeHeader dictionary={dictionary || {}} />;
},Dictionaries are passed as props through the component tree — there is no React Context provider for i18n.
Locale Detection in Client Components
Client components cannot access the [locale] route param directly. Instead, they derive the locale from the current pathname:
import { getCurrentLocale } from "@/lib/utils/locale";
import { usePathname } from "next/navigation";
const pathname = usePathname();
const locale = getCurrentLocale(pathname);Locale-Aware Links
When constructing links, use getLocaleAwareHref() from src/lib/utils.ts to prepend the correct locale prefix:
import { getLocaleAwareHref } from "@/lib/utils";
const href = getLocaleAwareHref("/about", locale);
// Returns "/en-us/about" or "/en-gb/about"Region-Specific Content
Route-Level Restrictions
Routes can be restricted to specific regions via the allowedRegions field in the route config:
"/bills": {
allowedRegions: ["uk"], // Only accessible in UK
},
"/colleges": {
allowedRegions: ["us"], // Only accessible in US
},SmartHeaderLayout enforces this by redirecting users who access a region-restricted page from the wrong locale.
Content-Level Filtering
Blog posts from Contentstack have a region field ("us", "uk", or "both") that is used to filter content per locale:
posts.filter(
(p) => !p.region || p.region === "both" || p.region === locale,
);Locale-Dependent Titles
Route config titles can be functions that receive the locale for conditional text:
"/roomie": {
title: ({ locale }) =>
`Find your perfect ${locale === "uk" ? "housemates" : "roommates"} on Housr`,
},