| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- package localization
- import (
- "embed"
- "encoding/json"
- "net/http"
- "strings"
- "sync"
- "github.com/TecharoHQ/anubis"
- "github.com/nicksnyder/go-i18n/v2/i18n"
- "golang.org/x/text/language"
- )
- //go:embed locales/*.json
- var localeFS embed.FS
- type LocalizationService struct {
- bundle *i18n.Bundle
- }
- var (
- globalService *LocalizationService
- once sync.Once
- )
- func NewLocalizationService() *LocalizationService {
- once.Do(func() {
- bundle := i18n.NewBundle(language.English)
- bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
- // Read all JSON files from the locales directory
- entries, err := localeFS.ReadDir("locales")
- if err != nil {
- // Try fallback - create a minimal service with default messages
- globalService = &LocalizationService{bundle: bundle}
- return
- }
- loadedAny := false
- for _, entry := range entries {
- if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".json") {
- filePath := "locales/" + entry.Name()
- _, err := bundle.LoadMessageFileFS(localeFS, filePath)
- if err != nil {
- // Log error but continue with other files
- continue
- }
- loadedAny = true
- }
- }
- if !loadedAny {
- // If no files were loaded successfully, create minimal service
- globalService = &LocalizationService{bundle: bundle}
- return
- }
- globalService = &LocalizationService{bundle: bundle}
- })
- // Safety check - if globalService is still nil, create a minimal one
- if globalService == nil {
- bundle := i18n.NewBundle(language.English)
- bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
- globalService = &LocalizationService{bundle: bundle}
- }
- return globalService
- }
- func (ls *LocalizationService) GetLocalizer(lang string) *i18n.Localizer {
- return i18n.NewLocalizer(ls.bundle, lang)
- }
- func (ls *LocalizationService) GetLocalizerFromRequest(r *http.Request) *i18n.Localizer {
- if ls == nil || ls.bundle == nil {
- // Fallback to a basic bundle if service is not properly initialized
- bundle := i18n.NewBundle(language.English)
- bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
- return i18n.NewLocalizer(bundle, "en")
- }
- acceptLanguage := r.Header.Get("Accept-Language")
- // Parse Accept-Language header to properly handle quality factors
- // The language.ParseAcceptLanguage function returns tags sorted by quality
- tags, _, err := language.ParseAcceptLanguage(acceptLanguage)
- if err != nil || len(tags) == 0 {
- return i18n.NewLocalizer(ls.bundle, "en")
- }
- // Convert parsed tags to strings for the localizer
- // We include both the full tag and base language to ensure proper matching
- langs := make([]string, 0, len(tags)*2+1)
- for _, tag := range tags {
- langs = append(langs, tag.String())
- // Also add base language (e.g., "en" for "en-GB") to help matching
- base, _ := tag.Base()
- if base.String() != tag.String() {
- langs = append(langs, base.String())
- }
- }
- langs = append(langs, "en") // Always include English as fallback
- return i18n.NewLocalizer(ls.bundle, langs...)
- }
- // SimpleLocalizer wraps i18n.Localizer with a more convenient API
- type SimpleLocalizer struct {
- Localizer *i18n.Localizer
- }
- // T provides a concise way to localize messages
- func (sl *SimpleLocalizer) T(messageID string) string {
- return sl.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: messageID})
- }
- // Get the language that is used by the localizer by retrieving a well-known string that is required to be present
- func (sl *SimpleLocalizer) GetLang() string {
- _, tag, err := sl.Localizer.LocalizeWithTag(&i18n.LocalizeConfig{MessageID: "loading"})
- if err != nil {
- return "en"
- }
- return tag.String()
- }
- // GetLocalizer creates a localizer based on the request's Accept-Language header or forcedLanguage option
- func GetLocalizer(r *http.Request) *SimpleLocalizer {
- var localizer *i18n.Localizer
- if anubis.ForcedLanguage == "" {
- localizer = NewLocalizationService().GetLocalizerFromRequest(r)
- } else {
- localizer = NewLocalizationService().GetLocalizer(anubis.ForcedLanguage)
- }
- return &SimpleLocalizer{Localizer: localizer}
- }
|