mirror of
https://github.com/retailcrm/mg-transport-core.git
synced 2024-11-24 22:26:04 +03:00
Add localizer interfaces (#46)
* add localizer interfaces * add 1.18 to the supported versions * use go 1.17 for lint for now * use localizer interfaces in the library * fix incorrect naming of the HTTPResponseLocalizer interface * fix coverage collection
This commit is contained in:
parent
b260c8fb50
commit
e7d06fa208
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -22,6 +22,7 @@ jobs:
|
|||||||
- name: Set up Go 1.17
|
- name: Set up Go 1.17
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
|
# TODO: Should migrate to 1.18 later
|
||||||
go-version: '1.17'
|
go-version: '1.17'
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
@ -35,7 +36,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ['1.16', '1.17']
|
go-version: ['1.16', '1.17', '1.18']
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go ${{ matrix.go-version }}
|
- name: Set up Go ${{ matrix.go-version }}
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
@ -49,6 +50,6 @@ jobs:
|
|||||||
run: go test ./... -v -cpu 2 -timeout 10s -race -cover -coverprofile=coverage.txt -covermode=atomic
|
run: go test ./... -v -cpu 2 -timeout 10s -race -cover -coverprofile=coverage.txt -covermode=atomic
|
||||||
- name: Coverage
|
- name: Coverage
|
||||||
run: |
|
run: |
|
||||||
go get -v -u github.com/axw/gocov/gocov
|
go install github.com/axw/gocov/gocov@latest
|
||||||
gocov convert ./coverage.txt | gocov report
|
gocov convert ./coverage.txt | gocov report
|
||||||
bash <(curl -s https://codecov.io/bash)
|
bash <(curl -s https://codecov.io/bash)
|
@ -137,7 +137,7 @@ func (e *Engine) Prepare() *Engine {
|
|||||||
e.LocaleMatcher = DefaultLocalizerMatcher()
|
e.LocaleMatcher = DefaultLocalizerMatcher()
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.isUnd(e.Localizer.LanguageTag) {
|
if e.isUnd(e.Localizer.Language()) {
|
||||||
e.Localizer.LanguageTag = DefaultLanguage
|
e.Localizer.LanguageTag = DefaultLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,16 +38,60 @@ type Localizer struct {
|
|||||||
TranslationsPath string
|
TranslationsPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocalizerInterface contains entire public interface of the localizer component.
|
||||||
type LocalizerInterface interface {
|
type LocalizerInterface interface {
|
||||||
GetLocalizedMessage(messageID string) string
|
MessageLocalizer
|
||||||
GetLocalizedTemplateMessage(messageID string, templateData map[string]interface{}) string
|
LocalizerWithMiddleware
|
||||||
Localize(messageID string) (string, error)
|
LocalizerWithFuncs
|
||||||
|
LocaleControls
|
||||||
|
HTTPResponseLocalizer
|
||||||
|
CloneableLocalizer
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageLocalizer can localize regular strings and strings with template parameters.
|
||||||
|
type MessageLocalizer interface {
|
||||||
|
GetLocalizedMessage(string) string
|
||||||
|
GetLocalizedTemplateMessage(string, map[string]interface{}) string
|
||||||
|
Localize(string) (string, error)
|
||||||
|
LocalizeTemplateMessage(string, map[string]interface{}) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalizerWithMiddleware can provide middlewares for usage in the app.
|
||||||
|
type LocalizerWithMiddleware interface {
|
||||||
|
LocalizationMiddleware() gin.HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalizerWithFuncs can provide template functions.
|
||||||
|
type LocalizerWithFuncs interface {
|
||||||
|
LocalizationFuncMap() template.FuncMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocaleControls is an instance of localizer with exposed locale controls.
|
||||||
|
type LocaleControls interface {
|
||||||
|
Preload([]language.Tag)
|
||||||
|
SetLocale(string)
|
||||||
|
SetLanguage(language.Tag)
|
||||||
|
Language() language.Tag
|
||||||
|
LoadTranslations()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPResponseLocalizer can localize strings and return them with HTTP error codes.
|
||||||
|
type HTTPResponseLocalizer interface {
|
||||||
|
BadRequestLocalized(string) (int, interface{})
|
||||||
|
UnauthorizedLocalized(string) (int, interface{})
|
||||||
|
ForbiddenLocalized(string) (int, interface{})
|
||||||
|
InternalServerErrorLocalized(string) (int, interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneableLocalizer is a localizer which can clone itself.
|
||||||
|
type CloneableLocalizer interface {
|
||||||
|
Clone() CloneableLocalizer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLocalizer returns localizer instance with specified parameters.
|
// NewLocalizer returns localizer instance with specified parameters.
|
||||||
// Usage:
|
// Usage:
|
||||||
// NewLocalizer(language.English, DefaultLocalizerMatcher(), "translations")
|
// NewLocalizer(language.English, DefaultLocalizerMatcher(), "translations")
|
||||||
func NewLocalizer(locale language.Tag, matcher language.Matcher, translationsPath string) *Localizer {
|
func NewLocalizer(locale language.Tag, matcher language.Matcher, translationsPath string) LocalizerInterface {
|
||||||
localizer := &Localizer{
|
localizer := &Localizer{
|
||||||
i18nStorage: &sync.Map{},
|
i18nStorage: &sync.Map{},
|
||||||
LocaleMatcher: matcher,
|
LocaleMatcher: matcher,
|
||||||
@ -66,7 +110,7 @@ func NewLocalizer(locale language.Tag, matcher language.Matcher, translationsPat
|
|||||||
// TODO This code should be covered with tests.
|
// TODO This code should be covered with tests.
|
||||||
func NewLocalizerFS(
|
func NewLocalizerFS(
|
||||||
locale language.Tag, matcher language.Matcher, translationsFS fs.FS,
|
locale language.Tag, matcher language.Matcher, translationsFS fs.FS,
|
||||||
) *Localizer {
|
) LocalizerInterface {
|
||||||
localizer := &Localizer{
|
localizer := &Localizer{
|
||||||
i18nStorage: &sync.Map{},
|
i18nStorage: &sync.Map{},
|
||||||
LocaleMatcher: matcher,
|
LocaleMatcher: matcher,
|
||||||
@ -97,7 +141,7 @@ func DefaultLocalizerMatcher() language.Matcher {
|
|||||||
// Clone *core.Localizer. Clone shares it's translations with the parent localizer. Language tag will not be shared.
|
// Clone *core.Localizer. Clone shares it's translations with the parent localizer. Language tag will not be shared.
|
||||||
// Because of that you can change clone's language without affecting parent localizer.
|
// Because of that you can change clone's language without affecting parent localizer.
|
||||||
// This method should be used when LocalizationMiddleware is not feasible (outside of *gin.HandlerFunc).
|
// This method should be used when LocalizationMiddleware is not feasible (outside of *gin.HandlerFunc).
|
||||||
func (l *Localizer) Clone() *Localizer {
|
func (l *Localizer) Clone() CloneableLocalizer {
|
||||||
clone := &Localizer{
|
clone := &Localizer{
|
||||||
i18nStorage: l.i18nStorage,
|
i18nStorage: l.i18nStorage,
|
||||||
TranslationsFS: l.TranslationsFS,
|
TranslationsFS: l.TranslationsFS,
|
||||||
@ -122,7 +166,7 @@ func (l *Localizer) Clone() *Localizer {
|
|||||||
// engine.Use(localizer.LocalizationMiddleware())
|
// engine.Use(localizer.LocalizationMiddleware())
|
||||||
func (l *Localizer) LocalizationMiddleware() gin.HandlerFunc {
|
func (l *Localizer) LocalizationMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
clone := l.Clone()
|
clone := l.Clone().(LocaleControls)
|
||||||
clone.SetLocale(c.GetHeader("Accept-Language"))
|
clone.SetLocale(c.GetHeader("Accept-Language"))
|
||||||
c.Set(LocalizerContextKey, clone)
|
c.Set(LocalizerContextKey, clone)
|
||||||
}
|
}
|
||||||
@ -262,7 +306,7 @@ func (l *Localizer) isUnd(tag language.Tag) bool {
|
|||||||
|
|
||||||
// getCurrentLocalizer returns *i18n.Localizer with current language tag.
|
// getCurrentLocalizer returns *i18n.Localizer with current language tag.
|
||||||
func (l *Localizer) getCurrentLocalizer() *i18n.Localizer {
|
func (l *Localizer) getCurrentLocalizer() *i18n.Localizer {
|
||||||
return l.getLocalizer(l.LanguageTag)
|
return l.getLocalizer(l.Language())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLocale will change language for current localizer.
|
// SetLocale will change language for current localizer.
|
||||||
@ -287,6 +331,11 @@ func (l *Localizer) SetLanguage(tag language.Tag) {
|
|||||||
l.LoadTranslations()
|
l.LoadTranslations()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Language returns current language tag.
|
||||||
|
func (l *Localizer) Language() language.Tag {
|
||||||
|
return l.LanguageTag
|
||||||
|
}
|
||||||
|
|
||||||
// FetchLanguage will load language from tag
|
// FetchLanguage will load language from tag
|
||||||
//
|
//
|
||||||
// Deprecated: Use `(*core.Localizer).LoadTranslations()` instead.
|
// Deprecated: Use `(*core.Localizer).LoadTranslations()` instead.
|
||||||
@ -344,13 +393,13 @@ func (l *Localizer) InternalServerErrorLocalized(err string) (int, interface{})
|
|||||||
|
|
||||||
// GetContextLocalizer returns localizer from context if it is present there.
|
// GetContextLocalizer returns localizer from context if it is present there.
|
||||||
// Language will be set using Accept-Language header and root language tag.
|
// Language will be set using Accept-Language header and root language tag.
|
||||||
func GetContextLocalizer(c *gin.Context) (loc *Localizer, ok bool) {
|
func GetContextLocalizer(c *gin.Context) (loc LocalizerInterface, ok bool) {
|
||||||
loc, ok = extractLocalizerFromContext(c)
|
loc, ok = extractLocalizerFromContext(c)
|
||||||
if loc != nil {
|
if loc != nil {
|
||||||
loc.SetLocale(c.GetHeader("Accept-Language"))
|
loc.SetLocale(c.GetHeader("Accept-Language"))
|
||||||
|
|
||||||
lang := GetRootLanguageTag(loc.LanguageTag)
|
lang := GetRootLanguageTag(loc.Language())
|
||||||
if lang != loc.LanguageTag {
|
if lang != loc.Language() {
|
||||||
loc.SetLanguage(lang)
|
loc.SetLanguage(lang)
|
||||||
loc.LoadTranslations()
|
loc.LoadTranslations()
|
||||||
}
|
}
|
||||||
@ -359,7 +408,7 @@ func GetContextLocalizer(c *gin.Context) (loc *Localizer, ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MustGetContextLocalizer returns Localizer instance if it exists in provided context. Panics otherwise.
|
// MustGetContextLocalizer returns Localizer instance if it exists in provided context. Panics otherwise.
|
||||||
func MustGetContextLocalizer(c *gin.Context) *Localizer {
|
func MustGetContextLocalizer(c *gin.Context) LocalizerInterface {
|
||||||
if localizer, ok := GetContextLocalizer(c); ok {
|
if localizer, ok := GetContextLocalizer(c); ok {
|
||||||
return localizer
|
return localizer
|
||||||
}
|
}
|
||||||
@ -367,13 +416,13 @@ func MustGetContextLocalizer(c *gin.Context) *Localizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// extractLocalizerFromContext returns localizer from context if it exist there.
|
// extractLocalizerFromContext returns localizer from context if it exist there.
|
||||||
func extractLocalizerFromContext(c *gin.Context) (*Localizer, bool) {
|
func extractLocalizerFromContext(c *gin.Context) (LocalizerInterface, bool) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if item, ok := c.Get(LocalizerContextKey); ok {
|
if item, ok := c.Get(LocalizerContextKey); ok {
|
||||||
if localizer, ok := item.(*Localizer); ok {
|
if localizer, ok := item.(LocalizerInterface); ok {
|
||||||
return localizer, true
|
return localizer, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ func createTestLangFiles(t *testing.T) {
|
|||||||
|
|
||||||
type LocalizerTest struct {
|
type LocalizerTest struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
localizer *Localizer
|
localizer LocalizerInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LocalizerTest) SetupSuite() {
|
func (l *LocalizerTest) SetupSuite() {
|
||||||
@ -92,9 +92,9 @@ func (l *LocalizerTest) Test_LocalizationMiddleware_Context() {
|
|||||||
assert.NotNil(l.T(), esLocalizer)
|
assert.NotNil(l.T(), esLocalizer)
|
||||||
assert.NotNil(l.T(), ruLocalizer)
|
assert.NotNil(l.T(), ruLocalizer)
|
||||||
|
|
||||||
assert.Equal(l.T(), language.English, enLocalizer.LanguageTag)
|
assert.Equal(l.T(), language.English, enLocalizer.Language())
|
||||||
assert.Equal(l.T(), language.Spanish, esLocalizer.LanguageTag)
|
assert.Equal(l.T(), language.Spanish, esLocalizer.Language())
|
||||||
assert.Equal(l.T(), language.Russian, ruLocalizer.LanguageTag)
|
assert.Equal(l.T(), language.Russian, ruLocalizer.Language())
|
||||||
|
|
||||||
assert.Equal(l.T(), "Test message", enLocalizer.GetLocalizedMessage("message"))
|
assert.Equal(l.T(), "Test message", enLocalizer.GetLocalizedMessage("message"))
|
||||||
assert.Equal(l.T(), "Mensaje de prueba", esLocalizer.GetLocalizedMessage("message"))
|
assert.Equal(l.T(), "Mensaje de prueba", esLocalizer.GetLocalizedMessage("message"))
|
||||||
@ -164,10 +164,10 @@ func (l *LocalizerTest) Test_Clone() {
|
|||||||
require.Nil(l.T(), recover())
|
require.Nil(l.T(), recover())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
localizer := l.localizer.Clone()
|
localizer := l.localizer.Clone().(LocalizerInterface)
|
||||||
localizer.SetLanguage(language.Russian)
|
localizer.SetLanguage(language.Russian)
|
||||||
|
|
||||||
assert.NotEqual(l.T(), l.localizer.LanguageTag, localizer.LanguageTag)
|
assert.NotEqual(l.T(), l.localizer.Language(), localizer.Language())
|
||||||
assert.Equal(l.T(), "Test message", l.localizer.GetLocalizedMessage("message"))
|
assert.Equal(l.T(), "Test message", l.localizer.GetLocalizedMessage("message"))
|
||||||
assert.Equal(l.T(), "Тестовое сообщение", localizer.GetLocalizedMessage("message"))
|
assert.Equal(l.T(), "Тестовое сообщение", localizer.GetLocalizedMessage("message"))
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ type SentryTagged interface {
|
|||||||
type Sentry struct {
|
type Sentry struct {
|
||||||
SentryConfig sentry.ClientOptions
|
SentryConfig sentry.ClientOptions
|
||||||
Logger logger.Logger
|
Logger logger.Logger
|
||||||
Localizer *Localizer
|
Localizer MessageLocalizer
|
||||||
AppInfo AppInfo
|
AppInfo AppInfo
|
||||||
SentryLoggerConfig SentryLoggerConfig
|
SentryLoggerConfig SentryLoggerConfig
|
||||||
ServerName string
|
ServerName string
|
||||||
|
Loading…
Reference in New Issue
Block a user