--- status: "published" title: "Adding new translations | Next.js Supabase SaaS Kit" label: "Adding new translations" description: "Learn how to add new languages, create translation files, and organize namespaces in your Next.js Supabase SaaS application." order: 1 --- This guide covers adding new languages, creating translation files, and organizing your translations into namespaces. {% sequence title="Steps to add new translations" description="Learn how to add new translations to your Next.js Supabase SaaS project." %} [Create language files](#1-create-language-files) [Register the language](#2-register-the-language) [Add custom namespaces](#3-add-custom-namespaces) [Translate email templates](#4-translate-email-templates) {% /sequence %} ## 1. Create Language Files Translation files live in `apps/web/i18n/messages/{locale}/`. Each language needs its own folder with JSON files matching your namespaces. ### Create the Language Folder Create a new folder using the [ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes): ```bash mkdir apps/web/i18n/messages/es ``` Common language codes: - `de` - German - `es` - Spanish - `fr` - French - `it` - Italian - `ja` - Japanese - `pt` - Portuguese - `zh` - Chinese ### Regional Language Codes For regional variants like `es-ES` (Spanish - Spain) or `pt-BR` (Portuguese - Brazil), use lowercase with a hyphen: ```bash # Correct mkdir apps/web/i18n/messages/es-es mkdir apps/web/i18n/messages/pt-br # Incorrect - will not work mkdir apps/web/i18n/messages/es-ES ``` The system normalizes language codes to lowercase internally. ### Copy and Translate Files Copy the English files as a starting point: ```bash cp apps/web/i18n/messages/en/*.json apps/web/i18n/messages/es/ ``` Then translate each JSON file. Here's an example for `common.json`: ```json title="apps/web/i18n/messages/es/common.json" { "homeTabLabel": "Inicio", "cancel": "Cancelar", "clear": "Limpiar", "goBack": "Volver", "tryAgain": "Intentar de nuevo", "loading": "Cargando. Por favor espere...", "routes": { "home": "Inicio", "account": "Cuenta", "billing": "Facturacion" } } ``` Keep the same key structure as the English files. Only translate the values. ## 2. Register the Language Add your new language to the locales configuration: ```tsx title="packages/i18n/src/locales.tsx" {6} /** * The list of supported locales. * Add more locales here as needed. */ export const locales: string[] = ['en', 'es', 'de', 'fr']; ``` The order matters for fallback behavior: 1. First locale is the default fallback 2. When a translation is missing, the system falls back through this list ### Verify the Registration After adding a language, verify it works: 1. Restart the development server 2. Navigate to your app with the locale prefix (e.g., `/es/home`) 3. You should see your translations appear ## 3. Add Custom Namespaces Namespaces organize translations by feature or domain. The default namespaces are registered in `apps/web/i18n/request.ts`: ```tsx title="apps/web/i18n/request.ts" const namespaces = [ 'common', // Shared UI elements 'auth', // Authentication flows 'account', // Account settings 'teams', // Team management 'billing', // Billing and subscriptions 'marketing', // Marketing pages ]; ``` ### Create a New Namespace #### Create the JSON file for each language: ```bash # Create for English touch apps/web/i18n/messages/en/projects.json # Create for other languages touch apps/web/i18n/messages/es/projects.json ``` #### Add your translations ```json title="apps/web/i18n/messages/en/projects.json" { "title": "Projects", "createProject": "Create Project", "projectName": "Project Name", "projectDescription": "Description", "deleteProject": "Delete Project", "confirmDelete": "Are you sure you want to delete this project?", "status": { "active": "Active", "archived": "Archived", "draft": "Draft" } } ``` #### Register the namespace: ```tsx title="apps/web/i18n/request.ts" {8} const namespaces = [ 'common', 'auth', 'account', 'teams', 'billing', 'marketing', 'projects', // Your new namespace ]; ``` #### Use the namespace in your components: ```tsx title="apps/web/app/[locale]/home/[account]/projects/page.tsx" import { Trans } from '@kit/ui/trans'; function ProjectsPage() { return (

); } ``` ### Namespace Best Practices **Keep namespaces focused**: Each namespace should cover a single feature or domain. ``` Good: - projects.json (project management) - invoices.json (invoicing feature) - notifications.json (notification system) Avoid: - misc.json (too vague) - page1.json (not semantic) ``` **Use consistent key naming**: ```json { "title": "Page title", "description": "Page description", "actions": { "create": "Create", "edit": "Edit", "delete": "Delete" }, "status": { "loading": "Loading...", "error": "An error occurred", "success": "Success!" } } ``` **Avoid duplicating common strings**: Use the `common` namespace for shared strings like "Cancel", "Save", "Loading". ## 4. Translate Email Templates Email templates have their own translation system in `packages/email-templates/src/locales/`. ### Email Translation Structure ``` packages/email-templates/src/locales/ └── en/ ├── account-delete-email.json ├── invite-email.json └── otp-email.json ``` ### Add Email Translations for a New Language #### Create the language folder: ```bash mkdir packages/email-templates/src/locales/es ``` #### Copy and translate the email files: ```bash cp packages/email-templates/src/locales/en/*.json packages/email-templates/src/locales/es/ ``` #### Translate the content: ```json title="packages/email-templates/src/locales/es/invite-email.json" { "subject": "Has sido invitado a unirte a un equipo", "heading": "Unete a {teamName} en {productName}", "hello": "Hola {invitedUserEmail},", "mainText": "{inviter} te ha invitado al equipo {teamName} en {productName}.", "joinTeam": "Unirse a {teamName}", "copyPasteLink": "o copia y pega esta URL en tu navegador:", "invitationIntendedFor": "Esta invitacion es para {invitedUserEmail}." } ``` Email templates support interpolation with `{variable}` syntax and basic HTML tags. ## Organizing Large Translation Files For applications with many translations, consider splitting by feature: ``` apps/web/i18n/messages/en/ ├── common.json # 50-100 keys max ├── auth.json ├── account.json ├── billing/ │ ├── subscriptions.json │ ├── invoices.json │ └── checkout.json └── features/ ├── projects.json ├── analytics.json └── integrations.json ``` Update your namespace registration accordingly: ```tsx const namespaces = [ 'common', 'auth', 'account', 'billing/subscriptions', 'billing/invoices', 'features/projects', ]; ``` ## Translation Workflow Tips ### Use Placeholders During Development When adding new features, start with English placeholders: ```json { "newFeature": "[TODO] New feature title", "newFeatureDescription": "[TODO] Description of the new feature" } ``` This makes untranslated strings visible and searchable. ### Maintain Translation Parity Keep all language files in sync. When adding a key to one language, add it to all: ```bash # Check for missing keys (example script) diff <(jq -r 'keys[]' messages/en/common.json | sort) \ <(jq -r 'keys[]' messages/es/common.json | sort) ``` ### Consider Translation Services For production applications, integrate with translation services: - [Crowdin](https://crowdin.com/) - [Lokalise](https://lokalise.com/) - [Phrase](https://phrase.com/) These services can: - Sync with your JSON files via CLI or CI/CD - Provide translator interfaces - Handle pluralization rules per language - Track translation coverage ## RTL Language Support For right-to-left languages like Arabic (`ar`) or Hebrew (`he`): 1. Add the language as normal to `packages/i18n/src/locales.tsx` 2. Create a client component to detect the current locale and set the `dir` attribute: ```tsx title="apps/web/components/rtl-provider.tsx" 'use client'; import { useEffect } from 'react'; import { useLocale } from 'next-intl'; const rtlLanguages = ['ar', 'he', 'fa', 'ur']; export function RtlProvider({ children }: { children: React.ReactNode }) { const locale = useLocale(); useEffect(() => { const isRtl = rtlLanguages.includes(locale); document.documentElement.dir = isRtl ? 'rtl' : 'ltr'; document.documentElement.lang = locale; }, [locale]); return children; } ``` 3. Wrap your app with the provider in `RootProviders`: ```tsx title="apps/web/components/root-providers.tsx" import { RtlProvider } from './rtl-provider'; export function RootProviders({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` 4. Use Tailwind's RTL utilities (`rtl:` prefix) for layout adjustments: ```tsx
{/* Content flows correctly in both directions */}
``` {% faq title="Frequently Asked Questions" items=[ {"question": "How do I verify my translations are working?", "answer": "Navigate to your app with the locale prefix in the URL (e.g., /es/home). If you have the Language Selector component configured, you can also use it in account settings to switch languages."}, {"question": "Do I need to translate every single key?", "answer": "No. Missing translations fall back to the default language (usually English). During development, you can translate incrementally. For production, ensure all user-facing strings are translated."}, {"question": "Can I use nested folders for namespaces?", "answer": "Yes. Create subfolders like billing/subscriptions.json and register them as 'billing/subscriptions' in the namespaces array in apps/web/i18n/request.ts. The resolver will load from the nested path."}, {"question": "How do I handle pluralization in different languages?", "answer": "next-intl uses ICU message format for pluralization. Define plural rules like {count, plural, one {# item} other {# items}}. ICU format automatically handles language-specific plural rules (e.g., Russian's complex plural categories)."}, {"question": "Should translation files be committed to git?", "answer": "Yes, translation JSON files should be version controlled. If using a translation management service, configure it to sync with your repository via pull requests."} ] /%} ## Upgrading from v2 {% callout title="Differences with v2" %} In v2, Makerkit used `i18next` for translations. In v3, the system uses `next-intl`. Key differences: - Translation files moved from `apps/web/public/locales/{locale}/` to `apps/web/i18n/messages/{locale}/` - Language settings moved from `apps/web/lib/i18n/i18n.settings.ts` to `packages/i18n/src/locales.tsx` - Namespace registration moved from `defaultI18nNamespaces` in `i18n.settings.ts` to `namespaces` in `apps/web/i18n/request.ts` - Translation keys use dot notation (`namespace.key`) instead of colon notation (`namespace:key`) - Interpolation uses single braces (`{var}`) instead of double braces (`{{var}}`) - Pluralization uses ICU format instead of i18next `_one`/`_other` suffixes For the full migration guide, see [Upgrading from v2 to v3](/docs/next-supabase-turbo/installation/v3-migration). {% /callout %} ## Related Documentation - [Using Translations](/docs/next-supabase-turbo/translations/using-translations) - Use translations in your components - [Language Selector](/docs/next-supabase-turbo/translations/language-selector) - Add a language switcher - [Email Translations](/docs/next-supabase-turbo/translations/email-translations) - Translate email templates