Claude-skill-registry i18n-manager
Expert in internationalization (i18n) and localization (l10n) with i18next, react-intl, multi-language support, RTL layouts, locale-specific formatting, translation management, and automated text extraction
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/i18n-manager" ~/.claude/skills/majiayu000-claude-skill-registry-i18n-manager && rm -rf "$T"
manifest:
skills/data/i18n-manager/SKILL.mdsource content
Internationalization Manager
Expert skill for building multi-language applications with complete i18n/l10n support. Specializes in i18next, react-intl, RTL layouts, locale formatting, translation workflows, and automated text extraction.
Core Capabilities
1. i18n Setup & Configuration
- i18next: Most popular i18n framework
- react-i18next: React integration
- react-intl: FormatJS implementation
- next-i18next: Next.js integration
- vue-i18n: Vue.js integration
- Language Detection: Browser, URL, cookie
- Namespace Organization: Modular translations
2. Translation Management
- JSON Format: Simple key-value translations
- Nested Keys: Hierarchical organization
- Pluralization: ICU message format
- Interpolation: Dynamic values in translations
- Context: Gender, formality variations
- Fallback: Missing translation handling
- Lazy Loading: Load translations on demand
3. RTL (Right-to-Left) Support
- Arabic: العربية
- Hebrew: עברית
- Persian: فارسی
- Urdu: اردو
- CSS Logical Properties: start/end vs left/right
- Mirroring: Icons, layouts, animations
- BiDi Text: Mixed LTR/RTL content
4. Locale-Specific Formatting
- Dates: Intl.DateTimeFormat
- Numbers: Intl.NumberFormat
- Currency: Format with symbols
- Relative Time: "2 hours ago"
- Lists: Conjunction formatting
- Phone Numbers: Country-specific formats
- Addresses: Locale-specific orders
5. Text Extraction & Automation
- Hardcoded Text Detection: Find non-i18n strings
- Key Generation: Auto-generate translation keys
- Missing Translations: Detect untranslated keys
- Unused Keys: Find and remove dead translations
- Translation Coverage: Report per-language coverage
- CI/CD Integration: Automated checks
6. Translation Workflow
- Developer Flow: Mark strings for translation
- Translator Flow: Provide context and examples
- Review Flow: Approval process
- Update Flow: Handle translation updates
- Version Control: Track translation changes
- Translation Services: Crowdin, Lokalise, POEditor
7. SEO & Performance
- URL Localization: /en/about, /es/acerca
- Meta Tags: Translated titles, descriptions
- hreflang: Search engine language hints
- Language Switcher: User language selection
- Code Splitting: Load only needed translations
- Caching: CDN and browser caching
Workflow
Phase 1: i18n Planning
-
Define Requirements
- Which languages? (start with 2-3)
- RTL support needed?
- Date/currency formats?
- Translation workflow?
-
Choose Solution
- i18next (flexible, popular)
- react-intl (FormatJS, enterprise)
- Custom solution
- Library integration
-
Plan Structure
- Translation file organization
- Namespace strategy
- Key naming convention
- Fallback language
Phase 2: Implementation
-
Setup i18n
- Install dependencies
- Configure i18n
- Create translation files
- Add language detector
-
Add Translations
- Extract hardcoded text
- Generate translation keys
- Create base language
- Add fallback translations
-
Implement RTL
- Add dir="rtl" support
- Convert CSS to logical properties
- Mirror icons/images
- Test layout
-
Add Formatting
- Date formatting
- Number formatting
- Currency formatting
- Relative time
Phase 3: Translation Workflow
-
Developer Tools
- Missing key warnings
- Translation extraction
- Key generation scripts
- Hot reload translations
-
Translator Tools
- Translation platform integration
- Context screenshots
- Translation memory
- Plural/gender rules
-
Quality Assurance
- Translation coverage reports
- Unused key detection
- Consistency checks
- Automated tests
Implementation Patterns
i18next Setup
// i18n.ts import i18n from 'i18next' import { initReactI18next } from 'react-i18next' import LanguageDetector from 'i18next-browser-languagedetector' import Backend from 'i18next-http-backend' i18n // Load translations from backend .use(Backend) // Detect user language .use(LanguageDetector) // Pass i18n instance to react-i18next .use(initReactI18next) // Initialize .init({ // Fallback language fallbackLng: 'en', // Supported languages supportedLngs: ['en', 'es', 'fr', 'de', 'ar', 'he'], // Debug mode (development only) debug: process.env.NODE_ENV === 'development', // Interpolation options interpolation: { escapeValue: false, // React already escapes }, // Backend options backend: { loadPath: '/locales/{{lng}}/{{ns}}.json', }, // Detection options detection: { order: ['querystring', 'cookie', 'localStorage', 'navigator'], caches: ['localStorage', 'cookie'], }, // Namespaces ns: ['common', 'auth', 'dashboard'], defaultNS: 'common', // React options react: { useSuspense: true, }, }) export default i18n
Translation Files
// public/locales/en/common.json { "app": { "title": "My Application", "description": "Welcome to our amazing app" }, "navigation": { "home": "Home", "about": "About", "contact": "Contact" }, "actions": { "save": "Save", "cancel": "Cancel", "delete": "Delete", "confirm": "Confirm" }, "messages": { "success": "Operation completed successfully", "error": "An error occurred", "loading": "Loading..." }, "validation": { "required": "This field is required", "email": "Please enter a valid email", "minLength": "Must be at least {{count}} characters" } } // public/locales/en/auth.json { "login": { "title": "Log In", "email": "Email Address", "password": "Password", "submit": "Log In", "forgotPassword": "Forgot password?", "noAccount": "Don't have an account?", "signUp": "Sign up" }, "register": { "title": "Create Account", "submit": "Sign Up", "hasAccount": "Already have an account?", "logIn": "Log in" } }
Using Translations in Components
// Component.tsx import { useTranslation } from 'react-i18next' export function Welcome() { const { t, i18n } = useTranslation('common') return ( <div> {/* Simple translation */} <h1>{t('app.title')}</h1> <p>{t('app.description')}</p> {/* Translation with interpolation */} <p>{t('validation.minLength', { count: 8 })}</p> {/* Current language */} <p>Language: {i18n.language}</p> {/* Change language */} <button onClick={() => i18n.changeLanguage('es')}> Español </button> </div> ) }
Pluralization
// Pluralization in i18next (ICU format) { "items": "{{count}} item", "items_one": "{{count}} item", "items_other": "{{count}} items", // Complex pluralization "cart": { "zero": "Your cart is empty", "one": "You have {{count}} item in your cart", "other": "You have {{count}} items in your cart" } }
// Usage const { t } = useTranslation() <p>{t('items', { count: 0 })}</p> // "0 items" <p>{t('items', { count: 1 })}</p> // "1 item" <p>{t('items', { count: 5 })}</p> // "5 items"
Context-Aware Translations
{ "friend": "Friend", "friend_male": "Friend (male)", "friend_female": "Friend (female)", "welcome": "Welcome", "welcome_formal": "Welcome (formal)", "welcome_informal": "Hi there!" }
// Usage with context const { t } = useTranslation() <p>{t('friend', { context: 'male' })}</p> // "Friend (male)" <p>{t('welcome', { context: 'formal' })}</p> // "Welcome (formal)"
RTL Support
// RTLProvider.tsx import { useEffect } from 'react' import { useTranslation } from 'react-i18next' const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur'] export function RTLProvider({ children }: { children: React.ReactNode }) { const { i18n } = useTranslation() useEffect(() => { const isRTL = RTL_LANGUAGES.includes(i18n.language) document.documentElement.dir = isRTL ? 'rtl' : 'ltr' document.documentElement.lang = i18n.language }, [i18n.language]) return <>{children}</> } // App.tsx import { RTLProvider } from './RTLProvider' function App() { return ( <RTLProvider> <YourApp /> </RTLProvider> ) }
RTL-Aware CSS
/* ❌ BAD - Hard-coded direction */ .sidebar { margin-left: 20px; text-align: left; } /* ✅ GOOD - Logical properties (auto RTL) */ .sidebar { margin-inline-start: 20px; text-align: start; } /* ✅ GOOD - RTL-specific rules */ [dir="rtl"] .sidebar { margin-right: 20px; margin-left: 0; } /* ✅ GOOD - CSS logical properties */ .container { padding-inline: 20px; /* horizontal padding */ padding-block: 10px; /* vertical padding */ border-inline-start: 1px; /* left border (LTR), right border (RTL) */ border-inline-end: 1px; /* right border (LTR), left border (RTL) */ }
Date Formatting
// DateFormatter.tsx import { useTranslation } from 'react-i18next' export function DateFormatter({ date }: { date: Date }) { const { i18n } = useTranslation() // Automatic locale-based formatting const formatted = new Intl.DateTimeFormat(i18n.language, { year: 'numeric', month: 'long', day: 'numeric', }).format(date) return <time dateTime={date.toISOString()}>{formatted}</time> } // Usage <DateFormatter date={new Date('2024-01-15')} /> // en: "January 15, 2024" // es: "15 de enero de 2024" // fr: "15 janvier 2024" // de: "15. Januar 2024"
Number & Currency Formatting
// NumberFormatter.tsx import { useTranslation } from 'react-i18next' export function CurrencyFormatter({ amount, currency = 'USD', }: { amount: number currency?: string }) { const { i18n } = useTranslation() const formatted = new Intl.NumberFormat(i18n.language, { style: 'currency', currency, }).format(amount) return <span>{formatted}</span> } // Usage <CurrencyFormatter amount={1234.56} currency="USD" /> // en-US: "$1,234.56" // de-DE: "1.234,56 $" // fr-FR: "1 234,56 $US" <CurrencyFormatter amount={1234.56} currency="EUR" /> // en-US: "€1,234.56" // de-DE: "1.234,56 €" // fr-FR: "1 234,56 €"
Language Switcher
// LanguageSwitcher.tsx import { useTranslation } from 'react-i18next' const LANGUAGES = [ { code: 'en', name: 'English', flag: '🇬🇧' }, { code: 'es', name: 'Español', flag: '🇪🇸' }, { code: 'fr', name: 'Français', flag: '🇫🇷' }, { code: 'de', name: 'Deutsch', flag: '🇩🇪' }, { code: 'ar', name: 'العربية', flag: '🇸🇦', rtl: true }, { code: 'he', name: 'עברית', flag: '🇮🇱', rtl: true }, ] export function LanguageSwitcher() { const { i18n } = useTranslation() const changeLanguage = async (lng: string) => { await i18n.changeLanguage(lng) // Update URL (optional) const url = new URL(window.location.href) url.searchParams.set('lng', lng) window.history.pushState({}, '', url) // Update dir attribute const isRTL = LANGUAGES.find((l) => l.code === lng)?.rtl document.documentElement.dir = isRTL ? 'rtl' : 'ltr' } return ( <div className="language-switcher"> <select value={i18n.language} onChange={(e) => changeLanguage(e.target.value)} aria-label="Select language" > {LANGUAGES.map((lang) => ( <option key={lang.code} value={lang.code}> {lang.flag} {lang.name} </option> ))} </select> </div> ) }
Translation Extraction Script
// scripts/extract-translations.ts import fs from 'fs' import path from 'path' import { glob } from 'glob' interface Translation { key: string defaultValue: string file: string line: number } async function extractTranslations() { const translations: Translation[] = [] // Find all source files const files = await glob('src/**/*.{ts,tsx}') for (const file of files) { const content = fs.readFileSync(file, 'utf-8') const lines = content.split('\n') // Find t('key') or t("key") calls const tRegex = /t\(['"]([^'"]+)['"]/g const matches = content.matchAll(tRegex) for (const match of matches) { const key = match[1] const lineNumber = content.substring(0, match.index).split('\n').length translations.push({ key, defaultValue: '', // Extract from default value if present file, line: lineNumber, }) } } // Group by namespace const byNamespace: Record<string, Translation[]> = {} for (const trans of translations) { const [namespace] = trans.key.split('.') if (!byNamespace[namespace]) { byNamespace[namespace] = [] } byNamespace[namespace].push(trans) } // Output JSON files for (const [namespace, keys] of Object.entries(byNamespace)) { const outputPath = path.join('public', 'locales', 'en', `${namespace}.json`) // Load existing translations let existing: Record<string, any> = {} if (fs.existsSync(outputPath)) { existing = JSON.parse(fs.readFileSync(outputPath, 'utf-8')) } // Add new keys for (const { key } of keys) { const keyPath = key.split('.') let current = existing for (let i = 1; i < keyPath.length; i++) { const part = keyPath[i] if (i === keyPath.length - 1) { if (!current[part]) { current[part] = `TODO: Translate ${key}` } } else { if (!current[part]) { current[part] = {} } current = current[part] } } } // Write back fs.writeFileSync(outputPath, JSON.stringify(existing, null, 2)) } console.log(`✅ Extracted ${translations.length} translation keys`) } extractTranslations()
Missing Translation Detection
// scripts/check-translations.ts import fs from 'fs' import path from 'path' const SUPPORTED_LANGUAGES = ['en', 'es', 'fr', 'de', 'ar', 'he'] const NAMESPACES = ['common', 'auth', 'dashboard'] function loadTranslations(lang: string, namespace: string): Record<string, any> { const filePath = path.join('public', 'locales', lang, `${namespace}.json`) if (!fs.existsSync(filePath)) { return {} } return JSON.parse(fs.readFileSync(filePath, 'utf-8')) } function getAllKeys(obj: Record<string, any>, prefix = ''): string[] { let keys: string[] = [] for (const [key, value] of Object.entries(obj)) { const fullKey = prefix ? `${prefix}.${key}` : key if (typeof value === 'object' && value !== null) { keys = keys.concat(getAllKeys(value, fullKey)) } else { keys.push(fullKey) } } return keys } function checkTranslations() { const baseLanguage = 'en' let hasErrors = false for (const namespace of NAMESPACES) { const baseTranslations = loadTranslations(baseLanguage, namespace) const baseKeys = new Set(getAllKeys(baseTranslations)) console.log(`\n📦 Namespace: ${namespace}`) console.log(` Base keys (${baseLanguage}): ${baseKeys.size}`) for (const lang of SUPPORTED_LANGUAGES) { if (lang === baseLanguage) continue const translations = loadTranslations(lang, namespace) const keys = new Set(getAllKeys(translations)) // Missing keys const missing = Array.from(baseKeys).filter((key) => !keys.has(key)) // Extra keys (not in base) const extra = Array.from(keys).filter((key) => !baseKeys.has(key)) if (missing.length > 0 || extra.length > 0) { hasErrors = true console.log(`\n ❌ ${lang}:`) if (missing.length > 0) { console.log(` Missing ${missing.length} keys:`) missing.slice(0, 5).forEach((key) => console.log(` - ${key}`)) if (missing.length > 5) { console.log(` ... and ${missing.length - 5} more`) } } if (extra.length > 0) { console.log(` Extra ${extra.length} keys:`) extra.slice(0, 5).forEach((key) => console.log(` - ${key}`)) } } else { console.log(` ✅ ${lang}: Complete (${keys.size} keys)`) } } } if (hasErrors) { console.log('\n❌ Translation check failed\n') process.exit(1) } else { console.log('\n✅ All translations are complete!\n') } } checkTranslations()
Best Practices
Translation Keys
// ✅ GOOD - Hierarchical, descriptive keys t('auth.login.title') t('dashboard.widgets.revenue.title') t('errors.validation.required') // ❌ BAD - Flat, unclear keys t('loginTitle') t('title1') t('err1')
Context & Examples
{ "button": { "save": "Save", "_comment": "Button text for saving changes", "_example": "Click 'Save' to save your changes" } }
Avoid HTML in Translations
// ❌ BAD { "message": "Click <a href='/help'>here</a> for help" } // ✅ GOOD { "message": "Click {{link}} for help", "link": "here" }
// Usage <Trans i18nKey="message"> Click <a href="/help">{{link: t('link')}}</a> for help </Trans>
Pluralization
{ "items_zero": "No items", "items_one": "{{count}} item", "items_other": "{{count}} items" }
Gender & Formality
{ "welcome_male": "Welcome, Mr. {{name}}", "welcome_female": "Welcome, Ms. {{name}}", "welcome_formal": "Good day, {{name}}", "welcome_informal": "Hey {{name}}!" }
When to Use This Skill
Activate this skill when you need to:
- Add multi-language support to an application
- Setup i18next or react-intl
- Implement RTL layouts
- Extract hardcoded text for translation
- Create translation management workflows
- Add locale-specific formatting (dates, numbers, currency)
- Build language switchers
- Integrate with translation services (Crowdin, Lokalise)
- Detect missing translations
- Optimize i18n performance
Integration with Agents
// Agent Explore → Find all hardcoded strings // i18n-manager → Extract and create translation keys // Example workflow: 1. Agent scans codebase for hardcoded text 2. Agent finds: 150 strings not using t() 3. i18n-manager generates translation keys 4. i18n-manager creates JSON files for all languages 5. Agent verifies: All text now translatable
Output Format
When implementing i18n, provide:
- Complete i18n Setup: Configuration files
- Translation Files: JSON with all keys
- Component Integration: How to use translations
- RTL Support: CSS and layout changes
- Extraction Scripts: Automated text extraction
- Testing Guide: How to test translations
- Documentation: Translation workflow for team
Always build applications that are accessible to users worldwide in their native language.