DocumentWDP-8
TitleInternationalization (i18n)
Version0.1.0-draft
StatusDraft
CategoryStandards Track (Core)
Created2025-11-30
Updated2025-12-25

WDP Part 8: Internationalization (i18n)

Internationalization support for the Waddling Diagnostic Protocol (WDP). Defines how diagnostic codes, messages, and metadata can be localized for different languages and cultures while maintaining hash stability and cross-language interoperability.

Abstract#

This document specifies internationalization (i18n) support for the Waddling Diagnostic Protocol (WDP). It defines how diagnostic codes, messages, and metadata can be localized for different languages and cultures while maintaining hash stability and cross-language interoperability.

Specification Navigation: See STRUCTURE.md for an overview of all WDP specification parts.

Conformance

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

Key Concepts:

  • Canonical codes (English) for hash computation
  • Display aliases (any language) for user presentation
  • Separate catalog files per locale
  • Independent translation workflow

1. Introduction#

1.1 Purpose

This specification defines how WDP diagnostic codes and messages can be localized for international audiences while maintaining:

  • Hash stability - Same error produces same compact ID across all languages
  • Interoperability - Services in different languages can communicate
  • User experience - Users see diagnostics in their native language

1.2 Scope

This document covers:

  • Canonical vs localized diagnostic codes
  • Catalog file structure for multiple languages
  • Code aliases (localized display codes)
  • Message translation and parameterization
  • Locale-specific formatting (dates, numbers, currency)
  • RTL language support
  • Translation workflows

1.3 Design Goals

  1. Separation of Identity and Presentation - Internal identity (hash) vs user display
  2. Any Language as Primary - Projects can work in their native language
  3. Hash Stability - Same diagnostic always produces same compact ID
  4. Independent Catalogs - Each language file is self-contained
  5. Simple Translation - Translators work on individual files
  6. Cultural Sensitivity - Respect linguistic diversity

2. Core i18n Principles#

2.1 Two-Layer Identity System

WDP diagnostics have two identities:

Internal Identity (Machine)

  • Canonical code: Always English (e.g., E.Auth.Token.001)
  • Compact ID: Hash of canonical code (e.g., Ay75d)
  • Purpose: Hash computation, logs, APIs, databases
  • Never changes: Required for interoperability

Display Identity (Human)

  • Alias: Localized code for display (e.g., E.認証.トークン.001)
  • Message: Localized text (e.g., "トークンは{{timestamp}}に期限切れになりました")
  • Purpose: User interface, error reports, documentation
  • Localizable: Can be any language

2.2 Why English for Canonical Codes?

Technical Requirement: The canonical 4-part code MUST be in English to ensure hash stability across implementations.

Rationale:

  1. Hash Determinism - Same error = same compact ID everywhere
  2. ASCII Compatibility - Works in URLs, terminals, file systems
  3. No Normalization Issues - Avoids Unicode problems
  4. Industry Standard - Aligns with programming conventions

Important: This does NOT require:

  • English messages for users
  • English documentation
  • English as team's working language

The canonical code is an internal identifier. Users see localized aliases.

2.3 Primary Locale Flexibility

While canonical codes are always English, the primary locale (the language your team works in) can be any language:

  • Japanese team → Primary locale: "ja"
  • French team → Primary locale: "fr"
  • Chinese team → Primary locale: "zh"
  • English team → Primary locale: "en"

Other languages are translations from your primary locale.

3. Canonical vs Display Identity#

3.1 What Never Changes (Canonical)

These are immutable across all languages:

  1. Canonical 4-part code (English)
E.Auth.Token.001
  1. Compact ID (hash of canonical code)
Ay75d
  1. Hash algorithm
xxHash64("E.Auth.Token.001", seed="wdp-v1") → Base62 → Ay75d

3.2 What Can Be Localized (Display)

These are localizable per language:

  1. Display alias (localized code)
  • English: E.Auth.Token.001
  • Japanese: E.認証.トークン.001
  • Chinese: E.认证.令牌.001
  1. Messages
  • English: "Token expired at {{timestamp}}"
  • Japanese: "トークンは{{timestamp}}に期限切れになりました"
  1. Descriptions, hints, resolutions (all text fields)

3.3 Hash Computation (Always from Canonical)

The compact ID is always computed from the English canonical code:

Javascript
// Canonical code (English)
const canonical = "E.Auth.Token.001";

// Compute hash
const hash = xxHash64(canonical, seed: "wdp-v1");
const compactId = base62Encode(hash).substring(0, 5);
// → "Ay75d"

// This hash is the same regardless of user's language!

All language catalogs must use the same compact ID:

