# Internationalisation — §17.3.2.20 `w:lang` and what depends on it
What this engine does with the document's declared language, and — more
usefully — what it does not. The second list is longer than the first, and is
here so that the next person to need one of those features finds the analysis
instead of redoing it.
## The `Locale` type
`Locale` (`render/resolve/locale.rs`) is `w:lang/@w:val` reduced to the
distinctions layout makes, and **not** a BCP-47 parser. The engine asks a
language exactly two questions — which character a `decimal` stop aligns on, and
whether §17.9.27's `ordinal` / `cardinalText` / `ordinalText` can be spelled — so
the type names the groups those two have distinct answers for:
| `English` | `.` | yes | `en-*`, and a document that declares no language |
| `CommaDecimal` | `,` | no | `de`, `fr`, `es`, `it`, `pt`, `ru`, `pl`, `nl`, the Nordics, most of Central/Eastern Europe |
| `PointDecimal` | `.` | no | `ja`, `zh`, `ko`, `he`, `th`, most of South and South-East Asia |
| `Unrecognised` | `.` | yes | anything else — **logs** |
`Unrecognised` answers exactly as `English` does, on purpose. Before `w:lang`
was read, every document got a decimal point and English number words; an
unfamiliar tag has to keep rendering as it does today rather than silently
losing content to a degrade. It is a separate variant because it is a different
*fact*: it logs, so an unhandled language is visible instead of quietly assumed
to be English.
Only `@w:val` is read. `@w:eastAsia` and `@w:bidi` name the languages of other
script runs, and neither question is asked of them.
Which layers a `Locale` is resolved *from* differs by consumer: a decimal tab
reads the paragraph's cascade (see
[Tabs](position-tabs.md#which-separator-and-whose-language), which also records
the one assumption in it), while a list label reads its own §17.9.23 cascade,
level `w:rPr` first — the same layer its font and colour come from.
**Known simplification.** Classification is by BCP-47 **primary subtag** — the
part before the first `-`. CLDR has regional overrides this ignores: `de-CH` and
`it-CH` write a point where `de` and `it` write a comma, `en-ZA` a comma where
`en` writes a point, and Latin-American Spanish splits both ways. Modelling them
means carrying a region table, which is the BCP-47 parsing this type exists to
avoid, and a half-filled table would look complete without being so. Arabic and
Persian are listed as point-decimal, right for their Latin-digit documents and
wrong for the Arabic-Indic `٫` some regions use. The warning names the tag, so a
document that lands on a wrong answer says so in the log.
When a language's number words are implemented, or a third question arrives, the
enum gains a variant and the compiler finds every site that must answer for it —
which is the whole reason it is an enum rather than a string compared afresh at
each call site.
## What is hand-rolled, and what a full ICU dependency would close
`Locale`'s four variants and its primary-subtag table are a **stopgap**, sized to
the two questions layout asks today. They are not an i18n strategy, and they do
not grow into one: every entry below needs CLDR data this engine does not carry,
and hand-rolling the next one is how a table that looks authoritative gets built
one plausible guess at a time.
| Decimal separator per **region** | `locale.rs` | Primary subtag only. `de-CH`, `en-ZA`, `es-MX` get their language's answer, not their region's |
| §17.9.27 number words in any language but English | `numbering.rs` (`to_cardinal_text`) | Digits. Needs CLDR spellout rules — ICU's rule-based number format |
| §17.18.59's other ~50 formats — `hebrew1`, `japaneseCounting`, `chineseCounting`, `thaiLetters`, `russianLower`, `arabicAlpha`, … | `st_enums.rs` (`StNumberFormat::Other`) | All degrade to `decimal`. Numbering-system and spellout data |
| UAX #14 line breaking | `fragment/text.rs` (`split_into_words`) | Splits on space, tab and hyphen. Thai, Lao, Khmer, Burmese and CJK write without spaces, so a paragraph in them **does not wrap at all** — the single hardest gap here |
| UAX #9 bidi reordering (§17.3.1.6 `w:bidi`, §17.3.2.30 `w:rtl`) | not implemented | Parsed into the model, never consumed. Arabic and Hebrew render in logical order |
| §17.16.4.2 date pictures | `field/format.rs` (`long_month_name`, `short_month_name`) | Month and day names hardcoded English, in every document |
That last row is the one worth noticing: it is the same missing thread as the
decimal tab and the number formats, in a third place nobody had listed. A German
document's `DATE \@ "MMMM"` field renders "August".
**The library.** ICU4X (the `icu` crate family, from unicode-org) is the option
that fits: pure Rust, so it adds no second C++ toolchain next to Skia, and its
components map onto the rows above — `icu_decimal`, `icu_segmenter` (which
carries the dictionary and LSTM models the space-less scripts need),
`icu_datetime`, and `unicode-bidi` for UAX #9. Adopting it is partly a
*replacement*: `unicode-segmentation`, `unicode-properties` and
`unicode-normalization` are already dependencies and overlap with
`icu_segmenter`, `icu_properties` and `icu_normalizer`.
**Check before committing to it:** rule-based spellout (`cardinalText`,
`ordinalText`, and most of §17.18.59) is `RuleBasedNumberFormat` in ICU4C, and
ICU4X's coverage of it needs verifying against the current release — it may not
be there, in which case that row needs a different answer from the rest.
**The cost is data, and it lands on the Python wheel.** ICU4X takes a data
provider; baked data can be trimmed to the components and locales actually used,
but "all CLDR locales" is megabytes. Decide the locale set deliberately and
measure the wheel, rather than discovering the size after the dependency is in.
Until that work happens, treat `Locale` as what it is: the smallest thing that
answers two questions correctly, with every one of its limits written down.