# Using Translations

> How to use translations in components, in route metadata, and with the Trans component.

*Canonical: https://makerkit.dev/docs/tanstack-prisma/internationalization/using-translations*

---

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

## In Components

Components — whether they render on the server during SSR or on the client — read translations through the `useTranslations` hook from `use-intl`. The hook reads from the context provided by `I18nProvider` in the root providers.

```typescript
import { useTranslations } from 'use-intl';

function SettingsPage() {
  const t = useTranslations('settings');

  return (
    <div>
      <h1>{t('pageTitle')}</h1>
      <p>{t('pageDescription')}</p>
    </div>
  );
}
```

### Multiple Namespaces

Load multiple namespaces by calling `useTranslations` multiple times:

```typescript
function DashboardPage() {
  const tCommon = useTranslations('common');
  const tBilling = useTranslations('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 `useTranslations` or use dot notation in the key:

```typescript
// Option 1: Namespace includes the path
const t = useTranslations('settings.personalSettings.name');
const title = t('title');

// Option 2: Access from a higher namespace with dot notation
const t = useTranslations('settings');
const title = t('personalSettings.name.title');
```

## Outside React Render (Route Metadata)

There are no React Server Components in TanStack Start. When you need a translation **outside** the React render tree — most commonly in a route's `head()` for the document title — use the synchronous `getTranslator` helper from `#/lib/i18n/translator.ts`. It returns a function that takes a **dotted message key** (no namespace argument) and returns a string.

```typescript
import { createFileRoute } from '@tanstack/react-router';

import { getTranslator } from '#/lib/i18n/translator.ts';

export const Route = createFileRoute('/_authenticated/settings/preferences')({
  head: () => {
    const t = getTranslator();

    return {
      meta: [{ title: t('settings.preferences.pageTitle') }],
    };
  },
  component: PreferencesPage,
});
```

You can also pass loader data into the meta and add a description:

```typescript
import { getTranslator } from '#/lib/i18n/translator.ts';

export const Route = createFileRoute('/_public/changelog/')({
  head: ({ loaderData }) => {
    const t = getTranslator();

    return {
      meta: [
        { title: t('marketing.changelog') },
        { name: 'description', content: t('marketing.changelogSubtitle') },
      ],
    };
  },
});
```

`getTranslator(locale?)` builds the translator from the bundled messages for the locale (defaulting to the default locale), so it is synchronous and safe to call inside `head()`.

## 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

### In Components

```typescript
import { useLocale } from 'use-intl';

function MyComponent() {
  const locale = useLocale();
  // locale = 'en', 'es', 'fr', etc.
}
```

### Outside Components

The active locale is resolved on the server and exposed through the root route context (it's also used to set `<html lang>`):

```typescript
import { Route as RootRoute } from '#/routes/__root';

function MyComponent() {
  const { locale } = RootRoute.useRouteContext();
  // locale = 'en', 'es', 'fr', etc.
}
```

On the server you can also call the `detectLocale()` server function directly (see the Overview).

## Navigation

Use TanStack Router's `Link` (re-exported from `@kit/i18n/navigation`) for internal navigation — the router `rewrite` localizes its `to` automatically, so links carry the active locale's prefix with no extra work. For dynamic/string hrefs that can't be typed against the route tree (e.g. CMS URLs), use `LocalizedLink` from `@kit/ui/localized-link`. Never use a raw `<a>` for internal links — it bypasses the router and won't be localized. The application surface (dashboard/settings/admin) is never prefixed regardless.

### Link Component

```typescript
import { Link } from '@tanstack/react-router';

<Link to="/settings">Settings</Link>

// With params for dynamic routes
<Link to="/blog/$slug" params={{ slug: 'hello-world' }}>Read post</Link>
```

### Programmatic Navigation

```typescript
import { useRouter } from '@tanstack/react-router';

function NavigationExample() {
  const router = useRouter();

  const handleClick = () => {
    router.navigate({ to: '/dashboard' });
  };
}
```

### Redirects

Use `redirect` from `@tanstack/react-router` inside loaders or `beforeLoad`:

```typescript
import { redirect } from '@tanstack/react-router';

export const Route = createFileRoute('/_authenticated/protected')({
  beforeLoad: async () => {
    const session = await getSession();

    if (!session) {
      throw redirect({ to: '/auth/sign-in' });
    }
  },
});
```

Client-side redirects are localized automatically by the router rewrite. For server-side (SSR) redirects to a public or auth route, localize the target explicitly with `localizeUrl(target, locale)`, sourcing the locale from route context; redirects to the app surface need no change. See `apps/web/src/lib/auth/guards.ts`.

## 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

`use-intl` provides locale-aware date and time formatting via the `useFormatter` hook.

```typescript
import { useFormatter } from 'use-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>
  );
}
```

## Number Formatting

Format numbers according to locale conventions:

```typescript
import { useFormatter } from 'use-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)
