Skip to content
Ridgun
English

42 The Ridgun Book

The locale Lib — CLDR Plural Rules, UTC Date & Number Formatting, and Language Autonyms

42.1. Why does the locale lib exist? (Pure-Ridgun CLDR Internationalization)

In systems programming, C's setlocale() mutates process-wide global state across threads, while linking libicu adds tens of megabytes of C++ dependencies. The locale lib (src/main/ridgun/lib/locale.rg, domain LOCALE) brings deterministic Unicode CLDR 46 rules and Howard Hinnant's $O(1)$ civil calendar directly into pure Ridgun:

  1. Dual-Mode CLDR Cardinal Plural Rules (.po-compatible 2-form default + strict CLDR 46 "many"): plural category, plural categories, and plural index provide the 2-form ["one", "other"] mode expected by Gettext .po catalogs for Western European locales (es, ca, gl, eu, en, pt, sv, it, de, nl), French 0/1 -> "one", 3-form Slavic rules (ru, uk, pl), 6-form Arabic rules (ar), and invariant East Asian rules (ja, zh, ko), while plural category cldr and plural categories cldr expose strict CLDR 46 "many" for exact millions (1000000, 2000000...) in es, ca, pt, it, and fr.
  2. UTC Date & Datetime Formatting (format date, format datetime, month name, weekday name): converts Unix epoch seconds in $O(1)$ into short, medium, long, and full locale patterns.
  3. Locale-Aware Integer & Decimal Formatting (format int, format decimal): thousands grouping and half-away-from-zero decimal rounding.
  4. Native Language Autonyms (language name) & BCP-47 / POSIX Tag Normalization (normalize locale) with deterministic fallback to "en".

42.2. Algebraic Data Types and Complete API Catalog of locale

type LocaleDatePatterns =
  ldp_short  : String
  ldp_medium : String
  ldp_long   : String
  ldp_full   : String

type LocaleSpec =
  loc_code          : String
  loc_autonym       : String
  loc_plural_family : String
  loc_thousands_sep : String
  loc_decimal_sep   : String
  loc_months_full   : [String]
  loc_months_short  : [String]
  loc_weekdays_full : [String]
  loc_weekdays_short: [String]
  loc_date_patterns : LocaleDatePatterns
