Files
craft-libs/crates/i18n/data/locales.py
aiongg 233e0b7aa0 i18n: plural rules by language, CLDR data for every locale, trn_plain
Plural rules are looked up by language alone: CLDR's parent of sr-Latn
and zh-Hant is the root, whose only category is other, so sr-latn rows
were rejected. The baked data covers every CLDR locale (10 KB): plural
rules for he, ro, nb, ca, sk, th, ms and the rest, and decimal symbols
for nb-NO, sk-SK, bn-BD, ar-MA and English in Europe. Format applies a
Region's separators only where the system set them, and an empty
grouping separator writes none.

trn_plain writes counts in 0-9 without grouping, for documents and file
names. reset_format goes back to the system region. trn_decimal and
Format write infinities and NaN as CLDR does and take the other form.
Tests that don't set a format use en-US, not the machine's region. The
ICU4X crates are pinned to ~2.3, the minor version the data is for.
2026-10-21 05:49:38 +00:00

71 lines
2.9 KiB
Python

"""Writes locales.rs from CLDR's JSON release: the languages with plural rules and every locale's
percent affixes.
Usage: locales.py <cldr-json-full.zip> <cldr version>
"""
import json
import re
import sys
import zipfile
def rust(s):
return '"' + "".join(c if " " <= c <= "~" and c not in '"\\' else f"\\u{{{ord(c):x}}}" for c in s) + '"'
def affixes(numbers, numsys):
pattern = numbers[f"percentFormats-numberSystem-{numsys}"]["standard"].split(";")[0].replace("'", "")
sign = numbers[f"symbols-numberSystem-{numsys}"]["percentSign"]
number = re.search(r"[#0,.]+", pattern)
return pattern[: number.start()].replace("%", sign), pattern[number.end() :].replace("%", sign)
def main():
path, version = sys.argv[1], sys.argv[2]
found = {}
with zipfile.ZipFile(path) as z:
plurals = json.loads(z.read("cldr-core/supplemental/plurals.json"))["supplemental"]["plurals-type-cardinal"]
languages = sorted(l for l in plurals if l != "root" and "-" not in l)
for name in z.namelist():
m = re.fullmatch(r"cldr-numbers-full/main/([^/]+)/numbers\.json", name)
if not m or m.group(1) == "root":
continue
locale = m.group(1)
numbers = json.loads(z.read(name))["main"][locale]["numbers"]
found[(locale, True)] = affixes(numbers, "latn")
default = numbers["defaultNumberingSystem"]
if default != "latn":
found[(locale, False)] = affixes(numbers, default)
# Kept only where the lookup by dropping trailing subtags would find something else.
kept = {}
for (locale, latin), value in sorted(found.items(), key=lambda e: (e[0][0].count("-"), e[0])):
parts = locale.split("-")
inherited = ("", "%")
for n in range(len(parts) - 1, 0, -1):
if ("-".join(parts[:n]), latin) in kept:
inherited = kept[("-".join(parts[:n]), latin)]
break
if value != inherited:
kept[(locale, latin)] = value
print(f"// @generated by generate.sh from Unicode CLDR {version} (Unicode License).")
print()
print("/// The languages CLDR has plural rules for; the baked data has them all.")
print("pub(crate) const PLURAL_LANGUAGES: &[&str] = &[")
for i in range(0, len(languages), 16):
print(" " + ", ".join(rust(l) for l in languages[i : i + 16]) + ",")
print("];")
print()
print("/// Percent signs around the number (CLDR's standard percent pattern and sign): locale, Latin")
print("/// digits or the locale's own, text before the number, text after it. A locale missing here")
print("/// takes its parent's by dropping trailing subtags, then `{n}%`.")
print("pub(crate) const PERCENT: &[(&str, bool, &str, &str)] = &[")
for (locale, latin), (before, after) in sorted(kept.items()):
print(f" ({rust(locale)}, {str(latin).lower()}, {rust(before)}, {rust(after)}),")
print("];")
main()