Internationalization and Localization

1. Language Declaration and BCP 47 Tags

Attribute/Tag Syntax Description Example
lang (HTML element) <html lang="en"> Primary language of the entire document <html lang="en-US"> - English (United States)
lang (on any element) <span lang="fr">Bonjour</span> Override language for specific content section Multi-language pages, foreign quotes
hreflang (links) <link rel="alternate" hreflang="es"> Language of linked resource Alternate language versions for SEO
BCP 47 Language Tag language-Script-REGION-variant Standard format for language identification zh-Hans-CN - Chinese, Simplified, China
Primary Language 2-3 letter code (ISO 639) Base language identifier en, fr, de, ja, ar
Script Subtag 4-letter code (ISO 15924) Writing system specification Hans (Simplified), Hant (Traditional), Latn (Latin)
Region Subtag 2-letter or 3-digit code Country/region specification US, GB, CN, BR

Example: Language declaration with BCP 47 tags

<!DOCTYPE html>
<!-- Primary language: English (United States) -->
<html lang="en-US">
<head>
  <meta charset="UTF-8">
  <title>Multilingual Website</title>
  
  <!-- Alternate language versions for SEO -->
  <link rel="alternate" hreflang="en-US" href="https://example.com/en-us/">
  <link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/">
  <link rel="alternate" hreflang="fr-FR" href="https://example.com/fr/">
  <link rel="alternate" hreflang="de-DE" href="https://example.com/de/">
  <link rel="alternate" hreflang="es-ES" href="https://example.com/es/">
  <link rel="alternate" hreflang="ja-JP" href="https://example.com/ja/">
  <link rel="alternate" hreflang="zh-Hans-CN" href="https://example.com/zh-cn/">
  <link rel="alternate" hreflang="zh-Hant-TW" href="https://example.com/zh-tw/">
  <link rel="alternate" hreflang="ar-SA" href="https://example.com/ar/">
  <link rel="alternate" hreflang="x-default" href="https://example.com/">
</head>
<body>
  <p>Welcome to our website.</p>
  
  <!-- Foreign language quote -->
  <blockquote lang="fr-FR">
    <p>La vie est belle.</p>
  </blockquote>
  
  <!-- Mixed language content -->
  <p>The Japanese word <span lang="ja">こんにちは</span> means "hello".</p>
  
  <!-- Arabic text section -->
  <section lang="ar-SA" dir="rtl">
    <h2>مرحبا</h2>
    <p>هذا نص باللغة العربية</p>
  </section>
</body>
</html>
Common Language Codes BCP 47 Tag Description Script/Region Notes
English (US) en-US American English Default English variant
English (UK) en-GB British English Different spelling (colour vs color)
Spanish (Spain) es-ES Castilian Spanish Differs from Latin American Spanish
Spanish (Mexico) es-MX Mexican Spanish Different vocabulary and idioms
Chinese (Simplified) zh-Hans-CN Mainland China Hans = Simplified characters
Chinese (Traditional) zh-Hant-TW Taiwan Hant = Traditional characters
Arabic (Saudi Arabia) ar-SA Saudi Arabic RTL text direction required
Portuguese (Brazil) pt-BR Brazilian Portuguese Distinct from European Portuguese
French (Canada) fr-CA Canadian French Different from France French
SEO Tip: Use hreflang="x-default" to specify the default version when no language matches. Always include bidirectional links (page A links to B, page B links to A) for proper SEO. Google requires consistency across all language versions.

2. Text Direction (LTR, RTL) and BiDi Support

Attribute Value Description Languages
dir="ltr" Left-to-Right Default text direction for most languages English, French, Spanish, German, Chinese, Japanese, Korean
dir="rtl" Right-to-Left Text flows from right to left Arabic, Hebrew, Persian, Urdu
dir="auto" Automatic direction Browser determines direction from first strong character User-generated content, mixed-direction text
BiDi Isolation <bdi> element Isolate text with different directionality Usernames, product names in RTL contexts
BiDi Override <bdo dir="rtl"> element Force specific text direction Override browser's direction algorithm
CSS direction direction: rtl; CSS property for text direction Styling RTL layouts
Logical Properties margin-inline-start Direction-agnostic CSS properties Adapts to LTR/RTL automatically