#Function / Signature (fn ::)Return Type
1fn :: loc ascii lower char IntInt
2fn :: loc tag base char String Int Int String IntString
3fn :: loc tag base loop String Int Int StringString
4fn :: loc extract base tag StringString
5fn :: loc nth str [String] Int StringString
6fn :: loc replace all loop String String String Int Int Int StringString
7fn :: loc replace all String String StringString
8fn :: loc nbspString
9fn :: loc spec esLocaleSpec
10fn :: loc spec caLocaleSpec
11fn :: loc spec euLocaleSpec
12fn :: loc spec glLocaleSpec
13fn :: loc spec ptLocaleSpec
14fn :: loc spec svLocaleSpec
15fn :: loc spec enLocaleSpec
16fn :: loc spec itLocaleSpec
17fn :: supported locales[String]
18fn :: default locales[LocaleSpec]
19fn :: register locale LocaleSpec [LocaleSpec][LocaleSpec]
20fn :: loc spec code LocaleSpecString
21fn :: loc spec autonym LocaleSpecString
22fn :: loc spec plural rule LocaleSpecString
23fn :: loc spec group sep LocaleSpecString
24fn :: loc spec dec sep LocaleSpecString
25fn :: loc spec months wide LocaleSpec[String]
26fn :: loc spec months short LocaleSpec[String]
27fn :: loc spec weekdays wide LocaleSpec[String]
28fn :: loc spec weekdays short LocaleSpec[String]
29fn :: loc spec date patterns LocaleSpecLocaleDatePatterns
30fn :: loc find in registry [LocaleSpec] StringLocaleSpec
31fn :: loc lookup base with [LocaleSpec] StringLocaleSpec
32fn :: lookup locale with [LocaleSpec] StringLocaleSpec
33fn :: loc lookup base StringLocaleSpec
34fn :: lookup locale StringLocaleSpec
35fn :: normalize locale StringString
36fn :: language name StringString
37fn :: loc abs int IntInt
38fn :: loc eval plural an String Int BoolString
39fn :: loc eval plural rule String Int BoolString
40fn :: loc categories for rule String Bool[String]
41fn :: plural category String IntString
42fn :: plural categories String[String]
43fn :: plural category cldr String IntString
44fn :: plural categories cldr String[String]
45fn :: loc cat to index StringInt
46fn :: plural index String IntInt
47fn :: loc floor div qr Int IntInt
48fn :: loc floor div Int IntInt
49fn :: loc floor mod r Int IntInt
50fn :: loc floor mod Int IntInt
51fn :: loc ymd month IntInt
52fn :: loc ymd year Int IntInt
53fn :: loc days to ymd IntInt Int Int
54fn :: loc iso weekday norm IntInt
55fn :: loc iso weekday from days IntInt
56fn :: loc pad2 IntString
57fn :: loc pad4 IntString
58fn :: month name String IntString
59fn :: month short name String IntString
60fn :: loc norm weekday idx IntInt
61fn :: loc weekday wide by nw String IntString
62fn :: weekday name String IntString
63fn :: loc weekday short by nw String IntString
64fn :: weekday short name String IntString
65fn :: loc ca prep char String IntString
66fn :: loc ca prep month StringString
67fn :: loc eu genitive len String IntString
68fn :: loc eu genitive month StringString
69fn :: loc select date pattern LocaleDatePatterns StringString
70fn :: loc select dt join LocaleDatePatternsString
71fn :: loc render date pattern LocaleSpec String Int Int Int IntString
72fn :: format date String String IntString
73fn :: loc format time style String Int Int IntString
74fn :: format datetime String String IntString
75fn :: loc join grouped String String StringString
76fn :: loc group digits loop String Int String StringString
77fn :: loc format uint digits String StringString
78fn :: loc pos int digits Int StringString
79fn :: loc uint to digits IntString
80fn :: loc prepend neg Int StringString
81fn :: format int String IntString
82fn :: loc pow10 Int IntInt
83fn :: loc pow10 real Int RealReal
84fn :: loc real to int loop Real Int Real IntInt
85fn :: loc real to uint RealInt
86fn :: loc pad left zeros String IntString
87fn :: loc abs float RealReal
88fn :: loc sign str float RealString
89fn :: loc clamp frac digits IntInt
90fn :: loc format decimal scaled String String String Real IntString
91fn :: loc format decimal impl String String String Real IntString
92fn :: format decimal String Real IntString

42.3. Exploring locale in Depth: Step-by-Step Runnable Examples

42.3.1. Example 1: CLDR Plural Rules (.po-Compatible Mode & Strict CLDR 46 Mode with "many")

fn :: test locale plural rules => ()
      test locale plural rules =
        assert str eq (plural category "es" 1) "one" "AC2: es 1 -> one"
        assert str eq (plural category "es" 0) "other" "AC2: es 0 -> other"
        assert str eq (plural category "es" 5) "other" "AC2: es 5 -> other"
        assert str eq (plural category "es" 1000000) "other" "AC2: es 1000000 default .po -> other"
        assert str eq (plural category "eu" 1) "one" "AC2: eu 1 -> one"
        assert str eq (plural category "eu" 2) "other" "AC2: eu 2 -> other"
        assert str eq (plural category "sv" 1) "one" "AC2: sv 1 -> one"
        assert str eq (plural category "sv" 2) "other" "AC2: sv 2 -> other"
        assert str eq (plural category "ca" 1) "one" "AC2: ca 1 -> one"
        assert str eq (plural category "gl" 1) "one" "AC2: gl 1 -> one"
        assert str eq (plural category "pt" 1) "one" "AC2: pt 1 -> one"
        assert str eq (plural category "en" 1) "one" "AC2: en 1 -> one"
        assert str eq (plural category "zz" 1) "one" "AC2: unknown 1 -> one"
        assert str eq (plural category "zz" 3) "other" "AC2: unknown 3 -> other"

        assert str eq (loc join strs (plural categories "es")) "one,other" "AC2: default plural categories es"
        assert str eq (loc join strs (plural categories "eu")) "one,other" "AC2: default plural categories eu"

        assert int equals (plural index "es" 1) 0 "AC2: plural index 1 -> 0"
        assert int equals (plural index "es" 5) 1 "AC2: plural index 5 -> 1"

        assert str eq (plural category cldr "es" 1000000) "many" "AC2: strict CLDR es 1000000 -> many"
        assert str eq (plural category cldr "ca" 2000000) "many" "AC2: strict CLDR ca 2000000 -> many"
        assert str eq (plural category cldr "pt" 1000000) "many" "AC2: strict CLDR pt 1000000 -> many"
        assert str eq (plural category cldr "es" 0) "other" "AC2: strict CLDR es 0 -> other"
        assert str eq (plural category cldr "eu" 1000000) "other" "AC2: strict CLDR eu 1000000 -> other"
        assert str eq (loc join strs (plural categories cldr "es")) "one,many,other" "AC2: strict CLDR categories es"
        assert str eq (loc join strs (plural categories cldr "sv")) "one,other" "AC2: strict CLDR categories sv"
        ()

