diff options
Diffstat (limited to 'internal/web')
| -rw-r--r-- | internal/web/render.go | 14 | ||||
| -rw-r--r-- | internal/web/render_test.go | 20 | ||||
| -rw-r--r-- | internal/web/server.go | 52 | ||||
| -rw-r--r-- | internal/web/server_test.go | 171 | ||||
| -rw-r--r-- | internal/web/templates/index.html | 4 | ||||
| -rw-r--r-- | internal/web/templates/settings.html | 7 |
6 files changed, 97 insertions, 171 deletions
diff --git a/internal/web/render.go b/internal/web/render.go index 2cbf331..3ea52e3 100644 --- a/internal/web/render.go +++ b/internal/web/render.go @@ -191,12 +191,9 @@ func buildColumnViews(secs []liturgy.Section, versions []string, lectionary, lan return views } -// interlinearVersions maps versions through the same bt->wuj substitution -// render.OfflineVersions performs for offline mode: "bt" (niedziela.pl -// paragraph text) carries no verse numbers and cannot interleave, so it is -// dropped, substituting "wuj" (the Polish-language bible version) in its -// place unless "wuj" was already selected. Reuses render.OfflineVersions -// rather than duplicating its two-line transform. +// interlinearVersions maps versions through render.OfflineVersions, which +// substitutes a legacy "bt" with "wuj" (unless "wuj" is already selected). +// Every remaining version is a versified corpus that interleaves cleanly. func interlinearVersions(versions []string) []string { return render.OfflineVersions(versions) } @@ -288,9 +285,8 @@ func webVersionLabel(v, lang string) string { return v } -// readerCorpusVersions are the versions the /reader offers: the four with an -// embedded full-text corpus. "bt" (the niedziela.pl scrape) has no corpus and -// cannot be read chapter-by-chapter. +// readerCorpusVersions are the versions the /reader offers: the four embedded +// full-text corpora. var readerCorpusVersions = []string{"wuj", "vul", "grb", "drb"} // UnionChapters returns the sorted union of chapter numbers a book has across diff --git a/internal/web/render_test.go b/internal/web/render_test.go index 6002927..39bcc58 100644 --- a/internal/web/render_test.go +++ b/internal/web/render_test.go @@ -80,13 +80,19 @@ func TestThemeCSSGuardRejectsInvalidNames(t *testing.T) { } func TestRenderReadingsEscapesScriptText(t *testing.T) { + // An attacker-controlled version code (?v=... reaches RenderReadings + // unfiltered) flows to render.GatherVersion as both the column label and the + // "(not in %s)" note. RenderReadings must HTML-escape it: it renders through + // html/template, never wrapping untrusted text in template.HTML. + const evil = "<script>alert(1)</script>" secs := []liturgy.Section{{ - Heading: "Test", - PartID: "pierwsze_czytanie", - Paragraphs: [][]string{{"<script>alert(1)</script>"}}, + Heading: "Ewangelia", + Citation: "J 20, 1. 11-18", + Ref: "John 20:1,11-18", + PartID: "pierwsze_czytanie", }} - html := string(RenderReadings(secs, []string{"bt"}, "new", "horizontal", "pl", liturgy.DayInfo{})) - if strings.Contains(html, "<script>alert(1)</script>") { + html := string(RenderReadings(secs, []string{evil}, "new", "horizontal", "pl", liturgy.DayInfo{})) + if strings.Contains(html, evil) { t.Errorf("raw <script> leaked into rendered output: %q", html) } if !strings.Contains(html, "<script>alert(1)</script>") { @@ -109,7 +115,7 @@ func TestRenderReadingsVertical(t *testing.T) { } func TestRenderReadingsInterlinear(t *testing.T) { - secs := []liturgy.Section{{Heading: "Ewangelia (J 20, 1. 11-18)", PartID: "ewangelia"}} + secs := []liturgy.Section{{Heading: "Ewangelia", Citation: "J 20, 1. 11-18", Ref: "John 20:1,11-18", PartID: "ewangelia"}} html := string(RenderReadings(secs, []string{"wuj", "vul"}, "new", "interlinear", "pl", liturgy.DayInfo{})) if !strings.Contains(html, "ilverse") { t.Errorf("interlinear output missing ilverse: %q", html[:min(300, len(html))]) @@ -130,7 +136,7 @@ func TestRenderReadingsInterlinear(t *testing.T) { } func TestRenderReadingsInterlinearExcludesBT(t *testing.T) { - secs := []liturgy.Section{{Heading: "Ewangelia (J 20, 1. 11-18)", PartID: "ewangelia"}} + secs := []liturgy.Section{{Heading: "Ewangelia", Citation: "J 20, 1. 11-18", Ref: "John 20:1,11-18", PartID: "ewangelia"}} html := string(RenderReadings(secs, []string{"bt", "vul"}, "new", "interlinear", "pl", liturgy.DayInfo{})) if strings.Contains(html, "Biblia Tysiąclecia (niedziela.pl)") { t.Errorf("interlinear output should substitute wuj for bt, not carry bt's label: %q", html[:min(300, len(html))]) diff --git a/internal/web/server.go b/internal/web/server.go index fce0f22..6e76d3f 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -35,7 +35,7 @@ import ( // bibleVersions is the fixed, Themes-independent list of scripture versions // the web UI's checkboxes offer -- independent of any one cfg.Versions, so // every visitor sees the same five choices regardless of their config file. -var bibleVersions = []string{"bt", "wuj", "vul", "grb", "drb"} +var bibleVersions = []string{"wuj", "vul", "grb", "drb"} // server holds the live, mutable config + book table so /settings can apply // changes to the running process. All handlers read a snapshot via get()/table(). @@ -142,17 +142,6 @@ func requestVersions(cfg config.Config, r *http.Request) []string { return []string{cfg.DefaultVersion} } -// withoutVersion returns versions with every occurrence of drop removed. -func withoutVersion(versions []string, drop string) []string { - kept := make([]string, 0, len(versions)) - for _, v := range versions { - if v != drop { - kept = append(kept, v) - } - } - return kept -} - // queryBool reads a truthy/falsy query param ("1"/"true"/"on"/"yes" vs. // "0"/"false"/"off"/"no"), falling back to def when the param is absent or // unrecognized. @@ -202,35 +191,19 @@ func resolveQuery(cfg config.Config, r *http.Request) (date, lectionary string, lectionary = requestLectionary(cfg, r) all = queryBool(r, "all", cfg.All) versions = requestVersions(cfg, r) - // "bt" (the niedziela modern scrape) is invalid for the traditional - // lectionary and is hidden in the form -- but a box hidden by CSS stays - // checked, so switching modern->traditional carries a phantom v=bt that - // EffectiveVersions would substitute to wuj, defeating "no version selected - // -> nothing". On an explicit form submit (vset) drop that phantom bt; a - // fresh visit (no vset) keeps bt so its bt->wuj default still shows. - if lectionary == "traditional" && r.URL.Query().Has("vset") { - versions = withoutVersion(versions, "bt") - } display = requestDisplay(cfg, r) return date, lectionary, all, versions, display } -// loadSections runs the readings router for one request: date/lectionary -// override cfg, all controls part filtering, and versions is swapped via -// render.OfflineVersions -- when cfg.Offline (any lectionary needs the -// network-free set), or when lectionary is "traditional" (pl is the -// niedziela.pl modern scrape, meaningless for missalemeum) -- before being -// handed back to the caller for rendering, so the caller's column labels -// always match what was actually loadable. dayInfo is the day's celebration -// identity (see liturgy.DayInfo), zero when the source carried none. +// loadSections computes one request's readings offline: date/lectionary +// override cfg, all controls part filtering, and versions is passed through +// render.EffectiveVersions (which maps any legacy "bt" to "wuj") so the +// caller's column labels always match what was actually loadable. dayInfo is +// the day's celebration identity (see liturgy.DayInfo), zero when none. func loadSections(cfg config.Config, lectionary, date string, all bool, versions []string) (secs []liturgy.Section, dayInfo liturgy.DayInfo, effVersions []string, err error) { cfg.Lectionary = lectionary effVersions = render.EffectiveVersions(versions, lectionary, cfg.Offline) - secs, dayInfo, err = readings.Load(cfg, readings.Options{ - Date: date, - Offline: cfg.Offline, - All: all, - }) + secs, dayInfo, err = readings.Load(cfg, readings.Options{Date: date, All: all}) return secs, dayInfo, effVersions, err } @@ -406,7 +379,7 @@ func calendarHandler(cfg config.Config) http.HandlerFunc { var days []export.CalendarDay for d := first; int(d.Month()) == month; d = d.AddDate(0, 0, 1) { cd := export.CalendarDay{Day: d.Day()} - if secs, info, lerr := readings.Load(cfg, readings.Options{Date: d.Format("2006-01-02"), Offline: cfg.Offline, All: false}); lerr == nil { + if secs, info, lerr := readings.Load(cfg, readings.Options{Date: d.Format("2006-01-02"), All: false}); lerr == nil { cd.Name = info.Name cd.Colour = info.Colour cd.Citation = readings.GospelCitation(secs) @@ -799,7 +772,6 @@ func settingsPost(s *server) http.HandlerFunc { cfg := s.get() // start from live cfg so Parts/SchemaVersion are preserved cfg.Lectionary = normLect(r.PostForm.Get("lectionary"), cfg.Lectionary) - cfg.TraditionalLang = pickLang(r.PostForm.Get("traditional_lang"), cfg.TraditionalLang) cfg.UILanguage = config.NormalizeUILanguage(r.PostForm.Get("ui_language")) cfg.SiglaStyle = config.NormalizeSiglaStyle(r.PostForm.Get("sigla_style")) cfg.WebDisplay = config.NormalizeDisplay(r.PostForm.Get("web_display")) @@ -865,14 +837,6 @@ func normLect(v, def string) string { return def } -// pickLang returns v if it is "pl"/"en", else def. -func pickLang(v, def string) string { - if v == "pl" || v == "en" { - return v - } - return def -} - func atoiOr(s string, def int) int { if n, err := strconv.Atoi(strings.TrimSpace(s)); err == nil { return n diff --git a/internal/web/server_test.go b/internal/web/server_test.go index 12542e5..57286d5 100644 --- a/internal/web/server_test.go +++ b/internal/web/server_test.go @@ -15,22 +15,9 @@ import ( "github.com/lukaszkasprzak/lectio/internal/liturgy" ) -// TestServer exercises NewServer's handler tree end to end via httptest, -// against the same fixture HTML/hook internal/readings uses (see -// readings_test.go TestLoadModernRoutes): no real network, no real browser. +// TestServer exercises NewServer's handler tree end to end via httptest. Every +// reading is computed offline (no network, no cache, no fixture server). func TestServer(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") - cacheHome := t.TempDir() - t.Setenv("XDG_CACHE_HOME", cacheHome) - srv := NewServer(config.Default()) t.Run("index page", func(t *testing.T) { @@ -41,20 +28,24 @@ func TestServer(t *testing.T) { } body := rec.Body.String() // config.Default() -> UILanguage "en", so the gospel heading's part - // label is localised to "Gospel" (render.LocalizeHeading); the - // citation stays exactly as scraped. + // label is localised to "Gospel" (render.LocalizeHeading). if !strings.Contains(body, "Gospel") { t.Errorf("body missing reading heading: %q", body) } + // ?v=wuj -> the gospel is rendered from the Wujek corpus ("grobu" is + // distinctly Polish Wujek verse text, not Latin/English). + if !strings.Contains(body, "grobu") { + t.Errorf("body missing Wujek verse text: %q", body) + } if !strings.Contains(body, "htmx") { t.Errorf("body missing htmx reference") } if !strings.Contains(body, `id="theme"`) { t.Errorf("body missing theme <link>") } - // Name is source-language (Polish), never translated, even - // though the surrounding chrome is English -- see RenderReadings. - if !strings.Contains(body, `class="dayinfo"`) || !strings.Contains(body, "Święto św. Marii Magdaleny") { + // The offline engine localises the celebration name to the UI language + // (en): 2026-07-22 is Saint Mary Magdalene. + if !strings.Contains(body, `class="dayinfo"`) || !strings.Contains(body, "Saint Mary Magdalene") { t.Errorf("body missing day-info header: %q", body) } }) @@ -187,18 +178,7 @@ func TestServer(t *testing.T) { // Regression coverage for the ?date= path-traversal finding: resolveQuery // must reject anything that isn't YYYY-MM-DD and fall back to today(), // the same "normalize, don't trust" pattern requestDisplay already uses. - t.Run("date path traversal does not read a planted cache file", func(t *testing.T) { - // cacheDir() == filepath.Join(cacheHome, "lectio"), so - // filepath.Join(cacheDir(), "../evil"+".json") resolves to - // cacheHome/evil.json -- one level *above* the real cache dir, and - // only reachable via an unvalidated "../" date. If the marker below - // ever appears in a response, liturgy.Load read this planted file. - evilPath := filepath.Join(cacheHome, "evil.json") - evilJSON := `[{"Heading":"LEAKED-VIA-TRAVERSAL","PartID":"ewangelia","Paragraphs":[["s"]]}]` - if err := os.WriteFile(evilPath, []byte(evilJSON), 0o644); err != nil { - t.Fatal(err) - } - + t.Run("date path traversal falls back to today, same as omitting date", func(t *testing.T) { baseline := httptest.NewRecorder() srv.ServeHTTP(baseline, httptest.NewRequest("GET", "/readings?v=wuj", nil)) // no date -> today() if baseline.Code != http.StatusOK { @@ -210,12 +190,8 @@ func TestServer(t *testing.T) { if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } - body := rec.Body.String() - if strings.Contains(body, "LEAKED-VIA-TRAVERSAL") { - t.Fatalf("traversal date reached the planted cache file outside the cache dir: %q", body) - } - if body != baseline.Body.String() { - t.Errorf("traversal date did not fall back to today() identically to omitting date\n got: %q\nwant: %q", body, baseline.Body.String()) + if rec.Body.String() != baseline.Body.String() { + t.Errorf("traversal date did not fall back to today() identically to omitting date\n got: %q\nwant: %q", rec.Body.String(), baseline.Body.String()) } }) @@ -234,23 +210,21 @@ func TestServer(t *testing.T) { }) } -// TestBTHiddenForTraditional checks index.html's server-side initial-hidden -// state for the "bt" version checkbox: hidden (inline style) when the -// lectionary is traditional (bt is meaningless for missalemeum -- see -// render.EffectiveVersions), present and visible otherwise. This is -// presentation-only: it does not touch which versions actually load. -func TestBTHiddenForTraditional(t *testing.T) { +// TestBTCheckboxRemoved checks index.html no longer renders a "bt" version +// checkbox (the niedziela.pl corpus was retired; bibleVersions is now +// wuj,vul,grb,drb) for either lectionary, while the real version boxes remain. +func TestBTCheckboxRemoved(t *testing.T) { srv := NewServer(config.Default()) - trad := httptest.NewRecorder() - srv.ServeHTTP(trad, httptest.NewRequest("GET", "/?lectionary=traditional", nil)) - if !strings.Contains(trad.Body.String(), `id="ver-bt" style="display:none"`) { - t.Errorf("bt checkbox not hidden for traditional") - } - modern := httptest.NewRecorder() - srv.ServeHTTP(modern, httptest.NewRequest("GET", "/?lectionary=new", nil)) - b := modern.Body.String() - if !strings.Contains(b, `id="ver-bt">`) || strings.Contains(b, `id="ver-bt" style="display:none"`) { - t.Errorf("bt checkbox should be visible for modern") + for _, lect := range []string{"traditional", "new"} { + rec := httptest.NewRecorder() + srv.ServeHTTP(rec, httptest.NewRequest("GET", "/?lectionary="+lect, nil)) + b := rec.Body.String() + if strings.Contains(b, `id="ver-bt"`) { + t.Errorf("%s: bt checkbox should no longer be rendered", lect) + } + if !strings.Contains(b, `id="ver-wuj"`) { + t.Errorf("%s: wuj checkbox missing", lect) + } } } @@ -325,17 +299,6 @@ func TestRenderOrErrorNoSectionsLang(t *testing.T) { // 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() @@ -422,11 +385,11 @@ func TestVsetEmptyShowsNothing(t *testing.T) { t.Errorf("readings with vset and no v should be empty, got %q", b) } - // Fresh visit (no vset): config default (bt for modern) box is checked. + // Fresh visit (no vset): the config default (vul) box is checked. fresh := httptest.NewRecorder() srv.ServeHTTP(fresh, httptest.NewRequest("GET", "/", nil)) - if !strings.Contains(fresh.Body.String(), `value="bt" checked`) { - t.Errorf("fresh visit should check the config default (bt) version box") + if !strings.Contains(fresh.Body.String(), `value="vul" checked`) { + t.Errorf("fresh visit should check the config default (vul) version box") } // Full page with vset and no v: no VERSION box checked (mono may be). @@ -439,32 +402,31 @@ func TestVsetEmptyShowsNothing(t *testing.T) { } } -// TestTraditionalDropsPhantomBT guards the traditional case of "no version -> -// nothing": a phantom checked-but-hidden bt (carried over from modern) must not -// substitute to wuj on an explicit form submit, but a fresh visit keeps the -// bt->wuj default. -func TestTraditionalDropsPhantomBT(t *testing.T) { +// TestBTSubstitutesToWuj: the legacy "bt" version has no corpus, so an explicit +// ?v=bt now renders the Wujek column instead (render.EffectiveVersions maps +// bt->wuj unconditionally, offline) for both lectionaries -- there is no longer +// a traditional-only "drop bt" special case. A fresh visit checks the config +// default (vul), never bt. +func TestBTSubstitutesToWuj(t *testing.T) { srv := NewServer(config.Default()) - // Explicit submit, only the phantom bt "checked": empty pane. - phantom := httptest.NewRecorder() - srv.ServeHTTP(phantom, httptest.NewRequest("GET", "/readings?vset=1&lectionary=traditional&v=bt", nil)) - if b := strings.TrimSpace(phantom.Body.String()); b != "" { - t.Errorf("traditional vset+v=bt should be empty, got %d bytes", len(b)) - } - - // Explicit submit, bt phantom + a real corpus version: still renders it. - withWuj := httptest.NewRecorder() - srv.ServeHTTP(withWuj, httptest.NewRequest("GET", "/readings?vset=1&lectionary=traditional&v=bt&v=wuj", nil)) - if !strings.Contains(withWuj.Body.String(), "block") { - t.Errorf("traditional vset+v=bt+v=wuj should still render wuj") + for _, lect := range []string{"new", "traditional"} { + rec := httptest.NewRecorder() + srv.ServeHTTP(rec, httptest.NewRequest("GET", "/readings?vset=1&date=2026-07-22&lectionary="+lect+"&v=bt", nil)) + b := rec.Body.String() + if strings.TrimSpace(b) == "" { + t.Errorf("%s: vset+v=bt should substitute wuj and render, got empty", lect) + } + if !strings.Contains(b, "Wujek") { + t.Errorf("%s: vset+v=bt should render the Wujek column: %q", lect, b) + } } - // Fresh visit (no vset): the bt->wuj default is kept and wuj is checked. + // Fresh traditional visit (no vset): the config default (vul) is checked. fresh := httptest.NewRecorder() srv.ServeHTTP(fresh, httptest.NewRequest("GET", "/?lectionary=traditional", nil)) - if !strings.Contains(fresh.Body.String(), `value="wuj" checked`) { - t.Errorf("fresh traditional visit should default to wuj (checked)") + if !strings.Contains(fresh.Body.String(), `value="vul" checked`) { + t.Errorf("fresh traditional visit should default to vul (checked)") } } @@ -521,13 +483,12 @@ func TestSettingsPostAppliesLive(t *testing.T) { form := url.Values{} form.Set("lectionary", "new") - form.Set("traditional_lang", "pl") form.Set("ui_language", "pl") // change it form.Set("sigla_style", "auto") form.Set("web_display", "vertical") form.Set("web_theme", "transfiguration") - form.Set("default_version", "bt") - form["versions"] = []string{"bt", "wuj", "vul", "grb", "drb"} + form.Set("default_version", "vul") + form["versions"] = []string{"wuj", "vul", "grb", "drb"} form.Set("books", string(bibleDefaultBooks())) post := httptest.NewRequest("POST", "/settings", strings.NewReader(form.Encode())) @@ -554,8 +515,8 @@ func TestSettingsPostInvalidBooks(t *testing.T) { form.Set("ui_language", "en") form.Set("web_display", "vertical") form.Set("web_theme", "transfiguration") - form.Set("default_version", "bt") - form["versions"] = []string{"bt"} + form.Set("default_version", "vul") + form["versions"] = []string{"wuj"} form.Set("books", "this is not [valid toml") post := httptest.NewRequest("POST", "/settings", strings.NewReader(form.Encode())) post.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -584,8 +545,8 @@ func TestSettingsPostEmptyBooksPreservesFile(t *testing.T) { form.Set("ui_language", "en") form.Set("web_display", "vertical") form.Set("web_theme", "transfiguration") - form.Set("default_version", "bt") - form["versions"] = []string{"bt"} // no "books" field + form.Set("default_version", "vul") + form["versions"] = []string{"wuj"} // no "books" field post := httptest.NewRequest("POST", "/settings", strings.NewReader(form.Encode())) post.Header.Set("Content-Type", "application/x-www-form-urlencoded") rec := httptest.NewRecorder() @@ -634,15 +595,21 @@ func TestBookmarksFlow(t *testing.T) { } } -func TestExportNoReadings(t *testing.T) { - t.Setenv("XDG_CACHE_HOME", t.TempDir()) - cfg := config.Default() - cfg.Offline = true // no network; uncached date -> no readings - srv := NewServer(cfg) +// TestExportSucceeds: the offline engine computes readings for any valid date, +// so /export always has content to render (there is no "no readings" 404/500 +// path for a normal date anymore). Even a far-future date exports successfully. +func TestExportSucceeds(t *testing.T) { + srv := NewServer(config.Default()) rec := httptest.NewRecorder() srv.ServeHTTP(rec, httptest.NewRequest("GET", "/export?fmt=md&date=2099-01-01", nil)) - if rec.Code != http.StatusNotFound && rec.Code != http.StatusInternalServerError { - t.Errorf("export with no readings status=%d (want 404/500)", rec.Code) + if rec.Code != http.StatusOK { + t.Fatalf("export status=%d (want 200), body=%q", rec.Code, rec.Body.String()) + } + if ct := rec.Header().Get("Content-Type"); !strings.Contains(ct, "markdown") { + t.Errorf("Content-Type = %q, want markdown", ct) + } + if !strings.Contains(rec.Body.String(), "## Gospel") { + t.Errorf("export body missing a gospel section:\n%s", rec.Body.String()) } } diff --git a/internal/web/templates/index.html b/internal/web/templates/index.html index c8a48ed..762ed7b 100644 --- a/internal/web/templates/index.html +++ b/internal/web/templates/index.html @@ -36,14 +36,14 @@ </span> <label>{{.L.Lectionary}} - <select name="lectionary" onchange="var b=document.getElementById('ver-bt');if(b)b.style.display=this.value==='traditional'?'none':'';"> + <select name="lectionary"> <option value="new" {{if eq .Lectionary "new"}}selected{{end}}>{{.L.OptModern}}</option> <option value="traditional" {{if eq .Lectionary "traditional"}}selected{{end}}>{{.L.OptTraditional}}</option> </select> </label> {{range .VersionOpts}} - <label id="ver-{{.Code}}"{{if and (eq .Code "bt") (eq $.Lectionary "traditional")}} style="display:none"{{end}}><input type="checkbox" name="v" value="{{.Code}}" {{if .Checked}}checked{{end}}> {{.Code}}</label> + <label id="ver-{{.Code}}"><input type="checkbox" name="v" value="{{.Code}}" {{if .Checked}}checked{{end}}> {{.Code}}</label> {{end}} <label>{{.L.Parts}} diff --git a/internal/web/templates/settings.html b/internal/web/templates/settings.html index e2ee39e..f4fae47 100644 --- a/internal/web/templates/settings.html +++ b/internal/web/templates/settings.html @@ -30,13 +30,6 @@ </select> </label> - <label>{{.L.WebTradLang}} - <select name="traditional_lang"> - <option value="pl" {{if eq .Cfg.TraditionalLang "pl"}}selected{{end}}>pl</option> - <option value="en" {{if eq .Cfg.TraditionalLang "en"}}selected{{end}}>en</option> - </select> - </label> - <label>{{.L.WebUILang}} <select name="ui_language"> <option value="en" {{if eq .Cfg.UILanguage "en"}}selected{{end}}>en</option> |
