Using Translations

How to use translations in Server Components, Client Components, and with the Trans component.

This guide covers how to access and display translations in your components using next-intl.

Server Components

In React Server Components, use getTranslations from next-intl/server. This is an async function that returns a translation function.

import { getTranslations } from 'next-intl/server';
async function SettingsPage() {
const t = await getTranslations('settings');
return (
<div>
<h1>{t('pageTitle')}</h1>
<p>{t('pageDescription')}</p>
</div>
);
}

Multiple Namespaces

Load multiple namespaces by calling getTranslations multiple times:

async function DashboardPage() {
const tCommon = await getTranslations('common');
const tBilling = await getTranslations('billing');
return (
<div>
<h1>{tCommon('dashboardTabLabel')}</h1>
<p>{tBilling('currentPlan')}</p>
</div>
);
}

Accessing Nested Keys

For deeply nested translations, you can either pass the full path to getTranslations or use dot notation in the key:

// Option 1: Namespace includes the path
const t = await getTranslations('settings.personalSettings.name');
const title = t('title');
// Option 2: Access from root namespace with dot notation
const t = await getTranslations('settings');
const title = t('personalSettings.name.title');

Page Metadata

Use getTranslations in generateMetadata for translated page titles and descriptions:

import { getTranslations } from 'next-intl/server';
import type { Metadata } from 'next';
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations('marketing');
return {
title: t('pageTitle'),
description: t('pageDescription'),
};
}

Client Components

In Client Components, use the useTranslations hook from next-intl:

'use client';
import { useTranslations } from 'next-intl';
function CreateOrganizationDialog() {
const t = useTranslations('organizations');
return (
<Dialog>
<DialogTitle>{t('createOrganizationDialogHeading')}</DialogTitle>
<DialogDescription>
{t('createOrganizationDialogDescription')}
</DialogDescription>
</Dialog>
);
}

The hook accesses translations from the context provided by I18nClientProvider in the root layout.

The Trans Component

For translations that include JSX elements or rich formatting, use the Trans component from @kit/ui/trans:

import { Trans } from '@kit/ui/trans';
// Basic usage
<Trans i18nKey="auth.welcomeMessage" />
// With interpolated values
<Trans
i18nKey="common.greeting"
values={{ name: user.name }}
/>
// With components for rich text
<Trans
i18nKey="auth.termsAgreement"
components={{
TermsLink: <a href="/terms" className="underline" />,
PrivacyLink: <a href="/privacy" className="underline" />,
}}
/>

Trans Component Props

PropTypeDescription
i18nKeystringTranslation key with namespace prefix (e.g., 'auth.login.title')
valuesobjectValues to interpolate into the translation
componentsobjectReact elements or functions for rich text
defaultsReactNodeFallback content if translation not found
nsstringOverride namespace (optional)

How Components Work

The components prop accepts either React elements or render functions:

// React element - children are replaced with translation content
<Trans
i18nKey="auth.termsAgreement"
components={{
TermsLink: <a href="/terms" className="underline" />,
}}
/>
// Render function - full control over rendering
<Trans
i18nKey="common.highlight"
components={{
bold: (chunks) => <strong className="font-bold">{chunks}</strong>,
}}
/>

For the translation:

{
"termsAgreement": "By signing up, you agree to our <TermsLink>Terms</TermsLink>.",
"highlight": "This is <bold>important</bold> text."
}

Translation Patterns

Flat Keys

Simple key-value pairs for straightforward translations:

{
"signUp": "Sign Up",
"signIn": "Sign In",
"forgotPassword": "Forgot Password?"
}
const t = useTranslations('auth');
t('signUp'); // "Sign Up"

Nested Keys

Organized hierarchical structure for related translations:

{
"routes": {
"account": "Account",
"members": "Members",
"billing": "Billing"
},
"roles": {
"owner": {
"label": "Owner"
},
"member": {
"label": "Member"
}
}
}
const t = useTranslations('common');
t('routes.account'); // "Account"
t('roles.owner.label'); // "Owner"

Interpolation

Dynamic values are inserted using curly braces:

