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
- Separation of Identity and Presentation - Internal identity (hash) vs user display
- Any Language as Primary - Projects can work in their native language
- Hash Stability - Same diagnostic always produces same compact ID
- Independent Catalogs - Each language file is self-contained
- Simple Translation - Translators work on individual files
- 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:
- Hash Determinism - Same error = same compact ID everywhere
- ASCII Compatibility - Works in URLs, terminals, file systems
- No Normalization Issues - Avoids Unicode problems
- 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:
- Canonical 4-part code (English)
E.Auth.Token.001- Compact ID (hash of canonical code)
Ay75d- Hash algorithm
xxHash64("E.Auth.Token.001", seed="wdp-v1") → Base62 → Ay75d3.2 What Can Be Localized (Display)
These are localizable per language:
- Display alias (localized code)
- English:
E.Auth.Token.001 - Japanese:
E.認証.トークン.001 - Chinese:
E.认证.令牌.001
- Messages
- English:
"Token expired at {{timestamp}}" - Japanese:
"トークンは{{timestamp}}に期限切れになりました"
- Descriptions, hints, resolutions (all text fields)
3.3 Hash Computation (Always from Canonical)
The compact ID is always computed from the English canonical code:
// 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:
// 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 # Arabic4.2 File Naming Convention
Catalog files MUST be named:
catalog-{locale}.jsonWhere {locale} is an IETF BCP 47 language tag.
Examples:
catalog-en.json- Englishcatalog-ja.json- Japanesecatalog-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:
{
"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 explanationhints(array of strings) - Actionable suggestionsresolution(string) - How to resolvedocs_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):
{
"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:
// 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.SequenceExample localization:
| Language | Alias |
|---|---|
| English | E.Auth.Token.001 |
| Japanese | E.認証.トークン.001 |
| Chinese | E.认证.令牌.001 |
| French | E.Auth.Jeton.001 |
| Spanish | E.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:
// 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:
// User reports: "E.認証.トークン.001"
const canonical = lookupCanonical("E.認証.トークン.001", locale="ja");
// → "E.Auth.Token.001"
const compactId = lookupCompactId(canonical);
// → "Ay75d"Internal Code → All Aliases:
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):
{
"E.Auth.Token.001": {
"message": "Token expired at {{timestamp}}"
}
}Localized versions:
// 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)
{
"message": "User {{username}} logged in at {{timestamp}}"
}Incorrect: {field} (single braces)
{
"message": "User {username} logged in at {timestamp}" // Invalid: must use double braces
}6.3 Field Interpolation
Clients replace placeholders with field values:
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:
{
"message": "Quota is {{quota_percent}}% full"
}NOT Supported:
{
"message": "Quota is {{quota_used * 100 / quota_limit}}% full" // Invalid: expressions not supported
}Solution: Server must calculate derived values:
{
"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
// 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.EXPIREDDisplay aliases SHOULD follow these guidelines:
For languages with uppercase/lowercase distinction (Latin, Cyrillic, Greek scripts), SHOULD use SCREAMING_SNAKE_CASE:
{
"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:
{
"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:
// 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
// wdp.config.json
{
"wdp": {
"primary_locale": "ja",
"supported_locales": ["ja", "en", "zh", "fr"]
}
}Option 2: Documentation
# 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
// 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
// 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
# 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:
// 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:
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:
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:
// 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:00ZArabic (RTL) - using localized alias:
خطأ: E.مصادقة.رمز.001
انتهت صلاحية الرمز المميز في 2024-01-15T10:30:00ZHebrew (RTL) - using localized alias:
שגיאה: E.אימות.אסימון.001
אסימון פג ב-2024-01-15T10:30:00ZNote: 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
<!-- 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:
// 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:
// 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:
// 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:
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:
// 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:
// 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:
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:
// 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-Languageheader - Browser
navigator.languageAPI - Application user preferences
- URL parameters (
?lang=ja) - Cookie/localStorage
- Operating system locale
12. Complete Examples#
12.1 Catalog Files
catalog-en.json (English)
{
"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)
{
"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)
{
"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)
{
"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:
{
"success": false,
"wd": {
"Ay75d": {
"f": {
"timestamp": "2024-01-15T10:30:00Z"
}
}
}
}Client expands (Japanese user):
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):
{
"success": false,
"error": {
"code": "E.認証.トークン.001",
"compact_id": "Ay75d",
"message": "トークンは2024-01-15T10:30:00Zに期限切れになりました",
"severity": "E",
"timestamp": "2024-01-15T10:30:00Z"
}
}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