42.3.2. Example 2: UTC Date & Datetime Formatting (short, medium, long, full)

fn :: test locale dates and datetimes => ()
      test locale dates and datetimes =
        ts_oct30 = 1793318400
        assert str eq (format date "es" "long" ts_oct30) "30 de octubre de 2026" "AC3: es long date 2026-10-30"
        assert str eq (format date "es" "short" ts_oct30) "30/10/2026" "AC3: es short date"
        assert str eq (format date "es" "medium" ts_oct30) "30 oct 2026" "AC3: es medium date"
        assert str eq (format date "es" "full" ts_oct30) "viernes, 30 de octubre de 2026" "AC3: es full date"

        assert str eq (format date "ca" "long" ts_oct30) "30 d'octubre de 2026" "AC3: ca long date vowel elision d'octubre"
        ts_may15 = 1778803200
        assert str eq (format date "ca" "long" ts_may15) "15 de maig de 2026" "AC3: ca long date consonant de maig"

        assert str eq (format date "eu" "short" ts_oct30) "2026/10/30" "AC3: eu short date Y/M/D"
        assert str eq (format date "eu" "long" ts_oct30) "2026(e)ko urriaren 30a" "AC3: eu long date genitive"
        assert str eq (format date "eu" "full" ts_oct30) "2026(e)ko urriaren 30a, ostirala" "AC3: eu full date"

        assert str eq (format date "gl" "long" ts_oct30) "30 de outubro de 2026" "AC3: gl long date"
        assert str eq (format date "pt" "long" ts_oct30) "30 de outubro de 2026" "AC3: pt long date"
        assert str eq (format date "pt" "full" ts_oct30) "sexta-feira, 30 de outubro de 2026" "AC3: pt full date"

        assert str eq (format date "sv" "short" ts_oct30) "2026-10-30" "AC3: sv short date ISO"
        assert str eq (format date "sv" "long" ts_oct30) "30 oktober 2026" "AC3: sv long date"
        assert str eq (format date "sv" "full" ts_oct30) "fredag 30 oktober 2026" "AC3: sv full date"

        assert str eq (format date "en" "long" ts_oct30) "October 30, 2026" "AC3: en long date"
        assert str eq (format date "en" "full" ts_oct30) "Friday, October 30, 2026" "AC3: en full date"

        assert str eq (format datetime "es" "long" (ts_oct30 + 52245)) "30 de octubre de 2026, 14:30:45" "AC3: es long datetime"
        assert str eq (format datetime "sv" "short" (ts_oct30 + 52245)) "2026-10-30 14:30" "AC3: sv short datetime"

        assert str eq (month name "es" 10) "octubre" "AC3: month name es 10"
        assert str eq (month name "eu" 10) "urria" "AC3: month name eu 10"
        assert str eq (weekday name "es" 5) "viernes" "AC3: weekday name es 5"
        assert str eq (weekday name "sv" 7) "söndag" "AC3: weekday name sv 7"
        assert str eq (weekday name "sv" 0) "söndag" "AC3: weekday name sv 0"
        ()

