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

{t('homeTabLabel')}

{t('homeTabDescription')}

); } ``` ### 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 (

); } ``` **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 ( ); } ``` ## 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 ( ); } ``` ### 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 {t('subscriptionSettingsTabLabel')}; } ``` ### 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 (

); } ``` ## Working with Translation Keys ### Key Format Translation keys use dot notation `namespace.keyPath`: ```tsx // Simple key // Nested key // 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 // 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 ``` ### 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 {email}" } ``` ```tsx }} /> ``` ## 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