1package i18n
2
3import "strings"
4
5// FallbackChain defines a sequence of languages to try when looking up translations.
6type FallbackChain struct {
7 langs []string
8}
9
10// NewFallbackChain creates a new fallback chain with a preferred language and defaults.
11// Example: NewFallbackChain("pt-BR", "pt", "en") creates chain: pt-BR → pt → en
12func NewFallbackChain(preferred string, defaults ...string) *FallbackChain {
13 chain := &FallbackChain{
14 langs: make([]string, 0, len(defaults)+2),
15 }
16
17 // Add preferred language
18 if preferred != "" {
19 chain.langs = append(chain.langs, preferred)
20
21 // If preferred has region code (e.g., "en-US"), also add base (e.g., "en")
22 if parts := strings.Split(preferred, "-"); len(parts) > 1 {
23 base := parts[0]
24 if !contains(chain.langs, base) {
25 chain.langs = append(chain.langs, base)
26 }
27 }
28 }
29
30 // Add fallback languages
31 for _, lang := range defaults {
32 if lang != "" && !contains(chain.langs, lang) {
33 chain.langs = append(chain.langs, lang)
34 }
35 }
36
37 return chain
38}
39
40// Resolve attempts to find a message in the fallback chain.
41// Returns the message, the language it was found in, and any error.
42func (f *FallbackChain) Resolve(bundle *Bundle, key string) (*Message, string, error) {
43 for _, lang := range f.langs {
44 msg, err := bundle.GetMessage(lang, key)
45 if err == nil {
46 return msg, lang, nil
47 }
48 }
49
50 return nil, "", ErrMessageNotFound
51}
52
53// Languages returns the ordered list of languages in the fallback chain.
54func (f *FallbackChain) Languages() []string {
55 return f.langs
56}
57
58// contains checks if a slice contains a string.
59func contains(slice []string, item string) bool {
60 for _, s := range slice {
61 if s == item {
62 return true
63 }
64 }
65 return false
66}