[agent/codex:gpt-5.4-mini] Read ~/spec/code/core/go/i18n/RFC.md fully. Find features de... #192

Merged
Virgil merged 1 commit from agent/read---spec-code-core-go-i18n-rfc-md-ful into dev 2026-04-02 09:57:20 +00:00

View file

@ -105,7 +105,8 @@ var _ core.Translator = (*Service)(nil)
// New creates a new i18n service with embedded locales.
//
// Example:
// svc, err := i18n.New(i18n.WithLanguage("en"))
//
// svc, err := i18n.New(i18n.WithLanguage("en"))
func New(opts ...Option) (*Service, error) {
return NewWithLoader(NewFSLoader(localeFS, "locales"), opts...)
}
@ -113,7 +114,8 @@ func New(opts ...Option) (*Service, error) {
// NewService creates a new i18n service with embedded locales.
//
// Example:
// svc, err := i18n.NewService(i18n.WithFallback("en"))
//
// svc, err := i18n.NewService(i18n.WithFallback("en"))
//
// This is a named alias for New that keeps the constructor intent explicit
// for callers that prefer service-oriented naming.
@ -124,7 +126,8 @@ func NewService(opts ...Option) (*Service, error) {
// NewWithFS creates a new i18n service loading locales from the given filesystem.
//
// Example:
// svc, err := i18n.NewWithFS(os.DirFS("."), "locales")
//
// svc, err := i18n.NewWithFS(os.DirFS("."), "locales")
func NewWithFS(fsys fs.FS, dir string, opts ...Option) (*Service, error) {
return NewWithLoader(NewFSLoader(fsys, dir), opts...)
}
@ -132,7 +135,8 @@ func NewWithFS(fsys fs.FS, dir string, opts ...Option) (*Service, error) {
// NewServiceWithFS creates a new i18n service loading locales from the given filesystem.
//
// Example:
// svc, err := i18n.NewServiceWithFS(os.DirFS("."), "locales")
//
// svc, err := i18n.NewServiceWithFS(os.DirFS("."), "locales")
func NewServiceWithFS(fsys fs.FS, dir string, opts ...Option) (*Service, error) {
return NewWithFS(fsys, dir, opts...)
}
@ -140,7 +144,8 @@ func NewServiceWithFS(fsys fs.FS, dir string, opts ...Option) (*Service, error)
// NewWithLoader creates a new i18n service with a custom loader.
//
// Example:
// svc, err := i18n.NewWithLoader(loader)
//
// svc, err := i18n.NewWithLoader(loader)
func NewWithLoader(loader Loader, opts ...Option) (*Service, error) {
if loader == nil {
return nil, log.E("NewWithLoader", "nil loader", nil)
@ -174,24 +179,7 @@ func NewWithLoader(loader Loader, opts ...Option) (*Service, error) {
return nil, log.E("NewWithLoader", "load locale: "+lang, err)
}
lang = normalizeLanguageTag(lang)
_, seen := s.messages[lang]
if existing, ok := s.messages[lang]; ok {
if existing == nil {
existing = make(map[string]Message)
s.messages[lang] = existing
}
maps.Copy(existing, messages)
} else {
s.messages[lang] = messages
}
if grammarDataHasContent(grammar) {
if seen {
MergeGrammarData(lang, grammar)
} else {
SetGrammarData(lang, grammar)
}
}
s.ingestLocaleData(lang, messages, grammar)
tag := language.Make(lang)
if !slices.Contains(s.availableLangs, tag) {
s.availableLangs = append(s.availableLangs, tag)
@ -216,7 +204,8 @@ func NewWithLoader(loader Loader, opts ...Option) (*Service, error) {
// NewServiceWithLoader creates a new i18n service with a custom loader.
//
// Example:
// svc, err := i18n.NewServiceWithLoader(loader)
//
// svc, err := i18n.NewServiceWithLoader(loader)
func NewServiceWithLoader(loader Loader, opts ...Option) (*Service, error) {
return NewWithLoader(loader, opts...)
}
@ -224,7 +213,8 @@ func NewServiceWithLoader(loader Loader, opts ...Option) (*Service, error) {
// Init initialises the default global service if none has been set via SetDefault.
//
// Example:
// if err := i18n.Init(); err != nil { return err }
//
// if err := i18n.Init(); err != nil { return err }
func Init() error {
if defaultService.Load() != nil {
return nil
@ -255,7 +245,9 @@ func Init() error {
// Default returns the global i18n service, initialising if needed.
//
// Example:
// svc := i18n.Default()
//
// svc := i18n.Default()
//
// Returns nil if initialisation fails (error is logged).
func Default() *Service {
if svc := defaultService.Load(); svc != nil {
@ -270,7 +262,9 @@ func Default() *Service {
// SetDefault sets the global i18n service.
//
// Example:
// i18n.SetDefault(svc)
//
// i18n.SetDefault(svc)
//
// Passing nil clears the default service.
func SetDefault(s *Service) {
defaultService.Store(s)
@ -288,7 +282,9 @@ func SetDefault(s *Service) {
// AddLoader loads translations from a Loader into the default service.
//
// Example:
// i18n.AddLoader(loader)
//
// i18n.AddLoader(loader)
//
// Call this from init() in packages that ship their own locale files:
//
// //go:embed *.json
@ -320,14 +316,7 @@ func (s *Service) loadJSON(lang string, data []byte) error {
Words: make(map[string]string),
}
flattenWithGrammar("", raw, messages, grammarData)
if existing, ok := s.messages[lang]; ok {
maps.Copy(existing, messages)
} else {
s.messages[lang] = messages
}
if grammarDataHasContent(grammarData) {
MergeGrammarData(lang, grammarData)
}
s.ingestLocaleData(lang, messages, grammarData)
return nil
}
@ -946,31 +935,41 @@ func (s *Service) AddLoader(loader Loader) error {
return log.E("Service.AddLoader", "load locale: "+lang, err)
}
lang = normalizeLanguageTag(lang)
s.mu.Lock()
if s.messages[lang] == nil {
s.messages[lang] = make(map[string]Message)
}
for k, v := range messages {
s.messages[lang][k] = v
}
// Merge grammar data into the global grammar store (merge, not replace,
// so that multiple loaders contribute entries for the same language).
if grammarDataHasContent(grammar) {
MergeGrammarData(lang, grammar)
}
tag := language.Make(lang)
if !slices.Contains(s.availableLangs, tag) {
s.availableLangs = append(s.availableLangs, tag)
}
s.mu.Unlock()
s.ingestLocaleData(lang, messages, grammar)
}
s.autoDetectLanguage()
return nil
}
func (s *Service) ingestLocaleData(lang string, messages map[string]Message, grammar *GrammarData) {
lang = normalizeLanguageTag(lang)
if lang == "" {
return
}
s.mu.Lock()
existing := s.messages[lang]
if existing == nil {
s.messages[lang] = make(map[string]Message, len(messages))
}
maps.Copy(s.messages[lang], messages)
tag := language.Make(lang)
if !slices.Contains(s.availableLangs, tag) {
s.availableLangs = append(s.availableLangs, tag)
}
s.mu.Unlock()
// Keep grammar merges outside the service mutex. Message-store updates are
// the only part that need to be serialized here.
if grammarDataHasContent(grammar) {
if existing != nil {
MergeGrammarData(lang, grammar)
} else {
SetGrammarData(lang, grammar)
}
}
}
func (s *Service) hasLocaleRegistrationLoaded(id int) bool {
s.mu.RLock()
defer s.mu.RUnlock()
@ -1040,7 +1039,9 @@ func translateOK(messageID, value string) bool {
// LoadFS loads additional locale files from a filesystem.
//
// Example:
// _ = svc.LoadFS(os.DirFS("."), "locales")
//
// _ = svc.LoadFS(os.DirFS("."), "locales")
//
// Deprecated: Use AddLoader(NewFSLoader(fsys, dir)) instead for proper grammar handling.
func (s *Service) LoadFS(fsys fs.FS, dir string) error {
loader := NewFSLoader(fsys, dir)