1package i18n
2
3import (
4 "fmt"
5 "os"
6 "path"
7 "path/filepath"
8 "strings"
9)
10
11// LoadTranslations loads all translation files into a bundle.
12// First attempts to load from embedded files, then checks for external files.
13func LoadTranslations(bundle *Bundle) error {
14 // Load from embedded files
15 if err := loadFromEmbedded(bundle); err != nil {
16 return fmt.Errorf("%w: embedded load failed: %w", ErrLoadFailed, err)
17 }
18
19 return nil
20}
21
22// loadFromEmbedded loads translation files from the embedded filesystem.
23func loadFromEmbedded(bundle *Bundle) error {
24 entries, err := localeFS.ReadDir("locales")
25 if err != nil {
26 return err
27 }
28
29 for _, entry := range entries {
30 if entry.IsDir() {
31 continue
32 }
33
34 filename := entry.Name()
35 if !strings.HasSuffix(filename, ".json") {
36 continue
37 }
38
39 // Read file (embed.FS always uses forward slashes, even on Windows)
40 data, err := localeFS.ReadFile(path.Join("locales", filename))
41 if err != nil {
42 continue
43 }
44
45 // Extract language code from filename (e.g., "en.json" -> "en")
46 lang := strings.TrimSuffix(filename, ".json")
47
48 // Load into bundle
49 if err := loadLanguageFile(bundle, lang, data); err != nil {
50 return err
51 }
52 }
53
54 return nil
55}
56
57// LoadFromDirectory loads translation files from a directory on disk.
58// This allows overriding embedded translations with external files.
59func LoadFromDirectory(bundle *Bundle, dir string) error {
60 entries, err := os.ReadDir(dir)
61 if err != nil {
62 return fmt.Errorf("%w: %w", ErrLoadFailed, err)
63 }
64
65 for _, entry := range entries {
66 if entry.IsDir() {
67 continue
68 }
69
70 filename := entry.Name()
71 if !strings.HasSuffix(filename, ".json") {
72 continue
73 }
74
75 // Read file
76 path := filepath.Join(dir, filename)
77 data, err := os.ReadFile(path)
78 if err != nil {
79 continue
80 }
81
82 // Extract language code
83 lang := strings.TrimSuffix(filename, ".json")
84
85 // Load into bundle
86 if err := loadLanguageFile(bundle, lang, data); err != nil {
87 return err
88 }
89 }
90
91 return nil
92}
93
94// loadLanguageFile parses and loads a single language file into the bundle.
95func loadLanguageFile(bundle *Bundle, lang string, data []byte) error {
96 messages, err := ParseJSON(data)
97 if err != nil {
98 return fmt.Errorf("%w: language %s: %w", ErrParseFailed, lang, err)
99 }
100
101 return bundle.AddMessages(lang, messages)
102}