diff --git a/.gitignore b/.gitignore index 600d2d3..8cd0df3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.vscode \ No newline at end of file +.vscode +.idea \ No newline at end of file diff --git a/README.md b/README.md index d4ee677..f479745 100644 --- a/README.md +++ b/README.md @@ -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", "€") ``` diff --git a/go.mod b/go.mod index bb30189..6fda384 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.apihub24.de/admin/translation go 1.22.0 + +require git.apihub24.de/admin/generic-di v1.4.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4b02ba5 --- /dev/null +++ b/go.sum @@ -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= diff --git a/translation.go b/translation.go index 28ab77d..3e686fd 100644 --- a/translation.go +++ b/translation.go @@ -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 } diff --git a/translation_test.go b/translation_test.go index 798136f..325960b 100644 --- a/translation_test.go +++ b/translation_test.go @@ -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 } }