Saltar al contenido
Ridgun
Español

42 El Libro de Ridgun

La lib locale — Reglas de Plurales CLDR, Formato UTC de Fechas y Números y Autónimos

42.1. ¿Por qué existe la lib locale? (El problema real y la filosofía Ridgun)

«¿Por qué necesito una lib locale escrita desde cero en Ridgun puro en lugar de depender de setlocale() de libc o arrastrar 30 MB de libicu?»

En sistemas reales (como pasarelas i18n, plantillas multilingües y motores de catálogos .po al estilo de babel), setlocale() en C muta estado global compartido entre hilos y libicu rompe cualquier despliegue ligero. La lib locale (src/main/ridgun/lib/locale.rg, dominio LOCALE) incorpora directamente en Ridgun puro las tablas Unicode CLDR 46 y el calendario civil UTC en $O(1)$ de Howard Hinnant:

  1. Reglas de plurales CLDR en doble modo (.po 2 formas por defecto + CLDR 46 estricto con "many"): plural category y plural index ofrecen por defecto el modo compatible con catálogos Gettext .po (["one", "other"] para es, ca, gl, eu, en, pt, sv, it, de, nl; regla n == 0 or n == 1 para fr; 3 formas eslavas para ru, uk, pl; 6 formas para ar; invariante para ja, zh, ko), mientras plural category cldr y plural categories cldr activan la categoría "many" de CLDR 46 para millones exactos (1000000, 2000000...) en es, ca, pt, it y fr.
  2. Formateo de fechas y marcas temporales UTC (format date, format datetime, month name, weekday name): convierte segundos Unix epoch mediante el algoritmo civil de Howard Hinnant en $O(1)$ y aplica los patrones short, medium, long y full de cada idioma (incluyendo declinaciones como "2026ko urriaren 31" en euskera o "31 d'octubre de 2026" en catalán).
  3. Formateo numérico con agrupación de miles y redondeo decimal half-away-from-zero (format int, format decimal): aplica separadores de miles (., ,, espacio o NBSP \u00a0) y separador decimal según el locale, con descomposición binaria IEEE-754 de 53 pasos sin libc.
  4. Autónimos nativos (language name) y normalización BCP-47 (normalize locale): convierte etiquetas como "es_ES.UTF-8" o "pt-BR" a su código base ("es", "pt") con fallback determinista a "en" y devuelve el autónimo nativo ("Español", "Català", "Galego", "Euskara", "English", "Português", "Svenska", "Italiano", etc.).

42.2. Artefactos nativos, importación y catálogo completo de la API de 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
#Función / Firma (fn ::)Tipo de Retorno
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. Explorando locale a fondo: Ejemplos paso a paso de cada funcionalidad

42.3.1. Ejemplo 1: Reglas de plurales CLDR (modo .po de 2 formas y modo estricto CLDR 46 con "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. Ejemplo 2: Formateo de fechas y marcas temporales UTC (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. Ejemplo 3: Formateo de enteros y decimales con agrupación de miles por locale

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. Ejemplo 4: Autónimos de idiomas (language name) y normalización BCP-47 / POSIX

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. Garantías algorítmicas, memoria RMM1/RMM2 y cierre del libro

  • Garantía algorítmica y de seguridad: Funciones 100 % puras y sin estado global mutable (TZ / LC_ALL), calendario civil UTC en $O(1)$ con validación de años bisiestos y fallback determinista a "en" ante etiquetas desconocidas.
  • Complejidad de pila $O(1)$ con TCO: Todos los recorridos de sustitución y agrupación en src/main/ridgun/lib/locale.rg son recursivos de cola.
  • Verificación continua: Probada exhaustivamente con src/test/ridgun/test locale.rg.

¡Has completado el recorrido exhaustivo por las 34 libs estándar de ridgun-libs!