Files
personotes/internal/i18n/i18n.go

140 lines
3.3 KiB
Go

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
}