Files
llmgw/internal/web/i18n_cluster_test.go
T
Ilker ManapandClaude Opus 5.5 11231d8c2b The portal in English and Turkish: a catalogue per language, a switcher at the foot of every page, landing.language, Turkish numbers and dates
Every template and handler message in internal/web goes through a JSON
catalogue under internal/web/locales/<code>/, with English as the fallback.
The language is the person's choice (a cookie set by /_lang/{code}), else
Accept-Language, else landing.language. Tests refuse a key some language
lacks, a translation with other arguments than English, and template text
left in English.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 00:12:53 +02:00

184 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package web
import (
"encoding/json"
"net/http"
"net/http/httptest"
"regexp"
"strings"
"testing"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/promq"
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
)
var clusterTR = map[string]string{"Accept-Language": "tr-TR,tr;q=0.9"}
func clusterTurkish(t *testing.T) *Lang {
t.Helper()
l := langByCode("tr")
if l == nil {
t.Fatal("the portal does not speak Turkish")
}
return l
}
func TestTheDashboardSpeaksTurkishWhenAsked(t *testing.T) {
now := time.Now().Truncate(time.Hour)
usage := fakeUsage{buckets: []store.Bucket{
{Start: now.Add(-time.Hour), Requests: 1500, Prompt: 4000, Completion: 900, LatencyMs: 120},
{Start: now, Requests: 95, Prompt: 9000, Completion: 2100, LatencyMs: 310},
}}
_, body := get(t, Dashboard(mapFleet{}, usage, "v1", 5*time.Second, true, NavExtras{}, nil),
"http://127.0.0.1:9090/", clusterTR)
for _, want := range []string{
`<html lang="tr">`, "<h1>Küme</h1>", "Kapasite", "<h2>Düğümler</h2>",
// A state is still its class, and a Turkish word.
`<td class="state state-ready">hazır</td>`,
// The GPU map: its modes, and what a card says under the pointer.
"<strong>meşguliyet</strong>", "gpu07 GPU 2 · code (code-1) · 93% meşgul · 39,1 / 80 GB",
// How long a replica has sat idle, in Turkish units.
"30dk0sn önce",
// The traffic chart's peak, with Turkish thousands.
"en yüksek 1,5 bin istek/sa",
"5 saniyede bir yenilenir",
} {
if !strings.Contains(body, want) {
t.Errorf("the Turkish dashboard lacks %q", want)
}
}
for _, english := range []string{"Capacity", "Holding hardware", "peak ", "requests/h", "No requests recorded"} {
if strings.Contains(body, english) {
t.Errorf("the Turkish dashboard still says %q", english)
}
}
}
func TestTheNodePageWritesTurkishNumbers(t *testing.T) {
q := func(expr string) string { return strings.Replace(expr, "%s", `"llmgw-node01"`, 1) }
h := &fakeHistory{byExpr: map[string][]promq.Series{
q(`llmgw_gpu_utilization_percent{node=%s} / 100`): {ts("0", 0.9, 0.97), ts("1", 0, 0)},
q(`llmgw_gpu_temperature_celsius{node=%s}`): {ts("0", 65, 71)},
}}
r := httptest.NewRequest(http.MethodGet, "http://x/admin/nodes/llmgw-node01", nil)
r.AddCookie(&http.Cookie{Name: langCookie, Value: "tr"})
w := httptest.NewRecorder()
nodeMux(gpuSource{}, h).ServeHTTP(w, r)
body := w.Body.String()
for _, want := range []string{
"64 çekirdek", "bellek 64%", "71,2 <span class=\"note\">/ 80 GB", "1.980 MHz",
"3 düzeltilemeyen ECC hatası", "<h2>GPU&#39;lar</h2>",
"Meşgul GPU, karta göre", "GPU sıcaklığı, °C", "Son bir saat",
// The chart's axis and peak in Turkish figures.
"en yüksek 100%",
} {
if !strings.Contains(body, want) {
t.Errorf("the Turkish node page lacks %q", want)
}
}
if strings.Contains(body, "uncorrected") || strings.Contains(body, "GPU busy, by card") {
t.Error("the Turkish node page still speaks English")
}
}
func TestChartTooltipsNameTheDayInTurkish(t *testing.T) {
tr := clusterTurkish(t)
// 5 October 2026 is a Monday.
at := time.Date(2026, 10, 5, 15, 4, 0, 0, time.Local)
if got := tr.hourLabel(at, 1500, tr.T("dash.unit.requests")); got != "Pzt 15:04 — 1,5 bin istek" {
t.Errorf("a Turkish tooltip reads %q", got)
}
if got := hourLabel(at, 1500, "requests"); got != "Mon 15:04 — 1.5k requests" {
t.Errorf("the English tooltip changed: %q", got)
}
bars := string(tr.BarChart(Series{Values: []float64{1, 2500}, Labels: []string{tr.hourLabel(at, 1, "istek")}}, "istek/sa"))
if !strings.Contains(bars, "<title>Pzt 15:04 — 1 istek</title>") || !strings.Contains(bars, "en yüksek 2,5 bin istek/sa") {
t.Errorf("the Turkish bar chart:\n%s", bars)
}
if got := string(tr.BarChart(Series{}, "istek")); !strings.Contains(got, "kayıt yok") {
t.Errorf("an empty Turkish chart: %s", got)
}
// The axis of a line chart, and its legend's fold.
c := tr.LineChart([]promq.Series{ts("a", 0.001, 0.004), ts("b", 0, 0.001), ts("c", 0, 0.001),
ts("d", 0, 0.001), ts("e", 0, 0.001)}, "%", 4)
if !strings.Contains(string(c.SVG), ">0,5%<") || c.Peak != "0,5%" {
t.Errorf("a Turkish percent axis: peak %q\n%s", c.Peak, c.SVG)
}
if last := c.Legend[len(c.Legend)-1]; last.Name != "diğer" {
t.Errorf("the folded line is called %q", last.Name)
}
// The coordinates are written the C way whatever the language.
if regexp.MustCompile(`points="[^"]*\d,\d+,`).MatchString(string(c.SVG)) {
t.Error("a Turkish decimal comma reached an SVG coordinate")
}
if got := tr.windowWord(6 * time.Hour); got != "6 saat" {
t.Errorf("six hours in Turkish: %q", got)
}
}
func TestTheFabricMapIsHandedItsWordsInThePagesLanguage(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "http://x/admin/fabrics", nil)
r.Header.Set("Accept-Language", "tr")
w := httptest.NewRecorder()
FabricsPage(topoSource{}, false, "v1")(w, r)
body := w.Body.String()
m := regexp.MustCompile(`(?s)<script type="application/json" id="fabric-i18n">(.*?)</script>`).FindStringSubmatch(body)
if m == nil {
t.Fatalf("the page hands the map no words:\n%s", body)
}
var words map[string]string
if err := json.Unmarshal([]byte(m[1]), &words); err != nil {
t.Fatalf("the words are not JSON: %v\n%s", err, m[1])
}
for k, want := range map[string]string{
"decimal": ",", "stat.switches": "anahtarlar", "ports_used": "{0}/{1} port",
"map_failed": "Harita çıkarılamadı: {0}",
} {
if words[k] != want {
t.Errorf("%s is %q, want %q", k, words[k], want)
}
}
for _, want := range []string{"<h1>Fabric&#39;ler</h1>", "Görünümü sıfırla", `placeholder="Düğüm bul"`} {
if !strings.Contains(body, want) {
t.Errorf("the Turkish fabrics page lacks %q", want)
}
}
// Every sentence the script falls back to has a Turkish text.
for _, s := range fabricJSStrings {
if words[s.key] == "" || strings.Contains(words[s.key], "%!") {
t.Errorf("the map's %q is %q", s.key, words[s.key])
}
}
}
func TestTheNVLinkAndLogPagesSpeakTurkish(t *testing.T) {
_, body := get(t, NVLinkPage(nvlSource{}, false, "v1"), "http://x/admin/nvlink", clusterTR)
for _, want := range []string{"<h1>NVLink alanları</h1>", "tepsi kullanılabilir", `class="tag bad">hata</span>`, "30 saniyede bir yenilenir"} {
if !strings.Contains(body, want) {
t.Errorf("the Turkish NVLink page lacks %q", want)
}
}
req := httptest.NewRequest("GET", "/admin/nodes/n6/logs?source=agent", nil)
req.SetPathValue("id", "n6")
req.Header.Set("Accept-Language", "tr")
rec := httptest.NewRecorder()
NodeLogsPage(logsFleet{}, false, "test")(rec, req)
for _, want := range []string{"ajanın günlüğü", "son 300 satır", "izle"} {
if !strings.Contains(rec.Body.String(), want) {
t.Errorf("the Turkish log page lacks %q", want)
}
}
}
func TestAChosenBuildIsAnnouncedInTheChoosersLanguage(t *testing.T) {
tr := clusterTurkish(t)
if got := buildNotice(tr, "gpu07", "vllm", "cu129"); !strings.Contains(got, "CUDA 12.9 için derlenmiş") {
t.Errorf("in Turkish: %q", got)
}
if got := buildNotice(english, "gpu07", "vllm", "none"); got != "gpu07 installs vllm no longer; the node is told now, and installs it in the background" {
t.Errorf("in English: %q", got)
}
}