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 messagesAdd 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.jsonapps/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 foldermkdir apps/web/i18n/messages/es# Copy all English files as templatescp 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.json3. 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.jsonLanguage Codes
Use standard ISO 639-1 language codes:
| Code | Language |
|---|---|
en | English |
es | Spanish |
fr | French |
de | German |
it | Italian |
pt | Portuguese |
ja | Japanese |
zh | Chinese |
ko | Korean |
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.DisplayNamesfor 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.tsasync 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
- Keep translations organized - Use namespaces to separate concerns
- Use nested structures - Group related translations together
- Be consistent - Follow the same naming patterns across namespaces
- Provide context - Use descriptive key names that indicate usage
- Test all locales - Verify translations display correctly in all supported languages
- Handle pluralization - Use ICU format for count-dependent text
- Consider text length - Translations may be longer in other languages; design UI flexibly