package i18n import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "sync" ) // Translator manages translations for multiple languages type Translator struct { translations map[string]map[string]interface{} mu sync.RWMutex defaultLang string } // New creates a new Translator with the specified default language func New(defaultLang string) *Translator { t := &Translator{ translations: make(map[string]map[string]interface{}), defaultLang: defaultLang, } return t } // LoadFromDir loads all translation files from a directory func (t *Translator) LoadFromDir(dir string) error { files, err := filepath.Glob(filepath.Join(dir, "*.json")) if err != nil { return fmt.Errorf("failed to list translation files: %w", err) } for _, file := range files { lang := strings.TrimSuffix(filepath.Base(file), ".json") if err := t.LoadLanguage(lang, file); err != nil { return fmt.Errorf("failed to load language %s: %w", lang, err) } } return nil } // LoadLanguage loads translations for a specific language from a JSON file func (t *Translator) LoadLanguage(lang, filePath string) error { data, err := os.ReadFile(filePath) if err != nil { return fmt.Errorf("failed to read translation file: %w", err) } var translations map[string]interface{} if err := json.Unmarshal(data, &translations); err != nil { return fmt.Errorf("failed to parse translation file: %w", err) } t.mu.Lock() t.translations[lang] = translations t.mu.Unlock() return nil } // T translates a key for the given language with optional arguments // Key format: "section.subsection.key" (e.g., "menu.home") // Arguments can be passed as a map for variable interpolation func (t *Translator) T(lang, key string, args ...map[string]string) string { t.mu.RLock() defer t.mu.RUnlock() // Try to get translation for specified language translation := t.getTranslation(lang, key) // Fallback to default language if not found if translation == "" && lang != t.defaultLang { translation = t.getTranslation(t.defaultLang, key) } // Return key if no translation found if translation == "" { return key } // Interpolate variables if args provided if len(args) > 0 && args[0] != nil { for k, v := range args[0] { placeholder := fmt.Sprintf("{{%s}}", k) translation = strings.ReplaceAll(translation, placeholder, v) } } return translation } // getTranslation retrieves a translation by key using dot notation func (t *Translator) getTranslation(lang, key string) string { langTranslations, ok := t.translations[lang] if !ok { return "" } parts := strings.Split(key, ".") var current interface{} = langTranslations for _, part := range parts { if m, ok := current.(map[string]interface{}); ok { current = m[part] } else { return "" } } if str, ok := current.(string); ok { return str } return "" } // GetAvailableLanguages returns a list of loaded languages func (t *Translator) GetAvailableLanguages() []string { t.mu.RLock() defer t.mu.RUnlock() langs := make([]string, 0, len(t.translations)) for lang := range t.translations { langs = append(langs, lang) } return langs } // GetTranslations returns all translations for a specific language func (t *Translator) GetTranslations(lang string) (map[string]interface{}, bool) { t.mu.RLock() defer t.mu.RUnlock() translations, ok := t.translations[lang] return translations, ok }