Example: RTL and BiDi text handling

<!-- RTL document (Arabic) -->
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <title>موقع عربي</title>
  <style>
    /* Direction-agnostic CSS using logical properties */
    .card {
      margin-inline-start: 1rem; /* Left in LTR, Right in RTL */
      margin-inline-end: 1rem;   /* Right in LTR, Left in RTL */
      padding-inline: 2rem;      /* Horizontal padding */
      border-inline-start: 2px solid blue; /* Left border in LTR */
    }
  </style>
</head>
<body>
  <h1>مرحبا بكم</h1>
  <p>هذا نص يُكتب من اليمين إلى اليسار</p>
  
  <!-- Embedding LTR text (English) in RTL context -->
  <p>
    المؤلف هو <bdi>John Smith</bdi> من أمريكا
  </p>
  
  <!-- User-generated content with auto direction -->
  <div class="comment" dir="auto">
    <!-- Browser detects direction from first strong character -->
    <p>This will be LTR</p>
  </div>
  
  <div class="comment" dir="auto">
    <p>هذا سيكون RTL</p>
  </div>
</body>
</html>

<!-- LTR document with embedded RTL content -->
<html lang="en" dir="ltr">
<body>
  <h1>Learning Arabic</h1>
  
  <!-- RTL section in LTR document -->
  <section lang="ar" dir="rtl">
    <h2>الأرقام العربية</h2>
    <p>١، ٢، ٣، ٤، ٥</p>
  </section>
  
  <!-- Bidirectional text with isolation -->
  <p>User <bdi>أحمد</bdi> has 15 messages.</p>
  
  <!-- Force direction override (rare use case) -->
  <p>Display in reverse: <bdo dir="rtl">ABCDEF</bdo> becomes FEDCBA</p>
</body>
</html>
CSS Logical Property Physical Equivalent (LTR) Physical Equivalent (RTL) Use Case
margin-inline-start margin-left margin-right Start margin (direction-aware)
margin-inline-end margin-right margin-left End margin (direction-aware)
padding-inline-start padding-left padding-right Start padding
border-inline-start border-left border-right Start border
inset-inline-start left right Positioning (absolute/fixed)
text-align: start text-align: left text-align: right Direction-aware alignment
text-align: end text-align: right text-align: left Opposite side alignment
RTL Common Pitfalls: Avoid using physical properties (left/right) in CSS for international sites. Use logical properties (inline-start/inline-end) instead. Icons and arrows may need to be flipped in RTL. Test with actual RTL content, not just reversing English text.

3. Character Encoding and Unicode Support

Encoding Syntax Description Coverage
UTF-8 <meta charset="UTF-8"> Universal character encoding (recommended) All languages, emojis, symbols
UTF-16 <meta charset="UTF-16"> 16-bit Unicode encoding Less efficient than UTF-8 for web
ISO-8859-1 (Latin-1) <meta charset="ISO-8859-1"> Western European languages only Legacy - use UTF-8
HTML Entities (Decimal) &#8364; Numeric character reference (€) Any Unicode character by code point
HTML Entities (Hex) &#x20AC; Hexadecimal character reference (€) Same as decimal, hex format
Named Entities &euro; &copy; Named character references Limited set of common characters
Unicode Escape (CSS/JS) \20AC (CSS), \u20AC (JS) Unicode escape sequences In stylesheets and scripts

Example: Character encoding and special characters

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- ALWAYS declare UTF-8 encoding first -->
  <meta charset="UTF-8">
  <title>Unicode Characters</title>
