package lang import ( "os" "path/filepath" "strings" "testing" "unicode" ) // reader_test.go: the reader words are DATA of the target language, read outside the pack loader and // outside Pack.Version() — a wording edit must never re-bill a book. const sampleReader = `# ru hole.pending ⚠ Не переведено ({chapter}.{unit}). hole.withheld ⚠ Не выпущено. hole.incomplete ⚠ Недостаёт {dropped}. hole.stale ⚠ Устарело ({chapter}.{unit}). hole.ghost ⚠ Глава {chapter}: вне нарезки {ghost}. notice.holes ⚠ Пропусков {holes} из {total}. notice.ghost ⚠ Вне нарезки {ghost}. ` func writeReader(t *testing.T, root, tgt, body string) { t.Helper() if err := os.MkdirAll(filepath.Join(root, tgt), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, tgt, readerFile), []byte(body), 0o644); err != nil { t.Fatal(err) } } func TestReaderWordsLoadAndFill(t *testing.T) { root := t.TempDir() writeReader(t, root, "ru", sampleReader) w, present, err := LoadReaderWords(root, "ru") if err != nil || !present { t.Fatalf("present=%v err=%v", present, err) } if got := FillReaderTemplate(w.HolePending, "chapter", "3", "unit", "0"); got != "⚠ Не переведено (3.0)." { t.Errorf("filled = %q", got) } if got := FillReaderTemplate(w.NoticeHoles, "holes", "2", "total", "37"); got != "⚠ Пропусков 2 из 37." { t.Errorf("filled = %q", got) } // A placeholder the caller did not pass stays visible rather than vanishing. if got := FillReaderTemplate("{x} {y}", "x", "1"); got != "1 {y}" { t.Errorf("unfilled placeholder must stay: %q", got) } } func TestReaderWordsAbsentIsTheNonVerbalForm(t *testing.T) { for _, root := range []string{"", t.TempDir()} { w, present, err := LoadReaderWords(root, "ru") if err != nil || present { t.Fatalf("root %q: present=%v err=%v", root, present, err) } if w != DefaultReaderWords() { t.Fatalf("root %q: absent file must yield the defaults", root) } } // The non-verbal form contains no letter of any language: a symbol and the numbers only. d := DefaultReaderWords() for name, tmpl := range map[string]string{"pending": d.HolePending, "withheld": d.HoleWithheld, "incomplete": d.HoleIncomplete, "stale": d.HoleStale, "ghost": d.HoleGhost, "notice": d.NoticeHoles, "notice-ghost": d.NoticeGhost} { bare := tmpl for _, ph := range []string{"{chapter}", "{unit}", "{dropped}", "{ghost}", "{holes}", "{total}"} { bare = strings.ReplaceAll(bare, ph, "") } for _, r := range bare { if unicode.IsLetter(r) { t.Errorf("default %s template %q carries a letter — that is a word the engine decided", name, tmpl) break } } } } func TestReaderWordsFailLoud(t *testing.T) { cases := map[string]string{ "unknown key": sampleReader + "hole.other\tx\n", "missing key": strings.Replace(sampleReader, "notice.ghost\t⚠ Вне нарезки {ghost}.\n", "", 1), "duplicate key": sampleReader + "hole.pending\tагаин\n", "empty value": strings.Replace(sampleReader, "⚠ Не выпущено.", "", 1), "no tab": strings.Replace(sampleReader, "hole.withheld\t", "hole.withheld ", 1), } for name, body := range cases { root := t.TempDir() writeReader(t, root, "ru", body) if _, _, err := LoadReaderWords(root, "ru"); err == nil { t.Errorf("%s: must fail loud", name) } } } // TestReaderWordsDoNotMoveThePackVersion is the money pin: reader.txt beside the pack changes NOTHING // the snapshot folds. (The stand proof is `current_snapshot` of the minirun export before and after.) func TestReaderWordsDoNotMoveThePackVersion(t *testing.T) { root := t.TempDir() writeSyntheticPack(t, root, "zh", "ru") before, err := Load(root, "zh", "ru") if err != nil { t.Fatal(err) } writeReader(t, root, "ru", sampleReader) after, err := Load(root, "zh", "ru") if err != nil { t.Fatal(err) } if before.Version() != after.Version() { t.Fatalf("adding /ru/reader.txt moved Pack.Version() %s → %s — a wording edit would re-bill every book of the pair", before.Version(), after.Version()) } // And the real shipped file loads. w, present, err := LoadReaderWords(filepath.Join("..", "..", "configs", "langpacks"), "ru") if err != nil || !present { t.Fatalf("configs/langpacks/ru/reader.txt: present=%v err=%v", present, err) } if !strings.Contains(w.HoleIncomplete, "{dropped}") || !strings.Contains(w.NoticeHoles, "{total}") { t.Errorf("shipped ru words lost a placeholder: %+v", w) } }