Files
pzmanager/internal/i18n/i18n_test.go
T
mrleo1nidandClaude Opus 5 9a224bc070
CI / test (push) Successful in 22s
Release / release (push) Successful in 29s
Русский и английский языки интерфейса
Язык выбирается в шапке панели, а до первого выбора берётся из настроек
браузера. Переведены интерфейс, подписи и описания всех параметров конфигов,
ошибки API и служебные сообщения менеджера в консоли.

Подписи параметров вынесены из app.js в hints-<язык>.json: их больше четырёхсот
на язык, и грузить чужой язык незачем. Сообщения менеджера переводятся на
выходе из API по словарю, где ключом служит русская фраза из кода, — вывод
самого игрового сервера через него проходит без изменений.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:14:21 +03:00

105 lines
3.6 KiB
Go

package i18n
import (
"net/http"
"strings"
"testing"
)
func TestTKnownMessage(t *testing.T) {
got := T(EN, "сервер не запущен")
if got != "the server is not running" {
t.Fatalf("T(EN, ...) = %q", got)
}
if got := T(RU, "сервер не запущен"); got != "сервер не запущен" {
t.Fatalf("русский язык не должен трогать сообщение: %q", got)
}
}
// Вывод игрового сервера проходит через перевод как есть: словарь его не знает.
func TestTKeepsUnknown(t *testing.T) {
line := "LOG : General f:1 Zombies spawned: 1000"
if got := T(EN, line); got != line {
t.Fatalf("незнакомая строка изменена: %q", got)
}
}
func TestTFormat(t *testing.T) {
got := T(EN, `профиль "modded" не найден`)
if got != `profile "modded" not found` {
t.Fatalf("T = %q", got)
}
}
// Вложенная ошибка переводится вместе с обёрткой, а более конкретный шаблон
// («прочитать конфиг») должен выиграть у общего («прочитать»).
func TestTNestedError(t *testing.T) {
msg := "конфиг сервера недоступен: прочитать конфиг /srv/servertest.ini: open: no such file"
want := "the server config is unavailable: read the config /srv/servertest.ini: open: no such file"
if got := T(EN, msg); got != want {
t.Fatalf("T = %q, ожидалось %q", got, want)
}
}
func TestTTaskName(t *testing.T) {
if got := T(EN, "Резервное копирование мира: modded"); got != "World backup: modded" {
t.Fatalf("T = %q", got)
}
}
// В переводе должно быть столько же подстановок, сколько в оригинале, иначе
// Sprintf выведет %!s(MISSING) прямо в панель.
func TestCatalogPlaceholders(t *testing.T) {
for _, f := range formats {
ru := strings.Count(f[0], "%s")
en := strings.Count(f[1], "%s")
if ru == 0 {
t.Errorf("шаблон без подстановки: %q", f[0])
}
if ru != en {
t.Errorf("%q: подстановок %d, в переводе %d", f[0], ru, en)
}
if strings.ContainsAny(strings.ReplaceAll(f[0], "%s", ""), "%") {
t.Errorf("в шаблоне остался другой глагол форматирования: %q", f[0])
}
}
for ru, en := range messages {
if strings.Contains(ru, "%") || strings.Contains(en, "%") {
t.Errorf("сообщение с подстановкой должно лежать в formats: %q", ru)
}
}
}
func TestFromRequest(t *testing.T) {
cases := []struct {
name string
cookie string
header string
want Lang
}{
{"по умолчанию", "", "", Default},
{"выбор человека", "en", "ru-RU,ru;q=0.9", EN},
{"мусор в куке", "klingon", "en-US,en;q=0.9", EN},
{"язык браузера", "", "en-GB,en;q=0.9", EN},
{"неизвестный язык браузера", "", "de-DE,de;q=0.9", Default},
{"первый понятный из списка", "", "de-DE;q=0.9,en;q=0.8", EN},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "/api/status", nil)
if err != nil {
t.Fatal(err)
}
if c.cookie != "" {
r.AddCookie(&http.Cookie{Name: CookieName, Value: c.cookie})
}
if c.header != "" {
r.Header.Set("Accept-Language", c.header)
}
if got := FromRequest(r); got != c.want {
t.Fatalf("FromRequest = %q, ожидалось %q", got, c.want)
}
})
}
}