Json
// catalog-en.json
{
  "E.Auth.Token.001": {
    "compact_id": "Ay75d"  // ← Same hash
  }
}

// catalog-ja.json
{
  "E.Auth.Token.001": {
    "compact_id": "Ay75d"  // ← Same hash
  }
}

4. Catalog File Structure#

4.1 One File Per Language

Each language has its own independent catalog file:

catalogs/
  catalog-en.json    # English
  catalog-ja.json    # Japanese
  catalog-zh.json    # Chinese (Simplified)
  catalog-zh-TW.json # Chinese (Traditional)
  catalog-es.json    # Spanish
  catalog-fr.json    # French
  catalog-de.json    # German
  catalog-ar.json    # Arabic

4.2 File Naming Convention

Catalog files MUST be named:

catalog-{locale}.json

Where {locale} is an IETF BCP 47 language tag.

Examples:

  • catalog-en.json - English
  • catalog-ja.json - Japanese
  • catalog-zh.json - Chinese (Simplified)
  • catalog-zh-TW.json - Chinese (Traditional, Taiwan)
  • catalog-es-MX.json - Spanish (Mexico)
  • catalog-pt-BR.json - Portuguese (Brazil)

4.3 Catalog File Format

Each catalog file has identical structure:

Json
{
  "wdp_version": "1.0",
  "locale": "ja",
  "namespace": "myapp",
  "diags": {
    "Ay75d": {
      "code": "E.Auth.Token.001",
      "alias": "E.認証.トークン.001",
      "message": "トークンは{{timestamp}}に期限切れになりました",
      "description": "セッションの有効期限が切れました",
      "hints": [
        "もう一度ログインしてください",
        "セッションは30分後に期限切れになります"
      ]
    },
    "mN3Yr": {
      "code": "E.Auth.Password.002",
      "alias": "E.認証.パスワード.002",
      "message": "パスワードが正しくありません(残り{{attempts}}回)",
      "description": "入力されたパスワードが正しくありません",
      "hints": [
        "パスワードを確認してください",
        "大文字と小文字を区別します"
      ]
    }
  }
}

4.4 Required Fields

Catalog Level (REQUIRED):

  • wdp_version (string) - WDP version (e.g., "1.0")
  • locale (string) - IETF BCP 47 language tag (e.g., "ja")
  • namespace (string) - Application namespace (e.g., "myapp")
  • diags (object) - Diagnostic entries keyed by compact ID (5-character Base62 hash)

Diagnostic Entry (REQUIRED):

  • code (string) - Canonical English code (e.g., "E.Auth.Token.001")
  • alias (string) - Localized display code (e.g., "E.認証.トークン.001")
  • message (string) - Localized message template with {{field}} placeholders

Diagnostic Entry (OPTIONAL):

  • description (string) - Detailed explanation
  • hints (array of strings) - Actionable suggestions
  • resolution (string) - How to resolve
  • docs_url (string) - Documentation link

4.5 Compact ID as Key

The top-level keys in diags MUST always be the compact ID (5-character Base62 hash):

Json
{
  "diags": {
    "Ay75d": {  // ← Compact ID as key - REQUIRED
      "code": "E.Auth.Token.001",  // ← Canonical (English) - REQUIRED
      "alias": "E.認証.トークン.001"  // ← Display (localized)
    }
  }
}

Canonical codes or localized codes as keys are NOT permitted:

Json
// Invalid - MUST use compact ID as key
{
  "diags": {
    "E.Auth.Token.001": { ... },  // Invalid: MUST use compact ID
    "E.認証.トークン.001": { ... }  // Invalid: MUST use compact ID
  }
}

5. Code Aliases#

5.1 What Are Code Aliases?

A code alias is a localized version of the diagnostic code for display purposes.

Example:

  • Canonical code: E.Auth.Token.001 (internal, English)
  • Japanese alias: E.認証.トークン.001 (display, Japanese)
  • Chinese alias: E.认证.令牌.001 (display, Chinese)

All three aliases map to the same internal code and same compact ID.

5.2 Alias Structure

Aliases follow the same 4-part structure but with localized words:

Severity.Component.Primary.Sequence

Example localization:

LanguageAlias
EnglishE.Auth.Token.001
JapaneseE.認証.トークン.001
ChineseE.认证.令牌.001
FrenchE.Auth.Jeton.001
SpanishE.Aut.Token.001

5.3 Severity Letter

The severity letter in display aliases SHOULD remain the same as the canonical code (English letter: E, W, C, B, S, K, I, T, H).

Rationale:

  • Consistent across all languages for log searching
  • Many languages use same letters (Spanish: Error = E, French: Erreur = E, German: Fehler = F)
  • Enables cross-language API integration
  • Single character is hard to translate meaningfully in all languages

