From 539a7c71b60115144959f712ef5966f48c63f9f0 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Tue, 28 Jul 2026 18:15:29 +0200 Subject: naming: localise liturgical day names in any language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add internal/naming, which renders temporal day names and celebration display names from the calendar engine's slugs in any language. English is the built-in baseline; a language is data -- an embedded lang/.ini (pl shipped) and/or a user file at /names/.ini that overrides it key by key. Names compose from a small vocabulary plus per-language format templates, so word order and grammatical case can differ (e.g. Polish genitive "3. Niedziela Okresu Zwykłego"); any string a language omits falls back to English. - Move day-name generation out of calendar (calendar.HumanizeSlug removed, calendar stays a pure engine) into naming.DayName; the English golden cases carry over verbatim. - naming.CelebrationName unifies the three duplicated resolvers (cli/readings/calfeed): name. -> English -> Latin -> humanized slug. Saint names stay in the calendar data (name.), overridable via calendar layers. - config: ui_language now accepts any code (lower-cased, not clamped to en/pl) so names/.ini applies; UI chrome still resolves en/pl and falls back to English. Add NamesDir(); wire naming.SetUserDir (and the previously unwired bible.SetUserCorporaDir) in the TUI and web mains too, so external corpora and name files work across all three binaries. --- internal/config/config.go | 36 +++++++++++++++++++++--------------- internal/config/config_test.go | 11 +++++++---- 2 files changed, 28 insertions(+), 19 deletions(-) (limited to 'internal/config') diff --git a/internal/config/config.go b/internal/config/config.go index 299c942..c9a00f2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -37,7 +37,7 @@ const configHeader = `# lectio configuration (INI). Full-line comments only (# o # width integer; 0 = detect terminal width # all true | false (all readings, or just the gospel) # offline true | false (never fetch; cache/sigla only) -# ui_language en | pl (interface chrome; readings stay source-language) +# ui_language any code (en, pl, ...) (chrome is en/pl, else English; day/saint names from names/.ini) # reading_lang language for offline reading text (e.g. en, pl); blank follows ui_language # reading_version force a specific bible corpus code (e.g. drb, wuj); blank = auto-resolve # sigla_style auto | polish | english | latin (citation dialect; auto follows ui_language) @@ -131,21 +131,14 @@ func NormalizeDisplay(display string) string { return d } -// validUILanguages are the two UI chrome languages lectio understands. -var validUILanguages = map[string]bool{ - "en": true, - "pl": true, -} - -// NormalizeUILanguage lower-cases lang and falls back to "en" when it is not -// one of validUILanguages -- the same lenient style as NormalizeDisplay (no -// error, just a safe default). +// NormalizeUILanguage lower-cases and trims a ui_language code. Any code is +// accepted: it selects day- and saint-name localisation (names/.ini, via +// internal/naming) and, for the UI chrome, resolves through i18n.Get, which +// falls back to English for anything other than the built-in "en"/"pl". So a +// user can set ui_language = fr, ship a names/fr.ini, and get French day names +// with English chrome -- no rebuild. func NormalizeUILanguage(lang string) string { - l := strings.ToLower(lang) - if !validUILanguages[l] { - return "en" - } - return l + return strings.ToLower(strings.TrimSpace(lang)) } // NormalizeSiglaStyle maps a sigla_style setting to one of "auto", "polish", @@ -354,6 +347,19 @@ func CorporaDir() (string, error) { return filepath.Join(filepath.Dir(p), "corpora"), nil } +// NamesDir is the user directory for drop-in day-name language files +// (.ini), alongside calendars/ and corpora/ under the lectio config dir. +// A file here overrides the built-in day names for its language key by key; +// combined with ui_language = , it localises the daily view to any +// language without a rebuild. Mirrors CalendarsDir/CorporaDir. +func NamesDir() (string, error) { + p, _, err := configPath() + if err != nil { + return "", err + } + return filepath.Join(filepath.Dir(p), "names"), nil +} + // BooksPath returns the path to the optional user books.ini (in the same // directory as the config file). There is no embedded seed on disk -- absence // means "use the built-in defaults". diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 1035426..dd75808 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -169,18 +169,21 @@ func TestUILanguageLoadsPL(t *testing.T) { } } -func TestUILanguageNormalizesUnknown(t *testing.T) { +func TestUILanguagePreservesAnyCode(t *testing.T) { + // Any ui_language code is kept (lower-cased): it drives day/saint-name + // localisation via names/.ini. The UI chrome, resolved through + // i18n.Get, still falls back to English for a code it has no table for. dir := t.TempDir() t.Setenv("XDG_CONFIG_HOME", dir) os.MkdirAll(filepath.Join(dir, "lectio"), 0o755) os.WriteFile(filepath.Join(dir, "lectio", "config.toml"), - []byte("ui_language = \"bogus\"\n"), 0o644) + []byte("ui_language = \"FR\"\n"), 0o644) cfg, err := Load() if err != nil { t.Fatal(err) } - if cfg.UILanguage != "en" { - t.Errorf("UILanguage = %q, want %q (normalized from bogus)", cfg.UILanguage, "en") + if cfg.UILanguage != "fr" { + t.Errorf("UILanguage = %q, want %q (lower-cased, not clamped)", cfg.UILanguage, "fr") } } -- cgit v1.3