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- Frenches- Spanishja- Japaneseko- Koreanzh- Chinese Simplifiedzh-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
localesarray - Example:
export const locales = ['en', 'fr', 'es', 'ko', 'zh', 'zh-tw', 'ja'] as const;
- Add the new locale code to the
-
Add language to LanguageSwitcher in
src/components/layout/NavBar/LanguageSwitcher.tsx:- Add a new entry to the
languagesarray with the locale key, native language label, andisAIflag - CRITICAL: Set
isAI: truefor all AI-generated translations (all new languages added via this skill) - CRITICAL: The
languagesarray MUST be ordered alphabetically by thekeyfield (locale code) - Example:
typescript
1{ 2 key: 'ja', 3 label: '日本語', 4 isAI: true, 5} - This adds an "AI" superscript indicator in the language dropdown to inform users the translation is AI-generated
- Add a new entry to the
-
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
- Copy the structure from
-
Translate blog content:
- For each article in
src/bundled_static/content/blog/[article]/ - Create a new
[locale].mdxfile (e.g.,ja.mdx) - Translate the MDX content including frontmatter and body
- Keep frontmatter structure consistent (title, slug, excerpt, etc.)
- For each article in
-
Configure fonts (if needed):
- Check if the language requires a specific Noto font (e.g.,
Noto Sans JPfor 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
- Check if the language requires a specific Noto font (e.g.,
-
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
- Use nested keys for organization (e.g.,
-
Add to all other locale files:
- Add the same key structure to every
messages/[locale].json - Translate the value appropriately for each language
- Add the same key structure to every
-
Use in components:
typescript1import { 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:checkcommand
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:
yaml1--- 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-latinvariable 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 consistencypnpm 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.tsxandsrc/app/globals.css
Key Patterns
typescript1// 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}