Files
langx/loader_test.go
T

196 lines
6.2 KiB
Go
Raw Permalink Normal View History

2026-09-20 22:20:16 +08:00
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)
}
}