Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
387 lines
11 KiB
Plaintext
387 lines
11 KiB
Plaintext
---
|
|
status: "published"
|
|
title: "Using translations in your Next.js Supabase project"
|
|
label: "Using translations"
|
|
description: "Learn how to use translations in Server Components, Client Components, and Server Actions with Makerkit's next-intl-based translation system."
|
|
order: 0
|
|
---
|
|
|
|
Makerkit uses `next-intl` for internationalization, abstracted behind the `@kit/i18n` package. This abstraction ensures future changes to the translation library won't break your code.
|
|
|
|
{% sequence title="Steps to use translations" description="Learn how to use translations in your Next.js Supabase project." %}
|
|
|
|
[Understand the translation architecture](#translation-architecture)
|
|
|
|
[Use translations in Server Components](#using-translations-in-server-components)
|
|
|
|
[Use translations in Client Components](#using-translations-in-client-components)
|
|
|
|
[Work with translation keys and namespaces](#working-with-translation-keys)
|
|
|
|
{% /sequence %}
|
|
|
|
## Translation Architecture
|
|
|
|
The translation system supports:
|
|
|
|
1. **Server Components (RSC)** - Access translations via `getTranslations` from `next-intl/server`
|
|
2. **Client Components** - Access translations via `useTranslations` from `next-intl`
|
|
3. **URL-based locale routing** - Locale is determined by the URL prefix (e.g., `/en/home`, `/es/home`)
|
|
|
|
Translation files are stored in `apps/web/i18n/messages/{locale}/`. The default structure includes:
|
|
|
|
```
|
|
apps/web/i18n/messages/
|
|
└── en/
|
|
├── common.json # Shared UI strings
|
|
├── auth.json # Authentication flows
|
|
├── account.json # Account settings
|
|
├── teams.json # Team management
|
|
├── billing.json # Billing and subscriptions
|
|
└── marketing.json # Marketing pages
|
|
```
|
|
|
|
## Using Translations in Server Components
|
|
|
|
Server Components can access translations directly using `getTranslations` from `next-intl/server`.
|
|
|
|
### Using getTranslations
|
|
|
|
```tsx title="apps/web/app/[locale]/home/page.tsx"
|
|
import { getTranslations } from 'next-intl/server';
|
|
|
|
export default async function HomePage() {
|
|
const t = await getTranslations('common');
|
|
|
|
return (
|
|
<div>
|
|
<h1>{t('homeTabLabel')}</h1>
|
|
<p>{t('homeTabDescription')}</p>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Using the Trans Component
|
|
|
|
The `Trans` component renders translated strings directly in JSX:
|
|
|
|
```tsx title="apps/web/app/[locale]/home/page.tsx"
|
|
import { Trans } from '@kit/ui/trans';
|
|
|
|
export default function HomePage() {
|
|
return (
|
|
<div>
|
|
<h1>
|
|
<Trans i18nKey="common.homeTabLabel" />
|
|
</h1>
|
|
|
|
<p>
|
|
<Trans i18nKey="common.homeTabDescription" />
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Import the `Trans` component from `@kit/ui/trans`** - the Makerkit wrapper handles server/client differences.
|
|
|
|
### Using Translations in Metadata
|
|
|
|
For page metadata, use `getTranslations` directly:
|
|
|
|
```tsx title="apps/web/app/[locale]/home/page.tsx"
|
|
import { getTranslations } from 'next-intl/server';
|
|
import { Trans } from '@kit/ui/trans';
|
|
|
|
export async function generateMetadata() {
|
|
const t = await getTranslations('common');
|
|
|
|
return {
|
|
title: t('homeTabLabel'),
|
|
};
|
|
}
|
|
|
|
export default function HomePage() {
|
|
return (
|
|
<Trans i18nKey="common.homeTabLabel" />
|
|
);
|
|
}
|
|
```
|
|
|
|
## Using Translations in Client Components
|
|
|
|
Client Components receive translations through the `NextIntlClientProvider` in the root layout.
|
|
|
|
### Using the useTranslations Hook
|
|
|
|
The `useTranslations` hook provides access to the translation function:
|
|
|
|
```tsx title="components/my-component.tsx"
|
|
'use client';
|
|
|
|
import { useTranslations } from 'next-intl';
|
|
|
|
export function MyComponent() {
|
|
const t = useTranslations();
|
|
|
|
return (
|
|
<button onClick={() => alert(t('common.cancel'))}>
|
|
{t('common.cancel')}
|
|
</button>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Specifying Namespaces
|
|
|
|
Load specific namespaces for scoped access:
|
|
|
|
```tsx title="components/billing-component.tsx"
|
|
'use client';
|
|
|
|
import { useTranslations } from 'next-intl';
|
|
|
|
export function BillingComponent() {
|
|
const t = useTranslations('billing');
|
|
|
|
// Keys without namespace prefix
|
|
return <span>{t('subscriptionSettingsTabLabel')}</span>;
|
|
}
|
|
```
|
|
|
|
### Using Trans in Client Components
|
|
|
|
The `Trans` component also works in Client Components:
|
|
|
|
```tsx title="components/welcome-message.tsx"
|
|
'use client';
|
|
|
|
import { Trans } from '@kit/ui/trans';
|
|
|
|
export function WelcomeMessage() {
|
|
return (
|
|
<p>
|
|
<Trans i18nKey="common.signedInAs" />
|
|
</p>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Working with Translation Keys
|
|
|
|
### Key Format
|
|
|
|
Translation keys use dot notation `namespace.keyPath`:
|
|
|
|
```tsx
|
|
// Simple key
|
|
<Trans i18nKey="common.cancel" />
|
|
|
|
// Nested key
|
|
<Trans i18nKey="common.routes.home" />
|
|
|
|
// With namespace in useTranslations
|
|
const t = useTranslations('auth');
|
|
t('signIn'); // Equivalent to 'auth.signIn'
|
|
```
|
|
|
|
### Interpolation
|
|
|
|
Pass dynamic values to translations using single braces:
|
|
|
|
```json title="apps/web/i18n/messages/en/common.json"
|
|
{
|
|
"pageOfPages": "Page {page} of {total}",
|
|
"showingRecordCount": "Showing {pageSize} of {totalCount} rows"
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
import { Trans } from '@kit/ui/trans';
|
|
|
|
// Using Trans component
|
|
<Trans
|
|
i18nKey="common.pageOfPages"
|
|
values={{ page: 1, total: 10 }}
|
|
/>
|
|
|
|
// Using t function
|
|
const t = useTranslations();
|
|
t('common.showingRecordCount', { pageSize: 25, totalCount: 100 });
|
|
```
|
|
|
|
### Nested Translations
|
|
|
|
Access nested objects with dot notation:
|
|
|
|
```json title="apps/web/i18n/messages/en/common.json"
|
|
{
|
|
"routes": {
|
|
"home": "Home",
|
|
"account": "Account",
|
|
"billing": "Billing"
|
|
},
|
|
"roles": {
|
|
"owner": {
|
|
"label": "Owner"
|
|
},
|
|
"member": {
|
|
"label": "Member"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<Trans i18nKey="common.routes.home" />
|
|
<Trans i18nKey="common.roles.owner.label" />
|
|
```
|
|
|
|
### HTML in Translations
|
|
|
|
For translations containing HTML, use the `Trans` component with components prop:
|
|
|
|
```json title="apps/web/i18n/messages/en/auth.json"
|
|
{
|
|
"clickToAcceptAs": "Click the button below to accept the invite as <b>{email}</b>"
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<Trans
|
|
i18nKey="auth.clickToAcceptAs"
|
|
values={{ email: user.email }}
|
|
components={{ b: <strong /> }}
|
|
/>
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Conditional Translations
|
|
|
|
```tsx
|
|
import { useTranslations, useLocale } from 'next-intl';
|
|
|
|
const t = useTranslations();
|
|
const locale = useLocale();
|
|
|
|
// Check current language
|
|
if (locale === 'en') {
|
|
// English-specific logic
|
|
}
|
|
|
|
// Translate with values
|
|
const label = t('optional.key', { name: 'World' });
|
|
```
|
|
|
|
### Pluralization
|
|
|
|
next-intl uses ICU message format for pluralization:
|
|
|
|
```json title="apps/web/i18n/messages/en/common.json"
|
|
{
|
|
"itemCount": "{count, plural, one {# item} other {# items}}"
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
t('common.itemCount', { count: 1 }); // "1 item"
|
|
t('common.itemCount', { count: 5 }); // "5 items"
|
|
```
|
|
|
|
### Date and Number Formatting
|
|
|
|
Use the standard `Intl` APIs alongside translations:
|
|
|
|
```tsx
|
|
const locale = useLocale();
|
|
|
|
const formattedDate = new Intl.DateTimeFormat(locale).format(date);
|
|
const formattedNumber = new Intl.NumberFormat(locale).format(1234.56);
|
|
```
|
|
|
|
## Server Actions
|
|
|
|
For Server Actions, use `getTranslations` from `next-intl/server`:
|
|
|
|
```tsx title="apps/web/lib/server/actions.ts"
|
|
'use server';
|
|
|
|
import { getTranslations } from 'next-intl/server';
|
|
|
|
export async function myServerAction() {
|
|
const t = await getTranslations('common');
|
|
|
|
// Use translations
|
|
const message = t('genericServerError');
|
|
|
|
return { error: message };
|
|
}
|
|
```
|
|
|
|
## Environment Variables
|
|
|
|
Configure language behavior with these environment variables:
|
|
|
|
```bash title=".env"
|
|
# Default language (fallback when user preference unavailable)
|
|
NEXT_PUBLIC_DEFAULT_LOCALE=en
|
|
```
|
|
|
|
The locale is determined by the URL prefix (e.g., `/en/`, `/es/`). When a user visits the root URL, they are redirected to their preferred locale based on:
|
|
|
|
1. The browser's `Accept-Language` header
|
|
2. Falls back to `NEXT_PUBLIC_DEFAULT_LOCALE`
|
|
|
|
## Troubleshooting
|
|
|
|
### Missing Translation Warning
|
|
|
|
If you see a missing translation warning, check:
|
|
|
|
1. The key exists in your translation file
|
|
2. All interpolation values are provided
|
|
3. The namespace is registered in `apps/web/i18n/request.ts`
|
|
|
|
### Translations Not Updating
|
|
|
|
If translations don't update after editing JSON files:
|
|
|
|
1. Restart the development server
|
|
2. Clear browser cache
|
|
3. Check for JSON syntax errors in translation files
|
|
|
|
{% faq
|
|
title="Frequently Asked Questions"
|
|
items=[
|
|
{"question": "How do I switch languages programmatically?", "answer": "Use router.replace() with the new locale from @kit/i18n/navigation. The locale is part of the URL path (e.g., /en/ to /es/), so changing language means navigating to the equivalent URL with a different locale prefix."},
|
|
{"question": "Why are my translations not showing?", "answer": "Check that the namespace is registered in the namespaces array in apps/web/i18n/request.ts, the JSON file exists in apps/web/i18n/messages/{locale}/, and verify the key uses dot notation (namespace.key not namespace:key)."},
|
|
{"question": "Can I use translations in Server Actions?", "answer": "Yes, import getTranslations from next-intl/server and call it at the start of your server action. Then use the returned t() function for translations."},
|
|
{"question": "What's the difference between Trans component and useTranslations hook?", "answer": "Trans is a React component that renders translated strings directly in JSX, supporting interpolation and HTML. useTranslations is a hook that returns a t() function for programmatic access to translations, useful for attributes, conditionals, or non-JSX contexts."},
|
|
{"question": "How do I handle missing translations during development?", "answer": "Missing translations log warnings to the console. Use [TODO] prefixes in your JSON values to make untranslated strings searchable. The system falls back to the key name if no translation is found."}
|
|
]
|
|
/%}
|
|
|
|
## Upgrading from v2
|
|
|
|
{% callout title="Differences with v2" %}
|
|
In v2, Makerkit used `i18next` and `react-i18next` for internationalization. In v3, the system uses `next-intl`. Key differences:
|
|
|
|
- Translation keys use dot notation (`namespace.key`) instead of colon notation (`namespace:key`)
|
|
- Interpolation uses single braces (`{var}`) instead of double braces (`{{var}}`)
|
|
- Server components use `getTranslations` from `next-intl/server` instead of `withI18n` HOC and `createI18nServerInstance`
|
|
- Client components use `useTranslations` from `next-intl` instead of `useTranslation` from `react-i18next`
|
|
- Translation files are in `apps/web/i18n/messages/{locale}/` instead of `apps/web/public/locales/{locale}/`
|
|
- Pluralization uses ICU format (`{count, plural, one {# item} other {# items}}`) instead of i18next `_one`/`_other` suffixes
|
|
- Locale is determined by URL prefix, not cookies
|
|
|
|
For the full migration guide, see [Upgrading from v2 to v3](/docs/next-supabase-turbo/installation/v3-migration).
|
|
{% /callout %}
|
|
|
|
## Related Documentation
|
|
|
|
- [Adding Translations](/docs/next-supabase-turbo/translations/adding-translations) - Add new languages and namespaces
|
|
- [Language Selector](/docs/next-supabase-turbo/translations/language-selector) - Let users change their language
|
|
- [Email Translations](/docs/next-supabase-turbo/translations/email-translations) - Translate email templates
|