// SPDX-License-Identifier: EUPL-1.2 // Internationalisation for the Core framework. // I18n collects locale mounts from services and delegates // translation to a registered Translator implementation (e.g., go-i18n). package core import ( "sync" ) // Translator defines the interface for translation services. // Implemented by go-i18n's Srv. type Translator interface { // Translate translates a message by its ID with optional arguments. Translate(messageID string, args ...any) Result // SetLanguage sets the active language (BCP47 tag, e.g., "en-GB", "de"). SetLanguage(lang string) error // Language returns the current language code. Language() string // AvailableLanguages returns all loaded language codes. AvailableLanguages() []string } // LocaleProvider is implemented by services that ship their own translation files. // Core discovers this interface during service registration and collects the // locale mounts. The i18n service loads them during startup. // // Usage in a service package: // // //go:embed locales // var localeFS embed.FS // // func (s *MyService) Locales() *Embed { // m, _ := Mount(localeFS, "locales") // return m // } type LocaleProvider interface { Locales() *Embed } // I18n manages locale collection and translation dispatch. type I18n struct { mu sync.RWMutex locales []*Embed // collected from LocaleProvider services locale string translator Translator // registered implementation (nil until set) } // AddLocales adds locale mounts (called during service registration). func (i *I18n) AddLocales(mounts ...*Embed) { i.mu.Lock() i.locales = append(i.locales, mounts...) i.mu.Unlock() } // Locales returns all collected locale mounts. func (i *I18n) Locales() Result { i.mu.RLock() out := make([]*Embed, len(i.locales)) copy(out, i.locales) i.mu.RUnlock() return Result{out, true} } // SetTranslator registers the translation implementation. // Called by go-i18n's Srv during startup. func (i *I18n) SetTranslator(t Translator) { i.mu.Lock() i.translator = t locale := i.locale i.mu.Unlock() if t != nil && locale != "" { _ = t.SetLanguage(locale) } } // Translator returns the registered translation implementation, or nil. func (i *I18n) Translator() Result { i.mu.RLock() t := i.translator i.mu.RUnlock() if t == nil { return Result{} } return Result{t, true} } // Translate translates a message. Returns the key as-is if no translator is registered. func (i *I18n) Translate(messageID string, args ...any) Result { i.mu.RLock() t := i.translator i.mu.RUnlock() if t != nil { return t.Translate(messageID, args...) } return Result{messageID, true} } // SetLanguage sets the active language and forwards to the translator if registered. func (i *I18n) SetLanguage(lang string) Result { if lang == "" { return Result{OK: true} } i.mu.Lock() i.locale = lang t := i.translator i.mu.Unlock() if t != nil { if err := t.SetLanguage(lang); err != nil { return Result{err, false} } } return Result{OK: true} } // Language returns the current language code, or "en" if not set. func (i *I18n) Language() string { i.mu.RLock() locale := i.locale i.mu.RUnlock() if locale != "" { return locale } return "en" } // AvailableLanguages returns all loaded language codes. func (i *I18n) AvailableLanguages() []string { i.mu.RLock() t := i.translator i.mu.RUnlock() if t != nil { return t.AvailableLanguages() } return []string{"en"} }