From f487e427e975af61275635d0d07201091e1d534f Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 3 Apr 2026 07:35:27 +0000 Subject: [PATCH] chore(i18n): align public API with AX Co-Authored-By: Virgil --- grammar.go | 5 ++--- handler.go | 2 ++ hooks.go | 4 ++++ i18n.go | 4 ++++ service.go | 5 +++++ state.go | 18 +++++++++++++----- types.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 76 insertions(+), 8 deletions(-) diff --git a/grammar.go b/grammar.go index a1e6a40..9798998 100644 --- a/grammar.go +++ b/grammar.go @@ -3,7 +3,6 @@ package i18n import ( "maps" "strconv" - "strings" "text/template" "unicode" @@ -702,7 +701,7 @@ func usesVowelSoundArticle(word string) bool { func looksLikeFrenchPlural(word string) bool { trimmed := core.Trim(word) - if trimmed == "" || strings.ContainsAny(trimmed, " \t") || isInitialism(trimmed) { + if trimmed == "" || core.Contains(trimmed, " ") || core.Contains(trimmed, "\t") || isInitialism(trimmed) { return false } lower := core.Lower(trimmed) @@ -1014,7 +1013,7 @@ func prefixWithArticle(article, word string) string { if article == "" || word == "" { return "" } - if strings.HasSuffix(article, "'") { + if core.HasSuffix(article, "'") { return article + word } return article + " " + word diff --git a/handler.go b/handler.go index 3a11259..d183b16 100644 --- a/handler.go +++ b/handler.go @@ -163,6 +163,8 @@ func (h NumericHandler) Handle(key string, args []any, next func() string) strin } // DefaultHandlers returns the built-in i18n.* namespace handlers. +// +// handlers := i18n.DefaultHandlers() func DefaultHandlers() []KeyHandler { return []KeyHandler{ LabelHandler{}, diff --git a/hooks.go b/hooks.go index a92ec53..26d5bfa 100644 --- a/hooks.go +++ b/hooks.go @@ -29,6 +29,8 @@ type localeProviderRegistration struct { } // LocaleProvider supplies one or more locale filesystems to the default service. +// +// i18n.RegisterLocaleProvider(myProvider) type LocaleProvider interface { LocaleSources() []FSSource } @@ -72,6 +74,8 @@ func RegisterLocales(fsys fs.FS, dir string) { // RegisterLocaleProvider registers a provider that can contribute locale files. // This is useful for packages that need to expose multiple locale sources as a // single unit. +// +// i18n.RegisterLocaleProvider(myProvider) func RegisterLocaleProvider(provider LocaleProvider) { if provider == nil { return diff --git a/i18n.go b/i18n.go index e8e2025..cf01f37 100644 --- a/i18n.go +++ b/i18n.go @@ -175,6 +175,8 @@ func CurrentDebug() bool { } // State returns a copy-safe snapshot of the default service configuration. +// +// state := i18n.State() func State() ServiceState { return defaultServiceValue(defaultServiceStateSnapshot(), func(svc *Service) ServiceState { return svc.State() @@ -182,6 +184,8 @@ func State() ServiceState { } // CurrentState is a more explicit alias for State. +// +// state := i18n.CurrentState() func CurrentState() ServiceState { return State() } diff --git a/service.go b/service.go index 44d0f75..759d29f 100644 --- a/service.go +++ b/service.go @@ -17,6 +17,9 @@ import ( ) // Service provides grammar-aware internationalisation. +// +// svc, err := i18n.New() +// i18n.SetDefault(svc) type Service struct { loader Loader messages map[string]map[string]Message // lang -> key -> message @@ -36,6 +39,8 @@ type Service struct { } // Option configures a Service during construction. +// +// svc, err := i18n.New(i18n.WithLanguage("en")) type Option func(*Service) // WithFallback sets the fallback language for missing translations. diff --git a/state.go b/state.go index f9e99ac..b01547a 100644 --- a/state.go +++ b/state.go @@ -1,8 +1,6 @@ package i18n import ( - "strings" - "dappco.re/go/core" ) @@ -55,6 +53,8 @@ func defaultServiceStateSnapshot() ServiceState { // ServiceState captures the current configuration of a service in one // copy-safe snapshot. +// +// state := i18n.CurrentState() type ServiceState struct { Language string RequestedLanguage string @@ -71,6 +71,9 @@ type ServiceState struct { } // HandlerTypeNames returns the short type names of the snapshot's handlers. +// +// names := i18n.CurrentState().HandlerTypeNames() +// // The returned slice is a fresh copy, so callers can inspect or mutate it // without affecting the snapshot. func (s ServiceState) HandlerTypeNames() []string { @@ -89,6 +92,8 @@ func (s ServiceState) HandlerTypeNames() []string { } // String returns a concise, stable summary of the service snapshot. +// +// fmt.Println(i18n.CurrentState().String()) func (s ServiceState) String() string { langs := "[]" if len(s.AvailableLanguages) > 0 { @@ -119,10 +124,11 @@ func (s ServiceState) String() string { func shortHandlerTypeName(handler KeyHandler) string { name := core.Sprintf("%T", handler) - if idx := strings.LastIndex(name, "."); idx >= 0 { - name = name[idx+1:] + parts := core.Split(name, ".") + if len(parts) > 0 { + name = parts[len(parts)-1] } - return strings.TrimPrefix(name, "*") + return core.TrimPrefix(name, "*") } func (s *Service) State() ServiceState { @@ -166,6 +172,8 @@ func (s *Service) String() string { } // CurrentState is a more explicit alias for State. +// +// state := i18n.CurrentState() func (s *Service) CurrentState() ServiceState { return s.State() } diff --git a/types.go b/types.go index 7c16616..9375341 100644 --- a/types.go +++ b/types.go @@ -17,6 +17,8 @@ import "sync" // --- Core Types --- // Mode determines how the service handles missing translation keys. +// +// i18n.SetMode(i18n.ModeStrict) type Mode int const ( @@ -39,6 +41,8 @@ func (m Mode) String() string { } // Formality represents the level of formality in translations. +// +// i18n.S("user", "Alex").Formal() type Formality int const ( @@ -48,6 +52,8 @@ const ( ) // TextDirection represents text directionality. +// +// if i18n.Direction() == i18n.DirRTL { /* ... */ } type TextDirection int const ( @@ -56,6 +62,8 @@ const ( ) // PluralCategory represents CLDR plural categories. +// +// cat := i18n.CurrentPluralCategory(2) type PluralCategory int const ( @@ -68,6 +76,8 @@ const ( ) // GrammaticalGender represents grammatical gender for nouns. +// +// i18n.S("user", "Alex").Gender("feminine") type GrammaticalGender int const ( @@ -80,6 +90,8 @@ const ( // --- Message Types --- // Message represents a translation — either a simple string or plural forms. +// +// msg := i18n.Message{One: "{{.Count}} file", Other: "{{.Count}} files"} type Message struct { Text string // Simple string value (non-plural) Zero string // count == 0 (Arabic, Latvian, Welsh) @@ -132,6 +144,8 @@ func (m Message) IsPlural() bool { // --- Subject Types --- // Subject represents a typed subject with metadata for semantic translations. +// +// subj := i18n.S("file", "config.yaml").Count(3).In("workspace") type Subject struct { Noun string // The noun type (e.g., "file", "repo") Value any // The actual value (e.g., filename) @@ -144,6 +158,8 @@ type Subject struct { // --- Intent Types --- // IntentMeta defines the behaviour of an intent. +// +// intent := i18n.Intent{Meta: i18n.IntentMeta{Type: "action", Verb: "delete"}} type IntentMeta struct { Type string // "action", "question", "info" Verb string // Reference to verb key @@ -153,6 +169,8 @@ type IntentMeta struct { } // Composed holds all output forms for an intent after template resolution. +// +// composed := i18n.ComposeIntent(i18n.Intent{Question: "Delete {{.Subject}}?"}, i18n.S("file", "config.yaml")) type Composed struct { Question string // "Delete config.yaml?" Confirm string // "Really delete config.yaml?" @@ -162,6 +180,8 @@ type Composed struct { } // Intent defines a semantic intent with templates for all output forms. +// +// intent := i18n.Intent{Question: "Delete {{.Subject}}?"} type Intent struct { Meta IntentMeta Question string // Template for question form @@ -186,6 +206,8 @@ type templateData struct { // --- Grammar Types --- // GrammarData holds language-specific grammar forms loaded from JSON. +// +// i18n.SetGrammarData("en", &i18n.GrammarData{Articles: i18n.ArticleForms{IndefiniteDefault: "a"}}) type GrammarData struct { Verbs map[string]VerbForms // verb -> forms Nouns map[string]NounForms // noun -> forms @@ -197,12 +219,16 @@ type GrammarData struct { } // VerbForms holds verb conjugations. +// +// forms := i18n.VerbForms{Past: "deleted", Gerund: "deleting"} type VerbForms struct { Past string // "deleted" Gerund string // "deleting" } // NounForms holds plural and gender information for a noun. +// +// forms := i18n.NounForms{One: "file", Other: "files"} type NounForms struct { One string // Singular form Other string // Plural form @@ -210,6 +236,8 @@ type NounForms struct { } // ArticleForms holds article configuration for a language. +// +// articles := i18n.ArticleForms{IndefiniteDefault: "a", IndefiniteVowel: "an"} type ArticleForms struct { IndefiniteDefault string // "a" IndefiniteVowel string // "an" @@ -218,12 +246,16 @@ type ArticleForms struct { } // PunctuationRules holds language-specific punctuation patterns. +// +// rules := i18n.PunctuationRules{LabelSuffix: ":", ProgressSuffix: "..."} type PunctuationRules struct { LabelSuffix string // ":" (French uses " :") ProgressSuffix string // "..." } // SignalData holds word lists used for disambiguation signals. +// +// signals := i18n.SignalData{VerbAuxiliaries: []string{"is", "was"}} type SignalData struct { NounDeterminers []string // Words that precede nouns: "the", "a", "this", "my", ... VerbAuxiliaries []string // Auxiliaries/modals before verbs: "is", "was", "will", ... @@ -235,6 +267,8 @@ type SignalData struct { // --- Number Formatting --- // NumberFormat defines locale-specific number formatting rules. +// +// fmt := i18n.NumberFormat{ThousandsSep: ",", DecimalSep: ".", PercentFmt: "%s%%"} type NumberFormat struct { ThousandsSep string // "," for en, "." for de DecimalSep string // "." for en, "," for de @@ -244,12 +278,18 @@ type NumberFormat struct { // --- Function Types --- // PluralRule determines the plural category for a count. +// +// rule := i18n.GetPluralRule("en") type PluralRule func(n int) PluralCategory // MissingKeyHandler receives missing key events. +// +// i18n.OnMissingKey(func(m i18n.MissingKey) {}) type MissingKeyHandler func(missing MissingKey) // MissingKey is dispatched when a translation key is not found in ModeCollect. +// +// func handle(m i18n.MissingKey) { _ = m.Key } type MissingKey struct { Key string Args map[string]any @@ -261,18 +301,24 @@ type MissingKey struct { // KeyHandler processes translation keys before standard lookup. // Handlers form a chain; each can handle a key or delegate to the next. +// +// i18n.AddHandler(i18n.LabelHandler{}) type KeyHandler interface { Match(key string) bool Handle(key string, args []any, next func() string) string } // Loader provides translation data to the Service. +// +// svc, err := i18n.NewWithLoader(loader) type Loader interface { Load(lang string) (map[string]Message, *GrammarData, error) Languages() []string } // Translator defines the interface for translation services. +// +// var t i18n.Translator = i18n.Default() type Translator interface { T(messageID string, args ...any) string SetLanguage(lang string) error