</head>
<body>
  <!-- Direct UTF-8 characters (recommended with UTF-8) -->
  <p>Currency: € £ ¥ ₹ ₽</p>
  <p>Symbols: © ® ™ § ¶ † ‡</p>
  <p>Math: × ÷ ± ≠ ≈ ∞ √ ∑</p>
  <p>Arrows: ← → ↑ ↓ ↔ ⇒ ⇔</p>
  <p>Emojis: 😀 😂 ❤️ 👍 🌟 🎉</p>
  
  <!-- HTML entities (decimal) -->
  <p>Euro: &#8364; (€)</p>
  <p>Copyright: &#169; (©)</p>
  <p>Heart: &#10084; (❤)</p>
  
  <!-- HTML entities (hexadecimal) -->
  <p>Euro: &#x20AC; (€)</p>
  <p>Em dash: &#x2014; (—)</p>
  <p>Bullet: &#x2022; (•)</p>
  
  <!-- Named entities -->
  <p>&euro; &copy; &reg; &trade;</p>
  <p>&lt; &gt; &amp; &quot; &apos;</p>
  <p>&nbsp; &mdash; &ndash; &hellip;</p>
  
  <!-- Diacritics and accents -->
  <p>French: café, naïve, résumé</p>
  <p>German: Grüße, Äpfel, Größe</p>
  <p>Spanish: ñ, á, é, í, ó, ú</p>
  
  <!-- Non-Latin scripts -->
  <p lang="el">Greek: Ελληνικά</p>
  <p lang="ru">Russian: Русский</p>
  <p lang="ar" dir="rtl">Arabic: العربية</p>
  <p lang="he" dir="rtl">Hebrew: עברית</p>
  <p lang="hi">Hindi: हिन्दी</p>
  <p lang="th">Thai: ไทย</p>
  <p lang="ja">Japanese: 日本語</p>
  <p lang="ko">Korean: 한국어</p>
  <p lang="zh">Chinese: 中文</p>
