140 lines
3.3 KiB
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
|
|
}
|