fallback.go

 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}