From ff4e841e096d0e30faac5acf210367de7e8a64bf Mon Sep 17 00:00:00 2001 From: myadream Date: Thu, 14 Nov 2024 21:23:25 +0800 Subject: [PATCH] feat: additional methods (#63) * add HasLang method * add GetCurrentLanguage and GetDefaultLanguage * add message type * ci: standardize Go setup and update CI configurations - Rename "Checkout" to "Checkout repository" - Rename "Set up Go" to "Setup go" - Change Go version specification to use `go-version-file: go.mod` and add `check-latest: true` - Update goreleaser action from v5 to v6 Signed-off-by: Bo-Yi Wu * chore: update dependencies to latest versions - Update `github.com/gin-gonic/gin` to v1.10.0 - Update `golang.org/x/text` to v0.16.0 - Update `github.com/bytedance/sonic` to v1.11.8 - Update `github.com/gabriel-vasile/mimetype` to v1.4.4 - Update `github.com/go-playground/validator/v10` to v10.22.0 - Update `github.com/goccy/go-json` to v0.10.3 - Update `github.com/klauspost/cpuid/v2` to v2.2.8 - Update `golang.org/x/arch` to v0.8.0 - Update `golang.org/x/crypto` to v0.24.0 - Update `golang.org/x/net` to v0.26.0 - Update `golang.org/x/sys` to v0.21.0 - Update `google.golang.org/protobuf` to v1.34.2 Signed-off-by: Bo-Yi Wu * chore: upgrade Go and dependencies to latest versions - Update Go version from 1.18 to 1.20 - Upgrade golang.org/x/text from v0.16.0 to v0.17.0 - Upgrade github.com/bytedance/sonic from v1.11.8 to v1.12.1 and its loader from v0.1.1 to v0.2.0 - Upgrade github.com/gabriel-vasile/mimetype from v1.4.4 to v1.4.5 - Upgrade golang.org/x/arch from v0.8.0 to v0.9.0 - Upgrade golang.org/x/net from v0.26.0 to v0.28.0 - Upgrade golang.org/x/sys from v0.21.0 to v0.24.0 Signed-off-by: appleboy * ci: update GitHub Actions workflow for Go versions - Remove macOS from the OS matrix in the GitHub Actions workflow - Update the Go versions to only include 1.20, 1.21, and 1.22 Signed-off-by: appleboy * ci: remove CodeQL analysis workflow file - The CodeQL analysis workflow file has been deleted. Signed-off-by: appleboy * chore: update Go version constraints for compatibility - Remove build constraints for Go version 1.16 Signed-off-by: appleboy * ci: update Go versions in CI workflows - Update Go versions in GitHub Actions workflow to include 1.23 and remove 1.20 Signed-off-by: appleboy * chore: update Go version and dependencies to latest releases - Update Go version from 1.20 to 1.21.0 - Upgrade `golang.org/x/text` from v0.17.0 to v0.18.0 - Upgrade `github.com/bytedance/sonic` from v1.12.1 to v1.12.3 - Upgrade `github.com/go-playground/validator/v10` from v10.22.0 to v10.22.1 - Upgrade `github.com/pelletier/go-toml/v2` from v2.2.2 to v2.2.3 - Upgrade `golang.org/x/arch` from v0.9.0 to v0.10.0 - Upgrade `golang.org/x/crypto` from v0.26.0 to v0.27.0 - Upgrade `golang.org/x/net` from v0.28.0 to v0.29.0 - Upgrade `golang.org/x/sys` from v0.24.0 to v0.25.0 Signed-off-by: appleboy * chore: update dependencies to latest versions - Update `github.com/nicksnyder/go-i18n/v2` to version `2.4.1` - Update `golang.org/x/text` to version `0.20.0` - Update `github.com/bytedance/sonic` to version `1.12.4` (indirect) - Update `github.com/bytedance/sonic/loader` to version `0.2.1` (indirect) - Update `github.com/gabriel-vasile/mimetype` to version `1.4.6` (indirect) - Update `github.com/klauspost/cpuid/v2` to version `2.2.9` (indirect) - Update `golang.org/x/arch` to version `0.12.0` (indirect) - Update `golang.org/x/crypto` to version `0.29.0` (indirect) - Update `golang.org/x/net` to version `0.31.0` (indirect) - Update `golang.org/x/sys` to version `0.27.0` (indirect) - Update `google.golang.org/protobuf` to version `1.35.1` (indirect) Signed-off-by: appleboy * chore: mark GinI18n recievers as public (#58) * docs: enhance code documentation for ginI18n implementation - Add detailed comments for the `ginI18nImpl` struct and its methods - Add detailed comments for the `GinI18n` interface and its methods - Add detailed comments for the `GetLngHandler` and `Option` types Signed-off-by: appleboy * add extend methods add extend methods * rebase master --------- Signed-off-by: Bo-Yi Wu Signed-off-by: appleboy Co-authored-by: Bo-Yi Wu Co-authored-by: Vincent Chi <7449005+chivincent@users.noreply.github.com> --- README.md | 123 ++++++++++++++++++++++++++++++++++++++++----------- ginI18n.go | 64 ++++++++++++++++++++++++++- i18n.go | 30 +++++++++++++ i18n_test.go | 111 +++++++++++++++++++++++++++++++++++++++++++--- interface.go | 13 ++++++ 5 files changed, 306 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 58ae10e..69e5deb 100644 --- a/README.md +++ b/README.md @@ -46,17 +46,40 @@ func main() { ctx.String(http.StatusOK, ginI18n.MustGetMessage(ctx, "welcome")) }) - router.GET("/:name", func(ctx *gin.Context) { - ctx.String(http.StatusOK, ginI18n.MustGetMessage( - ctx, - &i18n.LocalizeConfig{ - MessageID: "welcomeWithName", - TemplateData: map[string]string{ - "name": ctx.Param("name"), - }, - })) + router.GET("/messageId/:name", func(context *gin.Context) { + context.String(http.StatusOK, MustGetMessage(context, &i18n.LocalizeConfig{ + MessageID: "welcomeWithName", + TemplateData: map[string]string{ + "name": context.Param("name"), + }, + })) + }) + + router.GET("/messageType/:name", func(context *gin.Context) { + context.String(http.StatusOK, MustGetMessage(context, &i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: "welcomeWithName", + }, + TemplateData: map[string]string{ + "name": context.Param("name"), + }, + })) + }) + + router.GET("/exist/:lang", func(ctx *gin.Context) { + ctx.String(http.StatusOK, "%v", ginI18n.HasLang(ctx, ctx.Param("lang"))) }) + // get the default and current language + router.GET("/lang/default", func(context *gin.Context) { + context.String(http.StatusOK, "%s", GetDefaultLanguage(context).String()) + }) + + // get the current language + router.GET("/lang/current", func(context *gin.Context) { + context.String(http.StatusOK, "%s", GetCurrentLanguage(context).String()) + }) + if err := router.Run(":8080"); err != nil { log.Fatal(err) } @@ -97,17 +120,40 @@ func main() { ctx.String(http.StatusOK, ginI18n.MustGetMessage(ctx, "welcome")) }) - router.GET("/:name", func(ctx *gin.Context) { - ctx.String(http.StatusOK, ginI18n.MustGetMessage( - ctx, - &i18n.LocalizeConfig{ - MessageID: "welcomeWithName", - TemplateData: map[string]string{ - "name": ctx.Param("name"), - }, - })) + router.GET("/messageId/:name", func(context *gin.Context) { + context.String(http.StatusOK, MustGetMessage(context, &i18n.LocalizeConfig{ + MessageID: "welcomeWithName", + TemplateData: map[string]string{ + "name": context.Param("name"), + }, + })) + }) + + router.GET("/messageType/:name", func(context *gin.Context) { + context.String(http.StatusOK, MustGetMessage(context, &i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: "welcomeWithName", + }, + TemplateData: map[string]string{ + "name": context.Param("name"), + }, + })) + }) + + router.GET("/exist/:lang", func(ctx *gin.Context) { + ctx.String(http.StatusOK, "%v", ginI18n.HasLang(ctx, ctx.Param("lang"))) }) + // get the default and current language + router.GET("/lang/default", func(context *gin.Context) { + context.String(http.StatusOK, "%s", GetDefaultLanguage(context).String()) + }) + + // get the current language + router.GET("/lang/current", func(context *gin.Context) { + context.String(http.StatusOK, "%s", GetCurrentLanguage(context).String()) + }) + if err := router.Run(":8080"); err != nil { log.Fatal(err) } @@ -150,17 +196,40 @@ func main() { ctx.String(http.StatusOK, ginI18n.MustGetMessage(ctx, "welcome")) }) - router.GET("/:name", func(ctx *gin.Context) { - ctx.String(http.StatusOK, ginI18n.MustGetMessage( - ctx, - &i18n.LocalizeConfig{ - MessageID: "welcomeWithName", - TemplateData: map[string]string{ - "name": ctx.Param("name"), - }, - })) + router.GET("/messageId/:name", func(context *gin.Context) { + context.String(http.StatusOK, MustGetMessage(context, &i18n.LocalizeConfig{ + MessageID: "welcomeWithName", + TemplateData: map[string]string{ + "name": context.Param("name"), + }, + })) + }) + + router.GET("/messageType/:name", func(context *gin.Context) { + context.String(http.StatusOK, MustGetMessage(context, &i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: "welcomeWithName", + }, + TemplateData: map[string]string{ + "name": context.Param("name"), + }, + })) + }) + + router.GET("/exist/:lang", func(ctx *gin.Context) { + ctx.String(http.StatusOK, "%v", ginI18n.HasLang(ctx, ctx.Param("lang"))) }) + // get the default and current language + router.GET("/lang/default", func(context *gin.Context) { + context.String(http.StatusOK, "%s", GetDefaultLanguage(context).String()) + }) + + // get the current language + router.GET("/lang/current", func(context *gin.Context) { + context.String(http.StatusOK, "%s", GetCurrentLanguage(context).String()) + }) + if err := router.Run(":8080"); err != nil { log.Fatal(err) } diff --git a/ginI18n.go b/ginI18n.go index 7f69b18..5c144d5 100644 --- a/ginI18n.go +++ b/ginI18n.go @@ -1,4 +1,4 @@ -// ginI18nImpl is an implementation of the GinI18n interface, providing +// Package i18n ginI18nImpl is an implementation of the GinI18n interface, providing // localization support for Gin applications. It uses the go-i18n library // to manage and retrieve localized messages. // @@ -11,6 +11,9 @@ // Methods: // - GetMessage: Retrieves a localized message based on the provided context and parameter. // - MustGetMessage: Retrieves a localized message and returns an empty string if retrieval fails. +// - HasLang: Checks if a specific language is supported. +// - GetCurrentLanguage: Retrieves the current language based on the Gin context.. +// - GetDefaultLanguage: Retrieves the default language // - SetBundle: Sets the i18n.Bundle configuration. // - SetGetLngHandler: Sets the handler function to retrieve the language tag from the Gin context. // - loadMessageFiles: Loads all localization files into the bundle. @@ -42,6 +45,55 @@ type ginI18nImpl struct { getLngHandler GetLngHandler } +// GetDefaultLanguage retrieves the default language tag for the application. +// +// This method returns the default language tag that is used when no specific +// language is specified by the client or in the context. +// +// Parameters: +// - ctx: The Gin context from which to retrieve the message. +// +// Returns: +// - language.Tag: The default language tag. +func (i *ginI18nImpl) GetDefaultLanguage() language.Tag { + return i.defaultLanguage +} + +// GetCurrentLanguage retrieves the current language tag from the Gin context. +// +// This method extracts the language tag from the Gin context using the provided +// `getLngHandler` function. It uses this handler to obtain the language tag for +// the current request. If the language is not provided, it returns the default language. +// +// Parameters: +// - ctx: The Gin context from which to retrieve the message. +// +// Returns: +// - language.Tag: The language tag based on the context (either the specified language or the default language). +func (i *ginI18nImpl) GetCurrentLanguage(context *gin.Context) language.Tag { + return language.Make(i.getLngHandler(context, i.defaultLanguage.String())) +} + +// HasLang checks whether the specified language is supported by the application. +// +// This method checks if a language tag is available in the localizer map (`localizerByLng`), +// which stores localizers for all supported languages. If the language is supported, +// it returns `true`; otherwise, it returns `false`. +// +// Parameters: +// - ctx: The Gin context from which to retrieve the message. +// - language (string): The language tag (e.g., "en", "zh") to check. +// +// Returns: +// - bool: `true` if the language is supported, otherwise `false`. +func (i *ginI18nImpl) HasLang(language string) bool { + if _, exist := i.localizerByLng[language]; exist { + return true + } + + return false +} + // GetMessage retrieves a localized message based on the provided context and parameter. // If the message cannot be retrieved, it returns an empty string. // @@ -183,6 +235,16 @@ func (i *ginI18nImpl) getLocalizeConfig(param interface{}) (*i18n.LocalizeConfig MessageID: paramValue, } return localizeConfig, nil + case *i18n.Message: + localizeConfig := &i18n.LocalizeConfig{ + DefaultMessage: paramValue, + } + return localizeConfig, nil + case i18n.Message: + localizeConfig := &i18n.LocalizeConfig{ + DefaultMessage: ¶mValue, + } + return localizeConfig, nil case *i18n.LocalizeConfig: return paramValue, nil case i18n.LocalizeConfig: diff --git a/i18n.go b/i18n.go index a6e7781..54c41bf 100644 --- a/i18n.go +++ b/i18n.go @@ -2,6 +2,7 @@ package i18n import ( "github.com/gin-gonic/gin" + "golang.org/x/text/language" ) // newI18n ... @@ -66,3 +67,32 @@ func MustGetMessage(context *gin.Context, param interface{}) string { atI18n := context.MustGet("i18n").(GinI18n) return atI18n.MustGetMessage(context, param) } + +// HasLang check all i18n lang exists +// Example: +// HasLang(context, "ZH-cn") // return false or true +func HasLang(context *gin.Context, language string) bool { + atI18n := context.MustGet("i18n").(GinI18n) + return atI18n.HasLang(language) +} + +// GetDefaultLanguage get the default language +// Example: +// GetDefaultLanguage(context) +func GetDefaultLanguage(context *gin.Context) language.Tag { + atI18n := context.MustGet("i18n").(GinI18n) + return atI18n.GetDefaultLanguage() +} + +// GetCurrentLanguage get the current language +// Example: +// GetCurrentLanguage(context) +func GetCurrentLanguage(context *gin.Context) language.Tag { + atI18n := context.MustGet("i18n").(GinI18n) + return atI18n.GetCurrentLanguage(context) +} + +// I18n get GinI18n from gin.Context +func I18n(context *gin.Context) GinI18n { + return context.MustGet("i18n").(GinI18n) +} diff --git a/i18n_test.go b/i18n_test.go index 58a3ed7..4955464 100644 --- a/i18n_test.go +++ b/i18n_test.go @@ -2,6 +2,7 @@ package i18n import ( "context" + "fmt" "net/http" "net/http/httptest" "testing" @@ -20,7 +21,7 @@ func newServer() *gin.Engine { context.String(http.StatusOK, MustGetMessage(context, "welcome")) }) - router.GET("/:name", func(context *gin.Context) { + router.GET("/messageId/:name", func(context *gin.Context) { context.String(http.StatusOK, MustGetMessage(context, &i18n.LocalizeConfig{ MessageID: "welcomeWithName", TemplateData: map[string]string{ @@ -28,6 +29,27 @@ func newServer() *gin.Engine { }, })) }) + + router.GET("/messageType/:name", func(context *gin.Context) { + context.String(http.StatusOK, MustGetMessage(context, &i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: "welcomeWithName", + }, + TemplateData: map[string]string{ + "name": context.Param("name"), + }, + })) + }) + + router.GET("/exist/:lang", func(context *gin.Context) { + context.String(http.StatusOK, "%v", HasLang(context, context.Param("lang"))) + }) + router.GET("/lang/default", func(context *gin.Context) { + context.String(http.StatusOK, "%s", GetDefaultLanguage(context).String()) + }) + router.GET("/lang/current", func(context *gin.Context) { + context.String(http.StatusOK, "%s", GetCurrentLanguage(context).String()) + }) router.GET("/age/:age", func(context *gin.Context) { context.String(http.StatusOK, MustGetMessage(context, i18n.LocalizeConfig{ MessageID: "welcomeWithAge", @@ -75,9 +97,17 @@ func TestI18nEN(t *testing.T) { want: "hello", }, { - name: "hello alex", + name: "hello alex - messageId", + args: args{ + path: "/messageId/alex", + lng: language.English, + }, + want: "hello alex", + }, + { + name: "hello alex - messageType", args: args{ - path: "/alex", + path: "/messageType/alex", lng: language.English, }, want: "hello alex", @@ -100,9 +130,17 @@ func TestI18nEN(t *testing.T) { want: "hallo", }, { - name: "hallo alex", + name: "hallo alex - messageId", args: args{ - path: "/alex", + path: "/messageId/alex", + lng: language.German, + }, + want: "hallo alex", + }, + { + name: "hallo alex - messageType", + args: args{ + path: "/messageType/alex", lng: language.German, }, want: "hallo alex", @@ -125,9 +163,17 @@ func TestI18nEN(t *testing.T) { want: "bonjour", }, { - name: "bonjour alex", + name: "bonjour alex - messageId", + args: args{ + path: "/messageId/alex", + lng: language.French, + }, + want: "bonjour alex", + }, + { + name: "bonjour alex - messageType", args: args{ - path: "/alex", + path: "/messageType/alex", lng: language.French, }, want: "bonjour alex", @@ -140,6 +186,57 @@ func TestI18nEN(t *testing.T) { }, want: "j'ai 18 ans", }, + // has exist + { + name: "i81n lang exist", + args: args{ + path: fmt.Sprintf("/exist/%s", language.English.String()), + lng: language.English, + }, + want: "true", + }, + { + name: "i81n lang not exist", + args: args{ + path: fmt.Sprintf("/exist/%s", language.SimplifiedChinese.String()), + lng: language.English, + }, + want: "false", + }, + // default lang + { + name: "i81n is default " + language.English.String(), + args: args{ + path: "/lang/default", + lng: language.English, + }, + want: language.English.String(), + }, + { + name: "i81n is not default " + language.German.String(), + args: args{ + path: "/lang/default", + lng: language.German, + }, + want: language.English.String(), + }, + // current lang + { + name: "i81n is current " + language.English.String(), + args: args{ + path: "/lang/current", + lng: language.English, + }, + want: language.English.String(), + }, + { + name: "i81n is not current " + language.English.String(), + args: args{ + path: "/lang/current", + lng: language.German, + }, + want: language.German.String(), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/interface.go b/interface.go index 11f9fe9..5bd9bca 100644 --- a/interface.go +++ b/interface.go @@ -2,6 +2,7 @@ package i18n import ( "github.com/gin-gonic/gin" + "golang.org/x/text/language" ) // GinI18n is an interface that defines methods for internationalization (i18n) in a Gin web framework context. @@ -20,4 +21,16 @@ type GinI18n interface { // SetGetLngHandler sets the handler function to determine the language from the context. SetGetLngHandler(handler GetLngHandler) + + // HasLang checks if the given language is supported by the i18n bundle. + // It returns true if the language is supported, false otherwise. + HasLang(language string) bool + + // GetDefaultLanguage returns the default language tag. + // It returns the default language tag. + GetDefaultLanguage() language.Tag + + // GetCurrentLanguage returns the current language tag from the context. + // It returns the current language tag. + GetCurrentLanguage(context *gin.Context) language.Tag }