Managing Translations

How to add new messages, create namespaces, add languages, and configure language settings.

This guide covers how to manage translations in your application, including adding new messages, creating namespaces, and supporting multiple languages.

Adding New Messages

Locate the Namespace File

Translation files are stored in apps/web/i18n/messages/{locale}/:

apps/web/i18n/messages/en/
├── common.json # Shared UI labels
├── auth.json # Authentication
├── account.json # Account management
├── organizations.json # Team management
├── billing.json # Subscriptions
├── marketing.json # Marketing pages
├── settings.json # Settings pages
├── goodbye.json # Account deletion
└── errors.json # Error messages

Add Translation Keys

Open the appropriate namespace file and add your keys:

{
"existingKey": "Existing translation",
"newFeature": {
"title": "New Feature",
"description": "Description of the new feature",
"button": "Get Started"
}
}

Key Naming Conventions

  • Use camelCase for keys: pageTitle, submitButton
  • Use nested objects for related translations
  • Keep keys descriptive but concise
  • Group by feature or component

Examples:

{
"createProject": {
"title": "Create a New Project",
"description": "Start a new project to organize your work",
"nameLabel": "Project Name",
"namePlaceholder": "Enter project name",
"submitButton": "Create Project",
"cancelButton": "Cancel"
}
}

Adding New Namespaces

To create a new namespace for a feature:

1. Update the Namespaces Array

Edit apps/web/i18n/request.ts:

const namespaces = [
'common',
'auth',
'account',
'organizations',
'billing',
'marketing',
'settings',
'goodbye',
'errors',
'projects', // Add your new namespace
] as const;

2. Create the Namespace File

Create apps/web/i18n/messages/en/projects.json:

{
"pageTitle": "Projects",
"pageDescription": "Manage your projects",
"createProject": "Create Project",
"noProjects": "No projects yet",
"deleteConfirmation": "Are you sure you want to delete this project?"
}

3. Create Files for Other Locales

If supporting multiple languages, create the file for each locale:

  • apps/web/i18n/messages/es/projects.json
  • apps/web/i18n/messages/fr/projects.json

Adding New Languages

1. Add to Languages Array

Edit packages/i18n/src/routing.ts:

export const languages: string[] = [
defaultLanguage,
'es', // Spanish
'fr', // French
'de', // German
];

2. Create Translation Files

Create a new folder for the locale and copy all namespace files:

# Create the locale folder
mkdir apps/web/i18n/messages/es
# Copy all English files as templates
cp apps/web/i18n/messages/en/*.json apps/web/i18n/messages/es/

Then translate each file:

apps/web/i18n/messages/es/
├── common.json
├── auth.json
├── account.json
├── organizations.json
├── billing.json
├── marketing.json
├── settings.json
├── goodbye.json
└── errors.json

3. Add Email Template Translations

If your app sends transactional emails, add translations for email templates:

packages/email-templates/src/locales/es/
├── welcome-email.json
├── password-reset-email.json
└── verification-email.json

Language Codes

Use standard ISO 639-1 language codes:

CodeLanguage
enEnglish
esSpanish
frFrench
deGerman
itItalian
ptPortuguese
jaJapanese
zhChinese
koKorean

Language Settings UI

Users can change their language preference in the application settings.

Settings Page Location

The language preference is available at settings/preferences. This will only be visible if there are at least 2 languages.

This page is defined at apps/web/app/[locale]/(internal)/settings/preferences/page.tsx.

LanguagePreferenceCard Component

The language selector UI is provided by @kit/ui/language-selector:

import { LanguagePreferenceCard } from '@kit/ui/language-selector';
import { useRouter, usePathname } from '@kit/i18n/navigation';
import { languages } from '@kit/i18n/routing';
function PreferencesPage() {
const router = useRouter();
const pathname = usePathname();
return (
<LanguagePreferenceCard
locales={languages}
router={router}
pathname={pathname}
/>
);
}

Features:

  • Shows button grid for 3 or fewer languages
  • Shows dropdown selector for 4+ languages
  • Uses Intl.DisplayNames for native language names (e.g., "Español" for Spanish)
  • Handles loading state during locale transition

How Language is Stored

Language preference uses two mechanisms:

1. URL Routing (Primary)

The locale is part of the URL path using the [locale] parameter:

  • /about - English (default, no prefix)
  • /es/about - Spanish
  • /fr/about - French

2. Cookie Storage (Server-side)

A language cookie stores the preference for server-side operations like sending emails:

// In packages/better-auth/src/auth.ts
async function getLanguageFromRequest() {
const cookiesStore = await cookies();
return cookiesStore.get('language')?.value || 'en';
}

Visibility

The language settings card only appears when multiple languages are configured. If only one language is available (the default), the card is hidden.

Best Practices

  1. Keep translations organized - Use namespaces to separate concerns
  2. Use nested structures - Group related translations together
  3. Be consistent - Follow the same naming patterns across namespaces
  4. Provide context - Use descriptive key names that indicate usage
  5. Test all locales - Verify translations display correctly in all supported languages
  6. Handle pluralization - Use ICU format for count-dependent text
  7. Consider text length - Translations may be longer in other languages; design UI flexibly