| 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