i18n
Internationalization and translation management. Use when adding new translations, updating existing content, creating new language support, or working with locale routing.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for managing internationalization (i18n) and localization workflows within modern web applications. It streamlines the process of adding new language support, managing complex translation keys, and implementing locale-aware routing, ensuring a seamless multi-language experience while preventing technical issues like UTF-8 encoding errors in JSON files.
Use Cases
- Expanding a web application to new global markets by adding support for additional languages (e.g., French, German) through structured configuration and locale file creation.
- Updating existing UI text and microcopy across multiple locales simultaneously to ensure brand consistency and content accuracy.
- Implementing locale-aware routing and middleware to automatically detect and serve the correct language version based on user headers, cookies, or URL paths.
- Automating translation quality assurance by running validation scripts to identify missing keys, untranslated strings, or structural inconsistencies across different language files.
| name | i18n |
|---|---|
| description | Internationalization and translation management. Use when adding new translations, updating existing content, creating new language support, or working with locale routing. |
i18n (Internationalization)
CRITICAL: NEVER use the Edit tool on JSON locale files. Multi-byte UTF-8 characters (Japanese, etc.) cause crashes. ALWAYS use apply-inline command from the translation-helper.ts script to update translations.
Quick Reference
Directory Structure
i18n/
├── config.ts # Locale configuration, supported locales
└── (future files)
i18n.config.ts # next-intl request configuration
locales/
├── en/
│ └── common.json # English translations
└── es/
└── common.json # Spanish translations
app/[locale]/ # Locale-aware routes
├── layout.tsx # IntlProvider wrapper
├── page.tsx # Homepage
└── ... # Other pages
middleware.ts # Locale detection and routing
Key Files
| File | Purpose |
|---|---|
i18n/config.ts |
Defines SUPPORTED_LOCALES, DEFAULT_LOCALE, isValidLocale() |
i18n.config.ts |
next-intl server config with getRequestConfig |
locales/{locale}/common.json |
Translation strings organized by namespace |
middleware.ts |
Detects locale from URL/cookie/header, handles routing |
Adding New Translation Keys
1. Add to English First
Edit locales/en/common.json:
{
"namespace": {
"newKey": "English text here",
"anotherKey": "More text"
}
}
2. Add to All Other Locales
Edit locales/es/common.json (and any other supported locales):
{
"namespace": {
"newKey": "Texto en español aquí",
"anotherKey": "Más texto"
}
}
3. Use in Component
Client Component:
'use client';
import { useTranslations } from 'next-intl';
export function MyComponent() {
const t = useTranslations('namespace');
return <p>{t('newKey')}</p>;
}
Server Component:
import { getTranslations } from 'next-intl/server';
export default async function MyPage() {
const t = await getTranslations('namespace');
return <p>{t('newKey')}</p>;
}
Translation Namespaces
Organize translations by feature/area:
| Namespace | Purpose | Example Keys |
|---|---|---|
common |
Shared UI elements | loading, error, submit |
nav |
Navigation items | features, pricing, signIn |
footer |
Footer content | privacy, terms, copyright |
homepage |
Landing page | heroTitle, ctaButton |
dashboard |
Dashboard UI | credits, history, settings |
auth |
Auth forms | email, password, forgotPassword |
Adding a New Language
1. Update Locale Config
Edit i18n/config.ts:
export const SUPPORTED_LOCALES = ['en', 'es', 'fr'] as const; // Add 'fr'
export const locales = {
en: { label: 'English', country: 'US' },
es: { label: 'Español', country: 'ES' },
fr: { label: 'Français', country: 'FR' }, // Add French
} as const;
2. Add Flag Import (if using LocaleSwitcher)
Edit client/components/i18n/LocaleSwitcher.tsx:
import { US, ES, FR } from 'country-flag-icons/react/3x2';
const FlagComponents = {
US,
ES,
FR, // Add FR
} as const;
3. Create Translation File
Create locales/fr/common.json:
{
"nav": {
"features": "Fonctionnalités",
"pricing": "Tarifs"
}
// Copy structure from en/common.json and translate
}
4. Update Layout Metadata
Edit app/[locale]/layout.tsx alternates:
alternates: {
languages: {
en: '/',
es: '/es/',
fr: '/fr/', // Add French
},
},
Translating a Component
Before (Hardcoded)
export function PricingCard() {
return (
<div>
<h2>Pro Plan</h2>
<p>Best for professionals</p>
<button>Get Started</button>
</div>
);
}
After (Translated)
- Add translations to
locales/en/common.json:
{
"pricing": {
"proPlan": "Pro Plan",
"proDescription": "Best for professionals",
"getStarted": "Get Started"
}
}
- Add to
locales/es/common.json:
{
"pricing": {
"proPlan": "Plan Pro",
"proDescription": "Ideal para profesionales",
"getStarted": "Comenzar"
}
}
- Update component:
'use client';
import { useTranslations } from 'next-intl';
export function PricingCard() {
const t = useTranslations('pricing');
return (
<div>
<h2>{t('proPlan')}</h2>
<p>{t('proDescription')}</p>
<button>{t('getStarted')}</button>
</div>
);
}
Dynamic Values & Pluralization
Interpolation
{
"greeting": "Hello, {name}!"
}
t('greeting', { name: 'John' }); // "Hello, John!"
Pluralization
{
"credits": "{count, plural, =0 {No credits} =1 {1 credit} other {# credits}}"
}
t('credits', { count: 5 }); // "5 credits"
URL Routing Rules
| URL | Locale | Behavior |
|---|---|---|
/ |
en |
English (default, no prefix) |
/es/ |
es |
Spanish (explicit prefix) |
/pricing |
en |
Internally rewritten to /en/pricing |
/es/pricing |
es |
Spanish pricing page |
Middleware Locale Detection Priority
- URL Path -
/es/...→ Spanish - Cookie -
locale=es→ Spanish - Accept-Language Header - Browser preference
- Default - English (
en)
Common Issues
Translation Not Showing
- Check key exists in JSON file
- Verify namespace matches:
useTranslations('namespace') - Ensure component is wrapped by
NextIntlClientProvider - Check for typos in key path
Locale Not Switching
- Verify locale is in
SUPPORTED_LOCALES - Check middleware routing logic
- Ensure cookie is being set on switch
- Use
window.location.hrefnotrouter.pushfor full reload
Missing Translation Warning
If a key is missing, next-intl shows the key name. Add missing translations or use fallback:
t('maybeNotExist', { default: 'Fallback text' });
Checking Translations
Quick Check
Run the translation checker to verify completeness:
yarn i18n:check
This checks all locales for:
- Missing translation files
- Missing translation keys
- Untranslated content (values identical to English)
Script Options
The check script supports various options for targeted checks:
# Check specific locale only
yarn i18n:check --locale de
# Check specific namespace only
yarn i18n:check --namespace pricing
# Combine filters
yarn i18n:check --locale de --namespace pricing
# Output as JSON (useful for CI/CD)
yarn i18n:check --json
# Show verbose output (all checked items, not just missing)
yarn i18n:check --verbose
# Show debug info (file structure, key counts)
yarn i18n:check --debug
# Check only missing keys, not files
yarn i18n:check --keys-only
# Check only missing files, not keys
yarn i18n:check --files-only
Understanding the Report
The check report shows:
| Section | Description |
|---|---|
| Missing Files | Translation files that don't exist for a locale (e.g., pt/pricing.json missing) |
| Missing Keys | Keys present in English but missing in other locales |
| Untranslated Content | Keys where the value matches English exactly (may need translation) |
| Extra Files | Files in locale that don't exist in English (warnings) |
| Extra Keys | Keys in locale that don't exist in English (warnings) |
CI/CD Integration
The script exits with error code 1 if issues are found, making it suitable for CI/CD:
{
"scripts": {
"i18n:check": "tsx scripts/check-translations.ts",
"verify": "npm run tsc && npm run lint && npm run i18n:check"
}
}
Reference Locale
- Reference: English (
en) is the source of truth - All other locales are compared against English
- Always add new keys to English first, then translate
Common Workflows
Before committing translations:
yarn i18n:check
After adding new translation keys:
# Check all locales for the new keys
yarn i18n:check --namespace <your-namespace>
# Check specific locale
yarn i18n:check --locale pt --namespace <your-namespace>
Debugging translation issues:
# See full file structure and key counts
yarn i18n:check --debug
# See all checked items
yarn i18n:check --verbose
Checklist for New Translations
- Added key to
locales/en/common.json - Added translation to all other locale files (
es, etc.) - Used correct namespace in component
- Tested in all supported languages
- Ran
yarn i18n:checkto verify completeness - Ran
yarn verifybefore committing