</body>
</html>
Common HTML Entities Named Entity Decimal Hex Character
Non-breaking space &nbsp; &#160; &#xA0;
Em dash &mdash; &#8212; &#x2014;
En dash &ndash; &#8211; &#x2013;
Ellipsis &hellip; &#8230; &#x2026;
Left quote &ldquo; &#8220; &#x201C; "
Right quote &rdquo; &#8221; &#x201D; "
Apostrophe &rsquo; &#8217; &#x2019; '
Bullet &bull; &#8226; &#x2022;
Best Practice: Always use UTF-8 encoding. Direct Unicode characters are preferred over entities when using UTF-8. Use entities only for HTML-reserved characters (<, >, &, ") or when forced to use legacy encodings.

4. Date, Time, and Number Formatting

Element/Attribute Syntax Description Use Case
<time> element <time datetime="2025-12-22">Dec 22, 2025</time> Machine-readable date/time with human-readable display Events, publication dates, timestamps
datetime attribute datetime="2025-12-22T14:30:00Z" ISO 8601 format for dates and times Search engines, screen readers, scripts
Intl.DateTimeFormat (JS) new Intl.DateTimeFormat('en-US') JavaScript API for locale-specific formatting Dynamic date display based on user locale
Intl.NumberFormat (JS) new Intl.NumberFormat('de-DE') Format numbers according to locale Prices, quantities, percentages
Intl.RelativeTimeFormat (JS) new Intl.RelativeTimeFormat('en') Format relative time ("2 days ago") Social media timestamps, activity feeds
input type="date" <input type="date"> Native date picker with locale formatting Forms with date selection
input type="time" <input type="time"> Native time picker Appointment scheduling, time entry

Example: Date, time, and number formatting

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Localized Formatting</title>
</head>
<body>
  <!-- Time element with various formats -->
  <p>Published: <time datetime="2025-12-22T14:30:00Z">December 22, 2025 at 2:30 PM</time></p>
  <p>Event date: <time datetime="2025-12-31">New Year's Eve 2025</time></p>
  <p>Duration: <time datetime="PT2H30M">2 hours 30 minutes</time></p>
  
  <!-- Form with date/time inputs -->
  <form>
    <label>Appointment Date: <input type="date" name="date"></label>
    <label>Appointment Time: <input type="time" name="time"></label>
    <label>Birth Month: <input type="month" name="birth-month"></label>
    <label>Week: <input type="week" name="week"></label>
  </form>
  
  <!-- JavaScript locale-aware formatting -->
  <script>
    const date = new Date('2025-12-22T14:30:00Z');
    
    // Date formatting for different locales
    const usDate = new Intl.DateTimeFormat('en-US', {
      year: 'numeric', month: 'long', day: 'numeric'
    }).format(date); // December 22, 2025
    
    const ukDate = new Intl.DateTimeFormat('en-GB', {
      year: 'numeric', month: 'long', day: 'numeric'
    }).format(date); // 22 December 2025
    
    const frDate = new Intl.DateTimeFormat('fr-FR', {
      year: 'numeric', month: 'long', day: 'numeric'
    }).format(date); // 22 décembre 2025
    
    const jpDate = new Intl.DateTimeFormat('ja-JP', {
      year: 'numeric', month: 'long', day: 'numeric'
    }).format(date); // 2025年12月22日
    
    // Number formatting
    const price = 1234567.89;
    
    const usPrice = new Intl.NumberFormat('en-US', {
      style: 'currency', currency: 'USD'
    }).format(price); // $1,234,567.89
    
    const euPrice = new Intl.NumberFormat('de-DE', {
      style: 'currency', currency: 'EUR'
    }).format(price); // 1.234.567,89 €
    
    const jpPrice = new Intl.NumberFormat('ja-JP', {
      style: 'currency', currency: 'JPY'
    }).format(price); // ¥1,234,568 (no decimals)
    
    // Relative time formatting
    const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
    rtf.format(-1, 'day');    // "yesterday"
    rtf.format(2, 'day');     // "in 2 days"
    rtf.format(-3, 'month');  // "3 months ago"
    
    // Percentage formatting
    const percent = 0.456;
    const usPercent = new Intl.NumberFormat('en-US', {
      style: 'percent', minimumFractionDigits: 1
    }).format(percent); // 45.6%
    
    // Unit formatting
    const distance = 1234;
    const usDistance = new Intl.NumberFormat('en-US', {
      style: 'unit', unit: 'kilometer'
    }).format(distance); // 1,234 km
  </script>
  
  <!-- Display formatted values -->
  <div id="date-display"></div>
  <div id="number-display"></div>
</body>
</html>
Locale Date Format Time Format Number Format Currency Symbol
en-US MM/DD/YYYY 12-hour (AM/PM) 1,234.56 $ (before)
en-GB DD/MM/YYYY 24-hour 1,234.56 £ (before)
de-DE DD.MM.YYYY 24-hour 1.234,56 € (after)
fr-FR DD/MM/YYYY 24-hour 1 234,56 € (after, space)
ja-JP YYYY/MM/DD 24-hour 1,234.56 ¥ (before, no decimals)
zh-CN YYYY-MM-DD 24-hour 1,234.56 ¥ (before)
ar-SA DD/MM/YYYY 12-hour (AM/PM) 1,234.56 ر.س (after)

5. Font Selection for Multiple Languages

Technique Implementation Description Coverage
System Font Stack font-family: system-ui, -apple-system Use system default fonts for best language coverage All platforms, all scripts
Unicode Range (@font-face) unicode-range: U+0600-06FF; Load specific font for character range (Arabic) Optimize loading for specific scripts
Google Fonts (Noto Family) Noto Sans, Noto Serif Comprehensive multi-script font family 800+ languages, all scripts
Fallback Fonts sans-serif, serif, monospace Generic font families as final fallback Guaranteed rendering (browser default)
Language-Specific Fonts :lang() CSS selector Apply different fonts based on element language Optimized typography per language
Variable Fonts font-variation-settings Single font file with multiple styles Reduces HTTP requests, bandwidth

Example: Multi-language font configuration

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Multi-Language Fonts</title>
  <style>
    /* System font stack for broad language support */
    body {
      font-family: 
        system-ui,           /* Modern system default */
        -apple-system,       /* macOS/iOS */
        BlinkMacSystemFont,  /* Chrome on macOS */
        'Segoe UI',          /* Windows */
        Roboto,              /* Android, Chrome OS */
        'Noto Sans',         /* Cross-platform Unicode */
        'Helvetica Neue',    /* Legacy macOS */
        Arial,               /* Fallback */
        sans-serif,          /* Generic fallback */
        'Apple Color Emoji', /* Emoji support */
        'Segoe UI Emoji',
        'Noto Color Emoji';
    }
    
    /* Arabic-specific font */
    :lang(ar) {
      font-family: 'Noto Naskh Arabic', 'Arabic Typesetting', 'Simplified Arabic', serif;
    }
    
    /* Chinese fonts (Simplified) */
    :lang(zh-Hans),
    :lang(zh-CN) {
      font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
    }
    
    /* Chinese fonts (Traditional) */
    :lang(zh-Hant),
    :lang(zh-TW) {
      font-family: 'Noto Sans TC', 'PingFang TC', 'Microsoft JhengHei', sans-serif;
    }
    
    /* Japanese fonts */
    :lang(ja) {
      font-family: 'Noto Sans JP', 'Hiragino Sans', 'Yu Gothic', 'Meiryo', sans-serif;
    }
    
    /* Korean fonts */
    :lang(ko) {
      font-family: 'Noto Sans KR', 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
    }
    
    /* Hebrew fonts */
    :lang(he) {
      font-family: 'Noto Sans Hebrew', 'Arial Hebrew', 'David', serif;
    }
    
    /* Devanagari (Hindi, Sanskrit) */
    :lang(hi),
    :lang(sa) {
      font-family: 'Noto Sans Devanagari', 'Mangal', 'Nirmala UI', sans-serif;
    }
    
    /* Thai fonts */
    :lang(th) {
      font-family: 'Noto Sans Thai', 'Leelawadee UI', 'Thonburi', sans-serif;
    }
    
    /* @font-face with unicode-range for optimized loading */
    @font-face {
      font-family: 'WebFont';
      src: url('latin.woff2') format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F;
    }
    
    @font-face {
      font-family: 'WebFont';
      src: url('arabic.woff2') format('woff2');
      unicode-range: U+0600-06FF, U+FE70-FEFF; /* Arabic block */
    }
    
    @font-face {
      font-family: 'WebFont';
      src: url('chinese.woff2') format('woff2');
      unicode-range: U+4E00-9FFF; /* CJK Unified Ideographs */
    }
  </style>
  
  <!-- Google Fonts with multiple scripts -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;700&family=Noto+Sans+Arabic:wght@400;700&family=Noto+Sans+JP:wght@400;700&family=Noto+Sans+SC:wght@400;700&display=swap" rel="stylesheet">
</head>
<body>
  <h1>Multi-Language Typography</h1>
  
  <p lang="en">English: The quick brown fox jumps over the lazy dog.</p>
  <p lang="ar" dir="rtl">Arabic: الثعلب البني السريع يقفز فوق الكلب الكسول</p>
  <p lang="zh-Hans">Chinese (Simplified): 快速的棕色狐狸跳过懒狗</p>
  <p lang="zh-Hant">Chinese (Traditional): 快速的棕色狐狸跳過懶狗</p>
  <p lang="ja">Japanese: 速い茶色のキツネが怠惰な犬を飛び越える</p>
  <p lang="ko">Korean: 빠른 갈색 여우가 게으른 개를 뛰어넘습니다</p>
  <p lang="hi">Hindi: तेज भूरी लोमड़ी आलसी कुत्ते के ऊपर कूदती है</p>
  <p lang="th">Thai: สุนัขจิ้งจอกสีน้ำตาลที่รวดเร็วกระโดดข้ามสุนัขที่ขี้เกียจ</p>
  <p lang="he" dir="rtl">Hebrew: השועל החום המהיר קופץ מעל הכלב העצלן</p>
</body>
</html>
Script/Language Recommended Fonts Unicode Range Special Considerations
Latin Roboto, Open Sans, Inter, system-ui U+0000-00FF Most web fonts support Latin
Arabic Noto Naskh Arabic, Amiri, Cairo U+0600-06FF Requires contextual shaping, RTL support
Chinese (Simplified) Noto Sans SC, Source Han Sans SC U+4E00-9FFF Large font files (~5-10MB), use subsetting
Japanese Noto Sans JP, Source Han Sans JP U+3040-309F (Hiragana), U+30A0-30FF (Katakana) Includes Kanji (U+4E00-9FFF)
Korean Noto Sans KR, Spoqa Han Sans U+AC00-D7AF (Hangul) 11,172 syllable blocks
Devanagari (Hindi) Noto Sans Devanagari, Poppins U+0900-097F Complex ligatures, combining marks
Thai Noto Sans Thai, Sukhumvit U+0E00-0E7F Tone marks, complex rendering
Emoji Noto Color Emoji, Apple Color Emoji U+1F300-1F9FF Color fonts (SVG, CBDT, COLR)
Performance Tip: Use font-display: swap to prevent invisible text during font loading. For CJK fonts (Chinese, Japanese, Korean), subset fonts to reduce file size from 10MB+ to under 1MB by including only commonly used characters.

6. Content Localization Strategies

Strategy Implementation Description Best For
Separate HTML Files /en/page.html, /fr/page.html Different HTML file per language Static sites, complete translations
URL Path Strategy example.com/en/, example.com/fr/ Language in URL path SEO-friendly, clear separation
Subdomain Strategy en.example.com, fr.example.com Language as subdomain Large sites, regional content
Query Parameter example.com?lang=fr Language via query string Poor SEO - avoid
Cookie/LocalStorage Store user language preference Remember choice, load appropriate content User preference persistence
Accept-Language Header Server detects browser language Auto-redirect based on browser settings Initial language detection
JavaScript i18n Libraries i18next, FormatJS, Globalize Client-side translation and formatting SPAs, dynamic content
Server-Side Rendering Template engines with translation Generate localized HTML on server SEO, initial load performance

Example: Complete localization implementation

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Localized Website</title>
  
  <!-- hreflang for all language versions -->
  <link rel="alternate" hreflang="en" href="https://example.com/en/">
  <link rel="alternate" hreflang="fr" href="https://example.com/fr/">
  <link rel="alternate" hreflang="de" href="https://example.com/de/">
  <link rel="alternate" hreflang="ja" href="https://example.com/ja/">
  <link rel="alternate" hreflang="x-default" href="https://example.com/">
</head>
<body>
  <!-- Language selector -->
  <nav aria-label="Language selection">
    <select id="language-selector" onchange="changeLanguage(this.value)">
      <option value="en" selected>English</option>
      <option value="fr">Français</option>
      <option value="de">Deutsch</option>
      <option value="es">Español</option>
      <option value="ja">日本語</option>
      <option value="zh">中文</option>
      <option value="ar">العربية</option>
    </select>
  </nav>
  
  <!-- Content with data attributes for translation -->
  <h1 data-i18n="welcome">Welcome</h1>
  <p data-i18n="description">This is a localized website.</p>
  
  <!-- Dynamic content with placeholders -->
  <p data-i18n="greeting" data-i18n-options='{"name":"John"}'>
    Hello, John!
  </p>
  
  <!-- Date/time with locale formatting -->
  <p>
    <time datetime="2025-12-22T14:30:00Z" id="event-time"></time>
  </p>
  
  <!-- Price with currency formatting -->
  <p class="price" data-amount="1234.56" data-currency="USD"></p>
  
  <script>
    // Translation data (normally loaded from JSON files)
    const translations = {
      en: {
        welcome: 'Welcome',
        description: 'This is a localized website.',
        greeting: 'Hello, {{name}}!'
      },
      fr: {
        welcome: 'Bienvenue',
        description: 'Ceci est un site web localisé.',
        greeting: 'Bonjour, {{name}}!'
      },
      de: {
        welcome: 'Willkommen',
        description: 'Dies ist eine lokalisierte Website.',
        greeting: 'Hallo, {{name}}!'
      },
      ja: {
        welcome: 'ようこそ',
        description: 'これはローカライズされたウェブサイトです。',
        greeting: 'こんにちは、{{name}}さん!'
      }
    };
    
    // Detect user's language
    function detectLanguage() {
      // 1. Check localStorage
      const stored = localStorage.getItem('preferredLanguage');
      if (stored) return stored;
      
      // 2. Check browser language
      const browserLang = navigator.language.split('-')[0];
      if (translations[browserLang]) return browserLang;
      
      // 3. Default to English
      return 'en';
    }
    
    // Change language
    function changeLanguage(lang) {
      // Save preference
      localStorage.setItem('preferredLanguage', lang);
      
      // Update HTML lang attribute
      document.documentElement.lang = lang;
      
      // Update text direction for RTL languages
      if (lang === 'ar' || lang === 'he') {
        document.documentElement.dir = 'rtl';
      } else {
        document.documentElement.dir = 'ltr';
      }
      
      // Translate all elements
      document.querySelectorAll('[data-i18n]').forEach(el => {
        const key = el.getAttribute('data-i18n');
        let text = translations[lang][key];
        
        // Handle placeholders
        const options = el.getAttribute('data-i18n-options');
        if (options) {
          const vars = JSON.parse(options);
          Object.keys(vars).forEach(k => {
            text = text.replace(`{{${k}}}`, vars[k]);
          });
        }
        
        el.textContent = text;
      });
      
      // Format date/time
      const eventTime = document.getElementById('event-time');
      const date = new Date(eventTime.getAttribute('datetime'));
      eventTime.textContent = new Intl.DateTimeFormat(lang, {
        year: 'numeric', month: 'long', day: 'numeric',
        hour: 'numeric', minute: 'numeric'
      }).format(date);
      
      // Format price
      document.querySelectorAll('.price').forEach(el => {
        const amount = parseFloat(el.getAttribute('data-amount'));
        const currency = el.getAttribute('data-currency');
        el.textContent = new Intl.NumberFormat(lang, {
          style: 'currency', currency: currency
        }).format(amount);
      });
    }
    
    // Initialize on page load
    const currentLang = detectLanguage();
    document.getElementById('language-selector').value = currentLang;
    changeLanguage(currentLang);
  </script>
</body>
</html>
Localization Best Practices:
  • Store translations in separate JSON files, not in code
  • Use ICU MessageFormat for complex plurals and gender
  • Never concatenate translated strings
  • Leave space for text expansion (German +35%, French +20%)
  • Translate alt text, title attributes, error messages
  • Consider cultural differences (colors, images, gestures)
Common Pitfalls:
  • Hard-coding text in HTML/JS instead of using translation keys
  • Forgetting to translate meta tags, Open Graph data
  • Using flags to represent languages (flag ≠ language)
  • Not accounting for text direction (RTL/LTR)
  • Ignoring date/number/currency formatting differences
  • Assuming all users speak English as fallback

Internationalization Checklist

Resources: Use Google Translate API, DeepL, or professional translators for quality. Test with tools like BrowserStack for international browsers. Consider Crowdin, Phrase, or Lokalise for translation management. Follow W3C Internationalization guidelines (w3.org/International).