diff --git a/README.md b/README.md index f479745..d4ee677 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 embed.FS. +Next, you can create a Go file that embeds these JSON files using fs.Files. - translations/files.go @@ -49,21 +49,18 @@ Now, use the translation package to set up and retrieve translations on demand: package main import ( - di "git.apihub24.de/admin/generic-di" - "git.apihub24.de/admin/translation" - translations "{link to your translations folder}" + "git.apihub24.de/admin/translation" + translations "{link to your translations folder}" ) func main() { - // 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") + // give the Init Function the translations fs.Files + translation.Init(translations.Files) + // Optional you can change the Fallback Language + translation.SetDefaultCulture("de") - // get the Translation Values - translationService.Get("SOME_KEY", "de") + // get the Translation Values + translation.Get("SOME_KEY", "de") } ``` @@ -90,7 +87,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 -translationService.Get("SOME_KEY", "global_de") +translation.Get("SOME_KEY", "global_de") ``` ### Parameters @@ -106,5 +103,5 @@ For example: ``` ```go -translationService.Get("DYNAMIC", "de", "12,50", "€") +translation.Get("DYNAMIC", "de", "12,50", "€") ``` diff --git a/go.mod b/go.mod index 6fda384..bb30189 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ 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 deleted file mode 100644 index 4b02ba5..0000000 --- a/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -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 3e686fd..28ab77d 100644 --- a/translation.go +++ b/translation.go @@ -5,44 +5,22 @@ import ( "encoding/json" "fmt" "strings" - - "git.apihub24.de/admin/generic-di" ) -func init() { - di.Injectable(newTranslationService) +var defaultCulture = "en" +var sources = make(map[string]map[string]string) +var translationFiles embed.FS + +func SetDefaultCulture(culture string) { + defaultCulture = strings.ToLower(culture) } -type ITranslationService interface { - Init(files embed.FS) - SetDefaultCulture(culture string) - Get(key string, culture string, args ...string) string +func Init(files embed.FS) { + translationFiles = files } -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) +func Get(key string, culture string, args ...string) string { + source, err := loadSource(culture) if err != nil { return fmt.Sprintf("unknown error: %s", err.Error()) } @@ -60,22 +38,22 @@ func (translation *translationService) Get(key string, culture string, args ...s return value } -func (translation *translationService) loadSource(culture string) (map[string]string, error) { - source, ok := translation.sources[culture] +func loadSource(culture string) (map[string]string, error) { + source, ok := sources[culture] if ok { return source, nil } - data, err := translation.translationFiles.ReadFile(fmt.Sprintf("%s.json", strings.ToLower(culture))) + data, err := translationFiles.ReadFile(fmt.Sprintf("%s.json", strings.ToLower(culture))) if err != nil { - data, err = translation.translationFiles.ReadFile(fmt.Sprintf("%s.json", translation.defaultCulture)) + data, err = translationFiles.ReadFile(fmt.Sprintf("%s.json", defaultCulture)) if err != nil { - return source, fmt.Errorf("can not load translation source for culture %s DefaultCulture: %s", culture, translation.defaultCulture) + return source, fmt.Errorf("can not load translation source for culture %s DefaultCulture: %s", culture, defaultCulture) } } err = json.Unmarshal(data, &source) if err != nil { return source, fmt.Errorf("can not parse translation source %s", culture) } - translation.sources[culture] = source - return translation.sources[culture], nil + sources[culture] = source + return sources[culture], nil } diff --git a/translation_test.go b/translation_test.go index 325960b..798136f 100644 --- a/translation_test.go +++ b/translation_test.go @@ -1,89 +1,190 @@ package translation_test import ( - di "git.apihub24.de/admin/generic-di" + "os" + "testing" + "git.apihub24.de/admin/translation" exampletranslations "git.apihub24.de/admin/translation/example_translations" - "testing" ) -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 +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_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 TestMain(m *testing.M) { + translation.Init(exampletranslations.Files) + code := m.Run() + os.Exit(code) } -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 +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 + } + } + }) } } diff --git a/v2/LICENSE b/v2/LICENSE new file mode 100644 index 0000000..2efc0b9 --- /dev/null +++ b/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Markus Morgenstern + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/v2/README.md b/v2/README.md new file mode 100644 index 0000000..f479745 --- /dev/null +++ b/v2/README.md @@ -0,0 +1,110 @@ +# Translation + +A package that handles translations using JSON files. + +## Installation + +To include the translation package in your Go project, run the following command: + +```bash +go get git.apihub24.de/admin/translation +``` + +## Usage + +You must have a folder containing JSON files. Each file should hold an object with key-value pairs where the value is a string. + +- translations/de.json + +```json +{ + "SOME_KEY": "ein Wert" +} +``` + +- translations/en.json + +```json +{ + "SOME_KEY": "a Value" +} +``` + +Next, you can create a Go file that embeds these JSON files using embed.FS. + +- translations/files.go + +```go +package translations + +import "embed" + +//go:embed *.json +var Files embed.FS +``` + +Now, use the translation package to set up and retrieve translations on demand: + +```go +package main + +import ( + di "git.apihub24.de/admin/generic-di" + "git.apihub24.de/admin/translation" + translations "{link to your translations folder}" +) + +func main() { + // 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 + translationService.Get("SOME_KEY", "de") +} +``` + +### Error Handling & Fallback Behavior + +- If a translation key is not found for the requested language, the package will attempt to retrieve the translation from the default culture (set via SetDefaultCulture). +- If the key is not found even in the default culture, or if the specified language file does not exist, translation.Get will return the following string 'no value for key {key} found in source {culture}'. This helps in identifying missing translations directly in your application. + +## Features + +### Translation Keys + +You can use any translation key you want. The only thing to keep in mind is that your translation files must have the translation key as part of their filename. + +For example, if you want to split your translation files into modules, you can name them like this: + +- translations/global_de.json +- translations/global_en.json +- translations/mod1_de.json +- translations/mod1_en.json +- translations/mod2_de.json +- translations/mod2_en.json + +And the translation call would look like this: + +```go +translationService.Get("SOME_KEY", "global_de") +``` + +### Parameters + +You can use placeholders to create dynamic translation values. + +For example: + +```json +{ + "DYNAMIC": "Preis: %[1]s %[1]s" +} +``` + +```go +translationService.Get("DYNAMIC", "de", "12,50", "€") +``` diff --git a/v2/example_translations/de.json b/v2/example_translations/de.json new file mode 100644 index 0000000..ee1c2c6 --- /dev/null +++ b/v2/example_translations/de.json @@ -0,0 +1,4 @@ +{ + "KEY": "WERT", + "WITH_PARAM": "WERT %[1]s_%[2]s" +} diff --git a/v2/example_translations/en.json b/v2/example_translations/en.json new file mode 100644 index 0000000..b8354f3 --- /dev/null +++ b/v2/example_translations/en.json @@ -0,0 +1,4 @@ +{ + "KEY": "VALUE", + "WITH_PARAM": "VALUE %[1]s_%[2]s" +} diff --git a/v2/example_translations/files.go b/v2/example_translations/files.go new file mode 100644 index 0000000..ae2fd4b --- /dev/null +++ b/v2/example_translations/files.go @@ -0,0 +1,6 @@ +package exampletranslations + +import "embed" + +//go:embed *.json +var Files embed.FS diff --git a/v2/example_translations/fr.json b/v2/example_translations/fr.json new file mode 100644 index 0000000..ddffaea --- /dev/null +++ b/v2/example_translations/fr.json @@ -0,0 +1,4 @@ +{ + "KEY": "VALEUR", + "WITH_PARAM": "VALEUR %[1]s_%[2]s" +} diff --git a/v2/go.mod b/v2/go.mod new file mode 100644 index 0000000..cb89c59 --- /dev/null +++ b/v2/go.mod @@ -0,0 +1,5 @@ +module git.apihub24.de/admin/translation/v2 + +go 1.22.0 + +require git.apihub24.de/admin/generic-di v1.4.0 \ No newline at end of file diff --git a/v2/go.sum b/v2/go.sum new file mode 100644 index 0000000..d5c9738 --- /dev/null +++ b/v2/go.sum @@ -0,0 +1 @@ +git.apihub24.de/admin/generic-di v1.4.0/go.mod h1:VcHV8MOb1qhwabHdO09CpjEg2VaDesehul86g1iyOxY= diff --git a/v2/makefile b/v2/makefile new file mode 100644 index 0000000..0d27e72 --- /dev/null +++ b/v2/makefile @@ -0,0 +1,2 @@ +test: + - go test .\... \ No newline at end of file diff --git a/v2/translation.go b/v2/translation.go new file mode 100644 index 0000000..3e686fd --- /dev/null +++ b/v2/translation.go @@ -0,0 +1,81 @@ +package translation + +import ( + "embed" + "encoding/json" + "fmt" + "strings" + + "git.apihub24.de/admin/generic-di" +) + +func init() { + di.Injectable(newTranslationService) +} + +type ITranslationService interface { + Init(files embed.FS) + SetDefaultCulture(culture string) + Get(key string, culture string, args ...string) string +} + +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()) + } + value, ok := source[key] + if !ok { + return fmt.Sprintf("no value for key %s found in source %s", key, culture) + } + if len(args) > 0 { + var tmpArgs = make([]any, len(args)) + for idx, arg := range args { + tmpArgs[idx] = arg + } + return fmt.Sprintf(value, tmpArgs...) + } + return value +} + +func (translation *translationService) loadSource(culture string) (map[string]string, error) { + source, ok := translation.sources[culture] + if ok { + return source, nil + } + data, err := translation.translationFiles.ReadFile(fmt.Sprintf("%s.json", strings.ToLower(culture))) + if err != nil { + 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, translation.defaultCulture) + } + } + err = json.Unmarshal(data, &source) + if err != nil { + return source, fmt.Errorf("can not parse translation source %s", culture) + } + translation.sources[culture] = source + return translation.sources[culture], nil +} diff --git a/v2/translation_test.go b/v2/translation_test.go new file mode 100644 index 0000000..325960b --- /dev/null +++ b/v2/translation_test.go @@ -0,0 +1,89 @@ +package translation_test + +import ( + di "git.apihub24.de/admin/generic-di" + "git.apihub24.de/admin/translation" + exampletranslations "git.apihub24.de/admin/translation/example_translations" + "testing" +) + +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 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 + } +}