# Internationalization (i18n)
Locale routing, RTL support, and currency/date/number formatting for Next.js 14 using next-intl 3.x — ships with message files for English, French, German, Hindi, and Arabic.
## What's included
**Config & types**
- `SUPPORTED_LOCALES` — `['en', 'fr', 'de', 'hi', 'ar']` as a const tuple
- `SupportedLocale` — union type derived from the above
- `DEFAULT_LOCALE` — `'en'`
- `LOCALE_LABELS` — display names per locale (e.g. `hi → 'हिन्दी'`)
- `LOCALE_FLAGS` — emoji flags per locale
- `RTL_LOCALES` — locales that need `dir="rtl"` (currently `['ar']`)
- `isRTL(locale)` — returns boolean
- `isSupportedLocale(locale)` — type guard
**Formatting**
- `formatPrice(amountUSD, locale, currency?, usdToInrRate?)` — auto-converts to INR for `hi` locale; uses `Intl.NumberFormat` with no decimals
- `formatDate(date, locale, style?)` — locale-aware date; `style` defaults to `'medium'`
- `formatTime(date, locale)` — short time string
- `formatNumber(n, locale)` — locale-aware number (thousands separators, etc.)
- `formatRelativeTime(date, locale)` — "2 hours ago" / "in 3 days" via `Intl.RelativeTimeFormat`
**Message files**
- `EN_MESSAGES`, `FR_MESSAGES` — typed message objects covering `nav`, `hero`, `blocks`, `purchase`, `dashboard`, `affiliate`, `errors`, `common` — use these as the content for `messages/en.json` and `messages/fr.json`
**Config strings**
- `I18N_CONFIG_FILE` — ready-to-paste content for `i18n.ts` at project root
- `I18N_MIDDLEWARE` — ready-to-paste content for `middleware.ts`
- `LANGUAGE_SWITCHER_CODE` — full source for a `LanguageSwitcher` client component (copy to `components/LanguageSwitcher.tsx`)
## Setup
### 1. Install dependencies
```bash
npm install next-intl
```
### 2. Create `i18n.ts` at project root
Paste the contents of `I18N_CONFIG_FILE` exported from this block.
### 3. Update `next.config.js`
```js
const withNextIntl = require('next-intl/plugin')('./i18n.ts')
module.exports = withNextIntl({ /* your existing config */ })
```
### 4. Create message files
```bash
mkdir messages
```
Then create `messages/en.json` with the contents of `EN_MESSAGES`, `messages/fr.json` with `FR_MESSAGES`, and so on. For `de`, `hi`, `ar` — translate from `EN_MESSAGES` or use your own strings; the type shape is `typeof EN_MESSAGES`.
### 5. Add middleware
Paste `I18N_MIDDLEWARE` into your `middleware.ts`. If you already have middleware, merge the matcher config and wrap with `createMiddleware`.
### 6. Move `app/` to `app/[locale]/`
```
app/
[locale]/
layout.tsx
page.tsx
...
```
next-intl handles the routing from here.
### 7. Add RTL support in `app/[locale]/layout.tsx`
```tsx
import { isRTL } from '@/blocks/i18n'
export default function LocaleLayout({ children, params: { locale } }) {
return (
<html lang={locale} dir={isRTL(locale) ? 'rtl' : 'ltr'}>
<body>{children}</body>
</html>
)
}
```
## Usage examples
```tsx
// Translations in a server component
import { useTranslations } from 'next-intl'
export default function Nav() {
const t = useTranslations('nav')
return <a href="/blocks">{t('blocks')}</a>
}
```
```ts
// Formatting
import { formatPrice, formatDate, formatRelativeTime } from '@/blocks/i18n'
formatPrice(19, 'en') // $19
formatPrice(19, 'hi') // ₹1,596 (auto-converts at 84x)
formatDate(new Date(), 'fr') // 16 mars 2026
formatRelativeTime('2026-03-18', 'de') // gestern
```
```tsx
// Drop in the language switcher
// 1. Copy LANGUAGE_SWITCHER_CODE to components/LanguageSwitcher.tsx
// 2. Use it:
import { LanguageSwitcher } from '@/components/LanguageSwitcher'
<nav>
<LanguageSwitcher />
</nav>
```
## Notes
- `formatPrice` uses a hardcoded `usdToInrRate` of `84` by default — pass your env var value at call time (`formatPrice(price, locale, 'USD', Number(process.env.USD_TO_INR_RATE))`) if you want it dynamic
- `I18N_CONFIG_FILE` and `I18N_MIDDLEWARE` are exported as strings, not modules — copy-paste them into actual `.ts` files; do not import and re-export them
- German (`de`) and Arabic (`ar`) message files are not provided — `EN_MESSAGES` defines the required shape; TypeScript will enforce it via `typeof EN_MESSAGES`
- `localePrefix: 'as-needed'` means English URLs have no prefix (`/blocks`) while others do (`/fr/blocks`) — change to `'always'` in the middleware config if you want `/en/blocks` too