| name | nexty-i18n |
| description | Handle internationalization in NEXTY.DEV using next-intl. Use when adding translatable text, creating locale-aware pages, or formatting dates/numbers. Covers translation hooks, message files, and routing. |
Internationalization in NEXTY.DEV
Overview
- Library: next-intl
- Routing Config:
i18n/routing.ts
- Request Config:
i18n/request.ts
- Messages:
i18n/messages/{locale}/
Supported Locales
Configured in i18n/routing.ts:
export const LOCALES = ['en', 'zh', 'ja']
export const DEFAULT_LOCALE = 'en'
export const LOCALE_NAMES: Record<string, string> = {
'en': "English",
'zh': "ไธญๆ",
'ja': "ๆฅๆฌ่ช",
};
Message File Structure
Messages are organized by locale folder, with separate JSON files per page/feature:
i18n/messages/
โโโ en/
โ โโโ common.json # Shared translations (spread at root)
โ โโโ Landing.json # Landing page
โ โโโ Pricing.json # Pricing page
โ โโโ NotFound.json # 404 page
โ โโโ Glossary.json # Glossary page
โ โโโ Dashboard/
โ โโโ Admin/
โ โ โโโ Overview.json
โ โ โโโ Users.json
โ โ โโโ Orders.json
โ โ โโโ Prices.json
โ โ โโโ Blogs.json
โ โ โโโ Glossary.json
โ โ โโโ R2Files.json
โ โโโ User/
โ โโโ Settings.json
โ โโโ CreditHistory.json
โโโ zh/
โ โโโ (same structure)
โโโ ja/
โโโ (same structure)
Adding New Translations
1. Create/Edit Message Files
For a new feature, create JSON files in each locale folder:
{
"title": "My Feature",
"description": "This is my new feature",
"buttons": {
"save": "Save",
"cancel": "Cancel"
},
"messages": {
"success": "Operation completed successfully",
"error": "Something went wrong"
}
}
{
"title": "ๆ็ๅ่ฝ",
"description": "่ฟๆฏๆ็ๆฐๅ่ฝ",
"buttons": {
"save": "ไฟๅญ",
"cancel": "ๅๆถ"
},
"messages": {
"success": "ๆไฝๆๅๅฎๆ",
"error": "ๅบไบ็น้ฎ้ข"
}
}
{
"title": "ใใคๆฉ่ฝ",
"description": "ใใใฏ็งใฎๆฐๆฉ่ฝใงใ",
"buttons": {
"save": "ไฟๅญ",
"cancel": "ใญใฃใณใปใซ"
},
"messages": {
"success": "ๆไฝใๆญฃๅธธใซๅฎไบใใพใใ",
"error": "ๅ้กใ็บ็ใใพใใ"
}
}
2. Register in request.ts
Add the new message file to i18n/request.ts:
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
return {
locale,
messages: {
Landing: (await import(`./messages/${locale}/Landing.json`)).default,
MyFeature: (await import(`./messages/${locale}/MyFeature.json`)).default,
...common
}
};
});
3. Use in Server Components
import { getTranslations } from 'next-intl/server';
export default async function MyPage() {
const t = await getTranslations('MyFeature');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
<button>{t('buttons.save')}</button>
</div>
);
}
4. Use in Client Components
'use client';
import { useTranslations } from 'next-intl';
export function MyClientComponent() {
const t = useTranslations('MyFeature');
return (
<div>
<h1>{t('title')}</h1>
<button>{t('buttons.save')}</button>
</div>
);
}
Common Translations
Translations in common.json are spread at the root level, accessed without namespace:
const t = await getTranslations('Home');
const loginT = await getTranslations('Login');
const headerT = await getTranslations('Header');
Dynamic Values (Interpolation)
{
"greeting": "Hello, {name}!",
"itemCount": "You have {count} items"
}
t('greeting', { name: user.name })
t('itemCount', { count: items.length })
Pluralization
{
"items": "{count, plural, =0 {No items} one {# item} other {# items}}"
}
t('items', { count: 0 })
t('items', { count: 1 })
t('items', { count: 5 })
Date and Number Formatting
Server Components
import { getFormatter } from 'next-intl/server';
export default async function MyPage() {
const format = await getFormatter();
return (
<div>
<p>{format.dateTime(new Date(), { dateStyle: 'long' })}</p>
<p>{format.number(1234.56, { style: 'currency', currency: 'USD' })}</p>
<p>{format.relativeTime(new Date('2024-01-01'))}</p>
</div>
);
}
Client Components
'use client';
import { useFormatter } from 'next-intl';
export function MyClientComponent() {
const format = useFormatter();
return (
<div>
<p>{format.dateTime(new Date(), { dateStyle: 'long' })}</p>
<p>{format.number(1234.56, { style: 'currency', currency: 'USD' })}</p>
</div>
);
}
Locale-Aware Navigation
Use navigation utilities from i18n/routing.ts:
import { Link, redirect, usePathname, useRouter } from '@/i18n/routing';
<Link href="/dashboard">Dashboard</Link>
<Link href="/dashboard" locale="zh">Dashboard (Chinese)</Link>
redirect('/dashboard');
const router = useRouter();
router.push('/dashboard');
Get Current Locale
Server Components
import { getLocale } from 'next-intl/server';
export default async function MyPage() {
const locale = await getLocale();
}
Client Components
'use client';
import { useLocale } from 'next-intl';
export function MyComponent() {
const locale = useLocale();
}
Page Metadata with i18n
import { constructMetadata } from '@/lib/metadata';
import { Metadata } from 'next';
import { Locale } from '@/i18n/routing';
import { getTranslations } from 'next-intl/server';
type Params = Promise<{ locale: string }>;
export async function generateMetadata({
params,
}: { params: Params }): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'MyFeature' });
return constructMetadata({
page: 'MyFeature',
title: t('title'),
description: t('description'),
locale: locale as Locale,
path: '/my-feature',
});
}
Language Switcher
Use the existing LocaleSwitcher component:
import { LocaleSwitcher } from '@/components/LocaleSwitcher';
<LocaleSwitcher />
Routing Configuration
Key settings in i18n/routing.ts:
export const routing = defineRouting({
locales: LOCALES,
defaultLocale: DEFAULT_LOCALE,
localeDetection: process.env.NEXT_PUBLIC_LOCALE_DETECTION === 'true',
localePrefix: 'as-needed',
});
Checklist
- Create JSON message files in all locale folders (
en/, zh/, ja/)
- Register new message files in
i18n/request.ts
- Use
getTranslations in Server Components
- Use
useTranslations in Client Components
- Use
Link from @/i18n/routing for navigation
- Use formatters for dates/numbers/currencies
- Update metadata with translated title/description
- Test in all supported locales