

Most i18n examples stop at swapping strings. This starter covers what comes after: right-to-left layouts, dates and numbers that follow the reader’s locale, and search engines that can find every language version of a page.
What’s in it
- English, Arabic, Chinese, Spanish, and Japanese, with English served at
/and the others under/{locale}. - Automatic
dir="rtl"for Arabic, plus anOmitRTLhelper for pieces that must stay left-to-right, like code and phone numbers. - Currency, dates, compact numbers, and relative time formatted per locale through next-intl.
- Canonical URLs, hreflang alternates, a sitemap, robots rules, JSON-LD, and an OG image for every locale.
- Light and dark themes read from a cookie on the server, so the first paint uses the right theme.
- A ten-minute guide that traces one request end to end, with exercises that break things on purpose so you can see what the checks catch.
- Agent skills that teach coding assistants the project’s conventions.
Five things that must agree
Every i18n bug is one of these five disagreeing with another, so the starter keeps each in one place:
| Thing | Where it lives | How it goes wrong |
|---|---|---|
| The URL | routing.ts, proxy.ts | Japanese pages live at /jp while the language code is ja |
| The page language | <html lang dir> in the locale layout | Arabic renders left to right |
| The text | dictionary/*.json | A key exists in English and is missing in Arabic |
| The navigation | i18n/navigation.ts | A link drops the /ja prefix |
| The formatting | request.ts, regional.ts | A price shows dollars on the Spanish page |
Checks catch the rest. bun run i18n:check validates the locale registry and confirms every dictionary mirrors English: keys, ICU arguments, rich-text tags, and plural branches. bun run seo:verify checks hreflang reciprocity, canonicals, lang, JSON-LD, and sitemap agreement on a running site.
Decisions worth noting
The locale comes from next/root-params. Earlier versions called setRequestLocale in every page. next-intl deprecated it, and reading the locale from the root params removed that boilerplate from every route.
A nonce-based Content Security Policy. The proxy (Next.js 16’s replacement for middleware) generates a fresh nonce for each request, so inline scripts are allowed only when the server put them there.
No automatic locale detection. Cookies and the browser’s language never redirect the default URL, so / always serves English and every language has one stable address for search engines.
shadcn/ui on Base UI. The components moved from Radix to Base UI in the base-nova style. Base UI reads text direction from a provider, so menus and tabs follow the locale’s direction.
A UI audit for RTL and CJK. A dedicated pass fixed accessibility issues and typography problems in Arabic, Chinese, and Japanese that Latin-first defaults miss.
Status
Maintained since May 2024 and kept current with Next.js releases; it runs on Next.js 16.3.8.
Built with Next.js 16, React 19, next-intl 4, TypeScript, Tailwind CSS 4, shadcn/ui.