i18n Quick Reference Guide
A condensed reference for common i18n tasks, patterns, and troubleshooting. Keep this handy during development and deployment.
Quick Commands
IntlPull CLI
Terminal
1# Validate before deployment
2npx @intlpullhq/cli validate --all
3
4# Check translation coverage
5npx @intlpullhq/cli status
6
7# Download translations
8npx @intlpullhq/cli download --branch main --languages en,es,fr
9
10# Upload new keys
11npx @intlpullhq/cli upload
12
13# Find missing translations
14npx @intlpullhq/cli check
15
16# Auto-fix missing translations
17npx @intlpullhq/cli fixGit Workflow
Terminal
1# Create feature branch for new language
2git checkout -b feature/add-spanish
3
4# Check what changed
5npx @intlpullhq/cli diff
6
7# Commit translations
8git add locales/
9git commit -m "feat(i18n): add Spanish translations"Common Patterns
React (next-intl)
TSX
1import { useTranslations } from 'next-intl';
2
3export default function Component() {
4 const t = useTranslations('namespace');
5
6 // Simple translation
7 <h1>{t('title')}</h1>
8
9 // With variables
10 <p>{t('welcome', { name: user.name })}</p>
11
12 // With pluralization
13 <span>{t('items', { count: items.length })}</span>
14
15 // With rich text
16 <p>{t.rich('terms', {
17 link: (chunks) => <a href="/terms">{chunks}</a>
18 })}</p>
19}React (react-i18next)
TSX
1import { useTranslation } from 'react-i18next';
2
3export default function Component() {
4 const { t } = useTranslation('namespace');
5
6 // Simple translation
7 <h1>{t('title')}</h1>
8
9 // With variables
10 <p>{t('welcome', { name: user.name })}</p>
11
12 // With pluralization
13 <span>{t('items', { count: items.length })}</span>
14}Vue (vue-i18n)
VUE
1<template>
2 <!-- Simple translation -->
3 <h1>{{ $t('title') }}</h1>
4
5 <!-- With variables -->
6 <p>{{ $t('welcome', { name: user.name }) }}</p>
7
8 <!-- With pluralization -->
9 <span>{{ $tc('items', items.length) }}</span>
10</template>
11
12<script setup>
13import { useI18n } from 'vue-i18n';
14
15const { t, tc } = useI18n();
16</script>Backend (Node.js)
TypeScript
1import i18next from 'i18next';
2
3// Initialize
4await i18next.init({
5 lng: 'en',
6 resources: {
7 en: { translation: require('./locales/en.json') },
8 es: { translation: require('./locales/es.json') },
9 },
10});
11
12// Use in API responses
13app.get('/api/error', (req, res) => {
14 const lang = req.headers['accept-language'] || 'en';
15 res.json({
16 error: i18next.t('errors.not_found', { lng: lang })
17 });
18});Translation File Formats
JSON (Flat)
JSON
1{
2 "auth.login.title": "Sign In",
3 "auth.login.button": "Log In",
4 "auth.login.forgot": "Forgot password?"
5}JSON (Nested)
JSON
1{
2 "auth": {
3 "login": {
4 "title": "Sign In",
5 "button": "Log In",
6 "forgot": "Forgot password?"
7 }
8 }
9}JSON (with Pluralization)
JSON
1{
2 "items": {
3 "zero": "No items",
4 "one": "{{count}} item",
5 "other": "{{count}} items"
6 }
7}YAML
YAML
1auth:
2 login:
3 title: Sign In
4 button: Log In
5 forgot: Forgot password?ARB (Flutter)
JSON
1{
2 "@@locale": "en",
3 "loginTitle": "Sign In",
4 "@loginTitle": {
5 "description": "Title for login page"
6 },
7 "welcomeMessage": "Welcome, {name}!",
8 "@welcomeMessage": {
9 "description": "Welcome message with user name",
10 "placeholders": {
11 "name": {
12 "type": "String"
13 }
14 }
15 }
16}Key Naming Conventions
Recommended Structure
{namespace}.{section}.{element}.{variant}
Examples
auth.login.button.submit
auth.login.button.cancel
auth.login.error.invalid_credentials
auth.signup.form.email.label
auth.signup.form.email.placeholder
auth.signup.form.email.error.invalid
dashboard.header.nav.home
dashboard.header.nav.settings
dashboard.header.user.greeting
errors.validation.required
errors.validation.email.invalid
errors.validation.password.too_short
common.button.save
common.button.cancel
common.button.delete
common.loading
common.error.generic
Anti-Patterns (Avoid)
button1 // Not descriptive
text2 // Not descriptive
loginButtonText // Inconsistent casing
auth_login_button // Inconsistent separator
authLoginButton // Inconsistent format
Locale Codes
Common Locales
en English (generic)
en-US English (United States)
en-GB English (United Kingdom)
es Spanish (generic)
es-ES Spanish (Spain)
es-MX Spanish (Mexico)
fr French (generic)
fr-FR French (France)
fr-CA French (Canada)
de German
de-DE German (Germany)
de-AT German (Austria)
it Italian
pt Portuguese (generic)
pt-BR Portuguese (Brazil)
pt-PT Portuguese (Portugal)
ja Japanese
ko Korean
zh Chinese (generic)
zh-CN Chinese (Simplified)
zh-TW Chinese (Traditional)
ar Arabic
he Hebrew
ru Russian
nl Dutch
pl Polish
tr Turkish
RTL Languages
ar Arabic
he Hebrew
fa Persian/Farsi
ur Urdu
Date/Time Formatting
JavaScript (Intl API)
TypeScript
1// Date formatting
2const date = new Date();
3const formatter = new Intl.DateTimeFormat('es-ES', {
4 year: 'numeric',
5 month: 'long',
6 day: 'numeric'
7});
8formatter.format(date); // "12 de febrero de 2026"
9
10// Time formatting
11const timeFormatter = new Intl.DateTimeFormat('en-US', {
12 hour: 'numeric',
13 minute: 'numeric',
14 hour12: true
15});
16timeFormatter.format(date); // "3:45 PM"
17
18// Relative time
19const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
20rtf.format(-1, 'day'); // "yesterday"
21rtf.format(2, 'day'); // "in 2 days"date-fns
TypeScript
1import { format } from 'date-fns';
2import { es, fr, de } from 'date-fns/locale';
3
4format(new Date(), 'PPP', { locale: es }); // "12 de febrero de 2026"
5format(new Date(), 'PPP', { locale: fr }); // "12 février 2026"
6format(new Date(), 'PPP', { locale: de }); // "12. Februar 2026"Number/Currency Formatting
JavaScript (Intl API)
TypeScript
1// Number formatting
2const number = 1234567.89;
3new Intl.NumberFormat('de-DE').format(number); // "1.234.567,89"
4new Intl.NumberFormat('en-US').format(number); // "1,234,567.89"
5new Intl.NumberFormat('fr-FR').format(number); // "1 234 567,89"
6
7// Currency formatting
8const price = 1234.56;
9new Intl.NumberFormat('en-US', {
10 style: 'currency',
11 currency: 'USD'
12}).format(price); // "$1,234.56"
13
14new Intl.NumberFormat('de-DE', {
15 style: 'currency',
16 currency: 'EUR'
17}).format(price); // "1.234,56 €"
18
19new Intl.NumberFormat('ja-JP', {
20 style: 'currency',
21 currency: 'JPY'
22}).format(price); // "¥1,235"
23
24// Percentage
25new Intl.NumberFormat('en-US', {
26 style: 'percent'
27}).format(0.85); // "85%"RTL Support
CSS
CSS
1/* Use logical properties */
2.element {
3 margin-inline-start: 1rem; /* Instead of margin-left */
4 margin-inline-end: 1rem; /* Instead of margin-right */
5 padding-inline: 1rem; /* Instead of padding-left/right */
6}
7
8/* RTL-specific styles */
9[dir="rtl"] .element {
10 /* Styles for RTL languages */
11}
12
13/* Flip icons/images that indicate direction */
14[dir="rtl"] .arrow-right {
15 transform: scaleX(-1);
16}HTML
HTML
1<!-- Set direction on html element -->
2<html lang="ar" dir="rtl">
3
4<!-- Or on specific elements -->
5<div dir="rtl">
6 <p>هذا نص عربي</p>
7</div>React
TSX
1// Detect RTL
2const isRTL = ['ar', 'he', 'fa', 'ur'].includes(locale);
3
4// Apply direction
5<html lang={locale} dir={isRTL ? 'rtl' : 'ltr'}>SEO Configuration
hreflang Tags
HTML
1<!-- In <head> -->
2<link rel="alternate" hreflang="en" href="https://example.com/en/page" />
3<link rel="alternate" hreflang="es" href="https://example.com/es/page" />
4<link rel="alternate" hreflang="fr" href="https://example.com/fr/page" />
5<link rel="alternate" hreflang="x-default" href="https://example.com/en/page" />Next.js (App Router)
TSX
1// app/[locale]/layout.tsx
2export async function generateMetadata({ params }: { params: { locale: string } }) {
3 return {
4 alternates: {
5 canonical: `/${params.locale}`,
6 languages: {
7 'en': '/en',
8 'es': '/es',
9 'fr': '/fr',
10 },
11 },
12 };
13}Sitemap
XML
1<?xml version="1.0" encoding="UTF-8"?>
2<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
3 xmlns:xhtml="http://www.w3.org/1999/xhtml">
4 <url>
5 <loc>https://example.com/en/page</loc>
6 <xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page"/>
7 <xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/page"/>
8 </url>
9</urlset>Common Issues & Solutions
Issue: Translation keys showing instead of text
Cause: Translation file not loaded or key doesn't exist Solution:
Terminal
1# Check if translation exists
2npx @intlpullhq/cli check
3
4# Verify file is loaded
5console.log(i18n.getResourceBundle('en', 'namespace'));Issue: Variables not substituting
Cause: Incorrect placeholder syntax Solution:
JSON
1// Correct
2{ "welcome": "Hello, {{name}}!" }
3
4// Incorrect
5{ "welcome": "Hello, {name}!" }
6{ "welcome": "Hello, $name!" }Issue: Pluralization not working
Cause: Missing plural forms or incorrect count Solution:
JSON
1{
2 "items": {
3 "zero": "No items",
4 "one": "{{count}} item",
5 "other": "{{count}} items"
6 }
7}Issue: Layout breaks with long translations
Cause: Fixed widths or insufficient space Solution:
CSS
1/* Use flexible layouts */
2.button {
3 min-width: 100px;
4 width: auto;
5 padding: 0.5rem 1rem;
6}
7
8/* Add overflow handling */
9.text {
10 overflow-wrap: break-word;
11 word-break: break-word;
12}Issue: Special characters display as �
Cause: Incorrect encoding Solution:
HTML
<!-- Ensure UTF-8 encoding -->
<meta charset="UTF-8">JSON
// Save files as UTF-8 without BOMIssue: RTL layout broken
Cause: Using directional properties instead of logical Solution:
CSS
1/* Replace */
2margin-left: 1rem;
3
4/* With */
5margin-inline-start: 1rem;Performance Optimization
Code Splitting
TypeScript
1// Lazy load translations
2const loadTranslations = async (locale: string) => {
3 const translations = await import(`./locales/${locale}.json`);
4 return translations.default;
5};Caching
TypeScript
1// Cache translations in localStorage
2const CACHE_KEY = 'translations';
3const CACHE_VERSION = '1.0.0';
4
5function cacheTranslations(locale: string, data: any) {
6 localStorage.setItem(`${CACHE_KEY}_${locale}`, JSON.stringify({
7 version: CACHE_VERSION,
8 data,
9 }));
10}
11
12function getCachedTranslations(locale: string) {
13 const cached = localStorage.getItem(`${CACHE_KEY}_${locale}`);
14 if (!cached) return null;
15
16 const { version, data } = JSON.parse(cached);
17 if (version !== CACHE_VERSION) return null;
18
19 return data;
20}Bundle Size
Terminal
1# Analyze bundle size
2npx @next/bundle-analyzer
3
4# Check translation file sizes
5du -sh locales/*
6
7# Minify translation files
8npx json-minify locales/en.json > locales/en.min.jsonPseudo-Localization
What is Pseudo-Localization?
Testing technique that simulates translation without actual translation, helping identify i18n issues early.
Implementation
TypeScript
1// Simple pseudo-localization function
2function pseudoLocalize(text: string): string {
3 const accents: Record<string, string> = {
4 'a': 'ȧ', 'e': 'ḗ', 'i': 'ī', 'o': 'ǿ', 'u': 'ŭ',
5 'A': 'Ȧ', 'E': 'Ḗ', 'I': 'Ī', 'O': 'Ǿ', 'U': 'Ŭ',
6 };
7
8 // Add accents
9 let result = text.replace(/[aeiouAEIOU]/g, char => accents[char] || char);
10
11 // Extend length by 30% (simulate German/Finnish)
12 result = `[${result}]`;
13
14 return result;
15}
16
17// Usage
18pseudoLocalize("Hello World"); // "[Ḗḗllǿ Ẇǿrld]"What to Test
- All text displays correctly (no encoding issues)
- UI doesn't break with longer text
- No hardcoded strings (they won't be pseudo-localized)
- Placeholders work correctly
- Special characters display properly
Mobile Examples
iOS (Swift)
Swift
1// Simple localization
2let title = NSLocalizedString("login.title", comment: "Login screen title")
3
4// With variables
5let welcome = String(format: NSLocalizedString("welcome.message", comment: ""), userName)
6
7// Pluralization
8let itemCount = String.localizedStringWithFormat(
9 NSLocalizedString("items.count", comment: ""),
10 count
11)
12
13// Date formatting
14let formatter = DateFormatter()
15formatter.locale = Locale.current
16formatter.dateStyle = .medium
17let dateString = formatter.string(from: date)Android (Kotlin)
Kotlin
1// Simple localization
2val title = getString(R.string.login_title)
3
4// With variables
5val welcome = getString(R.string.welcome_message, userName)
6
7// Pluralization
8val itemCount = resources.getQuantityString(
9 R.plurals.items_count,
10 count,
11 count
12)
13
14// Date formatting
15val formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault())
16val dateString = formatter.format(date)React Native
TypeScript
1import { useTranslation } from 'react-i18next';
2import { Platform } from 'react-native';
3
4export default function Component() {
5 const { t } = useTranslation();
6
7 // Simple translation
8 <Text>{t('login.title')}</Text>
9
10 // Platform-specific
11 <Text>{t(`common.action_${Platform.OS}`)}</Text>
12 // Uses: common.action_ios or common.action_android
13
14 // With variables
15 <Text>{t('welcome.message', { name: user.name })}</Text>
16}Security Best Practices
Input Sanitization
TypeScript
1import DOMPurify from 'dompurify';
2
3// Sanitize before displaying
4function SafeTranslation({ text }: { text: string }) {
5 const sanitized = DOMPurify.sanitize(text);
6 return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
7}Locale Validation
TypeScript
1// Whitelist allowed locales
2const ALLOWED_LOCALES = ['en', 'es', 'fr', 'de', 'ja', 'ar'];
3
4function validateLocale(locale: string): string {
5 const normalized = locale.toLowerCase().split('-')[0];
6 return ALLOWED_LOCALES.includes(normalized) ? normalized : 'en';
7}
8
9// Use in route handler
10app.get('/:locale/*', (req, res) => {
11 const locale = validateLocale(req.params.locale);
12 // ... rest of handler
13});Translation File Integrity
TypeScript
1import crypto from 'crypto';
2
3// Generate checksum for translation file
4function generateChecksum(content: string): string {
5 return crypto.createHash('sha256').update(content).digest('hex');
6}
7
8// Verify before loading
9async function loadTranslations(locale: string) {
10 const content = await fetch(`/locales/${locale}.json`).then(r => r.text());
11 const checksum = generateChecksum(content);
12
13 // Verify against known good checksum
14 if (checksum !== KNOWN_CHECKSUMS[locale]) {
15 throw new Error('Translation file integrity check failed');
16 }
17
18 return JSON.parse(content);
19}Performance Tips
Lazy Loading
TypeScript
1// Next.js - lazy load translations
2import dynamic from 'next/dynamic';
3
4const TranslationProvider = dynamic(
5 () => import('./TranslationProvider'),
6 { ssr: false }
7);Memoization
TypeScript
1import { useMemo } from 'react';
2
3function useTranslation(namespace: string) {
4 const translations = useMemo(
5 () => loadTranslations(namespace),
6 [namespace]
7 );
8
9 return translations;
10}Service Worker Caching
TypeScript
1// Cache translation files
2self.addEventListener('install', (event) => {
3 event.waitUntil(
4 caches.open('translations-v1').then((cache) => {
5 return cache.addAll([
6 '/locales/en.json',
7 '/locales/es.json',
8 '/locales/fr.json',
9 ]);
10 })
11 );
12});Testing Checklist
Pre-Deployment
- All translations complete for target languages
- No missing translation keys
- Placeholders work correctly
- Pluralization works for all languages
- Date/time formatting correct
- Number/currency formatting correct
- RTL layout works (if applicable)
- SEO metadata translated
- hreflang tags configured
- All pages tested in all languages
Visual Testing
- Test with longest translations (German, Finnish)
- Test with shortest translations (Chinese, Japanese)
- Test button text doesn't overflow
- Test form layouts
- Test navigation menus
- Test modals and dialogs
- Test mobile responsive layouts
Browser Testing
- Chrome
- Firefox
- Safari
- Edge
- iOS Safari
- Android Chrome
Useful Resources
Documentation
Tools
Communities
Quick Tips:
- Always use translation keys, never hardcode strings
- Provide context for translators (comments, screenshots)
- Test with real translations, not Lorem Ipsum
- Use logical CSS properties for RTL support
- Validate translations before deployment
- Monitor for missing translations in production
- Keep translation files in version control
- Automate translation validation in CI/CD
- Test on real devices, not just emulators
- Plan for growth - design for 50+ languages from day one