diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index 68d7b768..2f48cac9 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -185,137 +185,6 @@ func _(messageID string, args ...any) string { return messageID } -// --- JSON parsing helpers --- - -// flatten recursively flattens nested maps into dot-notation keys. -func flatten(prefix string, data map[string]any, out map[string]Message) { - flattenWithGrammar(prefix, data, out, nil) -} - -// flattenWithGrammar recursively flattens nested maps and extracts grammar data. -func flattenWithGrammar(prefix string, data map[string]any, out map[string]Message, grammar *GrammarData) { - for key, value := range data { - fullKey := key - if prefix != "" { - fullKey = prefix + "." + key - } - - switch v := value.(type) { - case string: - out[fullKey] = Message{Text: v} - - case map[string]any: - // Check if this is a verb form object - // Grammar data lives under "gram.*" (a nod to Gram - grandmother) - if grammar != nil && isVerbFormObject(v) { - verbName := key - if strings.HasPrefix(fullKey, "gram.verb.") { - verbName = strings.TrimPrefix(fullKey, "gram.verb.") - } - forms := VerbForms{} - if base, ok := v["base"].(string); ok { - _ = base // base form stored but not used in VerbForms - } - if past, ok := v["past"].(string); ok { - forms.Past = past - } - if gerund, ok := v["gerund"].(string); ok { - forms.Gerund = gerund - } - grammar.Verbs[strings.ToLower(verbName)] = forms - continue - } - - // Check if this is a noun form object - if grammar != nil && isNounFormObject(v) { - nounName := key - if strings.HasPrefix(fullKey, "gram.noun.") { - nounName = strings.TrimPrefix(fullKey, "gram.noun.") - } - forms := NounForms{} - if one, ok := v["one"].(string); ok { - forms.One = one - } - if other, ok := v["other"].(string); ok { - forms.Other = other - } - if gender, ok := v["gender"].(string); ok { - forms.Gender = gender - } - grammar.Nouns[strings.ToLower(nounName)] = forms - continue - } - - // Check if this is an article object - if grammar != nil && fullKey == "gram.article" { - if indef, ok := v["indefinite"].(map[string]any); ok { - if def, ok := indef["default"].(string); ok { - grammar.Articles.IndefiniteDefault = def - } - if vowel, ok := indef["vowel"].(string); ok { - grammar.Articles.IndefiniteVowel = vowel - } - } - if def, ok := v["definite"].(string); ok { - grammar.Articles.Definite = def - } - continue - } - - // Check if this is a punctuation rules object - if grammar != nil && fullKey == "gram.punct" { - if label, ok := v["label"].(string); ok { - grammar.Punct.LabelSuffix = label - } - if progress, ok := v["progress"].(string); ok { - grammar.Punct.ProgressSuffix = progress - } - continue - } - - // Check if this is a base word in gram.word.* - if grammar != nil && strings.HasPrefix(fullKey, "gram.word.") { - wordKey := strings.TrimPrefix(fullKey, "gram.word.") - // v could be a string or a nested object - if str, ok := value.(string); ok { - if grammar.Words == nil { - grammar.Words = make(map[string]string) - } - grammar.Words[strings.ToLower(wordKey)] = str - } - continue - } - - // Check if this is a plural object (has CLDR plural category keys) - if isPluralObject(v) { - msg := Message{} - if zero, ok := v["zero"].(string); ok { - msg.Zero = zero - } - if one, ok := v["one"].(string); ok { - msg.One = one - } - if two, ok := v["two"].(string); ok { - msg.Two = two - } - if few, ok := v["few"].(string); ok { - msg.Few = few - } - if many, ok := v["many"].(string); ok { - msg.Many = many - } - if other, ok := v["other"].(string); ok { - msg.Other = other - } - out[fullKey] = msg - } else { - // Recurse into nested object - flattenWithGrammar(fullKey, v, out, grammar) - } - } - } -} - func detectLanguage(supported []language.Tag) string { langEnv := os.Getenv("LANG") if langEnv == "" { diff --git a/pkg/i18n/mutate.go b/pkg/i18n/mutate.go new file mode 100644 index 00000000..1f365204 --- /dev/null +++ b/pkg/i18n/mutate.go @@ -0,0 +1,133 @@ +// Package i18n provides internationalization for the CLI. +package i18n + +import "strings" + +// flatten recursively flattens nested maps into dot-notation keys. +func flatten(prefix string, data map[string]any, out map[string]Message) { + flattenWithGrammar(prefix, data, out, nil) +} + +// flattenWithGrammar recursively flattens nested maps and extracts grammar data. +func flattenWithGrammar(prefix string, data map[string]any, out map[string]Message, grammar *GrammarData) { + for key, value := range data { + fullKey := key + if prefix != "" { + fullKey = prefix + "." + key + } + + switch v := value.(type) { + case string: + out[fullKey] = Message{Text: v} + + case map[string]any: + // Check if this is a verb form object + // Grammar data lives under "gram.*" (a nod to Gram - grandmother) + if grammar != nil && isVerbFormObject(v) { + verbName := key + if strings.HasPrefix(fullKey, "gram.verb.") { + verbName = strings.TrimPrefix(fullKey, "gram.verb.") + } + forms := VerbForms{} + if base, ok := v["base"].(string); ok { + _ = base // base form stored but not used in VerbForms + } + if past, ok := v["past"].(string); ok { + forms.Past = past + } + if gerund, ok := v["gerund"].(string); ok { + forms.Gerund = gerund + } + grammar.Verbs[strings.ToLower(verbName)] = forms + continue + } + + // Check if this is a noun form object + if grammar != nil && isNounFormObject(v) { + nounName := key + if strings.HasPrefix(fullKey, "gram.noun.") { + nounName = strings.TrimPrefix(fullKey, "gram.noun.") + } + forms := NounForms{} + if one, ok := v["one"].(string); ok { + forms.One = one + } + if other, ok := v["other"].(string); ok { + forms.Other = other + } + if gender, ok := v["gender"].(string); ok { + forms.Gender = gender + } + grammar.Nouns[strings.ToLower(nounName)] = forms + continue + } + + // Check if this is an article object + if grammar != nil && fullKey == "gram.article" { + if indef, ok := v["indefinite"].(map[string]any); ok { + if def, ok := indef["default"].(string); ok { + grammar.Articles.IndefiniteDefault = def + } + if vowel, ok := indef["vowel"].(string); ok { + grammar.Articles.IndefiniteVowel = vowel + } + } + if def, ok := v["definite"].(string); ok { + grammar.Articles.Definite = def + } + continue + } + + // Check if this is a punctuation rules object + if grammar != nil && fullKey == "gram.punct" { + if label, ok := v["label"].(string); ok { + grammar.Punct.LabelSuffix = label + } + if progress, ok := v["progress"].(string); ok { + grammar.Punct.ProgressSuffix = progress + } + continue + } + + // Check if this is a base word in gram.word.* + if grammar != nil && strings.HasPrefix(fullKey, "gram.word.") { + wordKey := strings.TrimPrefix(fullKey, "gram.word.") + // v could be a string or a nested object + if str, ok := value.(string); ok { + if grammar.Words == nil { + grammar.Words = make(map[string]string) + } + grammar.Words[strings.ToLower(wordKey)] = str + } + continue + } + + // Check if this is a plural object (has CLDR plural category keys) + if isPluralObject(v) { + msg := Message{} + if zero, ok := v["zero"].(string); ok { + msg.Zero = zero + } + if one, ok := v["one"].(string); ok { + msg.One = one + } + if two, ok := v["two"].(string); ok { + msg.Two = two + } + if few, ok := v["few"].(string); ok { + msg.Few = few + } + if many, ok := v["many"].(string); ok { + msg.Many = many + } + if other, ok := v["other"].(string); ok { + msg.Other = other + } + out[fullKey] = msg + } else { + // Recurse into nested object + flattenWithGrammar(fullKey, v, out, grammar) + } + } + } +}