UI Display: Use emoji from Part 9 for visual presentation, SEPARATE from the code alias:

Javascript
// Display in UI
const emoji = getSeverityEmoji('E');  // Returns error emoji (❌)
const display = `${emoji} ${alias}`;  // Example: "❌ E.認証.トークン.001"

5.4 Uniqueness Constraint

Within each locale, aliases MUST be unique. A single alias cannot map to multiple diagnostic codes.

5.5 Bidirectional Lookup

The alias system supports bidirectional lookup:

Alias → Internal Code:

Javascript
// User reports: "E.認証.トークン.001"
const canonical = lookupCanonical("E.認証.トークン.001", locale="ja");
// → "E.Auth.Token.001"

const compactId = lookupCompactId(canonical);
// → "Ay75d"

Internal Code → All Aliases:

Javascript
const aliases = lookupAliases("E.Auth.Token.001");
// → {
//   en: "E.Auth.Token.001",
//   ja: "E.認証.トークン.001",
//   zh: "E.认证.令牌.001"
// }

6. Localized Messages#

6.1 Message Templates

Messages use {{field}} placeholder syntax (double braces):

Json
{
  "E.Auth.Token.001": {
    "message": "Token expired at {{timestamp}}"
  }
}

Localized versions:

Json
// catalog-en.json
{
  "message": "Token expired at {{timestamp}}"
}

// catalog-ja.json
{
  "message": "トークンは{{timestamp}}に期限切れになりました"
}

// catalog-zh.json
{
  "message": "令牌已在{{timestamp}}过期"
}

// catalog-es.json
{
  "message": "El token expiró en {{timestamp}}"
}

6.2 Placeholder Syntax

Correct: {{field}} (double braces)

Json
{
  "message": "User {{username}} logged in at {{timestamp}}"
}

Incorrect: {field} (single braces)

Json
{
  "message": "User {username} logged in at {timestamp}"  // Invalid: must use double braces
}

6.3 Field Interpolation

Clients replace placeholders with field values:

Javascript
const template = "Token expired at {{timestamp}}";
const fields = { timestamp: "2024-01-15T10:30:00Z" };

const message = template.replace(/\{\{(\w+)\}\}/g, (_, field) => 
  fields[field] || `{{${field}}}`
);

// → "Token expired at 2024-01-15T10:30:00Z"

6.4 No Template Expressions

WDP v1 supports only simple string replacement. Template expressions are NOT supported:

Supported:

Json
{
  "message": "Quota is {{quota_percent}}% full"
}

NOT Supported:

Json
{
  "message": "Quota is {{quota_used * 100 / quota_limit}}% full"  // Invalid: expressions not supported
}

Solution: Server must calculate derived values:

Json
{
  "compact_id": "wN4Qm",
  "fields": {
    "quota_used": "850",
    "quota_limit": "1000",
    "quota_percent": "85"  // ← Server calculates this
  }
}

6.5 Named Sequences and Internationalization

Named sequences (e.g., EXPIRED) present special challenges for internationalization because SCREAMING_SNAKE_CASE is English/Latin-centric.

For International Projects: Numeric Sequences are RECOMMENDED

Json
// RECOMMENDED (language-agnostic)
{
  "diags": {
    "Ay75d": {
      "code": "E.Auth.Token.001",
      "alias": "E.認証.トークン.001",
      "message": "トークンは{{timestamp}}に期限切れになりました"
    }
  }
}

// NOT RECOMMENDED for i18n (English-centric)
{
  "diags": {
    "xK4Np": {
      "code": "E.Auth.Token.EXPIRED",
      "alias": "E.認証.トークン.EXPIRED",
      "message": "トークンは{{timestamp}}に期限切れになりました"
    }
  }
}

If Using Named Sequences with Localized Aliases

Canonical code (English) MUST follow SCREAMING_SNAKE_CASE:

E.Auth.Token.EXPIRED

Display aliases SHOULD follow these guidelines:

For languages with uppercase/lowercase distinction (Latin, Cyrillic, Greek scripts), SHOULD use SCREAMING_SNAKE_CASE:

Json
{
  "diags": {
    "xK4Np": {
      "code": "E.Auth.Token.EXPIRED",
      "aliases": {
        "en": "E.Auth.Token.EXPIRED",
        "fr": "E.Auth.Jeton.EXPIRE",              // French with SCREAMING_SNAKE_CASE
        "es": "E.Aut.Token.EXPIRADO",             // Spanish with SCREAMING_SNAKE_CASE
        "de": "E.Auth.Token.ABGELAUFEN",          // German with SCREAMING_SNAKE_CASE
        "pt": "E.Auth.Token.EXPIRADO",            // Portuguese with SCREAMING_SNAKE_CASE
        "ru": "E.Auth.Token.ИСТЕК",               // Russian (Cyrillic) with uppercase
        "el": "E.Auth.Token.ΕΛΗΞΕ"                // Greek with uppercase
      }
    }
  }
}

