Chapter 34 · Volume IV — Runtime Subsystems

Localization & i18n

34.1 Overview

ADL provides built-in multi-language support through declarative localization. Every human-readable string in the system — field labels, validation messages, history events, notifications, documents, reports — can be translated. Translations live in the domain YAML (inline) or in satellite YAML files that are loaded alongside the domain model.

The localization system integrates with every output path: API responses include localized labels, history events render in the requested locale, validation errors appear in the user’s language, and notifications are sent in the recipient’s preferred language.


34.2 Activation

domain:
  name: awards
  version: "2.0.0"

  localization:
    defaultLocale: en
    supportedLocales: [en, es, fr, de, ja, zh, ar]
    fallbackChain: [requestedLocale, en]
    
    dateFormat:
      en: "MMM d, yyyy"
      es: "d 'de' MMM 'de' yyyy"
      fr: "d MMM yyyy"
      de: "d. MMM yyyy"
      ja: "yyyy年M月d日"
      zh: "yyyy年M月d日"
      ar: "d MMM yyyy"
    
    numberFormat:
      en:
        thousands: ","
        decimal: "."
        currencySymbol: "$"
        currencyPosition: before
      es:
        thousands: "."
        decimal: ","
        currencySymbol: "€"
        currencyPosition: after
      fr:
        thousands: " "
        decimal: ","
        currencySymbol: "€"
        currencyPosition: after
      de:
        thousands: "."
        decimal: ","
        currencySymbol: "€"
        currencyPosition: after
      ja:
        thousands: ","
        decimal: "."
        currencySymbol: "¥"
        currencyPosition: before
    
    textDirection:
      en: ltr
      es: ltr
      fr: ltr
      ar: rtl

34.3 Translation Structure

Inline Translations

Field labels and enum values can be declared inline:

resources:
  Award:
    label:
      en: "Award"
      es: "Premio"
      fr: "Subvention"
      de: "Auszeichnung"
      ja: "賞"
    
    plural:
      en: "Awards"
      es: "Premios"
      fr: "Subventions"
      de: "Auszeichnungen"
      ja: "賞"
    
    object:
      awardNumber:
        type: string
        required: true
        label:
          en: "Award Number"
          es: "Número de Premio"
          fr: "Numéro de Subvention"
      
      status:
        type: enum
        ref: '#/enums/AwardStatus'

enums:
  AwardStatus:
    values:
      pending:
        label:
          en: "Pending"
          es: "Pendiente"
          fr: "En attente"
        color: "#94a3b8"
      active:
        label:
          en: "Active"
          es: "Activo"
          fr: "Actif"
        color: "#10b981"

Satellite Translation Files

For large domains, translations can be extracted to separate files:

domain.yaml
locales/
  es.yaml
  fr.yaml
  de.yaml
  ja.yaml

File: locales/es.yaml

locale: es

resources:
  Award:
    label: "Premio"
    plural: "Premios"
    fields:
      awardNumber: "Número de Premio"
      title: "Título del Proyecto"
      status: "Estado"

enums:
  AwardStatus:
    pending: "Pendiente"
    active: "Activo"
    closed: "Cerrado"

history:
  events:
    activated:
      label: "Premio activado — Cuenta {accountNumber}"
    suspended:
      label: "Premio suspendido"

validation:
  messages:
    required: "{field} es obligatorio"
    minLength: "{field} debe tener al menos {min} caracteres"
    email: "{field} debe ser una dirección de correo válida"

notifications:
  templates:
    proposalApproved:
      subject: "Propuesta aprobada — {title}"
      body: "Su propuesta {title} ha sido aprobada."

The parser loads satellite files and merges translations into the domain model. Missing translations fall back through the fallbackChain.


34.4 Locale Resolution

The runtime determines which locale to use from the Accept-Language header:

GET /api/v1/awards/Award
Accept-Language: es-MX,es;q=0.9,en;q=0.8

Resolution algorithm:

  1. Parse Accept-Language header → [es-MX, es, en] (ordered by q-value)
  2. For each requested locale, check if it’s in supportedLocales
  3. If exact match found, use it
  4. If dialect match found (e.g., es-MXes), use base locale
  5. If no match, use defaultLocale

The resolved locale is included in the response metadata:

{
  "code": 200,
  "data": { ... },
  "_meta": {
    "locale": "es",
    "localeResolved": true
  }
}

34.5 What Gets Localized

Every human-readable string in the runtime:

ElementTranslation KeyExample
Resource labelresources.{name}.label”Award” → “Premio”
Resource pluralresources.{name}.plural”Awards” → “Premios”
Field labelresources.{name}.fields.{field}”Award Number” → “Número de Premio”
Enum valueenums.{name}.{value}”Pending” → “Pendiente”
State labelVia enum localization”In Closeout” → “En Cierre”
Transition labelstateMachine.transitions.{name}”Activate” → “Activar”
History event labelhistory.events.{name}.label”Award activated” → “Premio activado”
Validation messagevalidation.messages.{type}”is required” → “es obligatorio”
Rule messagerules.messages.{name}Rule violation text
Notification subjectnotifications.{name}.subjectEmail subject
Notification bodynotifications.{name}.bodyEmail/Slack body
Report headerreports.{name}.header.titleReport title
Document templatetemplates.{name}.bodyFull document text
Dashboard widgetdashboards.{name}.widgets.{widget}.labelWidget title
Error messagesystem.errors.{code}”Not found” → “No encontrado”

