From 6e4c5ad6cf8c8381cd597da2a2970f07e418a83f Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 24 Jun 2025 22:58:18 +0200 Subject: [PATCH] add documentation and makefile and license WIP Testing Problem --- LICENSE | 21 ++++ README.md | 107 +++++++++++++++++++++ makefile | 2 + translation_test.go | 228 +++++++++++++++++++++++++++++++++----------- 4 files changed, 303 insertions(+), 55 deletions(-) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 makefile diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2efc0b9 --- /dev/null +++ b/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/README.md b/README.md new file mode 100644 index 0000000..d4ee677 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# 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 fs.Files. + +- 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 ( + "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 Translation Values + translation.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 +translation.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 +translation.Get("DYNAMIC", "de", "12,50", "€") +``` diff --git a/makefile b/makefile new file mode 100644 index 0000000..0d27e72 --- /dev/null +++ b/makefile @@ -0,0 +1,2 @@ +test: + - go test .\... \ No newline at end of file diff --git a/translation_test.go b/translation_test.go index 4acce67..798136f 100644 --- a/translation_test.go +++ b/translation_test.go @@ -1,72 +1,190 @@ package translation_test import ( + "os" "testing" "git.apihub24.de/admin/translation" exampletranslations "git.apihub24.de/admin/translation/example_translations" ) -func Test_Translation_Init(t *testing.T) { - translation.Init(exampletranslations.Files) - if translation.Get("KEY", "de") != "WERT" { - t.Errorf("translations not initialized!") +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 Test_Translation_Get_de(t *testing.T) { +func TestMain(m *testing.M) { translation.Init(exampletranslations.Files) - value := translation.Get("KEY", "de") - if value != "WERT" { - t.Errorf("expect German KEY to have Value 'WERT' but was %s", value) - } + code := m.Run() + os.Exit(code) } -func Test_Translation_Get_en(t *testing.T) { - translation.Init(exampletranslations.Files) - value := translation.Get("KEY", "en") - if value != "VALUE" { - t.Errorf("expect English KEY to have Value 'VALUE' but was %s", value) - } -} - -func Test_Translation_Get_fr(t *testing.T) { - translation.Init(exampletranslations.Files) - value := translation.Get("KEY", "fr") - if value != "VALEUR" { - t.Errorf("expect France KEY to have Value 'VALEUR' but was %s", value) - } -} - -func Test_Translation_Get_Fallback_en(t *testing.T) { - translation.Init(exampletranslations.Files) - value := translation.Get("KEY", "notexists") - if value != "VALUE" { - t.Errorf("expect Fallback KEY to have Value 'VALUE' but was %s", value) - } -} - -func Test_Translation_Can_Change_Fallback_Language(t *testing.T) { - translation.Init(exampletranslations.Files) - translation.SetDefaultCulture("de") - value := translation.Get("KEY", "notexists") - if value != "WERT" { - t.Errorf("expect Fallback KEY to have Value 'WERT' but was %s", value) - } -} - -func Test_Translation_Get_With_Parameter(t *testing.T) { - translation.Init(exampletranslations.Files) - valueDe := translation.Get("WITH_PARAM", "de", "a", "b") - valueEn := translation.Get("WITH_PARAM", "en", "a", "b") - valueFr := translation.Get("WITH_PARAM", "fr", "a", "b") - if valueDe != "WERT a_b" { - t.Errorf("expect German KEY to have Value 'WERT a_b' but was %s", valueDe) - } - if valueEn != "VALUE a_b" { - t.Errorf("expect English KEY to have Value 'VALUE a_b' but was %s", valueEn) - } - if valueFr != "VALEUR a_b" { - t.Errorf("expect France KEY to have Value 'VALEUR a_b' but was %s", valueFr) +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 + } + } + }) } }