Язык выбирается в шапке панели, а до первого выбора берётся из настроек браузера. Переведены интерфейс, подписи и описания всех параметров конфигов, ошибки API и служебные сообщения менеджера в консоли. Подписи параметров вынесены из app.js в hints-<язык>.json: их больше четырёхсот на язык, и грузить чужой язык незачем. Сообщения менеджера переводятся на выходе из API по словарю, где ключом служит русская фраза из кода, — вывод самого игрового сервера через него проходит без изменений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
3.6 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|