For languages without case distinction (CJK, Arabic, Hebrew, Thai, Devanagari, etc.), use natural format:

Json
{
  "diags": {
    "xK4Np": {
      "code": "E.Auth.Token.EXPIRED",
      "aliases": {
        "ja": "E.認証.トークン.期限切れ",          // Note: No case available
        "zh": "E.认证.令牌.过期",                  // Note: No case available
        "ar": "E.Auth.Token.EXPIRED",            // Note: Could keep English or use Arabic
        "he": "E.Auth.Token.תוקף_פג",             // Note: Hebrew, no case
        "th": "E.Auth.Token.หมดอายุ",              // Note: Thai, no case
        "hi": "E.Auth.Token.समाप्त"               // Note: Hindi (Devanagari), no case
      }
    }
  }
}

6.6 Pluralization

For plural-sensitive messages, use ICU MessageFormat or similar:

Json
// catalog-en.json
{
  "diags": {
    "vR2Lq": {
      "code": "E.Validation.Items.001",
      "alias": "E.Validation.Items.001",
      "message": "{count, plural, one {# item failed} other {# items failed}}"
    }
  }
}

// catalog-ja.json (Japanese doesn't pluralize)
{
  "diags": {
    "vR2Lq": {
      "code": "E.Validation.Items.001",
      "alias": "E.検証.アイテム.001",
      "message": "{{count}}個のアイテムが失敗しました"
    }
  }
}

// catalog-pl.json (Polish has complex plural rules)
{
  "diags": {
    "vR2Lq": {
      "code": "E.Validation.Items.001",
      "alias": "E.Walidacja.Elementy.001",
      "message": "{count, plural, one {# element nie powiódł się} few {# elementy nie powiodły się} many {# elementów nie powiodło się}}"
    }
  }
}

Note: Pluralization is implementation-specific. Use standard i18n libraries (e.g., i18next, Intl.MessageFormat).

7. Translation Workflow#

7.1 No Master File Required

The specification does NOT require a master file containing all translations. Each language catalog is independent and self-contained.

7.2 Primary Locale

Each project designates one locale as the "primary" or "source" language. This is typically the language developers write diagnostics in.

How to declare primary locale:

Option 1: Configuration file

Json
// wdp.config.json
{
  "wdp": {
    "primary_locale": "ja",
    "supported_locales": ["ja", "en", "zh", "fr"]
  }
}

Option 2: Documentation

Markdown
# README.md

## Internationalization

Primary language: Japanese (`catalog-ja.json`)
All translations are made from Japanese.

7.3 Translation Process

Step 1: Developer creates diagnostic in primary language

Json
// catalog-ja.json (primary)
{
  "E.Auth.Token.001": {
    "compact_id": "Ay75d",
    "alias": "E.認証.トークン.001",
    "message": "トークンは{{timestamp}}に期限切れになりました",
    "description": "セッションの有効期限が切れました"
  }
}

Step 2: Translator receives primary catalog, creates translated catalog

Json
// catalog-en.json (translated from ja)
{
  "E.Auth.Token.001": {
    "compact_id": "Ay75d",
    "alias": "E.Auth.Token.001",
    "message": "Token expired at {{timestamp}}",
    "description": "Your session has expired"
  }
}

Step 3: Each translator works on their own file

  • English translator: catalog-en.json
  • Chinese translator: catalog-zh.json
  • French translator: catalog-fr.json

No merge conflicts! Each person works independently.

7.4 Version Control

Bash
# Developer adds new diagnostic (primary language)
git add catalogs/catalog-ja.json
git commit -m "feat: add token expiration error"

# English translator (separate commit, no conflicts)
git add catalogs/catalog-en.json
git commit -m "i18n: translate token expiration to English"

# Chinese translator (separate commit, no conflicts)
git add catalogs/catalog-zh.json
git commit -m "i18n: translate token expiration to Chinese"

8. Locale-Specific Formatting#

8.1 Date and Time Formatting

Use locale-appropriate date/time formats:

Javascript
// Server provides ISO 8601 timestamp
const fields = {
  timestamp: "2024-01-15T10:30:00Z"
};

// Client formats for user's locale
const date = new Date(fields.timestamp);

// English (US)
date.toLocaleString('en-US');
// → "1/15/2024, 10:30:00 AM"

// Japanese
date.toLocaleString('ja-JP');
// → "2024/1/15 10:30:00"

