package assets import ( "encoding/json" "fmt" "os" "path/filepath" ) // Lang 语言表:指定语言覆盖层 + en_us 回退层(设计.md §2.4、UI与输入.md §5)。 type Lang struct { keys map[string]string // 指定语言 fallback map[string]string // en_us 回退 } // Get 取翻译;缺失回退 en_us,再缺失返回键名本身(便于发现缺失键)。 func (l *Lang) Get(key string) string { if v, ok := l.keys[key]; ok { return v } if v, ok := l.fallback[key]; ok { return v } return key } // LoadLang 加载语言:先加载 en_us 作为回退层,再合并目标语言。 // 合并顺序:低优先级包 → 高优先级包(后者覆盖),与资源优先级链一致。 func (m *Manager) LoadLang(code string) (*Lang, error) { fallback := make(map[string]string) if code != "en_us" { if err := m.loadLangInto("en_us", fallback); err != nil { return nil, err } } keys := make(map[string]string) if err := m.loadLangInto(code, keys); err != nil { return nil, err } return &Lang{keys: keys, fallback: fallback}, nil } // loadLangInto 按优先级从低到高合并所有包的 lang/.json 到 dst。 func (m *Manager) loadLangInto(code string, dst map[string]string) error { var paths []string // 从低优先级到高优先级收集路径(逆序遍历包列表) for i := len(m.packs) - 1; i >= 0; i-- { p := m.packs[i] for _, ns := range p.namespaces() { path := filepath.Join(p.Root, "assets", ns, "lang", code+".json") if fileExists(path) { paths = append(paths, path) } } } if m.embeddedRoot != "" { path := filepath.Join(m.embeddedRoot, "lang", code+".json") if fileExists(path) { paths = append(paths, path) } } for _, path := range paths { b, err := os.ReadFile(path) if err != nil { return fmt.Errorf("assets.loadLangInto %s: %w", code, err) } var kv map[string]string if err := json.Unmarshal(b, &kv); err != nil { return fmt.Errorf("assets.loadLangInto %s: %w", code, err) } for k, v := range kv { dst[k] = v } } return nil }