diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-23 22:08:56 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-23 22:08:56 +0200 |
| commit | 390f8cb146c69d8d3a0e6e0d76ea3546f30a7611 (patch) | |
| tree | 99510b1fe2d80bfc1ab26473a53def3318bed256 /internal/web | |
| parent | 4c776396115c8120c3e1cb8fda993449fcdcd326 (diff) | |
| download | lectio-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/web')
| -rw-r--r-- | internal/web/render.go | 3 | ||||
| -rw-r--r-- | internal/web/render_test.go | 16 | ||||
| -rw-r--r-- | internal/web/server.go | 12 | ||||
| -rw-r--r-- | internal/web/server_test.go | 47 | ||||
| -rw-r--r-- | internal/web/templates/index.html | 2 |
5 files changed, 74 insertions, 6 deletions
diff --git a/internal/web/render.go b/internal/web/render.go index ca5bb89..208fc59 100644 --- a/internal/web/render.go +++ b/internal/web/render.go @@ -19,6 +19,7 @@ import ( "strings" "github.com/lukaszkasprzak/lectio/internal/bible" + "github.com/lukaszkasprzak/lectio/internal/i18n" "github.com/lukaszkasprzak/lectio/internal/liturgy" "github.com/lukaszkasprzak/lectio/internal/render" ) @@ -203,7 +204,7 @@ func buildInterlinearViews(secs []liturgy.Section, versions []string, lectionary } if len(sets) == 0 { - view.Note = "(brak wersetów do zestawienia interlinearnego)" + view.Note = i18n.Get(lang).NoInterlinearVerses views = append(views, view) continue } diff --git a/internal/web/render_test.go b/internal/web/render_test.go index ea44ed2..494cdcd 100644 --- a/internal/web/render_test.go +++ b/internal/web/render_test.go @@ -106,6 +106,22 @@ func TestRenderReadingsInterlinearNoVersifiedNote(t *testing.T) { if strings.Contains(html, "ilverse") { t.Errorf("expected no ilverse blocks when nothing resolves: %q", html) } + if !strings.Contains(html, "brak wersetów do zestawienia interlinearnego") { + t.Errorf("pl no-verses note missing/wrong: %q", html) + } +} + +// TestRenderReadingsInterlinearNoVersifiedNoteLang checks the interlinear +// "no verses to align" note (finding §1) follows lang, not always Polish. +func TestRenderReadingsInterlinearNoVersifiedNoteLang(t *testing.T) { + secs := []liturgy.Section{{Heading: "Bez odwołania", PartID: "ewangelia"}} + html := string(RenderReadings(secs, []string{"wuj"}, "new", "interlinear", "en")) + if !strings.Contains(html, "(no verses to align)") { + t.Errorf("en no-verses note missing/wrong: %q", html) + } + if strings.Contains(html, "brak wersetów") { + t.Errorf("en output should not carry the Polish no-verses note: %q", html) + } } // TestRenderReadingsLocalizesEN checks that lang="en" localises both the diff --git a/internal/web/server.go b/internal/web/server.go index 3b7102c..4c13576 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -188,6 +188,8 @@ type indexData struct { // set from i18n.Get(cfg.UILanguage) -- index.html references its // fields (e.g. {{.L.Lectionary}}) instead of hardcoded Polish text. L i18n.UI + // Lang is cfg.UILanguage, rendered into <html lang="...">. + Lang string } type versionOpt struct { @@ -245,6 +247,7 @@ func indexHandler(cfg config.Config) http.HandlerFunc { Mono: queryBool(r, "mono", cfg.WebMono), Reading: reading, L: i18n.Get(cfg.UILanguage), + Lang: cfg.UILanguage, } w.Header().Set("Content-Type", "text/html; charset=utf-8") @@ -269,15 +272,16 @@ func readingsHandler(cfg config.Config) http.HandlerFunc { } // renderOrError returns RenderReadings' fragment, or (on a readings.Load -// error) a small escaped error paragraph -- readings.Load errors are -// expected in normal operation (an unpublished date, no network while -// online, ...) so the pane should show them, not 500. +// error, or no sections at all for the date) a small escaped error +// paragraph, localised via lang -- readings.Load errors are expected in +// normal operation (an unpublished date, no network while online, ...) so +// the pane should show them, not 500. func renderOrError(secs []liturgy.Section, versions []string, lectionary, display, lang string, err error) template.HTML { if err != nil { return template.HTML(`<p class="error">` + template.HTMLEscapeString(err.Error()) + `</p>`) } if len(secs) == 0 { - return template.HTML(`<p class="error">brak czytań na ten dzień</p>`) + return template.HTML(`<p class="error">` + template.HTMLEscapeString(i18n.Get(lang).NoReadingsDay) + `</p>`) } return RenderReadings(secs, versions, lectionary, display, lang) } diff --git a/internal/web/server_test.go b/internal/web/server_test.go index 25d21f4..32ee412 100644 --- a/internal/web/server_test.go +++ b/internal/web/server_test.go @@ -227,6 +227,53 @@ func TestServer(t *testing.T) { }) } +// TestRenderOrErrorNoSectionsLang checks the "no readings at all for this +// day" fragment (finding §1) follows lang instead of always being Polish. +func TestRenderOrErrorNoSectionsLang(t *testing.T) { + html := string(renderOrError(nil, nil, "new", "horizontal", "en", nil)) + if !strings.Contains(html, "no readings for this day") { + t.Errorf("en renderOrError(no secs) = %q, want it to contain %q", html, "no readings for this day") + } + if strings.Contains(html, "czyta") { + t.Errorf("en renderOrError(no secs) should not carry Polish wording: %q", html) + } + + html = string(renderOrError(nil, nil, "new", "horizontal", "pl", nil)) + if !strings.Contains(html, "brak czytań na ten dzień") { + t.Errorf("pl renderOrError(no secs) = %q, want it to contain %q", html, "brak czytań na ten dzień") + } +} + +// TestIndexHTMLLangAttribute checks index.html's <html lang="..."> follows +// cfg.UILanguage (finding §6) instead of being hardcoded "pl". +func TestIndexHTMLLangAttribute(t *testing.T) { + html, err := os.ReadFile("../liturgy/testdata/2026-07-22.html") + if err != nil { + t.Fatal(err) + } + fixtureServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(html) + })) + defer fixtureServer.Close() + liturgy.SetBaseURL(fixtureServer.URL + "/liturgia/%s/Ewangelia") + t.Setenv("XDG_CACHE_HOME", t.TempDir()) + + cfg := config.Default() + cfg.UILanguage = "en" + rec := httptest.NewRecorder() + NewServer(cfg).ServeHTTP(rec, httptest.NewRequest("GET", "/?date=2026-07-22&v=wuj", nil)) + if !strings.Contains(rec.Body.String(), `<html lang="en">`) { + t.Errorf(`en index page missing <html lang="en">: %q`, rec.Body.String()[:min(400, rec.Body.Len())]) + } + + cfg.UILanguage = "pl" + rec = httptest.NewRecorder() + NewServer(cfg).ServeHTTP(rec, httptest.NewRequest("GET", "/?date=2026-07-22&v=wuj", nil)) + if !strings.Contains(rec.Body.String(), `<html lang="pl">`) { + t.Errorf(`pl index page missing <html lang="pl">: %q`, rec.Body.String()[:min(400, rec.Body.Len())]) + } +} + // TestChooseListener exercises chooseListener's port-selection logic // directly (no HTTP serving): port==0 prefers defaultWebPort (1099) and // falls back to a free OS port when 1099 is taken, and a non-zero port is diff --git a/internal/web/templates/index.html b/internal/web/templates/index.html index 8c9fd86..780224f 100644 --- a/internal/web/templates/index.html +++ b/internal/web/templates/index.html @@ -11,7 +11,7 @@ included too; they override "date" via hx-vals. The theme <select> has no server round trip: it swaps the #theme <link>'s href directly. */}} <!doctype html> -<html lang="pl"> +<html lang="{{.Lang}}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> |
