# Using Translations

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

*Canonical: https://makerkit.dev/docs/nextjs-drizzle/internationalization/using-translations*

---

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.

```typescript
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:

```typescript
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:

```typescript
// 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:

```typescript
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`:

```typescript
'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`:

```typescript
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

| Prop | Type | Description |
|------|------|-------------|
| `i18nKey` | `string` | Translation key with namespace prefix (e.g., `'auth.login.title'`) |
| `values` | `object` | Values to interpolate into the translation |
| `components` | `object` | React elements or functions for rich text |
| `defaults` | `ReactNode` | Fallback content if translation not found |
| `ns` | `string` | Override namespace (optional) |

### How Components Work

The `components` prop accepts either React elements or render functions:

```typescript
// 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:

```json
{
  "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:

```json
{
  "signUp": "Sign Up",
  "signIn": "Sign In",
  "forgotPassword": "Forgot Password?"
}
```

```typescript
const t = useTranslations('auth');
t('signUp'); // "Sign Up"
```

### Nested Keys

Organized hierarchical structure for related translations:

```json
{
  "routes": {
    "account": "Account",
    "members": "Members",
    "billing": "Billing"
  },
  "roles": {
    "owner": {
      "label": "Owner"
    },
    "member": {
      "label": "Member"
    }
  }
}
```

```typescript
const t = useTranslations('common');
t('routes.account');     // "Account"
t('roles.owner.label');  // "Owner"
```

### Interpolation

Dynamic values are inserted using curly braces:

```json
{
  "greeting": "Hello, {name}!",
  "itemCount": "You have {count} items",
  "planRenewal": "Renews every {interval} at {price}"
}
```

```typescript
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:

```json
{
  "invitations": "{count} {count, plural, one {invitation} other {invitations}} pending"
}
```

```typescript
t('invitations', { count: 1 }); // "1 invitation pending"
t('invitations', { count: 5 }); // "5 invitations pending"
```

More complex plural rules:

```json
{
  "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:

```json
{
  "termsAgreement": "By signing up, you agree to our <TermsLink>Terms of Service</TermsLink> and <PrivacyLink>Privacy Policy</PrivacyLink>."
}
```

```typescript
<Trans
  i18nKey="auth.termsAgreement"
  components={{
    TermsLink: <a href="/terms" className="underline" />,
    PrivacyLink: <a href="/privacy" className="underline" />,
  }}
/>
```

## Getting Current Locale

### Server-Side

```typescript
import { getLocale } from 'next-intl/server';

async function MyServerComponent() {
  const locale = await getLocale();
  // locale = 'en', 'es', 'fr', etc.
}
```

### Client-Side

```typescript
'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:

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

### Link Component

The `Link` component automatically prefixes paths with the current locale:

```typescript
// 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

```typescript
'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

```typescript
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:

```typescript
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

```typescript
'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

```typescript
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:

```typescript
'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 →](./managing-translations)
