Overview
This guide shows how to integrate Unlingo with Next.js applications using next-intl.Prerequisites
- Next.js 13+ application
- Basic knowledge of Next.js
- An Unlingo project with translations
Installation
Install the required dependencies:npm install next-intl
yarn add next-intl
pnpm add next-intl
Project Structure
Set up your Next.js project structure for internationalization:src/
├── app/
│ ├── [locale]/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── dashboard/
│ │ └── page.tsx
│ └── layout.tsx
├── i18n/
│ ├── request.ts
│ └── routing.ts
└── middleware.ts
Routing Configuration
Configure your locales and routing:import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
// A list of all locales that are supported
locales: ['en', 'es', 'fr', 'de'],
// Used when no locale matches
defaultLocale: 'en',
});
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
// A list of all locales that are supported
locales: ['en', 'es', 'fr', 'de'],
// Used when no locale matches
defaultLocale: 'en',
});
Request Configuration
Create a request configuration that fetches translations from Unlingo:import { getRequestConfig } from 'next-intl/server';
import { hasLocale } from 'next-intl';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
// This typically corresponds to the `[locale]` segment
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;
try {
// Fetch translations from Unlingo
const url = new URL('/v1/translations', 'https://api.unlingo.com');
url.searchParams.set('release', process.env.UNLINGO_RELEASE_TAG);
url.searchParams.set('namespace', process.env.UNLINGO_NAMESPACE);
url.searchParams.set('lang', locale);
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.UNLINGO_API_KEY}`,
'Content-Type': 'application/json',
},
// Cache for 5 minutes
next: { revalidate: 300 },
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const messages = await response.json();
return {
locale,
messages,
};
} catch (error) {
console.error('Failed to load translations:', error);
// Fallback to empty messages or default translations
return {
locale,
messages: {},
};
}
});
import { getRequestConfig } from 'next-intl/server';
import { hasLocale } from 'next-intl';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;
try {
// Fetch translations from Unlingo
const url = new URL('/v1/translations', 'https://api.unlingo.com');
url.searchParams.set('release', process.env.UNLINGO_RELEASE_TAG);
url.searchParams.set('namespace', process.env.UNLINGO_NAMESPACE);
url.searchParams.set('lang', locale);
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.UNLINGO_API_KEY}`,
'Content-Type': 'application/json',
},
// Cache for 5 minutes
next: { revalidate: 300 },
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const messages = await response.json();
return {
locale,
messages,
};
} catch (error) {
console.error('Failed to load translations:', error);
return {
locale,
messages: {},
};
}
});
Environment Variables
Add your Unlingo credentials to your environment file:.env.local
UNLINGO_API_KEY=your_api_key_here
UNLINGO_RELEASE_TAG=1.0.0
UNLINGO_NAMESPACE=translation
Middleware Configuration
Set up middleware to handle locale detection and routing:import createMiddleware from 'next-intl/middleware';
import { routing } from './src/i18n/routing';
export default createMiddleware(routing);
export const config = {
// Match only internationalized pathnames
matcher: ['/', '/(de|en|es|fr)/:path*'],
};
import createMiddleware from 'next-intl/middleware';
import { routing } from './src/i18n/routing';
export default createMiddleware(routing);
export const config = {
// Match only internationalized pathnames
matcher: ['/', '/(de|en|es|fr)/:path*'],
};
Next.js Configuration
Update yournext.config.js to include the internationalization plugin:
const withNextIntl = require('next-intl/plugin')(
// This is the default (also the `src` folder is supported out of the box)
'./src/i18n/request.ts'
);
module.exports = withNextIntl({
// Other Next.js configuration
});
import withNextIntl from 'next-intl/plugin';
const withNextIntlConfig = withNextIntl('./src/i18n/request.ts');
export default withNextIntlConfig({
// Other Next.js configuration
});
Root Layout
Set up the root layout:import { ReactNode } from 'react';
type Props = {
children: ReactNode;
};
// Since we have a `not-found.tsx` page on the root, a layout file
// is required, even if it's just passing children through.
export default function RootLayout({ children }: Props) {
return children;
}
// Since we have a `not-found.tsx` page on the root, a layout file
// is required, even if it's just passing children through.
export default function RootLayout({ children }) {
return children;
}
Locale Layout
Create a layout for your localized pages:import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
export default async function LocaleLayout({
children,
params: { locale }
}: {
children: React.ReactNode;
params: { locale: string };
}) {
// Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale as any)) {
notFound();
}
// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
export default async function LocaleLayout({ children, params: { locale } }) {
// Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale)) {
notFound();
}
// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>{children}</NextIntlClientProvider>
</body>
</html>
);
}
Using Translations in Server Components
Basic Usage
import { getTranslations } from 'next-intl/server';
export default async function HomePage() {
const t = await getTranslations('common');
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('description')}</p>
</div>
);
}
import { getTranslations } from 'next-intl/server';
export default async function HomePage() {
const t = await getTranslations('common');
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('description')}</p>
</div>
);
}
With Interpolation
import { getTranslations } from 'next-intl/server';
export default async function UserPage({ params }: { params: { userId: string } }) {
const t = await getTranslations('user');
const user = await getUserById(params.userId);
return (
<div>
<h1>{t('greeting', { name: user.name })}</h1>
<p>{t('lastSeen', { date: new Date(user.lastSeen) })}</p>
</div>
);
}
Using Translations in Client Components
For client components, use theuseTranslations hook:
'use client';
import { useLocale, useTranslations } from 'next-intl';
import { useRouter } from 'next/navigation';
export default function LanguageSwitcher() {
const t = useTranslations('common');
const locale = useLocale();
const router = useRouter();
const languages = [
{ code: 'en', name: 'English' },
{ code: 'es', name: 'Español' },
{ code: 'fr', name: 'Français' },
{ code: 'de', name: 'Deutsch' }
];
const changeLanguage = (newLocale: string) => {
router.push(`/${newLocale}`);
};
return (
<div className="language-switcher">
<label>{t('selectLanguage')}</label>
<select
value={locale}
onChange={(e) => changeLanguage(e.target.value)}
>
{languages.map((lang) => (
<option key={lang.code} value={lang.code}>
{lang.name}
</option>
))}
</select>
</div>
);
}
'use client';
import { useLocale, useTranslations } from 'next-intl';
import { useRouter } from 'next/navigation';
export default function LanguageSwitcher() {
const t = useTranslations('common');
const locale = useLocale();
const router = useRouter();
const languages = [
{ code: 'en', name: 'English' },
{ code: 'es', name: 'Español' },
{ code: 'fr', name: 'Français' },
{ code: 'de', name: 'Deutsch' },
];
const changeLanguage = newLocale => {
router.push(`/${newLocale}`);
};
return (
<div className='language-switcher'>
<label>{t('selectLanguage')}</label>
<select value={locale} onChange={e => changeLanguage(e.target.value)}>
{languages.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.name}
</option>
))}
</select>
</div>
);
}
Advanced Features
Multiple Namespaces
Configure multiple namespaces in your request configuration:// Enhanced i18n/request.ts
export default getRequestConfig(async ({ requestLocale }) => {
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;
const namespaces = ['common', 'navigation', 'dashboard', 'auth'];
const messages = {};
// Fetch all namespaces in parallel
await Promise.all(
namespaces.map(async namespace => {
try {
const url = new URL('/v1/translations', 'https://api.unlingo.com');
url.searchParams.set('release', process.env.UNLINGO_RELEASE_TAG);
url.searchParams.set('namespace', namespace);
url.searchParams.set('lang', locale);
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.UNLINGO_API_KEY}`,
'Content-Type': 'application/json',
},
next: { revalidate: 300 },
});
if (response.ok) {
const namespaceMessages = await response.json();
messages[namespace] = namespaceMessages;
}
} catch (error) {
console.error(`Failed to load ${namespace} namespace:`, error);
messages[namespace] = {};
}
})
);
return {
locale,
messages,
};
});
Next Steps
i18next
Integrate applications using i18next
Get Translation
Explore the API reference
