Locale YAML schema and inheritance
Orientation
Use this reference for file-level rules, merge behavior, lexical table shapes, and common authoring patterns. Surface fields and engine-specific values are in the linked companion references.
Example
locale: 'fr-CA'
variantOf: 'fr'
surfaces:
list:
engine: 'conjunction'
value: 'et'
A child mapping merges with its parent unless it changes engine; child
sequences replace parent sequences.
Scope
These YAML files are the single checked-in authoring surface for locale-owned generated behavior.
They are consumed only at build time by src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs.
They are not parsed at runtime.
File-Level Rules
- There is exactly one locale YAML file per locale code.
- The file name is the locale code, for example
en.yml,en-US.yml,pt-BR.yml. - Top-level properties are limited to:
localevariantOfsurfaces(required for non-variant locales; optional for variant locales with no overrides)
- Canonical surface names under
surfacesare limited to:listformatterphrasesinflectionnumberordinalclockcompasscalendar
- Unknown top-level keys are rejected by the generator.
- Locale words, switches, tables, and strategy choices belong here.
- Generator implementation contracts do not belong here.
- Shipped locales are expected to resolve both
number.wordsandnumber.parse, either locale-owned or same-language inherited with proof. number.formattingis a nested member undernumber, alongsidenumber.wordsandnumber.parse.inflectionmay inherit only from a locale with the same primary language subtag. A cross-language relationship such asnn→nbmust author its own inflection profile.
Merge Rules
Locale inheritance is resolved during code generation.
The rules are:
- Omitting a
surfaces.<surface>block inherits the parent surface unchanged. - A scalar child value replaces the parent scalar value.
- A sequence child value replaces the parent sequence value.
- A mapping child value merges recursively with the parent mapping.
- If a child mapping changes
engine, that mapping replaces the parent mapping entirely. - Do not use empty mappings to request built-in behavior. If a surface would only say "use the default", omit the block instead. Exception:
engine: 'default'is permitted onordinal.numeric,ordinal.date,ordinal.dateOnly, andclocksurfaces to explicitly opt into the built-in engine.
That means regional variants can now override only the fields they actually differ on. It does not mean inherited surfaces are automatically proved for parity purposes.
Example:
locale: 'en-IN'
variantOf: 'en'
surfaces:
number:
words:
engine: 'conjunctional-scale'
tensUnitsSeparator: ' '
scales:
-
value: 10000000
name: 'crore'
ordinalName: 'crore'
-
value: 100000
name: 'lakh'
ordinalName: 'lakh'
-
value: 1000
name: 'thousand'
ordinalName: 'thousand'
In that example:
- The locale still inherits the parent
number.words.andWord,number.words.unitsMap, andordinalsurface. - The
tensUnitsSeparatorscalar overrides only that one field. - The
scalessequence intentionally replaces the parent scale list.
Lexical Table Shapes
Most *Map fields are lexical tables. There are two supported authoring shapes.
- Dense sequence when every slot is meaningful:
digitWords:
- 'zero'
- 'one'
- 'two'
- Sparse numeric-slot mapping when the table starts at an offset or has intentional holes:
tensMap:
2: 'twenty'
3: 'thirty'
unitsOrdinalPrefixes:
0: 'zeroth'
1: 'first'
3: 'third'
Rules:
- Numeric keys are emitted as array indices.
- Missing numeric slots emit as empty strings.
- If the locale needs a real value at index
0, declare0:explicitly. - Do not use blank-string padding,
null: 0, or wrapper DSLs likefirstIndexandfillValue. - When a literal token is also a YAML keyword, quote it. For example, use
'null': 0in acardinalMapwhen the locale word is literally"null".
Common Authoring Patterns
These field-name patterns repeat across engines.
| Pattern | Meaning |
|---|---|
*Word | A literal locale word or phrase used verbatim by the runtime kernel. |
*Prefix | A literal prefix added before a stem, number, or token. |
*Suffix | A literal suffix appended after a stem, number, or token. |
*Joiner | A literal token inserted between number parts. |
*Separator | A literal separator used when joining composed pieces. |
*Map | A lexical table authored either as a dense sequence or as a sparse numeric-slot mapping. |
scales | A sequence of scale metadata records used for thousands, millions, crores, and similar groups. |
ordinal* | Fields used only for ordinal generation or ordinal parsing. |
negative* | Fields used only for negative number rendering or parsing. |
useCulture | Instructs the generated runtime path to pass the current culture into the shared kernel. |
Pitfall
Omission is the inheritance signal. Do not use empty mappings, cross-language parents, or child sequences that contain only the changed element. Do not use numeric-slot mappings merely to avoid writing a dense sequence whose every slot is meaningful.
Version notes
This file schema is for main/preview. Historical branches may not contain
the locale YAML compiler or may accept a different shape.