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:
- Parse
Accept-Languageheader →[es-MX, es, en](ordered by q-value) - For each requested locale, check if it’s in
supportedLocales - If exact match found, use it
- If dialect match found (e.g.,
es-MX→es), use base locale - 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:
| Element | Translation Key | Example |
|---|---|---|
| Resource label | resources.{name}.label | ”Award” → “Premio” |
| Resource plural | resources.{name}.plural | ”Awards” → “Premios” |
| Field label | resources.{name}.fields.{field} | ”Award Number” → “Número de Premio” |
| Enum value | enums.{name}.{value} | ”Pending” → “Pendiente” |
| State label | Via enum localization | ”In Closeout” → “En Cierre” |
| Transition label | stateMachine.transitions.{name} | ”Activate” → “Activar” |
| History event label | history.events.{name}.label | ”Award activated” → “Premio activado” |
| Validation message | validation.messages.{type} | ”is required” → “es obligatorio” |
| Rule message | rules.messages.{name} | Rule violation text |
| Notification subject | notifications.{name}.subject | Email subject |
| Notification body | notifications.{name}.body | Email/Slack body |
| Report header | reports.{name}.header.title | Report title |
| Document template | templates.{name}.body | Full document text |
| Dashboard widget | dashboards.{name}.widgets.{widget}.label | Widget title |
| Error message | system.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:
- Load locale-specific template:
"Premio activado — Cuenta {accountNumber}" - Interpolate with stored
label_data - 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
| Feature | Integration |
|---|---|
| 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