# Managing Translations

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

*Canonical: https://makerkit.dev/docs/nextjs-drizzle/internationalization/managing-translations*

---

This guide covers how to manage translations in your application, from adding new messages to supporting multiple languages.

## Adding New Messages

### 1. Identify the Namespace

Translation files are organized by namespace in `apps/web/i18n/messages/{locale}/`:

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

Choose the namespace that best matches where your translation will be used.

### 2. Add the Translation Key

Open the appropriate namespace file and add your key:

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

### 3. Use the Translation

```typescript
// In a Server Component
const t = await getTranslations('common');
const title = t('newFeature.title');

// In a Client Component
const t = useTranslations('common');
const title = t('newFeature.title');

// With Trans component
<Trans i18nKey="common.newFeature.title" />
```

## Key Naming Conventions

Follow these conventions for consistent translation keys:

| Convention | Example | Use Case |
|------------|---------|----------|
| camelCase | `pageTitle` | All keys |
| Nested objects | `dialog.title` | Related translations |
| Descriptive names | `createProjectButton` | Self-documenting keys |
| Component prefix | `inviteDialog.title` | Component-specific text |

**Example structure:**

```json
{
  "createProject": {
    "title": "Create a New Project",
    "description": "Start a new project to organize your work",
    "form": {
      "nameLabel": "Project Name",
      "namePlaceholder": "Enter project name",
      "submitButton": "Create Project",
      "cancelButton": "Cancel"
    },
    "success": "Project created successfully",
    "error": "Failed to create project"
  }
}
```

## Adding New Namespaces

When a feature grows large enough to warrant its own namespace:

### 1. Register the Namespace

Edit `apps/web/i18n/request.ts`:

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

### 2. Create the Translation File

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

```json
{
  "pageTitle": "Projects",
  "pageDescription": "Manage your projects",
  "createProject": "Create Project",
  "noProjects": "No projects yet",
  "deleteConfirmation": "Are you sure you want to delete this project?",
  "table": {
    "name": "Name",
    "status": "Status",
    "created": "Created",
    "actions": "Actions"
  }
}
```

### 3. Create Files for Other Locales

If you support multiple languages, create the same file for each locale:

```bash
# Copy as template
cp apps/web/i18n/messages/en/projects.json apps/web/i18n/messages/es/projects.json
```

Then translate the Spanish version.

## Adding New Languages

### 1. Add Locale to Configuration

Edit `packages/i18n/src/locales.tsx`:

```typescript
import { defaultLocale } from './default-locale';

export const locales: string[] = [
  defaultLocale,
  'es', // Spanish
  'fr', // French
  'de', // German
];
```

### 2. Create Translation Directory

```bash
# Create the locale folder
mkdir -p apps/web/i18n/messages/es

# Copy all English files as templates
cp apps/web/i18n/messages/en/*.json apps/web/i18n/messages/es/
```

### 3. Translate Files

Translate each JSON file in the new locale directory:

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

### 4. Add Email Template Translations (Optional)

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:

| Code | Language | Native Name |
|------|----------|-------------|
| `en` | English | English |
| `es` | Spanish | Espanol |
| `fr` | French | Francais |
| `de` | German | Deutsch |
| `it` | Italian | Italiano |
| `pt` | Portuguese | Portugues |
| `ja` | Japanese | Nihongo |
| `zh` | Chinese | Zhongwen |
| `ko` | Korean | Hangugeo |
| `ar` | Arabic | Al-Arabiyyah |

## Language Selector UI

MakerKit includes a language selector component that automatically appears when multiple languages are configured.

### Location

The language preference is available at `/settings/preferences`. This page only appears when 2 or more languages are configured.

Page file: `apps/web/app/[locale]/(internal)/settings/preferences/page.tsx`

### LanguagePreferenceCard Component

The `@kit/ui/language-selector` package provides the language selector:

```typescript
import { LanguagePreferenceCard } from '@kit/ui/language-selector';
import { locales } from '@kit/i18n/routing';

function PreferencesPage() {
  return (
    <LanguagePreferenceCard locales={locales} />
  );
}
```

**Behavior:**

- Shows button grid for 3 or fewer languages
- Shows dropdown selector for 4+ languages
- Displays native language names using `Intl.DisplayNames`
- Uses React transitions for smooth loading states

### How Language Switching Works

When a user selects a new language:

1. `useRouter().replace()` navigates to the same path with the new locale
2. The middleware updates the locale cookie
3. The page re-renders with the new language

```typescript
// Internal implementation
function useChangeLocale() {
  const pathname = usePathname();
  const router = useRouter();

  return useCallback((locale: string) => {
    startTransition(() => {
      router.replace(pathname, { locale });
    });
  }, [router, pathname]);
}
```

### Visibility Rules

The language card only renders when `locales.length > 1`. With a single language configured (the default), the preference card is hidden.

## How Language is Stored

Language preference uses URL-based routing:

| URL | Locale |
|-----|--------|
| `/about` | English (default, no prefix) |
| `/es/about` | Spanish |
| `/fr/about` | French |

The middleware also sets a `NEXT_LOCALE` cookie to remember the user's preference for subsequent visits.

## Translation Workflow

### Development Workflow

1. **Add keys in English first** in the appropriate namespace
2. **Use the translation** in your component
3. **Test the UI** to verify text displays correctly
4. **Add translations** for other supported languages

### Production Considerations

- **Missing translations**: Fall back to the key name and log warnings in development
- **Long text**: Some languages require more space (German is typically 30% longer than English)
- **RTL languages**: Consider layout implications for Arabic, Hebrew, etc.
- **Pluralization**: Test plural forms with different counts

### Translation Management Tools

For larger projects, consider using translation management platforms:

- **Crowdin** - Collaborative translation
- **Lokalise** - Developer-focused TMS
- **Phrase** - Enterprise translation management

These tools can sync with your JSON files and provide translator interfaces.

## Best Practices

### Organization

- **Keep namespaces focused**: One namespace per feature area
- **Use nested objects**: Group related translations
- **Consistent naming**: Follow the same patterns across namespaces
- **Descriptive keys**: Keys should indicate where they're used

### Content

- **Avoid concatenation**: Don't build sentences from fragments
- **Include context**: Add comments for translators when meaning is ambiguous
- **Handle plurals properly**: Use ICU format for count-dependent text
- **Test all locales**: Verify translations display correctly

### Technical

- **Validate JSON**: Ensure all translation files are valid JSON
- **Keep files in sync**: All locales should have the same keys
- **Use TypeScript**: Let the compiler catch missing keys
- **Test with long strings**: Some languages are more verbose

## Common Mistakes

### String Concatenation

**Bad:**
```typescript
// Don't concatenate translated strings
t('hello') + ' ' + t('world')
```

**Good:**
```json
{
  "greeting": "Hello, {name}!"
}
```

```typescript
t('greeting', { name: 'World' })
```

### Hardcoded Text in Components

**Bad:**
```typescript
<Button>Submit</Button>
```

**Good:**
```typescript
<Button>{t('submitButton')}</Button>
```

### Inconsistent Key Naming

**Bad:**
```json
{
  "page_title": "...",
  "PageDescription": "...",
  "button-label": "..."
}
```

**Good:**
```json
{
  "pageTitle": "...",
  "pageDescription": "...",
  "buttonLabel": "..."
}
```
