nuke/lib/localization/localization.go
fucksophie 896858e027
Some checks failed
Docker image builds / build (push) Waiting to run
Asset Build Verification / asset_verification (push) Has been cancelled
Docs deploy / build (push) Has been cancelled
Go Mod Tidy Check / go_mod_tidy_check (push) Has been cancelled
Go / go_tests (push) Has been cancelled
Package builds (unstable) / package_builds (push) Has been cancelled
Smoke tests / smoke-test (default-config-macro) (push) Has been cancelled
Smoke tests / smoke-test (docker-registry) (push) Has been cancelled
Smoke tests / smoke-test (double_slash) (push) Has been cancelled
Smoke tests / smoke-test (forced-language) (push) Has been cancelled
Smoke tests / smoke-test (git-clone) (push) Has been cancelled
Smoke tests / smoke-test (git-push) (push) Has been cancelled
Smoke tests / smoke-test (healthcheck) (push) Has been cancelled
Smoke tests / smoke-test (i18n) (push) Has been cancelled
Smoke tests / smoke-test (log-file) (push) Has been cancelled
Smoke tests / smoke-test (nginx) (push) Has been cancelled
Smoke tests / smoke-test (palemoon/amd64) (push) Has been cancelled
Smoke tests / smoke-test (robots_txt) (push) Has been cancelled
Check Spelling / Check Spelling (push) Has been cancelled
SSH CI / ssh (aarch64-16k) (push) Has been cancelled
SSH CI / ssh (aarch64-4k) (push) Has been cancelled
SSH CI / ssh (ppc64le) (push) Has been cancelled
SSH CI / ssh (riscv64) (push) Has been cancelled
zizmor / zizmor latest via PyPI (push) Has been cancelled
jane remover
2026-02-07 13:08:47 +02:00

136 lines
3.9 KiB
Go

package localization
import (
"embed"
"encoding/json"
"net/http"
"strings"
"sync"
"git.sad.ovh/sophie/nuke"
"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 nuke.ForcedLanguage == "" {
localizer = NewLocalizationService().GetLocalizerFromRequest(r)
} else {
localizer = NewLocalizationService().GetLocalizer(nuke.ForcedLanguage)
}
return &SimpleLocalizer{Localizer: localizer}
}