// German
date.toLocaleString('de-DE');
// → "15.1.2024, 10:30:00"

8.2 Number Formatting

Use locale-appropriate number formats:

Javascript
const value = 1234567.89;

// English (US) - comma thousands separator, period decimal
value.toLocaleString('en-US');
// → "1,234,567.89"

// French - space thousands separator, comma decimal
value.toLocaleString('fr-FR');
// → "1 234 567,89"

// Japanese - no thousands separator
value.toLocaleString('ja-JP');
// → "1234567.89"

8.3 Currency Formatting

Use locale-appropriate currency formats:

Javascript
const amount = 1234.56;

// US Dollars
amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
// → "$1,234.56"

// Japanese Yen
amount.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' });
// → "¥1,235" (no decimals)

// Euro (Germany)
amount.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' });
// → "1.234,56 €"

8.4 Recommendation

Server provides raw values, client formats for locale:

Javascript
// Server response
{
  "compact_id": "xY9Kp",
  "fields": {
    "amount": "1234.56",
    "currency": "USD",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}

// Client formats
const message = template
  .replace('{{amount}}', formatCurrency(fields.amount, fields.currency, locale))
  .replace('{{timestamp}}', formatDate(fields.timestamp, locale));

9. RTL and BiDi Support#

9.1 Right-to-Left Languages

WDP supports RTL languages (Arabic, Hebrew, Persian, etc.).

Key considerations:

  • Text direction is determined by locale
  • Diagnostic codes MUST remain LTR (left-to-right)
  • Messages SHOULD follow natural text direction

9.2 Display Considerations

English (LTR):

Error: E.Auth.Token.001
Token expired at 2024-01-15T10:30:00Z

Arabic (RTL) - using localized alias:

خطأ: ‪E.مصادقة.‎رمز.‎001‬
انتهت صلاحية الرمز المميز في 2024-01-15T10:30:00Z

Hebrew (RTL) - using localized alias:

שגיאה: ‪E.אימות.‎אסימון.‎001‬
אסימון פג ב-2024-01-15T10:30:00Z

Note: The severity letter (E, W, etc.) MUST remain LTR even in RTL text, but component and primary parts MAY be localized.

9.3 HTML/CSS Direction

Html
<!-- Arabic -->
<div dir="rtl" lang="ar">
  <p>خطأ: <code dir="ltr">E.مصادقة.رمز.001</code></p>
  <p>انتهت صلاحية الرمز المميز في 2024-01-15T10:30:00Z</p>
</div>

<!-- Hebrew -->
<div dir="rtl" lang="he">
  <p>שגיאה: <code dir="ltr">E.אימות.אסימון.001</code></p>
  <p>אסימון פג ב-2024-01-15T10:30:00Z</p>
</div>

Note: The dir="ltr" attribute on the code element ensures the alias displays correctly even when the alias contains RTL characters mixed with the LTR severity letter.

9.4 Bidirectional Algorithm

Implementations SHOULD use the Unicode Bidirectional Algorithm (UAX #9) for mixed LTR/RTL content:

Json
// catalog-ar.json
{
  "wdp_version": "1.0",
  "locale": "ar",
  "namespace": "myapp",
  "diags": {
    "Ay75d": {
      "code": "E.Auth.Token.001",
      "alias": "‪E.مصادقة.‎رمز.‎001‬",  // Arabic alias (severity letter remains LTR)
      "message": "انتهت صلاحية الرمز المميز في {{timestamp}}",
      "description": "انتهت صلاحية جلستك"
    }
  }
}

// catalog-he.json
{
  "wdp_version": "1.0",
  "locale": "he",
  "namespace": "myapp",
  "diags": {
    "Ay75d": {
      "code": "E.Auth.Token.001",
      "alias": "‪E.אימות.‎אסימון.‎001‬",  // Hebrew alias (severity letter remains LTR)
      "message": "אסימון פג ב-{{timestamp}}",
      "description": "הסשן שלך פג תוקף"
    }
  }
}

Rendering:

  • Overall direction: RTL (Arabic/Hebrew)
  • Severity letter (E, W, etc.): LTR (embedded in RTL context)
  • Component and Primary parts: Localized (may contain RTL characters)
  • Unicode BiDi algorithm handles mixing automatically

9.5 Alias Parsing in RTL Languages

This section clarifies how RTL aliases work and why no special parsing logic is required for RTL languages.

9.5.1 Logical Order vs Visual Order

Unicode text has two representations:

  • Logical order: How characters are stored in memory (always sequential)
  • Visual order: How characters appear on screen (affected by BiDi algorithm)

Key insight: WDP aliases are stored and parsed in logical order, which is always left-to-right at the byte level, regardless of the script used.

9.5.2 How RTL Aliases Are Stored

Consider an Arabic alias: E.مصادقة.رمز.001

This alias is stored in memory as a sequence of bytes in logical LTR order.

┌──────────────────────────────────────────────────────────────────────────────┐
│ POSITION (index):                                                            │
│   0    1    2    3    4    5    6    7    8    9   10   11   12  13  14  15  │
├──────────────────────────────────────────────────────────────────────────────┤
│ STORAGE (logical order - what's actually in memory):                         │
│   E    .   645  635  627  62F  642  629   .  631  645  632   .   0   0   1   │
│   ↑    ↑   └────── component ──────┘      ↑  └─ primary ─┘   ↑   └ seq ┘     │
│  LTR   .     (6 Arabic characters)        .  (3 Arabic)      .   (digits)    │
└──────────────────────────────────────────────────────────────────────────────┘

9.5.3 Parsing Works Identically for All Languages

Because the alias is stored in logical order, the same parsing code works for ALL languages:

Javascript
// This parser works for English, Japanese, Arabic, Hebrew - ALL languages
function parseAlias(alias) {
  const parts = alias.split('.');
  return {
    severity: parts[0],   // "E"
    component: parts[1],  // "مصادقة" (Arabic) or "Auth" (English)
    primary: parts[2],    // "رمز" (Arabic) or "Token" (English)
    sequence: parts[3]    // "001"
  };
}

No special RTL handling is needed. The .split('.') function operates on logical byte order, not visual rendering.

10. Fallback Strategies#

10.1 Fallback Chain

When a requested locale is unavailable, use a fallback chain:

Javascript
// User requests: zh-TW (Traditional Chinese, Taiwan)
// Fallback chain:
// 1. zh-TW (exact match)
// 2. zh (parent locale)
// 3. en (default/primary)
// 4. compact ID only (Ay75d)

async function loadCatalog(requestedLocale, primaryLocale = 'en') {
  const locales = [
    requestedLocale,           // zh-TW
    requestedLocale.split('-')[0], // zh
    primaryLocale,             // en
  ];
  
  for (const locale of locales) {
    try {
      const catalog = await fetch(`/catalogs/catalog-${locale}.json`);
      if (catalog.ok) return catalog.json();
    } catch (error) {
      continue;
    }
  }
  
  // No catalog available - display compact IDs only
  return null;
}

10.2 Partial Translation Handling

If a catalog has partial translations, fall back to primary locale for missing entries:

Javascript
function getMessage(compactId, locale, primaryLocale = 'en') {
  // Try requested locale (compact ID is the key)
  let entry = catalogs[locale]?.diags[compactId];
  if (entry?.message) return entry.message;
  
  // Fall back to primary locale
  entry = catalogs[primaryLocale]?.diags[compactId];
  if (entry?.message) return entry.message;
  
  // Fall back to compact ID
  return compactId;
}

10.3 Mixed Language Display

When displaying multiple diagnostics with different translation states:

Javascript
// Some messages available in user's language, others not
const diagnostics = [
  { code: "E.Auth.Token.001", locale: "ja" },     // Translated
  { code: "E.Payment.Failed.005", locale: "en" }, // Fallback to English
  { code: "E.Unknown.Error.999", locale: null },  // Only compact ID
];

Display:

❌ エラー: E.認証.トークン.001
   トークンは2024-01-15T10:30:00Zに期限切れになりました

❌ Error: E.Payment.Failed.005
   Payment processing failed (English fallback)

❌ Error: Ay75d
   (No translation available)

11. Implementation Guidelines#

11.1 Client-Side Pattern

Loading catalogs:

Javascript
// Detect user's language
const userLocale = navigator.language || 'en';

// Load catalog
async function loadCatalog(locale) {
  try {
    const response = await fetch(`https://cdn.example.com/wdp/v1/catalog-${locale}.json`);
    return response.json();
  } catch (error) {
    console.warn(`Failed to load catalog-${locale}.json:`, error);
    return null;
  }
}

// Initialize
const catalog = await loadCatalog(userLocale);

Expanding diagnostics:

Javascript
function expandDiagnostic(compactId, fields, catalog, locale) {
  // Look up entry directly by compact ID (the key)
  const entry = catalog.diags[compactId];
  
  if (!entry) {
    return { code: compactId, message: `Unknown error: ${compactId}` };
  }
  
  // Expand message template
  let message = entry.message;
  for (const [field, value] of Object.entries(fields)) {
    message = message.replace(new RegExp(`\\{\\{${field}\\}\\}`, 'g'), value);
  }
  
  return {
    code: entry.alias,
    compactId: compactId,
    message: message,
    description: entry.description,
    hints: entry.hints
  };
}

11.2 Server-Side Pattern

Generating diagnostics:

Javascript
// Server does NOT need to know user's language
// Just send canonical code + compact ID + fields

function generateDiagnostic(canonicalCode, fields) {
  const compactId = computeCompactId(canonicalCode);
  
  return {
    [compactId]: {
      f: fields
    }
  };
}

// Usage
app.post('/api/login', (req, res) => {
  if (tokenExpired) {
    res.status(401).json({
      wd: generateDiagnostic('E.Auth.Token.001', {
        timestamp: new Date().toISOString()
      })
    });
  }
});

11.3 Language Selection (Out of Scope)

How clients determine which language to request is NOT defined by this specification.

Standard approaches include:

  • HTTP Accept-Language header
  • Browser navigator.language API
  • Application user preferences
  • URL parameters (?lang=ja)
  • Cookie/localStorage
  • Operating system locale

12. Complete Examples#

12.1 Catalog Files

catalog-en.json (English)

Json
{
  "wdp_version": "1.0",
  "locale": "en",
  "namespace": "myapp",
  "diags": {
    "Ay75d": {
      "code": "E.Auth.Token.001",
      "alias": "E.Auth.Token.001",
      "message": "Token expired at {{timestamp}}",
      "description": "Your session has expired. Please log in again.",
      "hints": [
        "Click the 'Login' button to sign in",
        "Sessions expire after 30 minutes of inactivity"
      ],
      "docs_url": "https://docs.example.com/errors/auth-token-expired"
    },
    "mN3Yr": {
      "code": "E.Auth.Password.002",
      "alias": "E.Auth.Password.002",
      "message": "Incorrect password ({{attempts}} attempts remaining)",
      "description": "The password you entered is incorrect.",
      "hints": [
        "Check your password for typos",
        "Passwords are case-sensitive",
        "Use 'Forgot Password' to reset"
      ]
    },
    "wN4Qm": {
      "code": "W.Quota.Storage.001",
      "alias": "W.Quota.Storage.001",
      "message": "Storage quota is {{quota_percent}}% full",
      "description": "You are approaching your storage limit.",
      "hints": [
        "Delete unused files to free up space",
        "Upgrade your plan for more storage"
      ]
    }
  }
}

catalog-ja.json (Japanese)

Json
{
  "wdp_version": "1.0",
  "locale": "ja",
  "namespace": "myapp",
  "translated_from": "en",
  "diags": {
    "Ay75d": {
      "code": "E.Auth.Token.001",
      "alias": "E.認証.トークン.001",
      "message": "トークンは{{timestamp}}に期限切れになりました",
      "description": "セッションの有効期限が切れました。もう一度ログインしてください。",
      "hints": [
        "「ログイン」ボタンをクリックしてサインインしてください",
        "セッションは30分間操作がないと期限切れになります"
      ],
      "docs_url": "https://docs.example.com/ja/errors/auth-token-expired"
    },
    "mN3Yr": {
      "code": "E.Auth.Password.002",
      "alias": "E.認証.パスワード.002",
      "message": "パスワードが正しくありません(残り{{attempts}}回)",
      "description": "入力されたパスワードが正しくありません。",
      "hints": [
        "パスワードに入力ミスがないか確認してください",
        "パスワードは大文字と小文字を区別します",
        "「パスワードを忘れた場合」を使用してリセットしてください"
      ]
    },
    "wN4Qm": {
      "code": "W.Quota.Storage.001",
      "alias": "W.割当.ストレージ.001",
      "message": "ストレージ容量が{{quota_percent}}%使用されています",
      "description": "ストレージの上限に近づいています。",
      "hints": [
        "不要なファイルを削除してスペースを確保してください",
        "プランをアップグレードしてストレージを増やすことができます"
      ]
    }
  }
}

catalog-zh.json (Chinese Simplified)

Json
{
  "wdp_version": "1.0",
  "locale": "zh",
  "namespace": "myapp",
  "translated_from": "en",
  "diags": {
    "Ay75d": {
      "code": "E.Auth.Token.001",
      "alias": "E.认证.令牌.001",
      "message": "令牌已在{{timestamp}}过期",
      "description": "您的会话已过期。请重新登录。",
      "hints": [
        "点击'登录'按钮进行登录",
        "会话在30分钟无操作后过期"
      ],
      "docs_url": "https://docs.example.com/zh/errors/auth-token-expired"
    },
    "mN3Yr": {
      "code": "E.Auth.Password.002",
      "alias": "E.认证.密码.002",
      "message": "密码不正确(剩余{{attempts}}次尝试)",
      "description": "您输入的密码不正确。",
      "hints": [
        "检查密码是否有拼写错误",
        "密码区分大小写",
        "使用'忘记密码'进行重置"
      ]
    },
    "wN4Qm": {
      "code": "W.Quota.Storage.001",
      "alias": "W.配额.存储.001",
      "message": "存储配额已使用{{quota_percent}}%",
      "description": "您即将达到存储限制。",
      "hints": [
        "删除未使用的文件以释放空间",
        "升级您的计划以获得更多存储空间"
      ]
    }
  }
}

catalog-he.json (Hebrew - RTL)

Json
{
  "wdp_version": "1.0",
  "locale": "he",
  "namespace": "myapp",
  "translated_from": "en",
  "diags": {
    "Ay75d": {
      "code": "E.Auth.Token.001",
      "alias": "‪E.אימות.‎אסימון.‎001‬",
      "message": "אסימון פג ב-{{timestamp}}",
      "description": "הסשן שלך פג תוקף. אנא התחבר מחדש.",
      "hints": [
        "לחץ על כפתור 'התחברות' כדי להיכנס",
        "סשנים פגים לאחר 30 דקות של חוסר פעילות"
      ],
      "docs_url": "https://docs.example.com/he/errors/auth-token-expired"
    },
    "mN3Yr": {
      "code": "E.Auth.Password.002",
      "alias": "‪E.אימות.‎סיסמה.‎002‬",
      "message": "סיסמה שגויה (נותרו {{attempts}} ניסיונות)",
      "description": "הסיסמה שהזנת שגויה.",
      "hints": [
        "בדוק שאין שגיאות הקלדה בסיסמה",
        "סיסמאות רגישות לאותיות גדולות וקטנות",
        "השתמש ב'שכחתי סיסמה' לאיפוס"
      ]
    },
    "wN4Qm": {
      "code": "W.Quota.Storage.001",
      "alias": "‪W.מכסה.‎אחסון.‎001‬",
      "message": "מכסת האחסון מלאה ב-{{quota_percent}}%",
      "description": "אתה מתקרב למגבלת האחסון שלך.",
      "hints": [
        "מחק קבצים שאינם בשימוש כדי לפנות מקום",
        "שדרג את התוכנית שלך לקבלת יותר אחסון"
      ]
    }
  }
}

12.2 API Response Examples

Response with Compact ID (Client Expands)

Server response:

Json
{
  "success": false,
  "wd": {
    "Ay75d": {
      "f": {
        "timestamp": "2024-01-15T10:30:00Z"
      }
    }
  }
}

Client expands (Japanese user):

Javascript
const catalog = await fetch('/catalogs/catalog-ja.json').then(r => r.json());
const diagnostic = expandDiagnostic('Ay75d', { timestamp: '2024-01-15T10:30:00Z' }, catalog, 'ja');

console.log(diagnostic);
// {
//   code: "E.認証.トークン.001",
//   compactId: "Ay75d",
//   message: "トークンは2024-01-15T10:30:00Zに期限切れになりました",
//   description: "セッションの有効期限が切れました。もう一度ログインしてください。",
//   hints: ["「ログイン」ボタンをクリックしてサインインしてください", ...]
// }

Response with Pre-Expanded Message (Server-Side)

Server response (with Accept-Language: ja):

Json
{
  "success": false,
  "error": {
    "code": "E.認証.トークン.001",
    "compact_id": "Ay75d",
    "message": "トークンは2024-01-15T10:30:00Zに期限切れになりました",
    "severity": "E",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}

Appendix: Language Tag Reference#

Common IETF BCP 47 Language Tags

TagLanguage
enEnglish
en-USEnglish (United States)
en-GBEnglish (United Kingdom)
jaJapanese
zhChinese (Simplified)
zh-CNChinese (Simplified, China)
zh-TWChinese (Traditional, Taiwan)
esSpanish
es-MXSpanish (Mexico)
frFrench
fr-CAFrench (Canada)
deGerman
pt-BRPortuguese (Brazil)
arArabic
koKorean
ruRussian
itItalian
nlDutch
heHebrew
thThai

Reference: IANA Language Subtag Registry

References#

Normative References

  • Part 1: Severity - Severity level definitions
  • Part 2: Component - Component field specification
  • Part 3: Primary - Primary field specification
  • Part 4: Sequence - Sequence field specification
  • Part 5: Compact IDs - Hash computation and Base62 encoding
  • RFC 5646 - IETF BCP 47 Language Tags
  • Unicode UAX #9 - Unicode Bidirectional Algorithm

Informative References

  • Part 9: Presentation - Visual presentation guidelines
  • ICU MessageFormat - Pluralization and formatting
  • CLDR - Common Locale Data Repository
  • Intl API - JavaScript internationalization

End of Part 8: Internationalization