go/i18n.go

139 lines
3.4 KiB
Go
Raw Permalink Normal View History

// 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"}
}