Using translations in your Next.js Supabase project
Learn how to use translations in Server Components, Client Components, and Server Actions with Makerkit's i18next-based translation system.
Makerkit uses i18next for internationalization, abstracted behind the @kit/i18n package. This abstraction ensures future changes to the translation library won't break your code.
Steps to use translations
Learn how to use translations in your Next.js Supabase project.
Translation Architecture
The translation system supports:
- Server Components (RSC) - Initialize translations per-request with
withI18n - Client Components - Access translations via React context with
useTranslation - Server-side rendering - Hydrate translations from server to client
Translation files are stored in apps/web/public/locales/[language]/[namespace].json. The default structure includes:
apps/web/public/locales/└── 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 pagesUsing Translations in Server Components
Server Components require explicit initialization because they render in parallel. Without initialization, translations may not be available when the component renders.
Wrapping Pages with withI18n
Always wrap your page components with withI18n:
import { withI18n } from '~/lib/i18n/with-i18n';function HomePage() { return <div>Your page content</div>;}export default withI18n(HomePage);The withI18n higher-order component:
- Creates an i18n server instance before rendering
- Loads all configured namespaces
- Makes translations available to the component tree
Using the Trans Component
After initialization, use the Trans component for translations:
import { Trans } from '@kit/ui/trans';import { withI18n } from '~/lib/i18n/with-i18n';function HomePage() { return ( <div> <h1> <Trans i18nKey="common:homeTabLabel" /> </h1> <p> <Trans i18nKey="common:homeTabDescription" /> </p> </div> );}export default withI18n(HomePage);Import the Trans component from @kit/ui/trans - not directly from react-i18next. The Makerkit wrapper handles server/client differences.
Using Translations in Metadata
For page metadata, initialize the i18n instance directly:
import { Trans } from '@kit/ui/trans';import { withI18n } from '~/lib/i18n/with-i18n';import { createI18nServerInstance } from '~/lib/i18n/i18n.server';export async function generateMetadata() { const i18n = await createI18nServerInstance(); return { title: i18n.t('common:homeTabLabel'), };}function HomePage() { return ( <Trans i18nKey="common:homeTabLabel" /> );}export default withI18n(HomePage);Using Translations in Client Components
Client Components receive translations through the I18nProvider in the root layout.
Using the useTranslation Hook
The useTranslation hook provides access to the translation function:
'use client';import { useTranslation } from 'react-i18next';export function MyComponent() { const { t } = useTranslation(); return ( <button onClick={() => alert(t('common:cancel'))}> {t('common:cancel')} </button> );}Specifying Namespaces
Load specific namespaces for better code splitting:
'use client';import { useTranslation } from 'react-i18next';export function BillingComponent() { const { t } = useTranslation('billing'); // Keys without namespace prefix return <span>{t('subscriptionSettingsTabLabel')}</span>;}Using Trans in Client Components
The Trans component also works in Client Components:
'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 follow the pattern namespace:keyPath:
// Simple key<Trans i18nKey="common:cancel" />// Nested key<Trans i18nKey="common:routes.home" />// With default namespace in useTranslationconst { t } = useTranslation('auth');t('signIn'); // Equivalent to 'auth:signIn'Interpolation
Pass dynamic values to translations:
{ "pageOfPages": "Page {{page}} of {{total}}", "showingRecordCount": "Showing {{pageSize}} of {{totalCount}} rows"}import { Trans } from '@kit/ui/trans';// Using Trans component<Trans i18nKey="common:pageOfPages" values={{ page: 1, total: 10 }}/>// Using t functionconst { t } = useTranslation();t('common:showingRecordCount', { pageSize: 25, totalCount: 100 });Nested Translations
Access nested objects with dot notation:
{ "routes": { "home": "Home", "account": "Account", "billing": "Billing" }, "roles": { "owner": { "label": "Owner" }, "member": { "label": "Member" } }}<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:
{ "clickToAcceptAs": "Click the button below to accept the invite as <b>{{email}}</b>"}<Trans i18nKey="auth:clickToAcceptAs" values={{ email: user.email }} components={{ b: <strong /> }}/>Common Patterns
Conditional Translations
const { t, i18n } = useTranslation();// Check current languageif (i18n.language === 'en') { // English-specific logic}// Translate with fallbackconst label = t('optional:key', { defaultValue: 'Fallback text' });Pluralization
{ "itemCount": "{{count}} item", "itemCount_other": "{{count}} items"}t('common:itemCount', { count: 1 }); // "1 item"t('common:itemCount', { count: 5 }); // "5 items"Date and Number Formatting
For dates and numbers, use the standard Intl APIs alongside translations:
const { i18n } = useTranslation();const formattedDate = new Intl.DateTimeFormat(i18n.language).format(date);const formattedNumber = new Intl.NumberFormat(i18n.language).format(1234.56);Server Actions
For Server Actions, initialize translations at the start:
'use server';import { createI18nServerInstance } from '~/lib/i18n/i18n.server';export async function myServerAction() { const i18n = await createI18nServerInstance(); const t = i18n.t; // Use translations const message = t('common:genericServerError'); return { error: message };}Environment Variables
Configure language behavior with these environment variables:
# Default language (fallback when user preference unavailable)NEXT_PUBLIC_DEFAULT_LOCALE=en# Language priority: 'user' or 'application'# 'user': Respect browser Accept-Language header# 'application': Always use default localeNEXT_PUBLIC_LANGUAGE_PRIORITY=applicationWhen languagePriority is set to user, the system checks:
- The
langcookie (set when user changes language) - The browser's
Accept-Languageheader - Falls back to
NEXT_PUBLIC_DEFAULT_LOCALE
Troubleshooting
Missing Translation Warning
If you see Missing interpolation value for key, check:
- The key exists in your translation file
- All interpolation values are provided
- The namespace is loaded
Translations Not Updating
If translations don't update after editing JSON files:
- Restart the development server
- Clear browser cache
- Check for JSON syntax errors in translation files
Server Component Translation Missing
Ensure the page is wrapped with withI18n:
// Wrong - translations may not be availableexport default function Page() { ... }// Correctexport default withI18n(Page);Frequently Asked Questions
How do I switch languages programmatically?
Why are my translations not showing?
Can I use translations in Server Actions?
What's the difference between Trans component and useTranslation hook?
How do I handle missing translations during development?
Related Documentation
- Adding Translations - Add new languages and namespaces
- Language Selector - Let users change their language
- Email Translations - Translate email templates