├── testfiles ├── test1.txt ├── test2.txt ├── incorrect.json ├── incorrect.yaml ├── test1.yaml ├── test2.yaml ├── test1.json ├── test2.json └── README.md ├── .vscode ├── extensions.json └── settings.json ├── examples ├── 06-genders │ ├── README.md │ └── main.go ├── 01-basic-usage │ ├── README.md │ └── main.go ├── 07-pluralized-genders │ ├── README.md │ └── main.go ├── 02-variable-interpolation │ ├── README.md │ └── main.go ├── 04-default-pluralization │ ├── README.md │ └── main.go ├── 05-custom-pluralization │ ├── README.md │ └── main.go ├── 03-json-yaml-loaders │ ├── 01-from-strings │ │ ├── README.md │ │ └── main.go │ ├── 02-from-files │ │ ├── README.md │ │ ├── es │ │ │ ├── translations-02.yaml │ │ │ └── translations-01.yaml │ │ ├── en │ │ │ ├── translations-02.json │ │ │ └── translations-01.json │ │ └── main.go │ ├── 03-from-embed-fs │ │ ├── README.md │ │ ├── es │ │ │ ├── translations-02.yaml │ │ │ └── translations-01.yaml │ │ ├── en │ │ │ ├── translations-02.json │ │ │ └── translations-01.json │ │ └── main.go │ └── README.md ├── 09-advanced-example │ ├── .vscode │ │ └── settings.json │ ├── go.mod │ ├── README.md │ ├── go.sum │ ├── translations │ │ ├── en.yaml │ │ ├── pt.yaml │ │ ├── es.yaml │ │ └── fr.yaml │ ├── template.go │ ├── i18n.go │ ├── main.go │ └── views │ │ └── index.html └── 08-templating │ ├── go.mod │ ├── go.sum │ ├── README.md │ ├── i18n.go │ └── main.go ├── assets └── i18n-gopher.png ├── go.mod ├── CONTRIBUTORS.md ├── Taskfile.yml ├── read_file_from_fs.go ├── go.sum ├── execute_template.go ├── .gitignore ├── .github └── workflows │ └── tests.yml ├── execute_template_test.go ├── LICENSE ├── read_file_from_fs_test.go ├── types.go ├── CONTRIBUTING.md ├── loader_json.go ├── loader_yaml.go ├── loader_yaml_test.go ├── loader_json_test.go ├── README.md ├── i18n.go └── i18n_test.go /testfiles/test1.txt: -------------------------------------------------------------------------------- 1 | test 1 -------------------------------------------------------------------------------- /testfiles/test2.txt: -------------------------------------------------------------------------------- 1 | test 2 -------------------------------------------------------------------------------- /testfiles/incorrect.json: -------------------------------------------------------------------------------- 1 | incorrect json -------------------------------------------------------------------------------- /testfiles/incorrect.yaml: -------------------------------------------------------------------------------- 1 | incorrect yaml -------------------------------------------------------------------------------- /testfiles/test1.yaml: -------------------------------------------------------------------------------- 1 | - Key: hello 2 | Default: Hello -------------------------------------------------------------------------------- /testfiles/test2.yaml: -------------------------------------------------------------------------------- 1 | - Key: world 2 | Default: World -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["golang.go"] 3 | } -------------------------------------------------------------------------------- /examples/06-genders/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n 2 | 3 | Open `main.go` and follow the comments. 4 | -------------------------------------------------------------------------------- /testfiles/test1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "hello", 4 | "Default": "Hello" 5 | } 6 | ] -------------------------------------------------------------------------------- /testfiles/test2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "world", 4 | "Default": "World" 5 | } 6 | ] -------------------------------------------------------------------------------- /assets/i18n-gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eduardolat/goeasyi18n/HEAD/assets/i18n-gopher.png -------------------------------------------------------------------------------- /examples/01-basic-usage/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n 2 | 3 | Open `main.go` and follow the comments. 4 | -------------------------------------------------------------------------------- /examples/07-pluralized-genders/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n 2 | 3 | Open `main.go` and follow the comments. 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eduardolat/goeasyi18n 2 | 3 | go 1.19 4 | 5 | require gopkg.in/yaml.v3 v3.0.1 6 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | - [Eduardo Lat](https://github.com/eduardolat) - Main contributor. 4 | -------------------------------------------------------------------------------- /examples/02-variable-interpolation/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n 2 | 3 | Open `main.go` and follow the comments. 4 | -------------------------------------------------------------------------------- /examples/04-default-pluralization/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n 2 | 3 | Open `main.go` and follow the comments. 4 | -------------------------------------------------------------------------------- /examples/05-custom-pluralization/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n 2 | 3 | Open `main.go` and follow the comments. 4 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | tasks: 4 | test: 5 | cmd: go test . 6 | 7 | tidy: 8 | cmd: go mod tidy 9 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/01-from-strings/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n 2 | 3 | Open `main.go` and follow the comments. 4 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/02-from-files/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n 2 | 3 | Open `main.go` and follow the comments. 4 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/03-from-embed-fs/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n 2 | 3 | Open `main.go` and follow the comments. 4 | -------------------------------------------------------------------------------- /examples/09-advanced-example/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[html]": { 3 | "editor.formatOnSave": false 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /testfiles/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n Test Files 2 | 3 | On this folder you can find the test helper files for the Go Easy i18n package. 4 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/02-from-files/es/translations-02.yaml: -------------------------------------------------------------------------------- 1 | - Key: hello_admin 2 | Default: Hola {{.Name}}, eres un administrador 3 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/03-from-embed-fs/es/translations-02.yaml: -------------------------------------------------------------------------------- 1 | - Key: hello_admin 2 | Default: Hola {{.Name}}, eres un administrador, desde embed 3 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/02-from-files/es/translations-01.yaml: -------------------------------------------------------------------------------- 1 | - Key: hello_world 2 | Default: Hola Mundo 3 | 4 | - Key: hello_user 5 | Default: Hola {{.Name}} 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | 4 | "[go]": { 5 | "editor.formatOnSave": true, 6 | "editor.defaultFormatter": "golang.go" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/02-from-files/en/translations-02.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "hello_admin", 4 | "Default": "Hello {{.Name}}, you are an admin" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/03-from-embed-fs/en/translations-02.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "hello_admin", 4 | "Default": "Hello {{.Name}}, you are an admin, from embed" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/03-from-embed-fs/es/translations-01.yaml: -------------------------------------------------------------------------------- 1 | - Key: hello_world 2 | Default: Hola Mundo desde embed 3 | 4 | - Key: hello_user 5 | Default: Hola {{.Name}} desde embed 6 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/02-from-files/en/translations-01.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "hello_world", 4 | "Default": "Hello World" 5 | }, 6 | { 7 | "Key": "hello_user", 8 | "Default": "Hello {{.Name}}" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/03-from-embed-fs/en/translations-01.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "hello_world", 4 | "Default": "Hello World from embed" 5 | }, 6 | { 7 | "Key": "hello_user", 8 | "Default": "Hello {{.Name}} from embed" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /examples/08-templating/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eduardolat/goeasyi18n/examples/templating 2 | 3 | go 1.21.0 4 | 5 | require "github.com/eduardolat/goeasyi18n" v0.0.0 6 | replace "github.com/eduardolat/goeasyi18n" v0.0.0 => "../.." 7 | 8 | require gopkg.in/yaml.v3 v3.0.1 // indirect 9 | -------------------------------------------------------------------------------- /examples/09-advanced-example/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eduardolat/goeasyi18n/examples/advanced 2 | 3 | go 1.21.0 4 | 5 | require "github.com/eduardolat/goeasyi18n" v0.0.0 6 | replace "github.com/eduardolat/goeasyi18n" v0.0.0 => "../.." 7 | 8 | require gopkg.in/yaml.v3 v3.0.1 // indirect 9 | -------------------------------------------------------------------------------- /read_file_from_fs.go: -------------------------------------------------------------------------------- 1 | package goeasyi18n 2 | 3 | import ( 4 | "io" 5 | "io/fs" 6 | ) 7 | 8 | func readFileFromFS(filesystem fs.FS, file string) ([]byte, error) { 9 | fileData, err := filesystem.Open(file) 10 | if err != nil { 11 | return nil, err 12 | } 13 | defer fileData.Close() 14 | 15 | return io.ReadAll(fileData) 16 | } 17 | -------------------------------------------------------------------------------- /examples/09-advanced-example/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n - Advanced example 2 | 3 | In this example we will see how to use all the features of the library combined to build a full translated website using only the standard library and Go Easy i18n. 4 | 5 | Feel free to run this example and play with it. 6 | 7 | Start from `main.go` and then explore the other files. -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 2 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 3 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 4 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 5 | -------------------------------------------------------------------------------- /examples/08-templating/go.sum: -------------------------------------------------------------------------------- 1 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 2 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 3 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 4 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 5 | -------------------------------------------------------------------------------- /examples/09-advanced-example/go.sum: -------------------------------------------------------------------------------- 1 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 2 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 3 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 4 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 5 | -------------------------------------------------------------------------------- /execute_template.go: -------------------------------------------------------------------------------- 1 | package goeasyi18n 2 | 3 | import ( 4 | "bytes" 5 | "html/template" 6 | ) 7 | 8 | func ExecuteTemplate(templateStr string, data any) string { 9 | tmpl := template.Must(template.New("template").Parse(templateStr)) 10 | 11 | b := new(bytes.Buffer) 12 | 13 | err := tmpl.Execute(b, data) 14 | if err != nil { 15 | return "" 16 | } 17 | 18 | return b.String() 19 | } 20 | -------------------------------------------------------------------------------- /examples/09-advanced-example/translations/en.yaml: -------------------------------------------------------------------------------- 1 | - Key: hello_world 2 | Default: Hello World 3 | 4 | - Key: hello_name 5 | Default: Hello {{.Name}} 6 | 7 | - Key: unread_emails 8 | One: You have one unread email. 9 | Many: You have {{.Count}} unread emails. 10 | 11 | - Key: friend_unread_emails 12 | OneMale: He ({{.Name}}) sent you an email. 13 | OneFemale: She ({{.Name}}) sent you an email. 14 | ManyMale: He ({{.Name}}) sent you {{.Count}} emails. 15 | ManyFemale: She ({{.Name}}) sent you {{.Count}} emails. 16 | -------------------------------------------------------------------------------- /examples/09-advanced-example/translations/pt.yaml: -------------------------------------------------------------------------------- 1 | - Key: hello_world 2 | Default: Olá Mundo 3 | 4 | - Key: hello_name 5 | Default: Olá {{.Name}} 6 | 7 | - Key: unread_emails 8 | One: Você tem um e-mail não lido. 9 | Many: Você tem {{.Count}} e-mails não lidos. 10 | 11 | - Key: friend_unread_emails 12 | OneMale: Ele ({{.Name}}) te enviou um e-mail. 13 | OneFemale: Ela ({{.Name}}) te enviou um e-mail. 14 | ManyMale: Ele ({{.Name}}) te enviou {{.Count}} e-mails. 15 | ManyFemale: Ela ({{.Name}}) te enviou {{.Count}} e-mails. 16 | -------------------------------------------------------------------------------- /examples/09-advanced-example/translations/es.yaml: -------------------------------------------------------------------------------- 1 | - Key: hello_world 2 | Default: Hola Mundo 3 | 4 | - Key: hello_name 5 | Default: Hola {{.Name}} 6 | 7 | - Key: unread_emails 8 | One: Tienes un correo sin leer. 9 | Many: Tienes {{.Count}} correos sin leer. 10 | 11 | - Key: friend_unread_emails 12 | OneMale: Él ({{.Name}}) te ha enviado un correo. 13 | OneFemale: Ella ({{.Name}}) te ha enviado un correo. 14 | ManyMale: Él ({{.Name}}) te ha enviado {{.Count}} correos. 15 | ManyFemale: Ella ({{.Name}}) te ha enviado {{.Count}} correos. 16 | -------------------------------------------------------------------------------- /examples/09-advanced-example/translations/fr.yaml: -------------------------------------------------------------------------------- 1 | - Key: hello_world 2 | Default: Bonjour Monde 3 | 4 | - Key: hello_name 5 | Default: Bonjour {{.Name}} 6 | 7 | - Key: unread_emails 8 | One: Vous avez un courrier non lu. 9 | Many: Vous avez {{.Count}} courriers non lus. 10 | 11 | - Key: friend_unread_emails 12 | OneMale: Il ({{.Name}}) vous a envoyé un courrier. 13 | OneFemale: Elle ({{.Name}}) vous a envoyé un courrier. 14 | ManyMale: Il ({{.Name}}) vous a envoyé {{.Count}} courriers. 15 | ManyFemale: Elle ({{.Name}}) vous a envoyé {{.Count}} courriers. 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | matrix: 13 | go-version: ['1.19.x', '1.20.x', '1.21.x'] 14 | platform: [ubuntu-latest] 15 | 16 | runs-on: ${{ matrix.platform }} 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Set up Go 22 | uses: actions/setup-go@v4 23 | with: 24 | go-version: ${{ matrix.go-version }} 25 | 26 | - name: Run tests 27 | run: go test -v . 28 | -------------------------------------------------------------------------------- /examples/08-templating/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n - Templating example 2 | 3 | The templating feature allows you to use the i18n instance in your `text/template` and `html/template` templates with ease 🔥. 4 | 5 | This is really useful if you want to use the i18n instance in your web application that uses `html/template` templates. 6 | 7 | ## The example 8 | 9 | This example is divided in 2 files: 10 | 11 | - [main.go](main.go): Here is the code of the templating example. 12 | 13 | - [i18n.go](i18n.go): Here is the code of the i18n instance (If you read the other examples you should be familiar with this). 14 | 15 | Just run the program using `go run .` and you will see the output of the example. 16 | 17 | Feel free to play with the code and explore the example. 18 | -------------------------------------------------------------------------------- /examples/09-advanced-example/template.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "html/template" 6 | "os" 7 | ) 8 | 9 | func ExecuteTemplate(templatePath string, data any) (string, error) { 10 | 11 | // Prepare func map 12 | translateFunc := i18n.NewTemplatingTranslateFunc() 13 | funcs := template.FuncMap{ 14 | "Translate": translateFunc, // You can use any name you want, for example: "T" 15 | } 16 | 17 | // Read template file 18 | fileContent, err := os.ReadFile(templatePath) 19 | if err != nil { 20 | return "", err 21 | } 22 | 23 | // Execute template 24 | tmpl := template.Must(template.New("test").Funcs(funcs).Parse(string(fileContent))) 25 | result := new(bytes.Buffer) 26 | tmpl.Execute(result, data) 27 | 28 | if err != nil { 29 | return "", err 30 | } 31 | 32 | return result.String(), nil 33 | } 34 | -------------------------------------------------------------------------------- /execute_template_test.go: -------------------------------------------------------------------------------- 1 | package goeasyi18n 2 | 3 | import "testing" 4 | 5 | func TestExecuteTemplate(t *testing.T) { 6 | 7 | t.Run("should execute a template", func(t *testing.T) { 8 | executed := ExecuteTemplate("Hello {{.Name}}", struct{ Name string }{Name: "World"}) 9 | expected := "Hello World" 10 | 11 | if executed != expected { 12 | t.Errorf("Expected %s, got %s", expected, executed) 13 | } 14 | }) 15 | 16 | t.Run("should execute a template with two interpolations", func(t *testing.T) { 17 | data := struct{ FirstName, SurName string }{FirstName: "John", SurName: "Doe"} 18 | executed := ExecuteTemplate("Hello {{.FirstName}} {{.SurName}}", data) 19 | expected := "Hello John Doe" 20 | 21 | if executed != expected { 22 | t.Errorf("Expected %s, got %s", expected, executed) 23 | } 24 | }) 25 | 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 eduardolat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/README.md: -------------------------------------------------------------------------------- 1 | # Go Easy i18n 2 | 3 | Go Easy i18n allows you to load your translations in different ways: 4 | 5 | ## Manually 6 | 7 | If you have a small set of translations, you can load them manually as shown in the [Basic Usage Example.](../01-basic-usage/main.go) 8 | 9 | ## From JSON/YAML as Bytes or Strings 10 | 11 | You can fetch your JSON/YAML translations from a database or a remote API and load them into the i18n instance as part of the startup of your program, look at the example to see how to do it: 12 | 13 | [Bytes/String Example](./01-from-strings/main.go) 14 | 15 | ## From JSON/YAML files in your server file system 16 | 17 | You can load your translations from JSON/YAML files in your file system, look at the example to see how to do it: 18 | 19 | [Files Example](./02-from-files/main.go) 20 | 21 | ## From JSON/YAML files in `fs.FS` (`embed.FS`) file system 22 | 23 | If you want to load your translations from JSON/YAML files embedded in your binary or some other implementation of the `fs.FS` interface, look at the example to see how to do it: 24 | 25 | [`embed.FS` Example](./03-from-embed-fs/main.go) 26 | -------------------------------------------------------------------------------- /examples/09-advanced-example/i18n.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/eduardolat/goeasyi18n" 7 | ) 8 | 9 | var i18n *goeasyi18n.I18n 10 | 11 | //go:embed translations 12 | var translationsFS embed.FS 13 | 14 | func InitializeI18n() { 15 | i18n = goeasyi18n.NewI18n() 16 | 17 | enTranslations, err := goeasyi18n.LoadFromYamlFS( 18 | translationsFS, 19 | "translations/en.yaml", 20 | ) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | esTranslations, err := goeasyi18n.LoadFromYamlFS( 26 | translationsFS, 27 | "translations/es.yaml", 28 | ) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | ptTranslations, err := goeasyi18n.LoadFromYamlFS( 34 | translationsFS, 35 | "translations/pt.yaml", 36 | ) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | frTranslations, err := goeasyi18n.LoadFromYamlFS( 42 | translationsFS, 43 | "translations/fr.yaml", 44 | ) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | i18n.AddLanguage("en", enTranslations) 50 | i18n.AddLanguage("es", esTranslations) 51 | i18n.AddLanguage("pt", ptTranslations) 52 | i18n.AddLanguage("fr", frTranslations) 53 | } 54 | -------------------------------------------------------------------------------- /read_file_from_fs_test.go: -------------------------------------------------------------------------------- 1 | package goeasyi18n 2 | 3 | import ( 4 | "embed" 5 | "testing" 6 | ) 7 | 8 | //go:embed testfiles/* 9 | var readFSTestFiles embed.FS 10 | 11 | func TestReadFileFromFS(t *testing.T) { 12 | tests := []struct { 13 | filename string 14 | expectedData string 15 | expectedErr bool 16 | }{ 17 | { 18 | filename: "testfiles/test1.txt", 19 | expectedData: "test 1", 20 | expectedErr: false, 21 | }, 22 | { 23 | filename: "testfiles/test2.txt", 24 | expectedData: "test 2", 25 | expectedErr: false, 26 | }, 27 | { 28 | filename: "testfiles/not-exists.txt", 29 | expectedData: "", 30 | expectedErr: true, 31 | }, 32 | } 33 | 34 | for _, tt := range tests { 35 | t.Run(tt.filename, func(t *testing.T) { 36 | data, err := readFileFromFS(readFSTestFiles, tt.filename) 37 | 38 | if tt.expectedErr { 39 | if err == nil { 40 | t.Errorf("expected error, got nil") 41 | } 42 | } else { 43 | if err != nil { 44 | t.Errorf("didn't expect error, got %v", err) 45 | } 46 | } 47 | 48 | if string(data) != tt.expectedData { 49 | t.Errorf("expected data %q, got %q", tt.expectedData, string(data)) 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package goeasyi18n 2 | 3 | type TranslateString struct { 4 | Key string 5 | Default string 6 | 7 | // For pluralization 8 | Zero string // Optional 9 | One string // Optional 10 | Two string // Optional 11 | Few string // Optional 12 | Many string // Optional 13 | 14 | // For genders 15 | Male string // Optional 16 | Female string // Optional 17 | NonBinary string // Optional 18 | 19 | // For pluralization with male gender 20 | ZeroMale string // Optional 21 | OneMale string // Optional 22 | TwoMale string // Optional 23 | FewMale string // Optional 24 | ManyMale string // Optional 25 | 26 | // For pluralization with female gender 27 | ZeroFemale string // Optional 28 | OneFemale string // Optional 29 | TwoFemale string // Optional 30 | FewFemale string // Optional 31 | ManyFemale string // Optional 32 | 33 | // For pluralization with non binary gender 34 | ZeroNonBinary string // Optional 35 | OneNonBinary string // Optional 36 | TwoNonBinary string // Optional 37 | FewNonBinary string // Optional 38 | ManyNonBinary string // Optional 39 | } 40 | 41 | type TranslateStrings []TranslateString 42 | 43 | type PluralizationFunc func(count int) string 44 | 45 | type Data map[string]any 46 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/01-from-strings/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/eduardolat/goeasyi18n" 7 | ) 8 | 9 | func main() { 10 | // 1. Create a new i18n instance 11 | i18n := goeasyi18n.NewI18n() 12 | 13 | // 2. Load your translations from a database, api, etc. 14 | enTranslationsString := `[ 15 | { 16 | "Key": "hello_world", 17 | "Default": "Hello World" 18 | } 19 | ]` 20 | esTranslationsBytes := []byte(`[ 21 | { 22 | "Key": "hello_world", 23 | "Default": "Hola Mundo" 24 | } 25 | ]`) 26 | 27 | // 3. Then you can load the translations from your source 28 | // In this case we are using JSON but you can also use YAML 29 | enTranslations, err := goeasyi18n.LoadFromJsonString(enTranslationsString) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | // You can also load from bytes instead of strings 35 | esTranslations, err := goeasyi18n.LoadFromJsonBytes(esTranslationsBytes) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | // 4. Add your languages with their translations 41 | i18n.AddLanguage("en", enTranslations) 42 | i18n.AddLanguage("es", esTranslations) 43 | 44 | // 5. Get the translations 45 | ten := i18n.T("en", "hello_world") 46 | tes := i18n.T("es", "hello_world") 47 | 48 | fmt.Println(ten) 49 | fmt.Println(tes) 50 | 51 | /* 52 | Prints: 53 | Hello World 54 | Hola Mundo 55 | */ 56 | } 57 | -------------------------------------------------------------------------------- /examples/09-advanced-example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func handler(w http.ResponseWriter, r *http.Request) { 9 | // Get the lang query param, you are responsible in your app to get the lang 10 | // In this example is just passed as a query param but you can use a cookie 11 | // or an url segment or whatever you want 12 | lang := "en" // Default language 13 | if r.URL.Query().Get("lang") != "" { 14 | lang = r.URL.Query().Get("lang") 15 | } 16 | 17 | // Check if language is supported (you can return a 404 error if you want) 18 | hasLanguage := i18n.HasLanguage(lang) 19 | 20 | // Execute the template using the detected language 21 | templateData := map[string]any{ 22 | "Lang": lang, 23 | "HasLanguage": hasLanguage, 24 | } 25 | 26 | // Instead of using the {{Translate ...}} inside the template, you can also 27 | // translate the text here and pass it to the template for example: 28 | // someTranslation := i18n.Translate(lang, "some_translation_key") 29 | 30 | responseText, err := ExecuteTemplate("./views/index.html", templateData) 31 | if err != nil { 32 | http.Error(w, err.Error(), http.StatusInternalServerError) 33 | return 34 | } 35 | 36 | // Write the response 37 | w.Write([]byte(responseText)) 38 | } 39 | 40 | func main() { 41 | InitializeI18n() 42 | 43 | http.HandleFunc("/", handler) 44 | 45 | log.Println("Listening on http://localhost:9090") 46 | log.Fatal(http.ListenAndServe(":9090", nil)) 47 | } 48 | -------------------------------------------------------------------------------- /examples/02-variable-interpolation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/eduardolat/goeasyi18n" 7 | ) 8 | 9 | func main() { 10 | // 1. Create a new i18n instance 11 | i18n := goeasyi18n.NewI18n() 12 | 13 | // 2. Create your translations 14 | // You can add any variables to your translations 15 | // Use the syntax {{.VariableName}} 16 | enTranslations := goeasyi18n.TranslateStrings{ 17 | { 18 | Key: "hello_message", 19 | Default: "Hello {{.Name}} {{.SurName}}, welcome to Go Easy i18n!", 20 | }, 21 | } 22 | 23 | esTranslations := goeasyi18n.TranslateStrings{ 24 | { 25 | Key: "hello_message", 26 | Default: "¡Hola {{.Name}} {{.SurName}}, bienvenido a Go Easy i18n!", 27 | }, 28 | } 29 | 30 | // 3. Add your languages with their translations 31 | i18n.AddLanguage("en", enTranslations) 32 | i18n.AddLanguage("es", esTranslations) 33 | 34 | // 4. Crete the options for the translation with the variables 35 | // The Data field is a map[string]any that contains the variables to be replaced 36 | options := goeasyi18n.Options{ 37 | Data: map[string]any{ 38 | "Name": "John", 39 | "SurName": "Doe", 40 | }, 41 | } 42 | 43 | // 5. Get the translations using the options (with the variables) 44 | t1 := i18n.T("en", "hello_message", options) 45 | t2 := i18n.T("es", "hello_message", options) 46 | 47 | fmt.Println(t1) 48 | fmt.Println(t2) 49 | 50 | /* 51 | Prints: 52 | Hello John Doe, welcome to Go Easy i18n! 53 | ¡Hola John Doe, bienvenido a Go Easy i18n! 54 | */ 55 | } 56 | -------------------------------------------------------------------------------- /examples/08-templating/i18n.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/eduardolat/goeasyi18n" 5 | ) 6 | 7 | var i18n *goeasyi18n.I18n 8 | 9 | func InitializeI18n() { 10 | i18n = goeasyi18n.NewI18n() 11 | 12 | enTranslations := goeasyi18n.TranslateStrings{ 13 | { 14 | Key: "hello_message", 15 | Default: "Hello {{.Name}}, welcome to Go Easy i18n!", 16 | }, 17 | { 18 | Key: "unread_messages", 19 | One: "You have {{.Qty}} unread message.", 20 | Many: "You have {{.Qty}} unread messages.", 21 | }, 22 | { 23 | Key: "friend_update", 24 | Male: "{{.Name}} updated his status.", 25 | Female: "{{.Name}} updated her status.", 26 | NonBinary: "{{.Name}} updated their status.", 27 | }, 28 | { 29 | Key: "friend_request", 30 | OneMale: "He sent you a friend request.", 31 | ManyFemale: "She sent you {{.Qty}} friend requests.", 32 | }, 33 | } 34 | 35 | esTranslations := goeasyi18n.TranslateStrings{ 36 | { 37 | Key: "hello_message", 38 | Default: "¡Hola {{.Name}}, bienvenido a Go Easy i18n!", 39 | }, 40 | { 41 | Key: "unread_messages", 42 | One: "Tienes {{.Qty}} mensaje sin leer.", 43 | Many: "Tienes {{.Qty}} mensajes sin leer.", 44 | }, 45 | { 46 | Key: "friend_update", 47 | Male: "{{.Name}} actualizó su estado.", 48 | Female: "{{.Name}} actualizó su estado.", 49 | NonBinary: "{{.Name}} actualizó su estado.", 50 | }, 51 | { 52 | Key: "friend_request", 53 | OneMale: "Él te envió una solicitud de amistad.", 54 | ManyFemale: "Ella te envió {{.Qty}} solicitudes de amistad.", 55 | }, 56 | } 57 | 58 | i18n.AddLanguage("en", enTranslations) 59 | i18n.AddLanguage("es", esTranslations) 60 | } 61 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/02-from-files/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/eduardolat/goeasyi18n" 7 | ) 8 | 9 | func main() { 10 | // 1. Create a new i18n instance 11 | i18n := goeasyi18n.NewI18n() 12 | 13 | // 2. Load your translations from JSON or YAML files 14 | // You can load one or more files like goeasyi18n.LoadFromJsonFiles("./en/t1.json", "./en/t2.json") 15 | // You can use glob patterns like goeasyi18n.LoadFromJsonFiles("./en/*.json") 16 | // All the translation files get merged 17 | 18 | // Load english translations from JSON files 19 | enTranslations, err := goeasyi18n.LoadFromJsonFiles("./en/*.json") 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | // Load spanish translations from YAML files 25 | esTranslations, err := goeasyi18n.LoadFromYamlFiles("./es/*.yaml") 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | // 3. Add your languages with their translations 31 | i18n.AddLanguage("en", enTranslations) 32 | i18n.AddLanguage("es", esTranslations) 33 | 34 | // 4. Crete the options for the translations with/without interpolations 35 | options := goeasyi18n.Options{} 36 | optionsWithName := goeasyi18n.Options{ 37 | Data: map[string]string{ 38 | "Name": "John Doe", 39 | }, 40 | } 41 | 42 | // 5. Get the translations using the options (with the variables) 43 | ten1 := i18n.T("en", "hello_world", options) 44 | ten2 := i18n.T("en", "hello_user", optionsWithName) 45 | ten3 := i18n.T("en", "hello_admin", optionsWithName) 46 | 47 | tes1 := i18n.T("es", "hello_world", options) 48 | tes2 := i18n.T("es", "hello_user", optionsWithName) 49 | tes3 := i18n.T("es", "hello_admin", optionsWithName) 50 | 51 | fmt.Println(ten1) 52 | fmt.Println(ten2) 53 | fmt.Println(ten3) 54 | fmt.Println(tes1) 55 | fmt.Println(tes2) 56 | fmt.Println(tes3) 57 | 58 | /* 59 | Prints: 60 | Hello World 61 | Hello John Doe 62 | Hello John Doe, you are an admin 63 | Hola Mundo 64 | Hola John Doe 65 | Hola John Doe, eres un administrador 66 | */ 67 | } 68 | -------------------------------------------------------------------------------- /examples/04-default-pluralization/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/eduardolat/goeasyi18n" 7 | ) 8 | 9 | func main() { 10 | // 1. Create a new i18n instance 11 | i18n := goeasyi18n.NewI18n() 12 | 13 | // 2. Create your translations 14 | // If something goes wrong, the default value is used 15 | // The default pluralization only works for one and many keys 16 | // If the count (later in the Options) is 1, the one key is used 17 | // If the count (later in the Options) is greater than 1, the many key is used 18 | enTranslations := goeasyi18n.TranslateStrings{ 19 | { 20 | Key: "hello_emails", 21 | Default: "Hello, you have emails", 22 | One: "Hello, you have one email", 23 | Many: "Hello, you have {{.EmailQty}} emails", 24 | }, 25 | } 26 | 27 | esTranslations := goeasyi18n.TranslateStrings{ 28 | { 29 | Key: "hello_emails", 30 | Default: "Hola, tienes correos", 31 | One: "Hola, tienes un correo", 32 | Many: "Hola, tienes {{.EmailQty}} correos", 33 | }, 34 | } 35 | 36 | // 3. Add your languages with their translations 37 | i18n.AddLanguage("en", enTranslations) 38 | i18n.AddLanguage("es", esTranslations) 39 | 40 | // 4. Create the Options 41 | // The Count field is a *int that contains a number which is used to 42 | // select the correct pluralization key 43 | oneEmail := 1 // Get this value from your database or wherever you want 44 | oneEmailOptions := goeasyi18n.Options{ 45 | Count: &oneEmail, 46 | Data: map[string]any{ 47 | "EmailQty": oneEmail, 48 | }, 49 | } 50 | 51 | manyEmails := 5 // Get this value from your database or wherever you want 52 | manyEmailsOptions := goeasyi18n.Options{ 53 | Count: &manyEmails, 54 | Data: map[string]any{ 55 | "EmailQty": manyEmails, 56 | }, 57 | } 58 | 59 | // 5. You are done! 🎉 Just get that translations! 60 | ten1 := i18n.T("en", "hello_emails", oneEmailOptions) 61 | ten2 := i18n.T("en", "hello_emails", manyEmailsOptions) 62 | 63 | tes1 := i18n.T("es", "hello_emails", oneEmailOptions) 64 | tes2 := i18n.T("es", "hello_emails", manyEmailsOptions) 65 | 66 | fmt.Println(ten1) 67 | fmt.Println(ten2) 68 | fmt.Println(tes1) 69 | fmt.Println(tes2) 70 | 71 | /* 72 | Prints: 73 | Hello, you have one email 74 | Hello, you have 5 emails 75 | Hola, tienes un correo 76 | Hola, tienes 5 correos 77 | */ 78 | } 79 | -------------------------------------------------------------------------------- /examples/03-json-yaml-loaders/03-from-embed-fs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | 7 | "github.com/eduardolat/goeasyi18n" 8 | ) 9 | 10 | //go:embed en/*.json 11 | var enFS embed.FS 12 | 13 | //go:embed es/*.yaml 14 | var esFS embed.FS 15 | 16 | func main() { 17 | // With the embed.FS feature you can load the translations from 18 | // any fs.FS implementation, like embed.FS. 19 | // 20 | // This allows you to bundle your translations with your app 21 | // in the same binary file. 22 | 23 | // 1. Create a new i18n instance 24 | i18n := goeasyi18n.NewI18n() 25 | 26 | // 2. Load your translations from JSON or YAML files inside the embed.FS 27 | // You can load one or more files like goeasyi18n.LoadFromJsonFS(fs, "en/t1.json", "en/t2.json") 28 | // You can use glob patterns like goeasyi18n.LoadFromJsonFS(fs, "en/*.json") 29 | // All the translation files get merged 30 | 31 | // Load english translations from JSON files 32 | enTranslations, err := goeasyi18n.LoadFromJsonFS(enFS, "en/*.json") 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | // Load spanish translations from YAML files 38 | esTranslations, err := goeasyi18n.LoadFromYamlFS(esFS, "es/*.yaml") 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | // 3. Add your languages with their translations 44 | i18n.AddLanguage("en", enTranslations) 45 | i18n.AddLanguage("es", esTranslations) 46 | 47 | // 4. Crete the options for the translations with/without interpolations 48 | options := goeasyi18n.Options{} 49 | optionsWithName := goeasyi18n.Options{ 50 | Data: map[string]string{ 51 | "Name": "John Doe", 52 | }, 53 | } 54 | 55 | // 5. Get the translations using the options (with the variables) 56 | ten1 := i18n.T("en", "hello_world", options) 57 | ten2 := i18n.T("en", "hello_user", optionsWithName) 58 | ten3 := i18n.T("en", "hello_admin", optionsWithName) 59 | 60 | tes1 := i18n.T("es", "hello_world", options) 61 | tes2 := i18n.T("es", "hello_user", optionsWithName) 62 | tes3 := i18n.T("es", "hello_admin", optionsWithName) 63 | 64 | fmt.Println(ten1) 65 | fmt.Println(ten2) 66 | fmt.Println(ten3) 67 | fmt.Println(tes1) 68 | fmt.Println(tes2) 69 | fmt.Println(tes3) 70 | 71 | /* 72 | Prints: 73 | Hello World from embed 74 | Hello John Doe from embed 75 | Hello John Doe, you are an admin, from embed 76 | Hola Mundo desde embed 77 | Hola John Doe desde embed 78 | Hola John Doe, eres un administrador, desde embed 79 | */ 80 | } 81 | -------------------------------------------------------------------------------- /examples/08-templating/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "text/template" 6 | ) 7 | 8 | /* 9 | The templating feature allows you to make translations inside your templates. 10 | It works with both text/template and html/template. 11 | 12 | To pass the language use: "lang" "xxx" 13 | To pass the key use: "key" "xxx" 14 | To pass the count use: "count" "xxx" 15 | To pass the gender use: "gender" "male/female/nonbinary" 16 | 17 | All other "key" "value" pairs will be converted to strings and passed to 18 | the translation as variables. 19 | */ 20 | 21 | const templateText = `Welcome to Go Easy i18n! 22 | 23 | {{Translate "lang" "en" "key" "hello_message" "Name" "John Doe"}} 24 | {{Translate "lang" "es" "key" "hello_message" "Name" "John Doe"}} 25 | 26 | {{Translate "lang" "en" "key" "unread_messages" "count" "1" "Qty" "1"}} 27 | {{Translate "lang" "en" "key" "unread_messages" "count" "10" "Qty" "10"}} 28 | {{Translate "lang" "es" "key" "unread_messages" "count" "1" "Qty" "1"}} 29 | {{Translate "lang" "es" "key" "unread_messages" "count" "10" "Qty" "10"}} 30 | 31 | {{Translate "lang" "en" "key" "friend_update" "gender" "male" "Name" "John"}} 32 | {{Translate "lang" "en" "key" "friend_update" "gender" "female" "Name" "Jane"}} 33 | {{Translate "lang" "en" "key" "friend_update" "gender" "nonbinary" "Name" "Jane"}} 34 | {{Translate "lang" "es" "key" "friend_update" "gender" "male" "Name" "John"}} 35 | {{Translate "lang" "es" "key" "friend_update" "gender" "female" "Name" "Jane"}} 36 | {{Translate "lang" "es" "key" "friend_update" "gender" "nonbinary" "Name" "Jane"}} 37 | 38 | {{Translate "lang" "en" "key" "friend_request" "gender" "male" "count" "1" "Qty" "1"}} 39 | {{Translate "lang" "en" "key" "friend_request" "gender" "female" "count" "10" "Qty" "10"}} 40 | {{Translate "lang" "es" "key" "friend_request" "gender" "male" "count" "1" "Qty" "1"}} 41 | {{Translate "lang" "es" "key" "friend_request" "gender" "female" "count" "10" "Qty" "10"}} 42 | ` 43 | 44 | func main() { 45 | // 1. Initialize the i18n instance 46 | InitializeI18n() 47 | 48 | // 2. Create function to pass to the template func map 49 | translateFunc := i18n.NewTemplatingTranslateFunc() 50 | 51 | // 3. Create the template and pass the created function to the func map 52 | tmpl := template.Must(template.New("test").Funcs(template.FuncMap{ 53 | "Translate": translateFunc, // You can use any name you want, for example: "T" 54 | }).Parse(templateText)) 55 | 56 | // 4. Execute the template 57 | tmpl.Execute(os.Stdout, nil) 58 | } 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Go Easy i18n 2 | 3 | Thank you for considering contributing to Go Easy i18n! Whether it's code or documentation, we appreciate your help. If you have any questions or need clarification on the contribution process, feel free to open an issue. Here are some guidelines to ensure a smooth and efficient contribution process. 4 | 5 | ## Code of Conduct 6 | 7 | Before contributing, please read and adhere to our Code of Conduct. We expect all contributors to maintain a respectful and harassment-free environment. 8 | 9 | ## Contribution Process 10 | 11 | 1. **Fork the Repository**: 12 | - Fork the main repository to your personal GitHub account. 13 | 14 | 2. **Clone the Repository**: 15 | - Clone your fork to your local machine. 16 | ```bash 17 | git clone https://github.com/[your_username]/goeasyi18n.git 18 | ``` 19 | 20 | 3. **Create a Branch**: 21 | - Create a new branch for your feature or fix. 22 | ```bash 23 | git checkout -b your-branch-name 24 | ``` 25 | 26 | 4. **Make Your Changes**: 27 | - Implement your changes, ensuring you follow the project's best practices and coding styles. 28 | 29 | 5. **Run Tests**: 30 | - Ensure that all tests pass. If you're introducing a new feature or change, you must provide tests that cover your changes. 31 | 32 | 6. **Commit and Push**: 33 | - Commit your changes in lowercase and push them to your fork. 34 | ```bash 35 | git add . 36 | git commit -m "brief description of changes" 37 | git push origin your-branch-name 38 | ``` 39 | 40 | 7. **Open a Pull Request (PR)**: 41 | - Go to the main repository and click on "New Pull Request". Select your branch and propose the PR. 42 | 43 | ## Best Practices for PRs 44 | 45 | - **Clear Description**: Provide a detailed description of the changes you are proposing. 46 | - **Related Issues**: If your PR relates to an existing issue, make sure to reference it. 47 | - **Small Commits**: Try to make small, specific commits; this facilitates review and the merge process. 48 | - **Document Your Changes**: If you're introducing a new feature or making significant changes, make sure to update the relevant documentation. 49 | 50 | ## Communication 51 | 52 | If you have questions, concerns, or need clarifications, please open an issue. We value community feedback and will try to respond as soon as possible. 53 | 54 | ## Acknowledgments 55 | 56 | Thank you for your interest in improving Go Easy i18n! Whether it's through code or documentation, your contribution is invaluable to the community. 57 | -------------------------------------------------------------------------------- /loader_json.go: -------------------------------------------------------------------------------- 1 | package goeasyi18n 2 | 3 | import ( 4 | "encoding/json" 5 | "io/fs" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | // LoadFromJsonBytes loads a list of TranslateString 11 | // from the provided JSON bytes. 12 | func LoadFromJsonBytes( 13 | jsonBytes []byte, 14 | ) (TranslateStrings, error) { 15 | var translateStrings TranslateStrings 16 | err := json.Unmarshal(jsonBytes, &translateStrings) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | return translateStrings, nil 22 | } 23 | 24 | // LoadFromJsonString loads a list of TranslateString 25 | // from the provided JSON string. 26 | func LoadFromJsonString( 27 | jsonString string, 28 | ) (TranslateStrings, error) { 29 | return LoadFromJsonBytes([]byte(jsonString)) 30 | } 31 | 32 | // LoadFromJsonFiles loads a list of TranslateString from 33 | // one or multiple JSON files, allowing glob patterns 34 | // like "path/to/files/*.json". 35 | func LoadFromJsonFiles( 36 | filesOrGlobs ...string, 37 | ) (TranslateStrings, error) { 38 | var allTranslateStrings TranslateStrings 39 | 40 | for _, pattern := range filesOrGlobs { 41 | matches, err := filepath.Glob(pattern) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | for _, file := range matches { 47 | byteValue, err := os.ReadFile(file) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | translateString, err := LoadFromJsonBytes(byteValue) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | allTranslateStrings = append(allTranslateStrings, translateString...) 58 | } 59 | } 60 | 61 | return allTranslateStrings, nil 62 | } 63 | 64 | // LoadFromJsonFS loads a list of TranslateString from 65 | // one or multiple JSON files located within a provided 66 | // filesystem (fs.FS), allowing glob patterns 67 | // like "path/to/files/*.json". 68 | func LoadFromJsonFS( 69 | fileSystem fs.FS, 70 | filesOrGlobs ...string, 71 | ) (TranslateStrings, error) { 72 | var allTranslateStrings TranslateStrings 73 | 74 | for _, pattern := range filesOrGlobs { 75 | matches, err := fs.Glob(fileSystem, pattern) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | if len(matches) == 0 { 81 | continue 82 | } 83 | 84 | for _, file := range matches { 85 | byteValue, err := readFileFromFS(fileSystem, file) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | translateString, err := LoadFromJsonBytes(byteValue) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | allTranslateStrings = append(allTranslateStrings, translateString...) 96 | } 97 | } 98 | 99 | return allTranslateStrings, nil 100 | } 101 | -------------------------------------------------------------------------------- /loader_yaml.go: -------------------------------------------------------------------------------- 1 | package goeasyi18n 2 | 3 | import ( 4 | "encoding/json" 5 | "io/fs" 6 | "os" 7 | "path/filepath" 8 | 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | // LoadFromYamlBytes loads a list of TranslateString 13 | // from the provided YAML bytes. 14 | func LoadFromYamlBytes( 15 | yamlBytes []byte, 16 | ) (TranslateStrings, error) { 17 | // This function uses a bridge, it converts YAML to JSON before 18 | // parsing it as a TranslateString to avoid inconsistencies 19 | 20 | var parsedYaml any 21 | err := yaml.Unmarshal(yamlBytes, &parsedYaml) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | jsonBytes, err := json.Marshal(parsedYaml) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | var translateStrings TranslateStrings 32 | err = json.Unmarshal(jsonBytes, &translateStrings) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return translateStrings, nil 38 | } 39 | 40 | // LoadFromYamlString loads a list of TranslateString 41 | // from the provided YAML string. 42 | func LoadFromYamlString( 43 | yamlString string, 44 | ) (TranslateStrings, error) { 45 | return LoadFromYamlBytes([]byte(yamlString)) 46 | } 47 | 48 | // LoadFromYamlFiles loads a list of TranslateString from 49 | // one or multiple YAML files, allowing glob patterns 50 | // like "path/to/files/*.yaml". 51 | func LoadFromYamlFiles( 52 | filesOrGlobs ...string, 53 | ) (TranslateStrings, error) { 54 | var allTranslateStrings TranslateStrings 55 | 56 | for _, pattern := range filesOrGlobs { 57 | matches, err := filepath.Glob(pattern) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | for _, file := range matches { 63 | byteValue, err := os.ReadFile(file) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | translateString, err := LoadFromYamlBytes(byteValue) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | allTranslateStrings = append(allTranslateStrings, translateString...) 74 | } 75 | } 76 | 77 | return allTranslateStrings, nil 78 | } 79 | 80 | // LoadFromYamlFS loads a list of TranslateString from 81 | // one or multiple YAML files located within a provided 82 | // filesystem (fs.FS), allowing glob patterns 83 | // like "path/to/files/*.yaml". 84 | func LoadFromYamlFS( 85 | fileSystem fs.FS, 86 | filesOrGlobs ...string, 87 | ) (TranslateStrings, error) { 88 | var allTranslateStrings TranslateStrings 89 | 90 | for _, pattern := range filesOrGlobs { 91 | matches, err := fs.Glob(fileSystem, pattern) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | if len(matches) == 0 { 97 | continue 98 | } 99 | 100 | for _, file := range matches { 101 | byteValue, err := readFileFromFS(fileSystem, file) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | translateString, err := LoadFromYamlBytes(byteValue) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | allTranslateStrings = append(allTranslateStrings, translateString...) 112 | } 113 | } 114 | 115 | return allTranslateStrings, nil 116 | } 117 | -------------------------------------------------------------------------------- /examples/07-pluralized-genders/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/eduardolat/goeasyi18n" 7 | ) 8 | 9 | /* 10 | You have come very far!! 👏👏👏 11 | Now that you know how to pluralize and manage gender in your translations, 12 | let's do something interesting... Let's combine both features! 13 | 14 | Combining both features you will gain acces to all these keys in your translations: 15 | 16 | // For only pluralization 17 | Zero, One, Two, Few, Many 18 | 19 | // For only genders management 20 | Male, Female, NonBinary 21 | 22 | // For pluralization with male gender 23 | ZeroMale, OneMale, TwoMale, FewMale, ManyMale 24 | 25 | // For pluralization with female gender 26 | ZeroFemale, OneFemale, TwoFemale, FewFemale, ManyFemale 27 | 28 | // For pluralization with non binary gender 29 | ZeroNonBinary, OneNonBinary, TwoNonBinary, FewNonBinary, ManyNonBinary 30 | */ 31 | 32 | func main() { 33 | // 1. Create a new i18n instance 34 | i18n := goeasyi18n.NewI18n() 35 | 36 | // 2. Create your translations 37 | // If something goes wrong, the default value is used 38 | enTranslations := goeasyi18n.TranslateStrings{ 39 | { 40 | Key: "friend_emails", 41 | Default: "Hello, your friend have emails", 42 | OneMale: "Hello, he has one email", 43 | ManyFemale: "Hello, she has {{.EmailQty}} emails", 44 | // You can add as many combinations as you want 45 | }, 46 | } 47 | 48 | esTranslations := goeasyi18n.TranslateStrings{ 49 | { 50 | Key: "friend_emails", 51 | Default: "Hola, tu amigo tiene correos", 52 | OneMale: "Hola, él tiene un correo", 53 | ManyFemale: "Hola, ella tiene {{.EmailQty}} correos", 54 | }, 55 | } 56 | 57 | // 3. Add your languages with their translations 58 | // Yoy can also add custom pluralization rules but for this 59 | // example we will use the default ones to keep it simple 60 | i18n.AddLanguage("en", enTranslations) 61 | i18n.AddLanguage("es", esTranslations) 62 | 63 | // 4. Create the Options 64 | // The Gender field is a *string that contains the gender to use 65 | // Here you can use male, female, nonbinary or non-binary 66 | oneInt := 1 67 | manyInt := 10 68 | maleText := "male" 69 | femaleText := "female" 70 | 71 | oneMaleOptions := goeasyi18n.Options{ 72 | Gender: &maleText, 73 | Count: &oneInt, 74 | Data: map[string]any{ 75 | "EmailQty": oneInt, 76 | }, 77 | } 78 | 79 | manyFemaleOptions := goeasyi18n.Options{ 80 | Gender: &femaleText, 81 | Count: &manyInt, 82 | Data: map[string]any{ 83 | "EmailQty": manyInt, 84 | }, 85 | } 86 | 87 | // 5. You are done! 🎉 Just get that translations! 88 | ten1 := i18n.T("en", "friend_emails", oneMaleOptions) 89 | ten2 := i18n.T("en", "friend_emails", manyFemaleOptions) 90 | 91 | tes1 := i18n.T("es", "friend_emails", oneMaleOptions) 92 | tes2 := i18n.T("es", "friend_emails", manyFemaleOptions) 93 | 94 | fmt.Println(ten1) 95 | fmt.Println(ten2) 96 | fmt.Println(tes1) 97 | fmt.Println(tes2) 98 | 99 | /* 100 | Prints: 101 | Hello, he has one email 102 | Hello, she has 10 emails 103 | Hola, él tiene un correo 104 | Hola, ella tiene 10 correos 105 | */ 106 | } 107 | -------------------------------------------------------------------------------- /examples/06-genders/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/eduardolat/goeasyi18n" 7 | ) 8 | 9 | /* 10 | Gender Handling: Why is it useful? 11 | 12 | Let's say your app has a feature that says, "John liked your post" or "Emily liked your post." 13 | In some languages, the verb "liked" might change based on the gender of the person who 14 | liked the post. 15 | 16 | Example: 17 | - English: "He liked your post" vs "She liked your post" 18 | - Spanish: "A él le gustó tu publicación" vs "A ella le gustó tu publicación" 19 | 20 | With gender-specific translations, you can easily adapt the sentence structure to fit the 21 | gender, making your app more linguistically accurate and inclusive. 22 | 23 | No need for messy if-else statements to handle gender variations. Just set it up once, and 24 | the library takes care of the rest! 25 | */ 26 | 27 | func main() { 28 | // 1. Create a new i18n instance 29 | i18n := goeasyi18n.NewI18n() 30 | 31 | // 2. Create your translations 32 | // If something goes wrong, the default value is used 33 | // The gender keys are Male, Female and NonBinary 34 | enTranslations := goeasyi18n.TranslateStrings{ 35 | { 36 | Key: "friend_emails", 37 | Default: "Hello, your friend have emails", 38 | Male: "Hello, he has emails", 39 | Female: "Hello, she has emails", 40 | NonBinary: "Hello, your friend have emails", 41 | }, 42 | } 43 | 44 | esTranslations := goeasyi18n.TranslateStrings{ 45 | { 46 | Key: "friend_emails", 47 | Default: "Hola, tu amigo tiene correos", 48 | Male: "Hola, él tiene correos", 49 | Female: "Hola, ella tiene correos", 50 | NonBinary: "Hola, tu amigue tiene correos", 51 | }, 52 | } 53 | 54 | // 3. Add your languages with their translations 55 | i18n.AddLanguage("en", enTranslations) 56 | i18n.AddLanguage("es", esTranslations) 57 | 58 | // 4. Create the Options 59 | // The Gender field is a *string that contains the gender to use 60 | // Here you can use male, female, nonbinary or non-binary 61 | maleText := "male" 62 | femaleText := "female" 63 | nonbinaryText := "nonbinary" 64 | 65 | maleOptions := goeasyi18n.Options{ 66 | Gender: &maleText, 67 | } 68 | 69 | femaleOptions := goeasyi18n.Options{ 70 | Gender: &femaleText, 71 | } 72 | 73 | nonbinaryOptions := goeasyi18n.Options{ 74 | Gender: &nonbinaryText, 75 | } 76 | 77 | // 5. You are done! 🎉 Just get that translations! 78 | ten1 := i18n.T("en", "friend_emails", maleOptions) 79 | ten2 := i18n.T("en", "friend_emails", femaleOptions) 80 | ten3 := i18n.T("en", "friend_emails", nonbinaryOptions) 81 | 82 | tes1 := i18n.T("es", "friend_emails", maleOptions) 83 | tes2 := i18n.T("es", "friend_emails", femaleOptions) 84 | tes3 := i18n.T("es", "friend_emails", nonbinaryOptions) 85 | 86 | fmt.Println(ten1) 87 | fmt.Println(ten2) 88 | fmt.Println(ten3) 89 | fmt.Println(tes1) 90 | fmt.Println(tes2) 91 | fmt.Println(tes3) 92 | 93 | /* 94 | Prints: 95 | Hello, he has emails 96 | Hello, she has emails 97 | Hello, your friend have emails 98 | Hola, él tiene correos 99 | Hola, ella tiene correos 100 | Hola, tu amigue tiene correos 101 | */ 102 | } 103 | -------------------------------------------------------------------------------- /examples/01-basic-usage/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/eduardolat/goeasyi18n" 7 | ) 8 | 9 | func main() { 10 | // 1. Create a new i18n instance 11 | // You can skip the goeasyi18n.Config{} entirely if you are 12 | // ok with the default values. 13 | i18n := goeasyi18n.NewI18n(goeasyi18n.Config{ 14 | // You can set the fallback language (optional) 15 | // The default value is "en" 16 | FallbackLanguageName: "en", 17 | 18 | // You can disable the consistency check (optional) 19 | // By default, if you add a translation for a language 20 | // that has not the same keys as the other languages, 21 | // the i18n instance will log warnings. 22 | DisableConsistencyCheck: false, 23 | }) 24 | 25 | // 2. Create your translations 26 | enTranslations := goeasyi18n.TranslateStrings{ 27 | { 28 | Key: "hello_message", 29 | Default: "Hello, welcome to Go Easy i18n!", 30 | }, 31 | } 32 | 33 | esTranslations := goeasyi18n.TranslateStrings{ 34 | { 35 | Key: "hello_message", 36 | Default: "¡Hola, bienvenido a Go Easy i18n!", 37 | }, 38 | } 39 | 40 | // 3. Add your languages with their translations 41 | // The name of the language can be anything you want 42 | // You can use simple strings like "en" or "es" 43 | // You you can use the full language name like "english" or "spanish" 44 | // You can even use the IETF Language Tag like "en-US" or "es-ES" 45 | i18n.AddLanguage("en", enTranslations) 46 | 47 | // If the language has the same keys as the other languages, 48 | // the i18n instance will log warnings and return a slice of 49 | // inconsistencies as strings. You can disable this behavior 50 | // in the i18n instance config. 51 | inconsistencies := i18n.AddLanguage("es", esTranslations) 52 | fmt.Printf("Inconsistencies: %v\n", inconsistencies) 53 | // (no inconsistencies) 54 | // Prints: Inconsistencies: [] 55 | 56 | // 4. You are done! 🎉 Just get that translations! 57 | t1 := i18n.Translate("en", "hello_message", goeasyi18n.Options{}) 58 | // Or you can use the T method (it's just an alias for Translate) 59 | // and you can skip the options if you don't need them 60 | t2 := i18n.T("es", "hello_message") 61 | 62 | fmt.Println(t1) 63 | fmt.Println(t2) 64 | 65 | /* 66 | Prints: 67 | Hello, welcome to Go Easy i18n! 68 | ¡Hola, bienvenido a Go Easy i18n! 69 | */ 70 | 71 | // 5. (Extra) You can check if a language exists in the i18n instance 72 | enExists := i18n.HasLanguage("en") 73 | xxExists := i18n.HasLanguage("xx") 74 | 75 | fmt.Printf("en exists: %v\n", enExists) 76 | fmt.Printf("xx exists: %v\n", xxExists) 77 | 78 | /* 79 | Prints: 80 | en exists: true 81 | xx exists: false 82 | */ 83 | 84 | // 6. (Extra) You can create a translate function for a specific language 85 | // to prevent passing the language name every time you want to translate 86 | // something 87 | translateEn := i18n.NewLangTranslateFunc("en") 88 | translateEs := i18n.NewLangTranslateFunc("es") 89 | 90 | // You can skip the options if you don't need them 91 | t3 := translateEn("hello_message", goeasyi18n.Options{}) 92 | t4 := translateEs("hello_message") 93 | 94 | fmt.Println(t3) 95 | fmt.Println(t4) 96 | 97 | /* 98 | Prints: 99 | Hello, welcome to Go Easy i18n! 100 | ¡Hola, bienvenido a Go Easy i18n! 101 | */ 102 | } 103 | -------------------------------------------------------------------------------- /examples/09-advanced-example/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |{{Translate "lang" .Lang "key" "hello_world"}}
83 | 84 |{{Translate "lang" .Lang "key" "hello_name" "Name" "John Doe"}}
88 | 89 |{{Translate "lang" .Lang "key" "unread_emails" "count" "1" "Count" "1"}}
93 |{{Translate "lang" .Lang "key" "unread_emails" "count" "10" "Count" "10"}}
94 | 95 |{{Translate "lang" .Lang "key" "friend_unread_emails" "count" "1" "gender" "male" "Count" "1" "Name" "John"}}
99 |{{Translate "lang" .Lang "key" "friend_unread_emails" "count" "10" "gender" "male" "Count" "10" "Name" "John"}}
100 |{{Translate "lang" .Lang "key" "friend_unread_emails" "count" "1" "gender" "female" "Count" "1" "Name" "Jane"}}
101 |{{Translate "lang" .Lang "key" "friend_unread_emails" "count" "10" "gender" "female" "Count" "10" "Name" "Jane"}}
102 |106 | If you like 👍 this project, please give it a star on GitHub 107 |
108 | Star 109 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |