Internationalization Usage
Nexty.dev leverages next-intl
to provide comprehensive multilingual support, delivering a robust internationalization solution that includes route-level language switching, automatic language detection, and seamless locale management.
This guide covers the fundamentals of next-intl
usage, demonstrates how to add new languages and translation content, and explores the advanced internationalization capabilities that Nexty provides.
File Structure
i18n/
├── messages/ # Translation files directory
│ ├── en/ # English translations
│ │ └── common.json
│ ├── zh/ # Chinese translations
│ │ └── common.json
│ └── ja/ # Japanese translations
│ └── common.json
├── request.ts # next-intl request configuration
└── routing.ts # Routing and language configuration
components/
├── LocaleSwitcher.tsx # Language switcher component
└── LanguageDetectionAlert.tsx # Language detection alert component
stores/
└── localeStore.ts # Language state management
middleware.ts # Middleware configuration
next.config.mjs # Next.js configuration
Basic Usage
Using Translations in Components
In React components, use the useLocale
hook to retrieve the current locale and the useTranslations
hook to access translation content.
import { useLocale, useTranslations } from 'next-intl';
export default function MyComponent() {
const locale = useLocale();
const t = useTranslations('Home');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
Good to know
In APP Router mode,
useLocale
anduseTranslations
work in both client and server components. For server components, simply avoid using theasync
keyword in the component definition to ensure these hooks function properly while maintaining server-side rendering.
Using in Server Components
For async
server components, use getLocale
to retrieve the current locale and getTranslations
to access translation content.
import { getLocale, getTranslations } from 'next-intl/server';
export default async function ServerComponent() {
const locale = await getLocale();
const t = await getTranslations('Home');
return (
<div>
<h1>{t('title')}</h1>
</div>
);
}
Using Parameterized Translations
In JSON file:
{
"welcome": "Welcome, {name}!",
"countdown": "Closing in {countdown} seconds"
}
In component:
const message = t('welcome', { name: 'John' });
const timer = t('countdown', { countdown: countdown.toString() });
Using Raw Data
For complex data structures like arrays or objects, use the t.raw()
method to access the raw translation data:
const headerLinks = t.raw('Header.links') as HeaderLink[];
const features = t.raw('Landing.Features.items');
Internationalized Link Component
Use the Link
component exposed from @/i18n/routing
. The href
prop doesn't require language prefixes, as next-intl
will automatically handle locale routing based on the current language.
import { Link as I18nLink } from '@/i18n/routing';
<I18nLink href="/about">
{t('aboutUs')}
</I18nLink>
Internationalized useRouter
Use the useRouter
hook from @/i18n/routing
for programmatic navigation. Like the Link component, routes don't need language prefixes as next-intl
automatically handles locale routing.
import { useRouter } from '@/i18n/routing';
const router = useRouter();
const handleClick = () => {
router.push('/dashboard');
};
Steps to Add New Languages
Step 1. Update Language Configuration
Add the new language to your locale configuration in i18n/routing.ts
:
export const LOCALES = ['en', 'zh', 'ja', 'ko'] // Add Korean
export const DEFAULT_LOCALE = 'en'
export const LOCALE_NAMES: Record<string, string> = {
'en': "English",
'zh': "中文",
'ja': "日本語",
'ko': "한국어", // Add Korean display name
};
Step 2. Create Translation Files
Create a new directory for the language and copy existing translation files as a template:
mkdir i18n/messages/ko
cp i18n/messages/en/common.json i18n/messages/ko/common.json
Step 3. Translate Content
Translate all content in the new language files. Edit i18n/messages/ko/common.json
and replace English content with Korean translations.
Step 4. Update Middleware Configuration
Update the middleware matcher to include the new locale:
export const config = {
matcher: [
'/',
'/(en|zh|ja|ko)/:path*', // Add Korean
'/((?!api|_next|_vercel|auth|.*\\.|favicon.ico).*)'
]
};
Step 5. Update Route Redirects (Optional)
If needed, add corresponding redirect rules in next.config.mjs
:
{
source: "/ko/dashboard",
destination: "/ko/dashboard/settings",
permanent: true,
}
Good to know
To remove a language, reverse these steps: remove the locale from configuration, delete translation files, and update the middleware matcher.
Adding New Translation Namespaces
When adding new pages or features, it's recommended to create separate JSON files to organize translations into logical namespaces.
For example, alongside the existing common.json
, you can add a new Landing.json
for landing page translations:
i18n/messages/en/
├── common.json
├── Landing.json # New Landing addition
Update the Request Configuration:
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale;
}
const common = (await import(`./messages/${locale}/common.json`)).default;
const Landing = (await import(`./messages/${locale}/Landing.json`)).default; // Import Landing
return {
locale,
messages: {
...common,
Landing, // Import Landing
}
};
});
Using the new namespace
const t = useTranslations('Landing');
SSG (Static Site Generation) Best Practices
Good to know
- Before v2.3.0, dynamic methods in the layout prevented true SSG implementation. Pages were still server-side rendered (SSR), which maintained SEO benefits but didn't provide static generation performance gains.
- For v2.3.0 and later, please follow the implementation approach below for SSG.
For static content pages, such as blog detail pages, Privacy Policy, and Terms of Service pages, it's recommended to use SSG to reduce server CPU consumption and improve page loading performance.
Scenario 1: Pages with locale as the only dynamic parameter
Applicable pages: URLs like /privacy-policy
or /zh/privacy-policy
, where the only dynamic parameter is the locale prefix.
Implementation approach:
- Extract the
locale
parameter fromparams
(avoid usinggetLocale()
for SSG compatibility) - Implement
generateStaticParams
to pre-generate pages for all supported locales
export default async function Page({ params }: { params: Params }) {
// ✅ Extract locale from params for SSG compatibility
// ❌ Don't use getLocale() as it prevents static generation
const { locale } = await params;
// ... other code ...
return (
// ... page content ...
);
}
export async function generateStaticParams() {
return LOCALES.map((locale) => ({
locale,
}));
}
Scenario 2: Pages with multi-level dynamic routes
Applicable pages: URLs like /blogs/nexty-dev-stand-out
or /zh/blogs/nexty-dev-stand-out
, where dynamic parameters include both language prefix and content identifier (such as blog slugs).
Implementation approach: Follow the same principles, but generateStaticParams
must handle more complex parameter combinations.
export default async function BlogPage({ params }: { params: Params }) {
const { locale, slug } = await params;
// ... other code ...
return (
// ... page content ...
);
}
export async function generateStaticParams() {
// Generate all possible combinations of locale and slug
const allParams: { locale: string; slug: string }[] = [];
for (const locale of LOCALES) {
// Fetch all blog slugs for the current locale from your data source
const blogSlugs = await getBlogSlugs(locale); // Your data fetching logic
for (const slug of blogSlugs) {
allParams.push({ locale, slug });
}
}
return allParams;
}
Verifying SSG Implementation
After running npm run build
, verify that static HTML files are generated in the .next/server/app/en
directory:

The presence of .html
files confirms that your pages are being statically generated at build time.
Learn more: Check out the Next.js documentation on dynamic routes for additional details.
Nexty's Advanced Features
Automatic Language Detection and User Prompts
The system automatically detects the user's browser language on the user's first visit. If the detected language differs from the current website language, a language switch prompt is displayed with the following behavior:
- Auto-dismiss: Automatically closes after 10 seconds
- Manual control: Users can dismiss the prompt manually
- Persistent preference: Dismissal state is saved for 30 days using cookies
SEO Optimization
Each language version generates independent URLs and localized metadata to ensure optimal search engine indexing:
import { constructMetadata } from "@/lib/metadata";
export async function generateMetadata({ params }: MetadataProps): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Home" });
return constructMetadata({
page: "Home",
title: t("title"),
description: t("description"),
locale: locale as Locale,
path: `/`,
});
}