// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 package i18n import ( "os" "path/filepath" "regexp" "strings" "testing" ) func TestNormalize(t *testing.T) { cases := map[string]string{ "cs": "cs", "cs-CZ": "cs", "cs-CZ-x-praha": "cs", "EN": "en", "en-GB": "en", "de": "", "": "", } for in, want := range cases { if got := Normalize(in); got != want { t.Errorf("Normalize(%q) = %q, want %q", in, got, want) } } } func TestSimpleFallback(t *testing.T) { // English is the key: it always renders as itself. if got := Admin.T("en", "New post"); got != "New post" { t.Errorf("en simple = %q", got) } // A key the Czech table misses falls back to English, not to noise. if got := Admin.T("cs", "No such string in the catalogue"); got != "No such string in the catalogue" { t.Errorf("cs fallback = %q", got) } if got := Admin.T("cs", "New post"); got != "Nová publikace" { t.Errorf("cs New post = %q", got) } } func TestPlural(t *testing.T) { csCases := []struct { n int want string }{ {1, "Celkem 1 publikace"}, {2, "Celkem 2 publikace"}, {4, "Celkem 4 publikace"}, {5, "Celkem 5 publikací"}, {11, "Celkem 11 publikací"}, {12, "Celkem 12 publikací"}, {14, "Celkem 14 publikací"}, {22, "Celkem 22 publikace"}, {24, "Celkem 24 publikace"}, {25, "Celkem 25 publikací"}, {111, "Celkem 111 publikací"}, {124, "Celkem 124 publikace"}, } for _, c := range csCases { if got := Admin.N("cs", "posts.total", c.n); got != c.want { t.Errorf("cs posts.total(%d) = %q, want %q", c.n, got, c.want) } } if got := Admin.N("en", "posts.total", 1); got != "1 post in total" { t.Errorf("en posts.total(1) = %q", got) } if got := Admin.N("en", "posts.total", 3); got != "3 posts in total" { t.Errorf("en posts.total(3) = %q", got) } if got := Admin.N("cs", "no.such.id", 2); got != "no.such.id" { t.Errorf("missing plural id = %q", got) } } func TestFormat(t *testing.T) { if got := Admin.Tf("cs", "Scheduled for %s", "2026-09-19"); got != "Naplánováno na 2026-09-19" { t.Errorf("cs Tf = %q", got) } if got := Admin.Tf("en", "No such string %s", "x"); got != "No such string x" { t.Errorf("en Tf fallback = %q", got) } if got := Admin.Tf2("cs", "Upgrade to %s failed: %s", "v1.1", "boom"); got != "Aktualizace na v1.1 selhala: boom" { t.Errorf("cs Tf2 = %q", got) } } func TestHTML(t *testing.T) { en := Admin.TH("en", "Download a .tar.gz archive of your entire site.") if en != "Download a .tar.gz archive of your entire site." { t.Errorf("en TH = %q", en) } csOut := Admin.TH("cs", "Download a .tar.gz archive of your entire site.") if !strings.Contains(csOut, ".tar.gz") { t.Errorf("cs TH lost the markup: %q", csOut) } } // TestTemplatesAreTranslated walks the admin templates and asserts every // tr, trh and trf key exists in the Czech catalogue, and every trn id is a // known plural message. A string edited in a template without its Czech // counterpart fails here rather than silently rendering English. func TestTemplatesAreTranslated(t *testing.T) { root := filepath.Join("..", "..", "internal", "web", "templates") entries, err := os.ReadDir(root) if err != nil { t.Fatalf("read templates: %v", err) } callRe := regexp.MustCompile(`\b(tr|trh|trf)\s+"((?:[^"\\]|\\.)*)"`) // trn takes the count first: "trn .Count \"id\"" or // "trn (len .Posts) \"id\"", so the id is the first quoted string // after a run of non-quote characters on the same line. idRe := regexp.MustCompile(`\btrn\s+[^\n"]*"((?:[^"\\]|\\.)*)"`) for _, entry := range entries { if !strings.HasSuffix(entry.Name(), ".html") { continue } src, err := os.ReadFile(filepath.Join(root, entry.Name())) if err != nil { t.Fatalf("read %s: %v", entry.Name(), err) } for _, m := range callRe.FindAllStringSubmatch(string(src), -1) { fn, key := m[1], m[2] switch fn { case "tr", "trf": if _, ok := cs[key]; !ok { t.Errorf("%s: tr key %q has no Czech entry", entry.Name(), key) } case "trh": if _, ok := csHTML[key]; !ok { t.Errorf("%s: trh key %q has no Czech entry", entry.Name(), key) } } } for _, m := range idRe.FindAllStringSubmatch(string(src), -1) { if _, ok := pluralForms[m[1]]; !ok { t.Errorf("%s: trn id %q is not a plural message", entry.Name(), m[1]) } } } }