-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathi18n.go
More file actions
121 lines (104 loc) · 3.73 KB
/
i18n.go
File metadata and controls
121 lines (104 loc) · 3.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// SPDX-License-Identifier: EUPL-1.2
package api
import (
"github.com/gin-gonic/gin"
"golang.org/x/text/language"
)
// i18nContextKey is the Gin context key for the detected locale string.
const i18nContextKey = "i18n.locale"
// i18nMessagesKey is the Gin context key for the message lookup map.
const i18nMessagesKey = "i18n.messages"
// I18nConfig configures the internationalisation middleware.
type I18nConfig struct {
// DefaultLocale is the fallback locale when the Accept-Language header
// is absent or does not match any supported locale. Defaults to "en".
DefaultLocale string
// Supported lists the locale tags the application supports.
// Each entry should be a BCP 47 language tag (e.g. "en", "fr", "de").
// If empty, only the default locale is supported.
Supported []string
// Messages maps locale tags to key-value message pairs.
// For example: {"en": {"greeting": "Hello"}, "fr": {"greeting": "Bonjour"}}
// This is optional — handlers can use GetLocale() alone for custom logic.
Messages map[string]map[string]string
}
// WithI18n adds Accept-Language header parsing and locale detection middleware.
// The middleware uses golang.org/x/text/language for RFC 5646 language matching
// with quality weighting support. The detected locale is stored in the Gin
// context and can be retrieved by handlers via GetLocale().
//
// If messages are configured, handlers can look up localised strings via
// GetMessage(). This is a lightweight bridge — the go-i18n grammar engine
// can replace the message map later.
func WithI18n(cfg ...I18nConfig) Option {
return func(e *Engine) {
var config I18nConfig
if len(cfg) > 0 {
config = cfg[0]
}
if config.DefaultLocale == "" {
config.DefaultLocale = "en"
}
// Build the language.Matcher from supported locales.
tags := []language.Tag{language.Make(config.DefaultLocale)}
for _, s := range config.Supported {
tag := language.Make(s)
// Avoid duplicating the default if it also appears in Supported.
if tag != tags[0] {
tags = append(tags, tag)
}
}
matcher := language.NewMatcher(tags)
e.middlewares = append(e.middlewares, i18nMiddleware(matcher, config))
}
}
// i18nMiddleware returns Gin middleware that parses Accept-Language, matches
// it against supported locales, and stores the result in the context.
func i18nMiddleware(matcher language.Matcher, cfg I18nConfig) gin.HandlerFunc {
return func(c *gin.Context) {
accept := c.GetHeader("Accept-Language")
var locale string
if accept == "" {
locale = cfg.DefaultLocale
} else {
tags, _, _ := language.ParseAcceptLanguage(accept)
tag, _, _ := matcher.Match(tags...)
base, _ := tag.Base()
locale = base.String()
}
c.Set(i18nContextKey, locale)
// Attach the message map for this locale if messages are configured.
if cfg.Messages != nil {
if msgs, ok := cfg.Messages[locale]; ok {
c.Set(i18nMessagesKey, msgs)
} else if msgs, ok := cfg.Messages[cfg.DefaultLocale]; ok {
// Fall back to default locale messages.
c.Set(i18nMessagesKey, msgs)
}
}
c.Next()
}
}
// GetLocale returns the detected locale for the current request.
// Returns "en" if the i18n middleware was not applied.
func GetLocale(c *gin.Context) string {
if v, ok := c.Get(i18nContextKey); ok {
if s, ok := v.(string); ok {
return s
}
}
return "en"
}
// GetMessage looks up a localised message by key for the current request.
// Returns the message string and true if found, or empty string and false
// if the key does not exist or the i18n middleware was not applied.
func GetMessage(c *gin.Context, key string) (string, bool) {
if v, ok := c.Get(i18nMessagesKey); ok {
if msgs, ok := v.(map[string]string); ok {
if msg, ok := msgs[key]; ok {
return msg, true
}
}
}
return "", false
}