Reported from a photograph of a French screen: "Spot lifetime" and "Chase new grids" still in English in the DX Cluster settings. Diffing the two dictionaries turned up fifteen more — including the ENTIRE update panel, which is why an operator who reads French had nothing in French to react to when a new version appeared. All seventeen translated. Both dictionaries now hold 2685 keys with no difference either way. Every user-visible string ships in both languages: that was a rule, and until now only a rule. A test parses the two dictionary literals and compares their keys, so the next one is caught by `go test` instead of by an operator photographing their own screen. Verified it fails when a key is removed. A key missing from one side is not a blank — it falls back to the key itself, so the interface reads "clu.spotTtl" where a label belongs, or keeps the English text, which looks deliberate and is not.
60 lines
1.8 KiB
Go
60 lines
1.8 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Every user-visible string ships in BOTH languages. That is a project rule,
|
|
// and until now it was only a rule: seventeen keys had drifted into English
|
|
// only, among them the whole update panel and two DX-cluster settings, found by
|
|
// a French operator photographing his own screen.
|
|
//
|
|
// A key present in one dictionary and not the other falls back to the key
|
|
// itself, so the interface shows "clu.spotTtl" where a label belongs — or, as
|
|
// here, the English text, which reads as deliberate and is not.
|
|
func TestEveryStringIsInBothLanguages(t *testing.T) {
|
|
src, err := os.ReadFile("frontend/src/lib/i18n.tsx")
|
|
if err != nil {
|
|
t.Fatalf("read i18n.tsx: %v", err)
|
|
}
|
|
en := dictKeys(t, string(src), "const en: Dict = {")
|
|
fr := dictKeys(t, string(src), "const fr: Dict = {")
|
|
|
|
if len(en) == 0 || len(fr) == 0 {
|
|
t.Fatal("one of the dictionaries came back empty — this test has stopped checking anything")
|
|
}
|
|
for k := range en {
|
|
if !fr[k] {
|
|
t.Errorf("%q has no French — a French operator sees the English string, or the key itself", k)
|
|
}
|
|
}
|
|
for k := range fr {
|
|
if !en[k] {
|
|
t.Errorf("%q exists only in French — an English operator sees the key", k)
|
|
}
|
|
}
|
|
}
|
|
|
|
// dictKeys collects the keys of one dictionary literal: from its opening line
|
|
// to the closing brace in column 0.
|
|
func dictKeys(t *testing.T, src, opening string) map[string]bool {
|
|
t.Helper()
|
|
i := strings.Index(src, opening)
|
|
if i < 0 {
|
|
t.Fatalf("%q not found — the dictionaries have been renamed and this test needs updating", opening)
|
|
}
|
|
body := src[i+len(opening):]
|
|
if j := strings.Index(body, "\n};"); j >= 0 {
|
|
body = body[:j]
|
|
}
|
|
keyRe := regexp.MustCompile(`'([a-zA-Z0-9_.]+)'\s*:`)
|
|
out := map[string]bool{}
|
|
for _, m := range keyRe.FindAllStringSubmatch(body, -1) {
|
|
out[m[1]] = true
|
|
}
|
|
return out
|
|
}
|