42.3.3. Example 3: Locale-Aware Integer & Decimal Formatting (format int, format decimal)

fn :: test locale numbers => ()
      test locale numbers =
        nbsp = (char from code 194) + (char from code 160)
        assert str eq (format int "es" 1234567) "1.234.567" "AC4: format int es 1234567"
        assert str eq (format int "ca" 1234567) "1.234.567" "AC4: format int ca 1234567"
        assert str eq (format int "eu" 1234567) "1.234.567" "AC4: format int eu 1234567"
        assert str eq (format int "gl" 1234567) "1.234.567" "AC4: format int gl 1234567"
        assert str eq (format int "pt" 1234567) "1.234.567" "AC4: format int pt 1234567"
        assert str eq (format int "sv" 1234567) ("1" + nbsp + "234" + nbsp + "567") "AC4: format int sv 1234567 with NBSP"
        assert str eq (format int "en" 1234567) "1,234,567" "AC4: format int en 1234567"
        assert str eq (format int "es" (-1234567)) "-1.234.567" "AC4: format int es negative"
        assert str eq (format int "es" 42) "42" "AC4: format int es small"
        assert str eq (format int "es" 0) "0" "AC4: format int es zero"

        assert str eq (format decimal "es" 1234567.89 2) "1.234.567,89" "AC4: format decimal es"
        assert str eq (format decimal "sv" 1234.5 2) ("1" + nbsp + "234,50") "AC4: format decimal sv"
        assert str eq (format decimal "en" 1234.5 2) "1,234.50" "AC4: format decimal en"
        assert str eq (format decimal "es" (-19.99) 2) "-19,99" "AC4: format decimal negative"
        ()

42.3.4. Example 4: Native Language Autonyms (language name) & Tag Normalization

fn :: test locale autonyms normalization and registry => ()
      test locale autonyms normalization and registry =
        assert str eq (loc join strs (supported locales)) "es,ca,eu,gl,pt,sv,en" "AC1: supported locales order"
        assert str eq (language name "es") "Español" "AC1: language name es"
        assert str eq (language name "ca") "Català" "AC1: language name ca"
        assert str eq (language name "eu") "Euskara" "AC1: language name eu"
        assert str eq (language name "gl") "Galego" "AC1: language name gl"
        assert str eq (language name "pt") "Português" "AC1: language name pt"
        assert str eq (language name "sv") "Svenska" "AC1: language name sv"
        assert str eq (language name "en") "English" "AC1: language name en"
        assert str eq (language name "unknown") "English" "AC1: unknown locale falls back to English"
        assert str eq (language name "root") "English" "AC1: root locale falls back to English"
        assert str eq (normalize locale "es-ES") "es" "AC1: normalize locale es-ES"
        assert str eq (normalize locale "CA_ES.UTF-8") "ca" "AC1: normalize locale CA_ES.UTF-8"
        assert str eq (normalize locale "pt-BR") "pt" "AC1: normalize locale pt-BR"
        assert str eq (normalize locale "xx-YY") "en" "AC1: normalize unknown locale to en"

        fr_spec = LocaleSpec "fr" "Français" "one_zero_one" " " "," ["janvier"] ["janv."] ["lundi"] ["lun."] (LocaleDatePatterns "%DD/%MM/%YYYY" "%D %MMM %YYYY" "%D %MMMM %YYYY" "%EEEE %D %MMMM %YYYY" " ")
        reg2 = register locale fr_spec (default locales)
        found_fr = lookup locale with reg2 "fr-FR"
        assert str eq (loc spec autonym found_fr) "Français" "AC1: data-only LocaleSpec registration and lookup"
        ()

42.5. Algorithmic Guarantees and RMM1/RMM2 Memory

  • Algorithmic & safety guarantee: 100% pure functions with zero global mutable locale state, $O(1)$ proleptic Gregorian UTC calendar conversion, and deterministic fallback to "en".
  • Continuous verification: Verified by src/test/ridgun/test locale.rg.