Added basic i18n

This commit is contained in:
2026-03-10 15:18:34 +08:00
parent 093a8745ac
commit 7d1de5f781
25 changed files with 660 additions and 105 deletions

View File

@@ -1,10 +0,0 @@
package i18n
import (
query "github.com/tgckpg/golifehk/query"
)
type Generics struct {
Name *map[string]string
query.Words
}

8
i18n/distance.go Normal file
View File

@@ -0,0 +1,8 @@
package i18n
func FormatDistance(langPack LangPack, meters float64) string {
if meters < 1000 {
return UNITS_METER.Text(langPack, meters)
}
return UNITS_KM.Text(langPack, meters/1000)
}

66
i18n/functions.go Normal file
View File

@@ -0,0 +1,66 @@
package i18n
import (
"encoding/json"
"fmt"
"log"
"sync"
langRes "github.com/tgckpg/golifehk/resources/langpacks"
)
type LangPack map[string]string
var (
LangPacks = map[string]LangPack{}
defaultLang = "en"
langMu sync.RWMutex
)
func LoadKeys(lang string) (LangPack, error) {
langMu.RLock()
langPack, ok := LangPacks[lang]
langMu.RUnlock()
if ok {
return langPack, nil
}
langMu.Lock()
defer langMu.Unlock()
// check again after obtaining write lock
if langPack, ok := LangPacks[lang]; ok {
return langPack, nil
}
data, err := langRes.FS.ReadFile(lang + "/messages.json")
if err == nil {
var parsed LangPack
if err := json.Unmarshal(data, &parsed); err != nil {
log.Fatal(err)
}
LangPacks[lang] = parsed
return parsed, nil
}
if lang == defaultLang {
return nil, fmt.Errorf("No language packs available: %s", lang)
}
// avoid recursive locking mess; explicit fallback is cleaner
if fallback, ok := LangPacks[defaultLang]; ok {
return fallback, nil
}
data, err = langRes.FS.ReadFile("langpacks/" + defaultLang + "/messages.json")
if err != nil {
return nil, fmt.Errorf("No language packs available: %s", lang)
}
var fallback LangPack
if err := json.Unmarshal(data, &fallback); err != nil {
return nil, err
}
LangPacks[defaultLang] = fallback
return fallback, nil
}

15
i18n/messages.go Normal file
View File

@@ -0,0 +1,15 @@
package i18n
const (
DS_KMB_ETA_DEPARTED Key = "DS.KMB.ETA.DEPARTED"
DS_KMB_NEAREST_STOPS Key = "DS.KMB.NEAREST_STOPS"
DS_KMB_NO_NEAREST_STOPS Key = "DS.KMB.NO_NEAREST_STOPS"
DS_MTR_NEAREST_STOPS Key = "DS.MTR.NEAREST_STOPS"
DS_MTR_NO_NEAREST_STOPS Key = "DS.MTR.NO_NEAREST_STOPS"
DS_MTR_NO_SCHEDULES Key = "DS.MTR.NO_SCHEDULES"
NO_RESULTS Key = "NO_RESULTS"
UNITS_MINUTE Key = "UNITS.MINUTE"
UNITS_KM Key = "UNITS.KM"
UNITS_METER Key = "UNITS.METER"
UNITS_MILE Key = "UNITS.MILE"
)

31
i18n/race_test.go Normal file
View File

@@ -0,0 +1,31 @@
package i18n
import (
"sync"
"testing"
)
func TestLoadKeysRace(t *testing.T) {
// Reset shared global state so the test starts clean
LangPacks = map[string]LangPack{}
const goroutines = 200
var wg sync.WaitGroup
wg.Add(goroutines)
start := make(chan struct{})
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
<-start
_, _ = LoadKeys("en")
}()
}
// Release all goroutines at once to maximize overlap
close(start)
wg.Wait()
}

26
i18n/types.go Normal file
View File

@@ -0,0 +1,26 @@
package i18n
import (
"fmt"
query "github.com/tgckpg/golifehk/query"
)
type Generics struct {
Name *map[string]string
query.Words
}
type Key string
func (k Key) Text(langPack map[string]string, args ...any) string {
txt, ok := langPack[string(k)]
if !ok {
return string(k)
}
if len(args) > 0 {
return fmt.Sprintf(txt, args...)
}
return txt
}