Version Inicial
This commit is contained in:
160
internal/i18n/i18n.go
Normal file
160
internal/i18n/i18n.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// I18n es la estructura principal
|
||||
type I18n struct {
|
||||
current string
|
||||
fallback string
|
||||
locales map[string]map[string]string
|
||||
mu sync.RWMutex // protege acceso concurrente
|
||||
}
|
||||
|
||||
// New crea una instancia de i18n
|
||||
/* func New(defaultLocale, fallback string) *I18n {
|
||||
return &I18n{
|
||||
current: defaultLocale,
|
||||
fallback: fallback,
|
||||
locales: make(map[string]map[string]string),
|
||||
}
|
||||
}
|
||||
*/
|
||||
func New(path string, fallback string) (*I18n, error) {
|
||||
i := &I18n{
|
||||
current: fallback,
|
||||
fallback: fallback,
|
||||
locales: make(map[string]map[string]string),
|
||||
}
|
||||
|
||||
if err := i.loadDir(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(i.locales) == 0 {
|
||||
return nil, fmt.Errorf("no locales loaded from %s", path)
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (i *I18n) loadDir(path string) error {
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := file.Name()
|
||||
|
||||
if !strings.HasSuffix(name, ".json") {
|
||||
continue
|
||||
}
|
||||
|
||||
lang := strings.TrimSuffix(name, ".json")
|
||||
|
||||
if err := i.loadFile(filepath.Join(path, name), lang); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadLocale carga un archivo JSON en memoria
|
||||
func (i *I18n) LoadLocale(locale, path string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var parsed map[string]string
|
||||
if err := json.Unmarshal(data, &parsed); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.mu.Lock()
|
||||
i.locales[locale] = parsed
|
||||
i.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *I18n) loadFile(path string, lang string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var content map[string]string
|
||||
|
||||
if err := json.Unmarshal(data, &content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
|
||||
i.locales[lang] = content
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLocale cambia el idioma activo
|
||||
func (i *I18n) SetLocale(locale string) {
|
||||
i.mu.Lock()
|
||||
i.current = locale
|
||||
i.mu.Unlock()
|
||||
}
|
||||
|
||||
// T traduce una clave, opcionalmente interpolando variables
|
||||
func (i *I18n) T(key string, vars map[string]string) string {
|
||||
val := i.translate(key)
|
||||
if vars == nil {
|
||||
return val
|
||||
}
|
||||
// Interpolación simple: reemplaza {var} por su valor
|
||||
for k, v := range vars {
|
||||
val = strings.ReplaceAll(val, "{"+k+"}", v)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
// translate busca la clave en el idioma actual y fallback
|
||||
func (i *I18n) translate(key string) string {
|
||||
i.mu.RLock()
|
||||
defer i.mu.RUnlock()
|
||||
|
||||
// 1. Buscar en idioma actual
|
||||
if localeData, ok := i.locales[i.current]; ok {
|
||||
if val, ok := localeData[key]; ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Buscar en fallback
|
||||
if localeData, ok := i.locales[i.fallback]; ok {
|
||||
if val, ok := localeData[key]; ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Si no existe → devolver clave marcada
|
||||
return fmt.Sprintf("??%s??", key)
|
||||
}
|
||||
|
||||
// Helper para construir claves con seguridad
|
||||
func Key(parts ...string) string {
|
||||
return strings.Join(parts, ".")
|
||||
}
|
||||
Reference in New Issue
Block a user