i18n Translation Skill
Translate the Next.js web application to different languages using next-intl.
Project Structure
web/
├── src/
│ ├── i18n/
│ │ ├── locales.ts # Locale definitions and types
│ │ ├── request.ts # next-intl request configuration
│ │ └── routing.ts # Routing configuration
│ ├── app/
│ │ └── [locale]/ # Locale-based routes
│ │ └── layout.tsx # Layout with font configuration
│ └── bundled_static/
│ └── content/blog/ # Blog content by article and locale
│ └── [article]/
│ ├── en.mdx
│ ├── fr.mdx
│ └── ...
└── messages/
├── en.json # English (source of truth)
├── en.d.json.ts # TypeScript definitions
├── fr.json # French translations
├── es.json # Spanish translations
├── ja.json # Japanese translations
├── ko.json # Korean translations
├── zh.json # Chinese Simplified translations
└── zh-tw.json # Chinese Traditional translations
Supported Locales
Current supported locales are defined in src/i18n/locales.ts:
en - English (source of truth)
fr - French
es - Spanish
ja - Japanese
ko - Korean
zh - Chinese Simplified
zh-tw - Chinese Traditional
Workflows
Adding a New Locale
Follow these steps to add a new language to the application:
-
Update locale definitions in src/i18n/locales.ts:
- Add the new locale code to the
locales array
- Example:
export const locales = ['en', 'fr', 'es', 'ko', 'zh', 'zh-tw', 'ja'] as const;
-
Add language to LanguageSwitcher in src/components/layout/NavBar/LanguageSwitcher.tsx:
-
Create message file at messages/[locale].json:
- Copy the structure from
messages/en.json (source of truth)
- Translate all strings to the target language
- Maintain the same JSON structure and key names
-
Translate blog content:
- For each article in
src/bundled_static/content/blog/[article]/
- Create a new
[locale].mdx file (e.g., ja.mdx)
- Translate the MDX content including frontmatter and body
- Keep frontmatter structure consistent (title, slug, excerpt, etc.)
-
Configure fonts (if needed):
- Check if the language requires a specific Noto font (e.g.,
Noto Sans JP for Japanese)
- If needed, import the font in
src/app/[locale]/layout.tsx
- Add the font variable to
src/app/globals.css
- Note: Latin-specific fonts (like Baikal) have dedicated variables (e.g.,
--font-condensed-latin) for design-specific usage
-
Update this skill documentation (.agents/skills/i18n-translation/SKILL.md):
- Add the new locale to the "Supported Locales" section
- Maintain alphabetical order by locale code for consistency
- Example:
- 'ja' - Japanese
-
Restart development server for changes to take effect
Translating Existing Strings
To translate strings that are already defined in English:
- Locate the key in
messages/en.json
- Find the same key in the target locale file (e.g.,
messages/fr.json)
- Translate the value while preserving:
- JSON structure
- Key names
- Variable placeholders (e.g.,
{count}, {name})
- HTML entities if present
Adding New Translation Strings
When new UI text is added to the application:
-
Add to English source (messages/en.json):
- Use nested keys for organization (e.g.,
"HomePage": { "title": "..." })
- Choose clear, descriptive key names
-
Add to all other locale files:
- Add the same key structure to every
messages/[locale].json
- Translate the value appropriately for each language
-
Use in components:
typescript
1import { useTranslations } from 'next-intl';
2
3export function MyComponent() {
4 const t = useTranslations('HomePage');
5 return <h1>{t('title')}</h1>;
6}
-
Verify consistency using pnpm i18n:check command
Translating Blog Articles
Blog articles are stored as MDX files organized by article and locale:
-
Navigate to src/bundled_static/content/blog/[article]/
-
Create or edit the locale-specific MDX file (e.g., fr.mdx)
-
Translate frontmatter:
yaml
1---
2title: "Titre de l'article"
3slug: "slug-url"
4excerpt: "Courte description"
5image: "cover.avif"
6ogImage: "cover.jpg"
7createdAt: "2025-07-03"
8updatedAt: "2025-07-03"
9---
-
Translate body content:
- Translate all markdown content
- Keep code blocks and syntax intact
- Maintain heading structure and links
-
Restart dev server after making changes for them to take effect
Fixing Missing Translations
To identify and fix missing translations:
- Run consistency check:
pnpm i18n:check
- Review errors showing missing keys or locale files
- Add missing translations to the appropriate locale files
- Ensure structure matches
messages/en.json
Best Practices
Translation Quality
- Maintain natural phrasing in the target language, not literal translations
- Preserve tone and voice consistent with the brand
- Keep technical terms consistent (e.g., "API", "GitHub")
- Use proper capitalization and punctuation for the target language
- Consider cultural context for idioms and expressions
JSON Structure
- Always preserve the nested key structure from English
- Use double quotes for JSON strings
- Maintain proper indentation (2 spaces)
- Keep keys in the same order as the English file for easier comparison
- Do not include comments in JSON files
Font Configuration
- Latin scripts (English, French, Spanish): Use default fonts
- CJK languages (Chinese, Japanese, Korean): Configure Noto CJK fonts
- Right-to-left languages (Arabic, Hebrew): Ensure RTL support is configured
- Use
--font-condensed-latin variable for design-specific Latin fonts regardless of locale
Blog Content
- Translate all frontmatter fields except dates and image paths
- Keep slug values in the target language for SEO
- Maintain markdown formatting (headings, lists, links)
- Preserve code blocks without translation
- Keep image paths and links functional
Validation
After making translation changes:
- Run linting:
pnpm run lint --fix
- Check i18n consistency:
pnpm i18n:check
- Run tests:
pnpm run test:unit
- Restart dev server:
pnpm run dev
- Verify changes in browser for each affected locale
Common Issues
Missing Translation Keys
Symptom: Console warnings about missing translation keys
Solution: Add the missing key to all locale files with appropriate translations
Inconsistent JSON Structure
Symptom: i18n:check fails with structure errors
Solution: Ensure all locale files have the same nested key structure as en.json
Blog Content Not Updating
Symptom: Changes to MDX files not reflected in the app
Solution: Restart the development server (pnpm run dev)
Font Not Displaying Correctly
Symptom: Characters appear with wrong font or as boxes
Solution: Verify the correct Noto font is imported and configured in layout.tsx and globals.css
Build Failures After Translation
Symptom: Build fails with MDX processing errors
Solution: Check MDX frontmatter syntax and ensure all required fields are present
Quick Reference
Commands
pnpm i18n:check - Check translation consistency
pnpm run dev - Start dev server (restart after blog changes)
pnpm run build - Production build (validates all content)
File Locations
- Locale definitions:
src/i18n/locales.ts
- Language switcher:
src/components/layout/NavBar/LanguageSwitcher.tsx
- Message files:
messages/[locale].json
- Blog content:
src/bundled_static/content/blog/[article]/[locale].mdx
- Font config:
src/app/[locale]/layout.tsx and src/app/globals.css
Key Patterns
typescript
1// In components
2const t = useTranslations('SectionName');
3const text = t('keyName');
4
5// Message structure
6{
7 "SectionName": {
8 "keyName": "Translated text"
9 }
10}
11
12// LanguageSwitcher entry for new locale
13{
14 key: 'ja', // Locale code (array MUST be sorted alphabetically by this key)
15 label: '日本語', // Native language name
16 isAI: true, // Always true for AI-generated translations
17}