34.6 Localized API Responses

Schema Endpoint

GET /api/v1/domains/awards/schema
Accept-Language: es

Returns the full schema with Spanish labels:

{
  "resources": {
    "Award": {
      "label": "Premio",
      "plural": "Premios",
      "fields": {
        "awardNumber": {
          "type": "string",
          "required": true,
          "label": "Número de Premio"
        },
        "status": {
          "type": "enum",
          "ref": "AwardStatus",
          "label": "Estado",
          "options": [
            { "value": "pending", "label": "Pendiente", "color": "#94a3b8" },
            { "value": "active", "label": "Activo", "color": "#10b981" }
          ]
        }
      },
      "transitions": {
        "activate": { "label": "Activar" },
        "suspend": { "label": "Suspender" }
      }
    }
  }
}

A frontend can render a fully localized form — labels, enum dropdowns, transition buttons — entirely from this response. No client-side i18n framework needed.

Localized Validation Errors

{
  "code": 422,
  "error_code": "VALIDATION_ERROR",
  "errors": [
    {
      "field": "title",
      "label": "Título del Proyecto",
      "message": "Título del Proyecto es obligatorio",
      "rule": "required"
    }
  ]
}

Localized Rule Violations

{
  "code": 422,
  "error_code": "RULE_VIOLATION",
  "error_message": "El esfuerzo total comprometido para Dr. Smith sería 115%, superando el 100%",
  "rule": "effortCapEnforcement",
  "severity": "error"
}

34.7 History Event Localization

History events store both the pre-interpolated default label and the template+data for runtime localization.

Database Schema

CREATE TABLE {domain}_{resource}_history (
    id              TEXT PRIMARY KEY,
    event           TEXT NOT NULL,
    label           TEXT NOT NULL,
    label_template  TEXT,
    label_data      TEXT,
    ...
);

Write-Time Behavior

label = "Award activated — Account ACC-2024-567"
label_template = "history.events.activated.label"
label_data = { "accountNumber": "ACC-2024-567" }

Read-Time Behavior

Without locale header: return pre-interpolated label (zero overhead)

With Accept-Language: es:

  1. Load locale-specific template: "Premio activado — Cuenta {accountNumber}"
  2. Interpolate with stored label_data
  3. Return: "Premio activado — Cuenta ACC-2024-567"

This means events written before Spanish translations existed can still be displayed in Spanish after translations are added.


34.8 Locale-Aware Formatting

Number Formatting

1234567.89 formatted as money:
  en: $1,234,567.89
  es: 1.234.567,89 €
  fr: 1 234 567,89 €
  de: 1.234.567,89 €
  ja: ¥1,234,568

Date Formatting

2026-03-07 formatted as date:
  en: Mar 7, 2026
  es: 7 de mar de 2026
  fr: 7 mars 2026
  de: 7. März 2026
  ja: 2026年3月7日

Percentage Formatting

96.3 formatted as percentage:
  en: 96.3%
  fr: 96,3 %
  de: 96,3 %

The formatting engine uses the locale’s numberFormat and dateFormat configuration.


34.9 Translation Management

Coverage API

GET /api/v1/{domain}/localization/coverage
{
  "code": 200,
  "data": {
    "defaultLocale": "en",
    "supportedLocales": ["en", "es", "fr", "de", "ja"],
    "coverage": {
      "en": { "total": 347, "translated": 347, "percentage": 100 },
      "es": { "total": 347, "translated": 312, "missing": 35, "percentage": 89.9 },
      "fr": { "total": 347, "translated": 298, "missing": 49, "percentage": 85.9 },
      "de": { "total": 347, "translated": 210, "missing": 137, "percentage": 60.5 }
    }
  }
}

Missing Translations

GET /api/v1/{domain}/localization/missing?locale=es
{
  "locale": "es",
  "missing": [
    {
      "key": "resources.Subaward.label",
      "defaultValue": "Subaward",
      "context": "Resource label"
    },
    {
      "key": "history.events.subawardCreated.label",
      "defaultValue": "Subaward created — {subawardNumber}",
      "context": "History event label"
    }
  ]
}

Export/Import

GET /api/v1/{domain}/localization/export?locale=es&format=yaml
    — export as satellite YAML file

GET /api/v1/{domain}/localization/export?locale=es&format=xliff
    — export as XLIFF for professional translation tools

GET /api/v1/{domain}/localization/export?locale=es&format=csv
    — export as CSV for spreadsheet translation

POST /api/v1/{domain}/localization/import
    Content-Type: multipart/form-data
    file: es-translations.xliff
    — import translated XLIFF

XLIFF (XML Localization Interchange File Format) is the industry standard for professional translation workflows (Crowdin, Lokalise, Phrase, Transifex).


34.10 Integration Points

FeatureIntegration
Schema Endpoint (§5)Returns localized labels, enum values, transition names
Validation (§14)Error messages in requested locale with interpolated field labels
History (§21)Event labels localized at read time from stored template+data
Rules (§22)Violation messages localized with interpolated data
Notifications (§23)Template selection based on recipient’s locale preference
Reports (§26)Column labels and headers localized
Templates (§27)Document body selected by locale parameter
Dashboards (§26)Widget labels localized

End of Chapter 34.

Next: Chapter 35 — External Integrations