aboutsummaryrefslogtreecommitdiff
path: root/internal/i18n
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-23 22:08:56 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-23 22:08:56 +0200
commit390f8cb146c69d8d3a0e6e0d76ea3546f30a7611 (patch)
tree99510b1fe2d80bfc1ab26473a53def3318bed256 /internal/i18n
parent4c776396115c8120c3e1cb8fda993449fcdcd326 (diff)
downloadlectio-390f8cb146c69d8d3a0e6e0d76ea3546f30a7611.tar.gz
lectio-390f8cb146c69d8d3a0e6e0d76ea3546f30a7611.zip
i18n: fix consistency-review gaps in ui_language (en/pl chrome)
Closes six findings from review of the ui_language feature so English mode carries no leftover Polish UI text: - Web: interlinear "no verses" note and the empty-day error fragment now route through i18n instead of being unconditionally Polish. - render.GatherVersion/GatherVerses/resolveRef's four lookup/citation- failure blocks are now lang-aware (resolveRef gained a lang param). - i18n.ErrorPrefix/NoReadingsFor lost a stray extra trailing space, restoring the pre-i18n single-space TUI concatenation. - render.LocalizeHeading now matches a heading's actual leading label text against all known modern pl labels, rather than trusting the label keyed by sec.PartID -- fixes split-feast days where PartID is "drugie_czytanie" but the heading still reads "1. czytanie ...". - cli bannerFor is lang-specific again (pl "na", en "for"), restoring pl's exact pre-i18n wording instead of "--" for both languages. - index.html's <html lang> now follows cfg.UILanguage instead of being hardcoded "pl". gofmt/vet clean, go test ./... green, all three binaries build, no new go.mod deps. Consistency grep across cli/tui/web/render's live source turns up only two non-displayed identifiers (a PartID slug list and a pre-existing incipit-stripping constant on the pl scripture text itself).
Diffstat (limited to 'internal/i18n')
-rw-r--r--internal/i18n/i18n.go117
-rw-r--r--internal/i18n/i18n_test.go72
2 files changed, 145 insertions, 44 deletions
diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go
index a5cb77b..4d034a5 100644
--- a/internal/i18n/i18n.go
+++ b/internal/i18n/i18n.go
@@ -19,17 +19,38 @@ type UI struct {
// keeping the citation untouched.
PartLabel map[string]string
- // TUI keybar and status messages.
+ // TUI keybar and status messages. NoReadingsFor and ErrorPrefix each
+ // carry their own single trailing space (the TUI concatenates a date or
+ // error string directly onto them, no separator added at the call site).
FooterKeys, Loading, NoReadingsFor, ErrorPrefix, ErrorHint string
- // CLI banner label words: the banner is "<word> — <date>".
- BannerGospel, BannerReadings string
+ // CLI banner label words and connective: the banner is
+ // "<word> <BannerConnective> <date>" (e.g. "Gospel for 2026-07-22" /
+ // "Ewangelia na 2026-07-22").
+ BannerGospel, BannerReadings, BannerConnective string
// Web control labels.
Lectionary, OptModern, OptTraditional string
Parts, OptGospel, OptAll string
Layout, OptHorizontal, OptColumns string
OptInterlinear, Theme, Mono string
+
+ // NoInterlinearVerses is the web interlinear pane's note when no
+ // requested version could be interleaved for a section.
+ NoInterlinearVerses string
+
+ // NoReadingsDay is the web error fragment shown when readings.Load
+ // returns no sections at all for the requested date.
+ NoReadingsDay string
+
+ // NoVersion/NoVersionPartial/NoReference/NoReferenceErr are
+ // render.GatherVersion/GatherVerses' lookup/citation-failure blocks,
+ // rendered verbatim by cli/tui/web. NoVersion takes the version code;
+ // NoVersionPartial takes the version code and the comma-joined missing
+ // references; NoReferenceErr takes the underlying error (pl keeps %w so
+ // it still wraps, en uses %v -- the verb only controls
+ // errors.Unwrap-ability, both format identically via Error()).
+ NoVersion, NoVersionPartial, NoReference, NoReferenceErr string
}
// Get returns lang's chrome string set, falling back to English for
@@ -56,25 +77,32 @@ var enUI = UI{
"aklamacja": "Acclamation",
"ewangelia": "Gospel",
},
- FooterKeys: "tab/⇧tab version ←/→ day j/k scroll space/b page g/G top/bottom r refresh q quit",
- Loading: "loading…",
- NoReadingsFor: "no readings for ",
- ErrorPrefix: "error: ",
- ErrorHint: "change date (←/→) or refresh (r)",
- BannerGospel: "Gospel",
- BannerReadings: "Readings",
- Lectionary: "lectionary",
- OptModern: "modern",
- OptTraditional: "traditional",
- Parts: "parts",
- OptGospel: "Gospel",
- OptAll: "all parts",
- Layout: "layout",
- OptHorizontal: "horizontal",
- OptColumns: "columns",
- OptInterlinear: "interlinear",
- Theme: "theme",
- Mono: "mono",
+ FooterKeys: "tab/⇧tab version ←/→ day j/k scroll space/b page g/G top/bottom r refresh q quit",
+ Loading: "loading…",
+ NoReadingsFor: "no readings for ",
+ ErrorPrefix: "error: ",
+ ErrorHint: "change date (←/→) or refresh (r)",
+ BannerGospel: "Gospel",
+ BannerReadings: "Readings",
+ BannerConnective: "for",
+ Lectionary: "lectionary",
+ OptModern: "modern",
+ OptTraditional: "traditional",
+ Parts: "parts",
+ OptGospel: "Gospel",
+ OptAll: "all parts",
+ Layout: "layout",
+ OptHorizontal: "horizontal",
+ OptColumns: "columns",
+ OptInterlinear: "interlinear",
+ Theme: "theme",
+ Mono: "mono",
+ NoInterlinearVerses: "(no verses to align)",
+ NoReadingsDay: "no readings for this day",
+ NoVersion: "(not in %s)",
+ NoVersionPartial: "(not in %s: %s)",
+ NoReference: "(no reference)",
+ NoReferenceErr: "(no reference: %v)",
}
var plUI = UI{
@@ -92,23 +120,30 @@ var plUI = UI{
"aklamacja": "Aklamacja",
"ewangelia": "Ewangelia",
},
- FooterKeys: "tab/⇧tab wersja ←/→ dzień j/k przewiń spacja/b strona g/G góra/dół r odśwież q wyjście",
- Loading: "ładowanie…",
- NoReadingsFor: "brak czytań na ",
- ErrorPrefix: "błąd: ",
- ErrorHint: "zmień datę (←/→) lub odśwież (r)",
- BannerGospel: "Ewangelia",
- BannerReadings: "Czytania",
- Lectionary: "lekcjonarz",
- OptModern: "nowy",
- OptTraditional: "tradycyjny",
- Parts: "zakres",
- OptGospel: "Ewangelia",
- OptAll: "wszystkie części",
- Layout: "układ",
- OptHorizontal: "poziomo",
- OptColumns: "kolumny",
- OptInterlinear: "interlinearnie",
- Theme: "motyw",
- Mono: "mono",
+ FooterKeys: "tab/⇧tab wersja ←/→ dzień j/k przewiń spacja/b strona g/G góra/dół r odśwież q wyjście",
+ Loading: "ładowanie…",
+ NoReadingsFor: "brak czytań na ",
+ ErrorPrefix: "błąd: ",
+ ErrorHint: "zmień datę (←/→) lub odśwież (r)",
+ BannerGospel: "Ewangelia",
+ BannerReadings: "Czytania",
+ BannerConnective: "na",
+ Lectionary: "lekcjonarz",
+ OptModern: "nowy",
+ OptTraditional: "tradycyjny",
+ Parts: "zakres",
+ OptGospel: "Ewangelia",
+ OptAll: "wszystkie części",
+ Layout: "układ",
+ OptHorizontal: "poziomo",
+ OptColumns: "kolumny",
+ OptInterlinear: "interlinearnie",
+ Theme: "motyw",
+ Mono: "mono",
+ NoInterlinearVerses: "(brak wersetów do zestawienia interlinearnego)",
+ NoReadingsDay: "brak czytań na ten dzień",
+ NoVersion: "(brak w „%s”)",
+ NoVersionPartial: "(brak w „%s”: %s)",
+ NoReference: "(brak odwołania)",
+ NoReferenceErr: "(brak odwołania: %w)",
}
diff --git a/internal/i18n/i18n_test.go b/internal/i18n/i18n_test.go
index 8c6f137..59771a8 100644
--- a/internal/i18n/i18n_test.go
+++ b/internal/i18n/i18n_test.go
@@ -1,6 +1,9 @@
package i18n
-import "testing"
+import (
+ "strings"
+ "testing"
+)
func TestGetTheme(t *testing.T) {
if got := Get("pl").Theme; got != "motyw" {
@@ -56,12 +59,21 @@ func TestTUIStrings(t *testing.T) {
if en.Loading != "loading…" || pl.Loading != "ładowanie…" {
t.Errorf("Loading en=%q pl=%q", en.Loading, pl.Loading)
}
- if en.NoReadingsFor != "no readings for " || pl.NoReadingsFor != "brak czytań na " {
+ if en.NoReadingsFor != "no readings for " || pl.NoReadingsFor != "brak czytań na " {
t.Errorf("NoReadingsFor en=%q pl=%q", en.NoReadingsFor, pl.NoReadingsFor)
}
- if en.ErrorPrefix != "error: " || pl.ErrorPrefix != "błąd: " {
+ if en.ErrorPrefix != "error: " || pl.ErrorPrefix != "błąd: " {
t.Errorf("ErrorPrefix en=%q pl=%q", en.ErrorPrefix, pl.ErrorPrefix)
}
+ // Both carry exactly one trailing space: the TUI concatenates a date or
+ // error string directly onto them with no separator of its own (see
+ // internal/tui bodyLines: ui.NoReadingsFor+m.date, ui.ErrorPrefix+err).
+ if strings.HasSuffix(en.NoReadingsFor, " ") || strings.HasSuffix(pl.NoReadingsFor, " ") {
+ t.Errorf("NoReadingsFor has 2+ trailing spaces: en=%q pl=%q", en.NoReadingsFor, pl.NoReadingsFor)
+ }
+ if strings.HasSuffix(en.ErrorPrefix, " ") || strings.HasSuffix(pl.ErrorPrefix, " ") {
+ t.Errorf("ErrorPrefix has 2+ trailing spaces: en=%q pl=%q", en.ErrorPrefix, pl.ErrorPrefix)
+ }
if en.ErrorHint != "change date (←/→) or refresh (r)" || pl.ErrorHint != "zmień datę (←/→) lub odśwież (r)" {
t.Errorf("ErrorHint en=%q pl=%q", en.ErrorHint, pl.ErrorHint)
}
@@ -76,6 +88,60 @@ func TestBannerWords(t *testing.T) {
if en.BannerReadings != "Readings" || pl.BannerReadings != "Czytania" {
t.Errorf("BannerReadings en=%q pl=%q", en.BannerReadings, pl.BannerReadings)
}
+ if en.BannerConnective != "for" || pl.BannerConnective != "na" {
+ t.Errorf("BannerConnective en=%q pl=%q", en.BannerConnective, pl.BannerConnective)
+ }
+}
+
+// TestWebFragmentMessages covers the two web-only messages (interlinear
+// "no verses" note, "no readings at all for this day" error fragment) that
+// were unconditionally Polish before this fix.
+func TestWebFragmentMessages(t *testing.T) {
+ en := Get("en")
+ pl := Get("pl")
+ if en.NoInterlinearVerses != "(no verses to align)" {
+ t.Errorf("en.NoInterlinearVerses = %q", en.NoInterlinearVerses)
+ }
+ if pl.NoInterlinearVerses != "(brak wersetów do zestawienia interlinearnego)" {
+ t.Errorf("pl.NoInterlinearVerses = %q", pl.NoInterlinearVerses)
+ }
+ if en.NoReadingsDay != "no readings for this day" {
+ t.Errorf("en.NoReadingsDay = %q", en.NoReadingsDay)
+ }
+ if pl.NoReadingsDay != "brak czytań na ten dzień" {
+ t.Errorf("pl.NoReadingsDay = %q", pl.NoReadingsDay)
+ }
+}
+
+// TestLookupFailureMessages covers render.GatherVersion/GatherVerses/
+// resolveRef's four lookup/citation-failure templates.
+func TestLookupFailureMessages(t *testing.T) {
+ en := Get("en")
+ pl := Get("pl")
+
+ cases := []struct {
+ name, en, pl string
+ }{
+ {"NoVersion", en.NoVersion, pl.NoVersion},
+ {"NoVersionPartial", en.NoVersionPartial, pl.NoVersionPartial},
+ {"NoReference", en.NoReference, pl.NoReference},
+ {"NoReferenceErr", en.NoReferenceErr, pl.NoReferenceErr},
+ }
+ want := map[string][2]string{
+ "NoVersion": {"(not in %s)", "(brak w „%s”)"},
+ "NoVersionPartial": {"(not in %s: %s)", "(brak w „%s”: %s)"},
+ "NoReference": {"(no reference)", "(brak odwołania)"},
+ "NoReferenceErr": {"(no reference: %v)", "(brak odwołania: %w)"},
+ }
+ for _, c := range cases {
+ w := want[c.name]
+ if c.en != w[0] {
+ t.Errorf("en.%s = %q, want %q", c.name, c.en, w[0])
+ }
+ if c.pl != w[1] {
+ t.Errorf("pl.%s = %q, want %q", c.name, c.pl, w[1])
+ }
+ }
}
func TestWebControlLabels(t *testing.T) {