feat: implement localization system (#716)
* lib/localization: implement localization system Locale files are placed in lib/localization/locales/. If you add a locale, update manifest.json with available locales. * Exclude locales from check spelling * tests(lib/localization): add comprehensive translations test Signed-off-by: Xe Iaso <me@xeiaso.net> * fix(challenge/metarefresh): enable localization Signed-off-by: Xe Iaso <me@xeiaso.net> * fix: use simple syntax for localization in templ Also localize CELPHASE into French according to the wishes of the artist. Signed-off-by: Xe Iaso <me@xeiaso.net> * chore: spelling Signed-off-by: Xe Iaso <me@xeiaso.net> * chore:(js): fix forbidden patterns Signed-off-by: Xe Iaso <me@xeiaso.net> * chore: add goi18n to tools Signed-off-by: Xe Iaso <me@xeiaso.net> * test(lib/localization): dynamically determine the list of supported languages Signed-off-by: Xe Iaso <me@xeiaso.net> --------- Signed-off-by: Xe Iaso <me@xeiaso.net> Co-authored-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
parent
c2423d0688
commit
ad5430612f
24 changed files with 1205 additions and 314 deletions
116
lib/localization/localization_test.go
Normal file
116
lib/localization/localization_test.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package localization
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
)
|
||||
|
||||
func TestLocalizationService(t *testing.T) {
|
||||
service := NewLocalizationService()
|
||||
|
||||
t.Run("English localization", func(t *testing.T) {
|
||||
localizer := service.GetLocalizer("en")
|
||||
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
|
||||
if result != "Loading..." {
|
||||
t.Errorf("Expected 'Loading...', got '%s'", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("French localization", func(t *testing.T) {
|
||||
localizer := service.GetLocalizer("fr")
|
||||
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
|
||||
if result != "Chargement..." {
|
||||
t.Errorf("Expected 'Chargement...', got '%s'", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("All required keys exist in English", func(t *testing.T) {
|
||||
localizer := service.GetLocalizer("en")
|
||||
requiredKeys := []string{
|
||||
"loading", "why_am_i_seeing", "protected_by", "made_with",
|
||||
"mascot_design", "try_again", "go_home", "javascript_required",
|
||||
}
|
||||
|
||||
for _, key := range requiredKeys {
|
||||
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: key})
|
||||
if result == "" {
|
||||
t.Errorf("Key '%s' returned empty string", key)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("All required keys exist in French", func(t *testing.T) {
|
||||
localizer := service.GetLocalizer("fr")
|
||||
requiredKeys := []string{
|
||||
"loading", "why_am_i_seeing", "protected_by", "made_with",
|
||||
"mascot_design", "try_again", "go_home", "javascript_required",
|
||||
}
|
||||
|
||||
for _, key := range requiredKeys {
|
||||
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: key})
|
||||
if result == "" {
|
||||
t.Errorf("Key '%s' returned empty string", key)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type manifest struct {
|
||||
SupportedLanguages []string `json:"supported_languages"`
|
||||
}
|
||||
|
||||
func loadManifest(t *testing.T) manifest {
|
||||
t.Helper()
|
||||
|
||||
fin, err := localeFS.Open("locales/manifest.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fin.Close()
|
||||
|
||||
var result manifest
|
||||
if err := json.NewDecoder(fin).Decode(&result); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func TestComprehensiveTranslations(t *testing.T) {
|
||||
service := NewLocalizationService()
|
||||
|
||||
var translations = map[string]any{}
|
||||
fin, err := localeFS.Open("locales/en.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fin.Close()
|
||||
|
||||
if err := json.NewDecoder(fin).Decode(&translations); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var keys []string
|
||||
for k := range translations {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, lang := range loadManifest(t).SupportedLanguages {
|
||||
t.Run(lang, func(t *testing.T) {
|
||||
loc := service.GetLocalizer(lang)
|
||||
sl := SimpleLocalizer{Localizer: loc}
|
||||
for _, key := range keys {
|
||||
t.Run(key, func(t *testing.T) {
|
||||
if result := sl.T(key); result == "" {
|
||||
t.Error("key not defined")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue