Internationalization (i18n) & Localized Routing
HiTech Insurance serves a diverse demographic across Texas, California, and national commercial trucking corridors. To deliver an equitable, native digital experience, the platform enforces strict bilingual parity across English (en) and Spanish (es) using next-intl (v4.8.3) and Next.js 15 App Router.
1. Routing Configuration & Navigation API
Localized routing is configured in src/i18n/routing.ts using defineRouting:
import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
locales: ["en", "es"],
defaultLocale: "en",
localePrefix: "always", // Enforces /en/... or /es/... across all public routes
});
To prevent broken links or missing locale prefixes when navigating client-side, the application exposes wrapped navigation primitives via src/i18n/navigation.ts:
import { createNavigation } from "next-intl/navigation";
import { routing } from "@/i18n/routing";
export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing);
Every internal link in components imports Link from @/i18n/navigation rather than next/link. This automatically prepends the active locale without requiring manual string concatenation.
2. Dynamic Parallel Catalog Loader (request.ts)
Instead of maintaining a single massive 50,000-line translation file that degrades memory and slows cold starts, HiTech splits translation keys across 34 domain-scoped JSON catalogs:
src/messages/
├── en/
│ ├── about.json ├── insuranceMenu.json
│ ├── aboutUsMenu.json ├── insurancePlatform.json
│ ├── application.json ├── location.json
│ ├── auth.json ├── location-detail.json
│ ├── autoInsurance.json ├── personalInfo.json
│ ├── blog.json ├── policy.json
│ ├── california_notice.json ├── privacyAppeal.json
│ ├── career.json ├── privacyIntake.json
│ ├── cards.json ├── refer.json
│ ├── claims.json ├── referDashboard.json
│ ├── contact.json ├── scheduleACall.json
│ ├── coverage.json ├── smsPolicy.json
│ ├── coverages.json ├── testimonial.json
│ ├── faq.json ├── trust.json
│ ├── footer.json ├── work.json
│ ├── getAQuote.json └── ...
└── es/
└── (Exact 1:1 mirroring of all 34 catalog files)
High-Performance In-Memory Merging
In src/i18n/request.ts, catalogs are loaded concurrently using Promise.all and cached in a module-scoped Map to guarantee sub-millisecond retrieval on repeat requests:
type Messages = Record<string, any>;
const messagesCache = new Map<string, Messages>();
async function loadLocaleMessages(locale: string): Promise<Messages> {
const cached = messagesCache.get(locale);
if (cached) {
return cached;
}
const [
homeMessages, cardMessages, trustMessages, coverageMessages,
workMessages, testimonialMessages, faqMessages, applicationMessages,
footerMessages, claimMessages, contactMessages, locationMessages,
aboutMessages, smsMessages, californiaMessages, careerMessages,
referMessages, personalMessages, privacyIntakeMessages, privacyAppealMessages,
blogMessages, policyMessages, autoInsuranceMessages, locationDetailMessages,
insuranceMenuMessages, insuranceDetailMessages, aboutUsMenuMessages,
authMessages, scheduleACallMessages, getAQuoteMessages, insuranceFieldsMessages,
referDashboardMessages, coveragesMessages, insurancePlatformMessages,
] = await Promise.all([
import(`../messages/${locale}/home.json`).then((m) => m.default),
import(`../messages/${locale}/cards.json`).then((m) => m.default),
// ... all 34 catalogs imported concurrently
]);
const merged = {
...homeMessages,
...cardMessages,
...trustMessages,
// ... merged into single flat namespace
};
messagesCache.set(locale, merged);
return merged;
}
3. Server & Client Usage Patterns
In React Server Components (RSC)
Server Components retrieve localized strings using getTranslations:
import { getTranslations } from "next-intl/server";
export default async function InsuranceHero({ locale }: { locale: string }) {
const t = await getTranslations({ locale, namespace: "autoInsurance" });
return (
<section>
<h1>{t("heroTitle")}</h1>
<p>{t("heroSubtitle")}</p>
</section>
);
}
In Client Components
Interactive components utilize the useTranslations hook:
"use client";
import { useTranslations } from "next-intl";
export function QuoteButton() {
const t = useTranslations("getAQuote");
return <button>{t("submitButton")}</button>;
}
4. Language Switcher & State Preservation
The application header features an accessible language toggle component (src/components/common/language-toggle.tsx).
When a visitor switches from English to Spanish on a deep route (e.g. /en/insurance/truck-insurance?quoteId=HT-9821):
- The component reads the current pathname using
usePathname(). - It strips the
/enprefix and swaps it with/es. - It preserves all query parameters (
?quoteId=...). - It performs a soft transition via
router.replace(), updating the document language attribute (<html lang="es">) and re-rendering with Spanish copy instantly.