// Package miot_test provides black-box tests for miot_i18n.go internationalization. package miot_test import ( "os" "path/filepath" "testing" miot "miot" ) // ============================================================================ // NewMIoTI18n // ============================================================================ func TestI18n_New(t *testing.T) { i18n := miot.NewMIoTI18n("zh-Hans") if i18n == nil { t.Fatal("NewMIoTI18n returned nil") } // Should not panic on any operation before Init s := i18n.Translate("any.key", nil) if s != "" { t.Errorf("expected empty string before Init, got %q", s) } } func TestI18n_NewEmptyLang(t *testing.T) { // Empty language should still create valid instance i18n := miot.NewMIoTI18n("") if i18n == nil { t.Fatal("NewMIoTI18n with empty lang returned nil") } } // ============================================================================ // Init / Deinit // ============================================================================ func TestI18n_InitWithValidJSON(t *testing.T) { // Create a temporary i18n JSON file i18nDir := filepath.Join(t.TempDir(), "i18n") os.MkdirAll(i18nDir, 0755) jsonContent := `{ "device": { "light": { "name": "Light", "description": "Smart Light" }, "sensor": { "temperature": "Temperature Sensor" } }, "common": { "on": "On", "off": "Off" } }` os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(jsonContent), 0644) // Override RootPath for the test origRoot := miot.RootPath miot.RootPath = filepath.Dir(i18nDir) defer func() { miot.RootPath = origRoot }() i18n := miot.NewMIoTI18n("en") i18n.Init() // Verify translation result := i18n.Translate("device.light.name", nil) if result != "Light" { t.Errorf("Translate(device.light.name) = %q, want %q", result, "Light") } result = i18n.Translate("device.sensor.temperature", nil) if result != "Temperature Sensor" { t.Errorf("Translate = %q, want %q", result, "Temperature Sensor") } } func TestI18n_InitTwice(t *testing.T) { // Should not reload if already initialized i18n := miot.NewMIoTI18n("en") i18n.Init() // first init i18n.Init() // second init should be no-op // Just verify no panic } // ============================================================================ // Deinit // ============================================================================ func TestI18n_Deinit(t *testing.T) { i18nDir := filepath.Join(t.TempDir(), "i18n") os.MkdirAll(i18nDir, 0755) os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(`{"greeting": "Hello"}`), 0644) origRoot := miot.RootPath miot.RootPath = filepath.Dir(i18nDir) defer func() { miot.RootPath = origRoot }() i18n := miot.NewMIoTI18n("en") i18n.Init() // Before Deinit result := i18n.Translate("greeting", nil) if result != "Hello" { t.Fatalf("Translate before Deinit should work, got %q", result) } // After Deinit i18n.Deinit() result = i18n.Translate("greeting", nil) if result != "" { t.Errorf("Translate after Deinit should return empty, got %q", result) } } // ============================================================================ // Init with missing file // ============================================================================ func TestI18n_InitMissingFile(t *testing.T) { origRoot := miot.RootPath miot.RootPath = t.TempDir() defer func() { miot.RootPath = origRoot }() i18n := miot.NewMIoTI18n("fantasy-lang") // Should not panic i18n.Init() // Translate should return empty result := i18n.Translate("any.key", nil) if result != "" { t.Errorf("translate with missing file should return empty, got %q", result) } } // ============================================================================ // Translate — nested keys // ============================================================================ func TestI18n_TranslateNestedKey(t *testing.T) { i18nDir := filepath.Join(t.TempDir(), "i18n") os.MkdirAll(i18nDir, 0755) jsonContent := `{ "a": { "b": { "c": "deep_value" } } }` os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(jsonContent), 0644) origRoot := miot.RootPath miot.RootPath = filepath.Dir(i18nDir) defer func() { miot.RootPath = origRoot }() i18n := miot.NewMIoTI18n("en") i18n.Init() result := i18n.Translate("a.b.c", nil) if result != "deep_value" { t.Errorf("nested translate = %q, want 'deep_value'", result) } } // ============================================================================ // Translate — missing key // ============================================================================ func TestI18n_TranslateMissingKey(t *testing.T) { i18nDir := filepath.Join(t.TempDir(), "i18n") os.MkdirAll(i18nDir, 0755) os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(`{"hello": "world"}`), 0644) origRoot := miot.RootPath miot.RootPath = filepath.Dir(i18nDir) defer func() { miot.RootPath = origRoot }() i18n := miot.NewMIoTI18n("en") i18n.Init() // Missing key result := i18n.Translate("missing.key", nil) if result != "" { t.Errorf("missing key translate = %q, want empty", result) } // Partial match result = i18n.Translate("hello.world", nil) if result != "" { t.Errorf("partial match translate = %q, want empty (hello is not a map)", result) } } // ============================================================================ // Translate — placeholders // ============================================================================ func TestI18n_TranslateWithReplace(t *testing.T) { i18nDir := filepath.Join(t.TempDir(), "i18n") os.MkdirAll(i18nDir, 0755) jsonContent := `{ "message": "Hello {name}, you have {count} messages" }` os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(jsonContent), 0644) origRoot := miot.RootPath miot.RootPath = filepath.Dir(i18nDir) defer func() { miot.RootPath = origRoot }() i18n := miot.NewMIoTI18n("en") i18n.Init() result := i18n.Translate("message", map[string]string{ "name": "Alice", "count": "5", }) want := "Hello Alice, you have 5 messages" if result != want { t.Errorf("Translate with replace = %q, want %q", result, want) } } func TestI18n_TranslateWithReplaceNil(t *testing.T) { i18nDir := filepath.Join(t.TempDir(), "i18n") os.MkdirAll(i18nDir, 0755) os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(`{"greet": "Hello {name}"}`), 0644) origRoot := miot.RootPath miot.RootPath = filepath.Dir(i18nDir) defer func() { miot.RootPath = origRoot }() i18n := miot.NewMIoTI18n("en") i18n.Init() // replace = nil → no substitution result := i18n.Translate("greet", nil) if result != "Hello {name}" { t.Errorf("Translate with nil replace = %q, want 'Hello {name}'", result) } } func TestI18n_TranslateWithPartialReplace(t *testing.T) { i18nDir := filepath.Join(t.TempDir(), "i18n") os.MkdirAll(i18nDir, 0755) os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(`{"msg": "Hello {name} from {city}"}`), 0644) origRoot := miot.RootPath miot.RootPath = filepath.Dir(i18nDir) defer func() { miot.RootPath = origRoot }() i18n := miot.NewMIoTI18n("en") i18n.Init() // Only replace "name", leave "{city}" as-is result := i18n.Translate("msg", map[string]string{"name": "Bob"}) if result != "Hello Bob from {city}" { t.Errorf("partial replace = %q, want 'Hello Bob from {city}'", result) } } // ============================================================================ // Translate — non-string value // ============================================================================ func TestI18n_TranslateNonStringValue(t *testing.T) { i18nDir := filepath.Join(t.TempDir(), "i18n") os.MkdirAll(i18nDir, 0755) // Value is a nested object (map), not a string jsonContent := `{ "config": { "settings": { "timeout": 30 } } }` os.WriteFile(filepath.Join(i18nDir, "en.json"), []byte(jsonContent), 0644) origRoot := miot.RootPath miot.RootPath = filepath.Dir(i18nDir) defer func() { miot.RootPath = origRoot }() i18n := miot.NewMIoTI18n("en") i18n.Init() // config.settings is an object, not string → return "" result := i18n.Translate("config.settings", nil) if result != "" { t.Errorf("non-string value should return empty, got %q", result) } } // ============================================================================ // Translate — empty key // ============================================================================ func TestI18n_TranslateEmptyKey(t *testing.T) { i18n := miot.NewMIoTI18n("en") result := i18n.Translate("", nil) if result != "" { t.Errorf("empty key should return empty, got %q", result) } result = i18n.Translate("", map[string]string{"x": "y"}) if result != "" { t.Errorf("empty key with replace should return empty, got %q", result) } } // ============================================================================ // Init to verify i18n JSON files exist on disk // ============================================================================ func TestI18n_InitFromProjectFiles(t *testing.T) { // This test verifies that actual i18n JSON files exist in the project // and can be loaded. Relies on RootPath being set to workspace. // Find the i18n directory relative to test file // The i18n directory is at go-miot/i18n/ i18nDir := filepath.Join(miot.RootPath, "i18n") entries, err := os.ReadDir(i18nDir) if err != nil { t.Skipf("i18n directory not found at %s, skipping: %v", i18nDir, err) } if len(entries) == 0 { t.Skip("i18n directory is empty, skipping") } // Pick first JSON file var firstLang string for _, e := range entries { if filepath.Ext(e.Name()) == ".json" { firstLang = e.Name()[:len(e.Name())-5] // strip .json break } } if firstLang == "" { t.Skip("no JSON files in i18n directory, skipping") } i18n := miot.NewMIoTI18n(firstLang) i18n.Init() // Should have data loaded // Test a few common translation keys commonKeys := []string{ "device.switch.on", "device.switch.off", } for _, key := range commonKeys { result := i18n.Translate(key, nil) if result == "" { t.Logf("note: key %q has no translation for %s", key, firstLang) } } }