package langx_test import ( "embed" "os" "path/filepath" "testing" "testing/fstest" "github.com/yuninks/langx" ) //go:embed testdata/langs var embeddedLangs embed.FS func writeFile(t *testing.T, dir, rel, body string) { t.Helper() p := filepath.Join(dir, filepath.FromSlash(rel)) if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { t.Fatalf("创建目录失败: %v", err) } if err := os.WriteFile(p, []byte(body), 0o644); err != nil { t.Fatalf("写入文件失败: %v", err) } } func TestRegisterDir(t *testing.T) { reset(t) dir := t.TempDir() writeFile(t, dir, "code.json", "{\"login_success\":200,\"error\":400}") writeFile(t, dir, "zh.json", "{\"login_success\":\"成功\",\"welcome\":\"你好 #name#\"}") writeFile(t, dir, "en-US.json", "{\"login_success\":\"success\"}") writeFile(t, dir, "notes.txt", "这不是 json") writeFile(t, dir, "zh/extra.json", "{\"bye\":\"再见\"}") // 子目录并入 zh writeFile(t, dir, "en-US/more.json", "{\"bye\":\"bye\"}") // 子目录并入 en-US if err := langx.RegisterDir(dir); err != nil { t.Fatalf("RegisterDir: %v", err) } assertStrings(t, "Langs()", langx.Langs(), []string{"en-us", "zh"}) if got := langx.GetCode("login_success"); got != 200 { t.Fatalf("code.json 未生效: %d", got) } if got := langx.GetMsg("zh", "bye"); got != "再见" { t.Fatalf("子目录文件未并入 zh: %q", got) } if got := langx.GetMsg("en-US", "bye"); got != "bye" { t.Fatalf("子目录文件未并入 en-US: %q", got) } if got := langx.GetMsg("en-GB", "login_success"); got != "success" { t.Fatalf("区域回退失败: %q", got) } // 再次加载同一目录: 合并语义, 不报错也不丢数据 if err := langx.RegisterDir(dir); err != nil { t.Fatalf("重复加载: %v", err) } if got := langx.GetMsg("zh", "login_success"); got != "成功" { t.Fatalf("重复加载后数据异常: %q", got) } } // 旧实现在目录不存在时会 panic func TestRegisterDirMissing(t *testing.T) { reset(t) if err := langx.RegisterDir(filepath.Join(t.TempDir(), "not-exist")); err == nil { t.Fatal("目录不存在时应返回错误") } if err := langx.RegisterDir(" "); err == nil { t.Fatal("空目录名应返回错误") } if err := langx.RegisterDir(t.TempDir()); err != nil { t.Fatalf("空目录应加载成功(无数据): %v", err) } } // 解析失败必须整体失败, 不能留下半加载状态 func TestRegisterDirInvalidJSON(t *testing.T) { reset(t) dir := t.TempDir() writeFile(t, dir, "zh.json", "{\"k\":\"v\"}") writeFile(t, dir, "en.json", "{坏掉的 json") writeFile(t, dir, "code.json", "{\"k\":200}") if err := langx.RegisterDir(dir); err == nil { t.Fatal("非法 json 应返回错误") } if len(langx.Langs()) != 0 || langx.HasCode("k") { t.Fatalf("加载失败却写入了数据: langs=%v", langx.Langs()) } } // 结构不符合约定时也应报错 func TestRegisterDirWrongSchema(t *testing.T) { reset(t) dir := t.TempDir() writeFile(t, dir, "code.json", "{\"k\":\"not-int\"}") if err := langx.RegisterDir(dir); err == nil { t.Fatal("code.json 类型错误应返回错误") } dir2 := t.TempDir() writeFile(t, dir2, "zh.json", "[\"not\",\"object\"]") if err := langx.RegisterDir(dir2); err == nil { t.Fatal("语言文件类型错误应返回错误") } } func TestRegisterFSContainer(t *testing.T) { reset(t) fsys := fstest.MapFS{ "i18n/code.json": {Data: []byte("{\"ok\":200}")}, "i18n/zh.json": {Data: []byte("{\"hello\":\"你好\"}")}, "i18n/en.json": {Data: []byte("{\"hello\":\"Hello\"}")}, "i18n/README.md": {Data: []byte("忽略我")}, "i18n/zh/extra.json": {Data: []byte("{\"bye\":\"再见\"}")}, } if err := langx.RegisterFS(fsys); err != nil { t.Fatalf("RegisterFS: %v", err) } // 容器目录 i18n 被自动跳过, 非 json 文件被忽略 assertStrings(t, "Langs()", langx.Langs(), []string{"en", "zh"}) if got := langx.GetCode("ok"); got != 200 { t.Fatalf("容器内的 code.json 未生效: %d", got) } if got := langx.GetMsg("zh", "hello"); got != "你好" { t.Fatalf("GetMsg = %q", got) } if got := langx.GetMsg("zh", "bye"); got != "再见" { t.Fatalf("语言子目录未合并: %q", got) } } // 只有语言目录、根目录没有 json 的布局 func TestRegisterFSLanguageDirs(t *testing.T) { reset(t) fsys := fstest.MapFS{ "langs/zh/a.json": {Data: []byte("{\"a\":\"甲\"}")}, "langs/zh/b.json": {Data: []byte("{\"b\":\"乙\"}")}, "langs/en/a.json": {Data: []byte("{\"a\":\"A\"}")}, "langs/code.json": {Data: []byte("{\"ok\":200}")}, } if err := langx.RegisterFS(fsys); err != nil { t.Fatalf("RegisterFS: %v", err) } assertStrings(t, "Langs()", langx.Langs(), []string{"en", "zh"}) if got := langx.GetMsg("zh", "a"); got != "甲" { t.Fatalf("zh/a.json = %q", got) } if got := langx.GetMsg("zh", "b"); got != "乙" { t.Fatalf("zh/b.json 未合并 = %q", got) } if got := langx.GetMsg("en", "a"); got != "A" { t.Fatalf("en/a.json = %q", got) } if got := langx.GetCode("ok"); got != 200 { t.Fatalf("code.json 未生效: %d", got) } } func TestRegisterFSErrors(t *testing.T) { reset(t) if err := langx.RegisterFS(nil); err == nil { t.Fatal("nil fs.FS 应返回错误") } fsys := fstest.MapFS{"zh.json": {Data: []byte("{坏")}} if err := langx.RegisterFS(fsys); err == nil { t.Fatal("非法 json 应返回错误") } if len(langx.Langs()) != 0 { t.Fatalf("失败后不应有数据: %v", langx.Langs()) } // 没有任何 json 文件 empty := fstest.MapFS{"readme.md": {Data: []byte("x")}} if err := langx.RegisterFS(empty); err != nil { t.Fatalf("无 json 文件不应报错: %v", err) } } func TestRegisterEmbed(t *testing.T) { reset(t) if err := langx.RegisterEmbed(embeddedLangs); err != nil { t.Fatalf("RegisterEmbed: %v", err) } // testdata/langs 是容器目录, 会被自动跳过; notes.txt 会被忽略 assertStrings(t, "Langs()", langx.Langs(), []string{"en", "zh"}) if got := langx.GetCode("login_success"); got != 200 { t.Fatalf("响应码未加载: %d", got) } code, msg := langx.GetTransFormat("en", "welcome", map[string]string{"name": "Tom"}) if code != 200 || msg != "Hello Tom" { t.Fatalf("GetTransFormat = (%d, %q)", code, msg) } if got := langx.GetMsg("zh", "bye"); got != "再见" { t.Fatalf("testdata/langs/zh/extra.json 未合并: %q", got) } }