Introduction
Internationalization
Deployment
Advanced Usage
Advanced internationalization techniques
Advanced Internationalization Techniques
This guide covers advanced techniques for using the internationalization package in your Next.js application.
Handling Complex Translations
Nested Namespaces
You can use nested namespaces to organize your translations and access them efficiently:
// Access deeply nested translations
const { t } = useTranslation('web.products.categories.electronics');
// Now you can use shorter keys
t('title') // Instead of t('web.products.categories.electronics.title')
Rich Text Formatting
For translations that include HTML or formatting:
// In your translation file:
// {
// "welcome": "Welcome to <strong>our website</strong>!"
// }
// In your component:
'use client';
import { useTranslation } from '@repo/internationalization/useTranslation';
import parse from 'html-react-parser';
export default function WelcomeMessage() {
const { t } = useTranslation('web.home');
// Parse HTML in translation
const welcomeMessage = parse(t('welcome'));
return <div className="welcome">{welcomeMessage}</div>;
}
Be careful with HTML in translations to avoid XSS vulnerabilities. Consider using a safer alternative like Markdown.
Date and Number Formatting
Advanced Date Formatting
Use the Intl API for consistent date formatting across locales:
'use client';
import { useTranslation } from '@repo/internationalization/useTranslation';
import { useParams } from 'next/navigation';
export default function EventDate({ date }: { date: Date }) {
const { t } = useTranslation('web.events');
const { locale } = useParams();
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat(locale as string, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'short'
}).format(date);
};
return (
<div className="event-date">
<span>{t('startsAt')}</span>
<time dateTime={date.toISOString()}>{formatDate(date)}</time>
</div>
);
}
Currency Formatting
Format currency values according to the user’s locale:
'use client';
import { useTranslation } from '@repo/internationalization/useTranslation';
import { useParams } from 'next/navigation';
export default function ProductPrice({ price }: { price: number }) {
const { t } = useTranslation('web.products');
const { locale } = useParams();
// Map locale to currency code
const getCurrencyCode = (locale: string) => {
const currencyMap: Record<string, string> = {
en: 'USD',
tr: 'TRY',
es: 'EUR',
de: 'EUR',
fr: 'EUR'
};
return currencyMap[locale] || 'USD';
};
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat(locale as string, {
style: 'currency',
currency: getCurrencyCode(locale as string)
}).format(amount);
};
return (
<div className="product-price">
<span>{t('price')}</span>
<strong>{formatCurrency(price)}</strong>
</div>
);
}
Optimizing Performance
Lazy Loading Translations
For large applications, you can lazy load translations to improve initial load time:
// app/[locale]/layout.tsx
import { Suspense } from 'react';
import { getDictionary } from '@repo/internationalization';
import LoadingFallback from '@/components/LoadingFallback';
export default async function Layout({
children,
params
}: {
children: React.ReactNode;
params: { locale: string };
}) {
const dictionary = await getDictionary(params.locale);
return (
<html lang={params.locale}>
<body>
<header>
{/* Header content */}
</header>
<main>
<Suspense fallback={<LoadingFallback />}>
{children}
</Suspense>
</main>
<footer>
{/* Footer content */}
</footer>
</body>
</html>
);
}
Memoizing Translations
Use memoization to prevent unnecessary re-renders:
'use client';
import { useTranslation } from '@repo/internationalization/useTranslation';
import { useMemo } from 'react';
export default function MemoizedComponent() {
const { t } = useTranslation('web.common');
// Memoize expensive translation operations
const translatedItems = useMemo(() => {
return Array.from({ length: 100 }, (_, i) => ({
id: i,
title: t(`items.${i}.title`),
description: t(`items.${i}.description`)
}));
}, [t]);
return (
<div className="items-list">
{translatedItems.map(item => (
<div key={item.id} className="item">
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
))}
</div>
);
}
SEO Optimization
Hreflang Tags
Add hreflang tags to improve SEO for multilingual content:
// app/[locale]/layout.tsx
import { getDictionary } from '@repo/internationalization';
import { i18nConfig } from '@repo/internationalization/i18nConfig';
import { Metadata } from 'next';
export async function generateMetadata({
params
}: {
params: { locale: string }
}): Promise<Metadata> {
const dictionary = await getDictionary(params.locale);
return {
title: dictionary.web.home.meta.title,
description: dictionary.web.home.meta.description,
alternates: {
languages: Object.fromEntries(
i18nConfig.locales.map(locale => [
locale,
`/${locale}${params.locale === i18nConfig.defaultLocale ? '' : '/'}`
])
)
}
};
}
export default async function Layout({ children, params }) {
// Layout implementation
}
Localized Sitemaps
Create localized sitemaps for better search engine indexing:
// app/sitemap.ts
import { i18nConfig } from '@repo/internationalization/i18nConfig';
export default async function sitemap() {
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://example.com';
// Define your routes
const routes = [
'',
'/about',
'/contact',
'/blog'
];
// Generate entries for all locales
const entries = i18nConfig.locales.flatMap(locale =>
routes.map(route => ({
url: `${baseUrl}/${locale}${route}`,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: route === '' ? 1 : 0.8,
alternateRefs: i18nConfig.locales
.filter(l => l !== locale)
.map(l => ({
href: `${baseUrl}/${l}${route}`,
hreflang: l
}))
}))
);
return entries;
}
Advanced Routing
Locale-Aware API Routes
Create API routes that respect the user’s locale:
// app/api/[locale]/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getDictionary } from '@repo/internationalization';
export async function GET(
request: NextRequest,
{ params }: { params: { locale: string } }
) {
const dictionary = await getDictionary(params.locale);
// Get data based on locale
const data = await fetchLocalizedData(params.locale);
return NextResponse.json({
title: dictionary.web.api.dataTitle,
description: dictionary.web.api.dataDescription,
items: data.map(item => ({
...item,
// Translate category names
category: dictionary.web.categories[item.categoryKey] || item.categoryKey
}))
});
}
Dynamic Route Generation
Generate routes for all supported locales:
// app/[locale]/blog/[slug]/page.tsx
import { getDictionary } from '@repo/internationalization';
import { i18nConfig } from '@repo/internationalization/i18nConfig';
// Generate static params for all locales
export async function generateStaticParams() {
const posts = await fetchAllBlogPosts();
return i18nConfig.locales.flatMap(locale =>
posts.map(post => ({
locale,
slug: post.slug
}))
);
}
export default async function BlogPost({
params
}: {
params: { locale: string; slug: string }
}) {
const dictionary = await getDictionary(params.locale);
const post = await fetchBlogPost(params.slug, params.locale);
return (
<article>
<h1>{post.title}</h1>
<div className="content">{post.content}</div>
</article>
);
}
Testing Internationalization
Unit Testing Translations
Test that your components display the correct translations:
// __tests__/components/Header.test.tsx
import { render, screen } from '@testing-library/react';
import Header from '@/components/Header';
import { TranslationProvider } from '@repo/internationalization/TranslationProvider';
// Mock dictionaries
const mockEnDictionary = {
web: {
header: {
home: 'Home',
about: 'About',
contact: 'Contact'
}
}
};
const mockTrDictionary = {
web: {
header: {
home: 'Ana Sayfa',
about: 'Hakkımızda',
contact: 'İletişim'
}
}
};
describe('Header Component', () => {
test('renders English navigation', () => {
render(
<TranslationProvider locale="en" messages={mockEnDictionary}>
<Header />
</TranslationProvider>
);
expect(screen.getByText('Home')).toBeInTheDocument();
expect(screen.getByText('About')).toBeInTheDocument();
expect(screen.getByText('Contact')).toBeInTheDocument();
});
test('renders Turkish navigation', () => {
render(
<TranslationProvider locale="tr" messages={mockTrDictionary}>
<Header />
</TranslationProvider>
);
expect(screen.getByText('Ana Sayfa')).toBeInTheDocument();
expect(screen.getByText('Hakkımızda')).toBeInTheDocument();
expect(screen.getByText('İletişim')).toBeInTheDocument();
});
});
E2E Testing with Different Locales
Test the full user experience with different locales:
// cypress/e2e/internationalization.cy.ts
describe('Internationalization', () => {
it('should display content in English by default', () => {
cy.visit('/');
cy.get('h1').should('contain', 'Transform Your Business Operations Today');
cy.get('nav').should('contain', 'Home');
});
it('should display content in Turkish when visiting /tr route', () => {
cy.visit('/tr');
cy.get('h1').should('contain', 'İşletme Operasyonlarınızı Bugün Dönüştürün');
cy.get('nav').should('contain', 'Ana Sayfa');
});
it('should allow switching languages', () => {
cy.visit('/en');
cy.get('.language-switcher').contains('Türkçe').click();
cy.url().should('include', '/tr');
cy.get('h1').should('contain', 'İşletme Operasyonlarınızı Bugün Dönüştürün');
});
});
Integration with Third-Party Services
Integrating with Translation Services
Automate translation updates using external translation services:
// scripts/update-translations.ts
import fs from 'fs';
import path from 'path';
import { TranslationService } from 'some-translation-api';
async function updateTranslations() {
// Read source translations
const sourcePath = path.join(process.cwd(), 'dictionaries/en.json');
const sourceTranslations = JSON.parse(fs.readFileSync(sourcePath, 'utf8'));
// Get target locales
const config = JSON.parse(
fs.readFileSync(path.join(process.cwd(), 'languine.json'), 'utf8')
);
const targetLocales = config.locale.targets;
// Initialize translation service
const translationService = new TranslationService({
apiKey: process.env.TRANSLATION_API_KEY
});
// Update each target locale
for (const locale of targetLocales) {
console.log(`Updating translations for ${locale}...`);
const targetPath = path.join(process.cwd(), `dictionaries/${locale}.json`);
let targetTranslations = {};
// Load existing translations if available
if (fs.existsSync(targetPath)) {
targetTranslations = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
}
// Find missing or updated translations
const updatedTranslations = await translateMissingKeys(
sourceTranslations,
targetTranslations,
locale,
translationService
);
// Write updated translations
fs.writeFileSync(
targetPath,
JSON.stringify(updatedTranslations, null, 2),
'utf8'
);
console.log(`Updated translations for ${locale}`);
}
}
// Helper function to translate missing keys
async function translateMissingKeys(source, target, locale, service) {
// Implementation details
}
updateTranslations().catch(console.error);
Next Steps
- Advanced Internationalization Techniques
- Handling Complex Translations
- Nested Namespaces
- Rich Text Formatting
- Date and Number Formatting
- Advanced Date Formatting
- Currency Formatting
- Optimizing Performance
- Lazy Loading Translations
- Memoizing Translations
- SEO Optimization
- Hreflang Tags
- Localized Sitemaps
- Advanced Routing
- Locale-Aware API Routes
- Dynamic Route Generation
- Testing Internationalization
- Unit Testing Translations
- E2E Testing with Different Locales
- Integration with Third-Party Services
- Integrating with Translation Services
- Next Steps