make di ready for better testing

This commit is contained in:
admin 2025-07-06 13:00:03 +02:00
parent 6e4c5ad6cf
commit 15ed60c621
6 changed files with 138 additions and 207 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
.vscode
.vscode
.idea

View File

@ -30,7 +30,7 @@ You must have a folder containing JSON files. Each file should hold an object wi
}
```
Next, you can create a Go file that embeds these JSON files using fs.Files.
Next, you can create a Go file that embeds these JSON files using embed.FS.
- translations/files.go
@ -49,18 +49,21 @@ Now, use the translation package to set up and retrieve translations on demand:
package main
import (
"git.apihub24.de/admin/translation"
translations "{link to your translations folder}"
di "git.apihub24.de/admin/generic-di"
"git.apihub24.de/admin/translation"
translations "{link to your translations folder}"
)
func main() {
// give the Init Function the translations fs.Files
translation.Init(translations.Files)
// Optional you can change the Fallback Language
translation.SetDefaultCulture("de")
// get the Service from DI
translationService := di.Inject[translation.ITranslationService]()
// give the Init Function the translations fs.Files
translationService.Init(translations.Files)
// Optional you can change the Fallback Language
translationService.SetDefaultCulture("de")
// get the Translation Values
translation.Get("SOME_KEY", "de")
// get the Translation Values
translationService.Get("SOME_KEY", "de")
}
```
@ -87,7 +90,7 @@ For example, if you want to split your translation files into modules, you can n
And the translation call would look like this:
```go
translation.Get("SOME_KEY", "global_de")
translationService.Get("SOME_KEY", "global_de")
```
### Parameters
@ -103,5 +106,5 @@ For example:
```
```go
translation.Get("DYNAMIC", "de", "12,50", "€")
translationService.Get("DYNAMIC", "de", "12,50", "€")
```

2
go.mod
View File

@ -1,3 +1,5 @@
module git.apihub24.de/admin/translation
go 1.22.0
require git.apihub24.de/admin/generic-di v1.4.0

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
git.apihub24.de/admin/generic-di v1.4.0 h1:0mQnpAcavMLBcnF5UO+tUI7abZ6zQPleqPsjEk3WIaU=
git.apihub24.de/admin/generic-di v1.4.0/go.mod h1:VcHV8MOb1qhwabHdO09CpjEg2VaDesehul86g1iyOxY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

View File

@ -5,22 +5,44 @@ import (
"encoding/json"
"fmt"
"strings"
"git.apihub24.de/admin/generic-di"
)
var defaultCulture = "en"
var sources = make(map[string]map[string]string)
var translationFiles embed.FS
func SetDefaultCulture(culture string) {
defaultCulture = strings.ToLower(culture)
func init() {
di.Injectable(newTranslationService)
}
func Init(files embed.FS) {
translationFiles = files
type ITranslationService interface {
Init(files embed.FS)
SetDefaultCulture(culture string)
Get(key string, culture string, args ...string) string
}
func Get(key string, culture string, args ...string) string {
source, err := loadSource(culture)
type translationService struct {
defaultCulture string
translationFiles embed.FS
sources map[string]map[string]string
}
func newTranslationService() ITranslationService {
return &translationService{
defaultCulture: "en",
translationFiles: embed.FS{},
sources: make(map[string]map[string]string),
}
}
func (translation *translationService) Init(files embed.FS) {
translation.translationFiles = files
}
func (translation *translationService) SetDefaultCulture(culture string) {
translation.defaultCulture = strings.ToLower(culture)
}
func (translation *translationService) Get(key string, culture string, args ...string) string {
source, err := translation.loadSource(culture)
if err != nil {
return fmt.Sprintf("unknown error: %s", err.Error())
}
@ -38,22 +60,22 @@ func Get(key string, culture string, args ...string) string {
return value
}
func loadSource(culture string) (map[string]string, error) {
source, ok := sources[culture]
func (translation *translationService) loadSource(culture string) (map[string]string, error) {
source, ok := translation.sources[culture]
if ok {
return source, nil
}
data, err := translationFiles.ReadFile(fmt.Sprintf("%s.json", strings.ToLower(culture)))
data, err := translation.translationFiles.ReadFile(fmt.Sprintf("%s.json", strings.ToLower(culture)))
if err != nil {
data, err = translationFiles.ReadFile(fmt.Sprintf("%s.json", defaultCulture))
data, err = translation.translationFiles.ReadFile(fmt.Sprintf("%s.json", translation.defaultCulture))
if err != nil {
return source, fmt.Errorf("can not load translation source for culture %s DefaultCulture: %s", culture, defaultCulture)
return source, fmt.Errorf("can not load translation source for culture %s DefaultCulture: %s", culture, translation.defaultCulture)
}
}
err = json.Unmarshal(data, &source)
if err != nil {
return source, fmt.Errorf("can not parse translation source %s", culture)
}
sources[culture] = source
return sources[culture], nil
translation.sources[culture] = source
return translation.sources[culture], nil
}

View File

@ -1,190 +1,89 @@
package translation_test
import (
"os"
"testing"
di "git.apihub24.de/admin/generic-di"
"git.apihub24.de/admin/translation"
exampletranslations "git.apihub24.de/admin/translation/example_translations"
"testing"
)
const (
paramTranslationValue = "tlv"
)
type testCaseError struct {
format string
params []string
}
type testCase struct {
name string
fallbackCulture string
keys []string
keyParams [][]string
cultures []string
expectedValues []string
errorMessages []testCaseError
}
func getTestCases() []testCase {
return []testCase{
{
name: "Test Translation.Init",
fallbackCulture: "",
keys: []string{"KEY"},
keyParams: [][]string{},
cultures: []string{"de"},
expectedValues: []string{"WERT"},
errorMessages: []testCaseError{
{
format: "translations not initialized!",
params: []string{},
},
},
},
{
name: "Test Translation.Get de",
fallbackCulture: "",
keys: []string{"KEY"},
keyParams: [][]string{},
cultures: []string{"de"},
expectedValues: []string{"WERT"},
errorMessages: []testCaseError{
{
format: "expect German KEY to have Value 'WERT' but was %[1]s",
params: []string{paramTranslationValue},
},
},
},
{
name: "Test Translation.Get en",
fallbackCulture: "",
keys: []string{"KEY"},
keyParams: [][]string{},
cultures: []string{"en"},
expectedValues: []string{"VALUE"},
errorMessages: []testCaseError{
{
format: "expect English KEY to have Value 'VALUE' but was %[1]s",
params: []string{paramTranslationValue},
},
},
},
{
name: "Test Translation.Get fr",
fallbackCulture: "",
keys: []string{"KEY"},
keyParams: [][]string{},
cultures: []string{"fr"},
expectedValues: []string{"VALEUR"},
errorMessages: []testCaseError{
{
format: "expect France KEY to have Value 'VALEUR' but was %[1]s",
params: []string{paramTranslationValue},
},
},
},
{
name: "Test Translation.Get missing Culture Fallback to English",
fallbackCulture: "",
keys: []string{"KEY"},
keyParams: [][]string{},
cultures: []string{"notexists"},
expectedValues: []string{"VALUE"},
errorMessages: []testCaseError{
{
format: "expect Fallback KEY to have Value 'VALUE' but was %[1]s",
params: []string{paramTranslationValue},
},
},
},
{
name: "Test Translation.Get can change Fallback to German",
fallbackCulture: "de",
keys: []string{"KEY"},
keyParams: [][]string{},
cultures: []string{"notexists"},
expectedValues: []string{"WERT"},
errorMessages: []testCaseError{
{
format: "expect Fallback KEY to have Value 'WERT' but was %[1]s",
params: []string{paramTranslationValue},
},
},
},
{
name: "Test Translation.Get return Error message when Key not exists",
fallbackCulture: "",
keys: []string{"notexists"},
keyParams: [][]string{},
cultures: []string{"de"},
expectedValues: []string{"no value for key notexists found in source de"},
errorMessages: []testCaseError{
{
format: "expect to get the missing key and culture but get: %[1]s",
params: []string{paramTranslationValue},
},
},
},
{
name: "Test Translation.Get with Parameters",
fallbackCulture: "",
keys: []string{"WITH_PARAM", "WITH_PARAM", "WITH_PARAM"},
keyParams: [][]string{{"a", "b"}, {"a", "b"}, {"a", "b"}},
cultures: []string{"de", "en", "fr"},
expectedValues: []string{"WERT a_b", "VALUE a_b", "VALEUR a_b"},
errorMessages: []testCaseError{
{
format: "expect German KEY to have Value 'WERT a_b' but was %[1]s",
params: []string{paramTranslationValue},
},
{
format: "expect English KEY to have Value 'VALUE a_b' but was %[1]s",
params: []string{paramTranslationValue},
},
{
format: "expect France KEY to have Value 'VALEUR a_b' but was %[1]s",
params: []string{paramTranslationValue},
},
},
},
func TestTranslationService_Init_And_Get_de(t *testing.T) {
translationService := di.Inject[translation.ITranslationService]()
translationService.Init(exampletranslations.Files)
value := translationService.Get("KEY", "de")
if value != "WERT" {
t.Errorf("expect 'WERT' for key 'KEY' but was %s", value)
return
}
}
func TestMain(m *testing.M) {
translation.Init(exampletranslations.Files)
code := m.Run()
os.Exit(code)
}
func Test(t *testing.T) {
for _, tc := range getTestCases() {
t.Run(tc.name, func(t *testing.T) {
if len(tc.fallbackCulture) > 0 {
translation.SetDefaultCulture(tc.fallbackCulture)
}
for idx := range tc.keys {
value := translation.Get(tc.keys[idx], tc.cultures[idx])
if idx < len(tc.keyParams) {
value = translation.Get(tc.keys[idx], tc.cultures[idx], tc.keyParams[idx]...)
}
if len(tc.fallbackCulture) > 0 {
translation.SetDefaultCulture("en")
}
if value != tc.expectedValues[idx] {
params := make([]any, 0)
for _, key := range tc.errorMessages[idx].params {
if key == paramTranslationValue {
params = append(params, value)
continue
}
params = append(params, key)
}
t.Errorf(tc.errorMessages[idx].format, params...)
return
}
}
})
func TestTranslationService_Get_de_en_fr(t *testing.T) {
translationService := di.Inject[translation.ITranslationService]()
translationService.Init(exampletranslations.Files)
value := translationService.Get("KEY", "de")
if value != "WERT" {
t.Errorf("expect 'WERT' for key 'KEY' but was %s", value)
return
}
value = translationService.Get("KEY", "en")
if value != "VALUE" {
t.Errorf("expect 'VALUE' for key 'KEY' but was %s", value)
return
}
value = translationService.Get("KEY", "fr")
if value != "VALEUR" {
t.Errorf("expect 'VALEUR' for key 'KEY' but was %s", value)
return
}
}
func TestTranslationService_Fallback_Is_en(t *testing.T) {
translationService := di.Inject[translation.ITranslationService]("TestTranslationService_Fallback_Is_en")
translationService.Init(exampletranslations.Files)
value := translationService.Get("KEY", "notexists")
if value != "VALUE" {
t.Errorf("expect 'VALUE' for key 'KEY' as Fallback but was %s", value)
return
}
}
func TestTranslationService_SetDefaultCulture_de(t *testing.T) {
translationService := di.Inject[translation.ITranslationService]("TestTranslationService_SetDefaultCulture_de")
translationService.Init(exampletranslations.Files)
translationService.SetDefaultCulture("de")
value := translationService.Get("KEY", "notexists")
if value != "WERT" {
t.Errorf("expect 'WERT' for key 'KEY' as Fallback but was %s", value)
return
}
}
func TestTranslationService_Error_Message_On_Key_Not_Exists(t *testing.T) {
translationService := di.Inject[translation.ITranslationService]()
translationService.Init(exampletranslations.Files)
value := translationService.Get("notexists", "de")
if value != "no value for key notexists found in source de" {
t.Errorf("expect Error Message 'no value for key notexists found in source de' but was %s", value)
return
}
}
func TestTranslationService_Get_Key_With_Parameter(t *testing.T) {
translationService := di.Inject[translation.ITranslationService]()
translationService.Init(exampletranslations.Files)
value := translationService.Get("WITH_PARAM", "de", "a", "b")
if value != "WERT a_b" {
t.Errorf("expect 'WERT a_b' for key 'WITH_PARAM' but was %s", value)
return
}
value = translationService.Get("WITH_PARAM", "en", "a", "b")
if value != "VALUE a_b" {
t.Errorf("expect 'VALUE a_b' for key 'WITH_PARAM' but was %s", value)
return
}
value = translationService.Get("WITH_PARAM", "fr", "a", "b")
if value != "VALEUR a_b" {
t.Errorf("expect 'VALEUR a_b' for key 'WITH_PARAM' but was %s", value)
return
}
}