[agent/codex:gpt-5.4-mini] Read ~/spec/code/core/go/i18n/RFC.md fully. Find ONE feature... #13

Merged
Virgil merged 1 commit from agent/read---spec-code-core-go-i18n-rfc-md-ful into dev 2026-04-01 04:47:43 +00:00
6 changed files with 82 additions and 6 deletions

View file

@ -35,6 +35,9 @@ func MergeGrammarData(lang string, data *GrammarData) {
maps.Copy(existing.Verbs, data.Verbs)
maps.Copy(existing.Nouns, data.Nouns)
maps.Copy(existing.Words, data.Words)
if data.Number != (NumberFormat{}) {
existing.Number = data.Number
}
}
// IrregularVerbs returns a copy of the irregular verb forms map.

View file

@ -222,6 +222,20 @@ func flattenWithGrammar(prefix string, data map[string]any, out map[string]Messa
continue
}
// Number formatting rules
if grammar != nil && fullKey == "gram.number" {
if thousands, ok := v["thousands"].(string); ok {
grammar.Number.ThousandsSep = thousands
}
if decimal, ok := v["decimal"].(string); ok {
grammar.Number.DecimalSep = decimal
}
if percent, ok := v["percent"].(string); ok {
grammar.Number.PercentFmt = percent
}
continue
}
// CLDR plural object
if isPluralObject(v) {
msg := Message{}

View file

@ -85,6 +85,17 @@ func TestFSLoaderLoad(t *testing.T) {
t.Errorf("punct.progress = %q, want '...'", grammar.Punct.ProgressSuffix)
}
// Number formatting from gram.number
if grammar.Number.ThousandsSep != "," {
t.Errorf("number.thousands = %q, want ','", grammar.Number.ThousandsSep)
}
if grammar.Number.DecimalSep != "." {
t.Errorf("number.decimal = %q, want '.'", grammar.Number.DecimalSep)
}
if grammar.Number.PercentFmt != "%s%%" {
t.Errorf("number.percent = %q, want '%%s%%%%'", grammar.Number.PercentFmt)
}
// Words from gram.word.*
if len(grammar.Words) == 0 {
t.Error("grammar has 0 words")
@ -135,6 +146,11 @@ func TestFlattenWithGrammar(t *testing.T) {
"label": ":",
"progress": "...",
},
"number": map[string]any{
"thousands": ",",
"decimal": ".",
"percent": "%s%%",
},
"article": map[string]any{
"indefinite": map[string]any{
"default": "a",
@ -179,6 +195,11 @@ func TestFlattenWithGrammar(t *testing.T) {
t.Errorf("punct.label = %q, want ':'", grammar.Punct.LabelSuffix)
}
// Number formatting extracted
if grammar.Number.ThousandsSep != "," {
t.Errorf("number.thousands = %q, want ','", grammar.Number.ThousandsSep)
}
// Articles extracted
if grammar.Articles.IndefiniteDefault != "a" {
t.Errorf("article.indefinite.default = %q, want 'a'", grammar.Articles.IndefiniteDefault)
@ -188,6 +209,9 @@ func TestFlattenWithGrammar(t *testing.T) {
if msg, ok := messages["prompt.yes"]; !ok || msg.Text != "y" {
t.Errorf("prompt.yes not flattened correctly, got %+v", messages["prompt.yes"])
}
if _, ok := messages["gram.number.thousands"]; ok {
t.Error("gram.number.thousands should not be flattened into messages")
}
}
func TestFlattenPluralObject(t *testing.T) {

View file

@ -9,15 +9,27 @@ import (
func getNumberFormat() NumberFormat {
lang := currentLangForGrammar()
if idx := indexAny(lang, "-_"); idx > 0 {
lang = lang[:idx]
}
if fmt, ok := numberFormats[lang]; ok {
if fmt, ok := getLocaleNumberFormat(lang); ok {
return fmt
}
if idx := indexAny(lang, "-_"); idx > 0 {
if fmt, ok := getLocaleNumberFormat(lang[:idx]); ok {
return fmt
}
}
return numberFormats["en"]
}
func getLocaleNumberFormat(lang string) (NumberFormat, bool) {
if data := GetGrammarData(lang); data != nil && data.Number != (NumberFormat{}) {
return data.Number, true
}
if fmt, ok := numberFormats[lang]; ok {
return fmt, true
}
return NumberFormat{}, false
}
// FormatNumber formats an integer with locale-specific thousands separators.
func FormatNumber(n int64) string {
return formatIntWithSep(n, getNumberFormat().ThousandsSep)

View file

@ -143,3 +143,25 @@ func TestFormatOrdinal(t *testing.T) {
}
}
}
func TestFormatNumberFromLocale(t *testing.T) {
svc, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
SetDefault(svc)
if err := SetLanguage("fr"); err != nil {
t.Fatalf("SetLanguage(fr) failed: %v", err)
}
if got := FormatNumber(1234567); got != "1 234 567" {
t.Errorf("FormatNumber(fr) = %q, want %q", got, "1 234 567")
}
if got := FormatDecimal(1234.56); got != "1 234,56" {
t.Errorf("FormatDecimal(fr) = %q, want %q", got, "1 234,56")
}
if got := FormatPercent(0.85); got != "85 %" {
t.Errorf("FormatPercent(fr) = %q, want %q", got, "85 %")
}
}

View file

@ -52,7 +52,7 @@ type TextDirection int
const (
DirLTR TextDirection = iota // Left-to-right
DirRTL // Right-to-left
DirRTL // Right-to-left
)
// PluralCategory represents CLDR plural categories.
@ -193,6 +193,7 @@ type GrammarData struct {
Words map[string]string // base word translations
Punct PunctuationRules // language-specific punctuation
Signals SignalData // disambiguation signal word lists
Number NumberFormat // locale-specific number formatting
}
// VerbForms holds verb conjugations.
@ -385,7 +386,7 @@ var irregularVerbs = map[string]VerbForms{
"rebel": {Past: "rebelled", Gerund: "rebelling"}, "excel": {Past: "excelled", Gerund: "excelling"},
"cancel": {Past: "cancelled", Gerund: "cancelling"}, "travel": {Past: "travelled", Gerund: "travelling"},
"label": {Past: "labelled", Gerund: "labelling"}, "model": {Past: "modelled", Gerund: "modelling"},
"level": {Past: "levelled", Gerund: "levelling"},
"level": {Past: "levelled", Gerund: "levelling"},
"format": {Past: "formatted", Gerund: "formatting"},
"analyse": {Past: "analysed", Gerund: "analysing"},
"organise": {Past: "organised", Gerund: "organising"},