Skip to main content
Version: 4.0 (next)

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

  1. There is exactly one locale YAML file per locale code.
  2. The file name is the locale code, for example en.yml, en-US.yml, pt-BR.yml.
  3. Top-level properties are limited to:
    • locale
    • variantOf
    • surfaces (required for non-variant locales; optional for variant locales with no overrides)
  4. Canonical surface names under surfaces are limited to:
    • list
    • formatter
    • phrases
    • inflection
    • number
    • ordinal
    • clock
    • compass
    • calendar
  5. Unknown top-level keys are rejected by the generator.
  6. Locale words, switches, tables, and strategy choices belong here.
  7. Generator implementation contracts do not belong here.
  8. Shipped locales are expected to resolve both number.words and number.parse, either locale-owned or same-language inherited with proof.
  9. number.formatting is a nested member under number, alongside number.words and number.parse.
  10. inflection may inherit only from a locale with the same primary language subtag. A cross-language relationship such as nnnb must author its own inflection profile.

Merge Rules

Locale inheritance is resolved during code generation.

The rules are:

  1. Omitting a surfaces.<surface> block inherits the parent surface unchanged.
  2. A scalar child value replaces the parent scalar value.
  3. A sequence child value replaces the parent sequence value.
  4. A mapping child value merges recursively with the parent mapping.
  5. If a child mapping changes engine, that mapping replaces the parent mapping entirely.
  6. 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 on ordinal.numeric, ordinal.date, ordinal.dateOnly, and clock surfaces 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:

  1. The locale still inherits the parent number.words.andWord, number.words.unitsMap, and ordinal surface.
  2. The tensUnitsSeparator scalar overrides only that one field.
  3. The scales sequence intentionally replaces the parent scale list.

Lexical Table Shapes

Most *Map fields are lexical tables. There are two supported authoring shapes.

  1. Dense sequence when every slot is meaningful:
digitWords:
- 'zero'
- 'one'
- 'two'
  1. 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:

  1. Numeric keys are emitted as array indices.
  2. Missing numeric slots emit as empty strings.
  3. If the locale needs a real value at index 0, declare 0: explicitly.
  4. Do not use blank-string padding, null: 0, or wrapper DSLs like firstIndex and fillValue.
  5. When a literal token is also a YAML keyword, quote it. For example, use 'null': 0 in a cardinalMap when the locale word is literally "null".

Common Authoring Patterns

These field-name patterns repeat across engines.

PatternMeaning
*WordA literal locale word or phrase used verbatim by the runtime kernel.
*PrefixA literal prefix added before a stem, number, or token.
*SuffixA literal suffix appended after a stem, number, or token.
*JoinerA literal token inserted between number parts.
*SeparatorA literal separator used when joining composed pieces.
*MapA lexical table authored either as a dense sequence or as a sparse numeric-slot mapping.
scalesA 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.
useCultureInstructs 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.