{
"greeting": "Hello, {name}!",
"itemCount": "You have {count} items",
"planRenewal": "Renews every {interval} at {price}"
}
t('greeting', { name: 'John' }); // "Hello, John!"
t('itemCount', { count: 5 }); // "You have 5 items"
t('planRenewal', { interval: 'month', price: '$9' });

Pluralization

Use ICU message format for count-dependent text:

{
"invitations": "{count} {count, plural, one {invitation} other {invitations}} pending"
}
t('invitations', { count: 1 }); // "1 invitation pending"
t('invitations', { count: 5 }); // "5 invitations pending"

More complex plural rules:

{
"items": "{count, plural, =0 {No items} one {# item} other {# items}}"
}

Rich Text

For translations that need markup, use XML-like tags that map to components:

{
"termsAgreement": "By signing up, you agree to our <TermsLink>Terms of Service</TermsLink> and <PrivacyLink>Privacy Policy</PrivacyLink>."
}
<Trans
i18nKey="auth.termsAgreement"
components={{
TermsLink: <a href="/terms" className="underline" />,
PrivacyLink: <a href="/privacy" className="underline" />,
}}
/>

Getting Current Locale

Server-Side

import { getLocale } from 'next-intl/server';
async function MyServerComponent() {
const locale = await getLocale();
// locale = 'en', 'es', 'fr', etc.
}

Client-Side

'use client';
import { useLocale } from 'next-intl';
function MyClientComponent() {
const locale = useLocale();
// locale = 'en', 'es', 'fr', etc.
}

Locale-Aware Navigation

Use navigation utilities from @kit/i18n/navigation for automatic locale handling:

import {
Link,
redirect,
permanentRedirect,
useRouter,
usePathname
} from '@kit/i18n/navigation';

The Link component automatically prefixes paths with the current locale:

// Current locale is 'es'
<Link href="/settings">Settings</Link>
// Renders: <a href="/es/settings">Settings</a>
// For default locale 'en' (with localePrefix: 'as-needed')
<Link href="/settings">Settings</Link>
// Renders: <a href="/settings">Settings</a>

Programmatic Navigation

'use client';
import { useRouter, usePathname } from '@kit/i18n/navigation';
function NavigationExample() {
const router = useRouter();
const pathname = usePathname();
const handleClick = () => {
router.push('/dashboard'); // Locale is handled automatically
};
// pathname returns the path without locale prefix
console.log(pathname); // '/settings' not '/es/settings'
}

Server-Side Redirects

import { redirect, permanentRedirect } from '@kit/i18n/navigation';
async function ProtectedPage() {
const session = await getSession();
if (!session) {
redirect('/auth/sign-in'); // Redirects with correct locale
}
}

Checking if Translation Exists

Use the has method to check if a translation key exists:

const t = useTranslations('common');
if (t.has('newFeature.title')) {
return <h1>{t('newFeature.title')}</h1>;
}
return <h1>Default Title</h1>;

This is useful for conditional rendering based on translation availability.

Date and Time Formatting

next-intl provides locale-aware date and time formatting.

Client Components

'use client';
import { useFormatter } from 'next-intl';
function DateDisplay({ date }: { date: Date }) {
const format = useFormatter();
return (
<div>
{/* Full date */}
<p>{format.dateTime(date, { dateStyle: 'full' })}</p>
{/* Relative time */}
<p>{format.relativeTime(date)}</p>
{/* Custom format */}
<p>{format.dateTime(date, {
year: 'numeric',
month: 'long',
day: 'numeric'
})}</p>
</div>
);
}

Server Components

import { getFormatter } from 'next-intl/server';
async function ServerDateDisplay({ date }: { date: Date }) {
const format = await getFormatter();
return <p>{format.dateTime(date, { dateStyle: 'long' })}</p>;
}

Number Formatting

Format numbers according to locale conventions:

'use client';
import { useFormatter } from 'next-intl';
function PriceDisplay({ amount, currency }: { amount: number; currency: string }) {
const format = useFormatter();
return (
<span>
{format.number(amount, {
style: 'currency',
currency: currency
})}
</span>
);
}

Next: Managing Translations →