Files
2026-06-02 23:14:41 +08:00

170 lines
4.0 KiB
Go

package core
import (
"sync"
"testing"
)
func TestI18n_DefaultLanguage(t *testing.T) {
i := NewI18n(LangEnglish)
got := i.T(MsgStarting)
if got == "" {
t.Error("expected non-empty message")
}
}
func TestI18n_Chinese(t *testing.T) {
i := NewI18n(LangChinese)
got := i.T(MsgStarting)
if got == "" {
t.Error("expected non-empty message")
}
// Should contain Chinese characters, not English
if got == "⏳ Processing..." {
t.Error("expected Chinese translation, got English")
}
}
func TestI18n_FallbackToEnglish(t *testing.T) {
i := NewI18n(Language("nonexistent"))
got := i.T(MsgStarting)
if got == "" {
t.Error("should fallback to English")
}
}
func TestI18n_MissingKey(t *testing.T) {
i := NewI18n(LangEnglish)
got := i.T(MsgKey("totally_missing_key"))
if got != "[totally_missing_key]" && got != "" {
t.Logf("missing key returned %q (acceptable: placeholder or empty)", got)
}
}
func TestI18n_Tf(t *testing.T) {
i := NewI18n(LangEnglish)
got := i.Tf(MsgNameSet, "myname", "abc123")
if got == "" {
t.Error("Tf should return non-empty formatted message")
}
}
func TestI18n_AllKeysHaveEnglish(t *testing.T) {
for key, langs := range messages {
if _, ok := langs[LangEnglish]; !ok {
t.Errorf("message key %q missing English translation", key)
}
}
}
func TestDetectLanguage(t *testing.T) {
tests := []struct {
text string
wantLang Language
}{
// Japanese Hiragana
{"こんにちは", LangJapanese},
{"あいうえお", LangJapanese},
// Japanese Katakana
{"カタカナ", LangJapanese},
// Chinese
{"你好", LangChinese},
{"中文测试", LangChinese},
// Spanish
{"¿Cómo estás?", LangSpanish},
{"Niño español", LangSpanish},
{"¡Hola!", LangSpanish},
// English (default)
{"Hello world", LangEnglish},
{"Just normal text", LangEnglish},
{"", LangEnglish},
}
for _, tt := range tests {
t.Run(string(tt.wantLang), func(t *testing.T) {
got := DetectLanguage(tt.text)
if got != tt.wantLang {
t.Errorf("DetectLanguage(%q) = %v, want %v", tt.text, got, tt.wantLang)
}
})
}
}
func TestIsChinese(t *testing.T) {
// Chinese characters (CJK Unified Ideographs)
if !isChinese('中') {
t.Error("'中' should be detected as Chinese")
}
if !isChinese('文') {
t.Error("'文' should be detected as Chinese")
}
// Not Chinese
if isChinese('a') {
t.Error("'a' should not be Chinese")
}
if isChinese('ア') {
t.Error("Japanese katakana 'ア' should not be Chinese")
}
}
// TestI18n_ConcurrentAccess is a regression test for a real data race on
// I18n's lang/detected fields. cc-connect fans out platform message
// handlers concurrently; each one calls DetectAndSet (writes detected),
// while typing/reply paths call T / CurrentLang concurrently (read
// lang/detected). Without a mutex `go test -race` flagged real races on
// these fields. This test exercises the same access pattern under -race
// so a future edit that drops the mutex breaks loudly instead of
// silently re-introducing the race.
func TestI18n_ConcurrentAccess(t *testing.T) {
i := NewI18n(LangAuto)
i.SetSaveFunc(func(Language) error { return nil })
const goroutines = 16
const iterations = 200
texts := []string{"hello world", "你好世界", "こんにちは", "¡Hola amigo!"}
var wg sync.WaitGroup
for g := 0; g < goroutines; g++ {
wg.Add(1)
go func(g int) {
defer wg.Done()
for n := 0; n < iterations; n++ {
switch (g + n) % 4 {
case 0:
i.DetectAndSet(texts[n%len(texts)])
case 1:
_ = i.T(MsgStarting)
case 2:
_ = i.CurrentLang()
case 3:
_ = i.IsZhLike()
}
}
}(g)
}
wg.Wait()
}
func TestIsJapanese(t *testing.T) {
// Hiragana
if !isJapanese('あ') {
t.Error("Hiragana 'あ' should be Japanese")
}
// Katakana
if !isJapanese('ア') {
t.Error("Katakana 'ア' should be Japanese")
}
// Half-width Katakana
if !isJapanese('゚') {
t.Error("Half-width Katakana should be Japanese")
}
// Not Japanese
if isJapanese('中') {
t.Error("Chinese should not be Japanese")
}
if isJapanese('a') {
t.Error("ASCII 'a' should not be Japanese")
}
}