From cb3379f41c06a94085c77fe359709b46712850dc Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Mon, 27 Jul 2026 14:59:14 +0200 Subject: feat: books.toml -> books.ini, multi-language (add Latin dialect), config-scoped citation display books.ini with [en]/[pl]/[la]; parseBooks reads INI (bible drops go-toml). Latin dialect resolves EF citations (Eccli->Sirach, Apoc->Revelation, Luc...); sigla_style gains 'latin'. Reading citations now DISPLAY in the configured sigla (FormatRef(SiglaLang)), not raw English. One-shot books.toml->books.ini user migration (go-toml now config-only). Web settings editor -> books.ini. --- internal/config/config.go | 74 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 9 deletions(-) (limited to 'internal/config') diff --git a/internal/config/config.go b/internal/config/config.go index 54185d1..8d2ddca 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -38,7 +38,7 @@ const configHeader = `# lectio configuration (INI). Full-line comments only (# o # 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) -# sigla_style auto | polish | english (citation dialect; auto follows ui_language) +# sigla_style auto | polish | english | latin (citation dialect; auto follows ui_language) # web_theme a theme name (see docs/THEMES.md) # web_port integer; 0 = try 1099, then any free port # web_display horizontal | vertical | interlinear @@ -135,6 +135,8 @@ func NormalizeSiglaStyle(s string) string { switch strings.ToLower(strings.TrimSpace(s)) { case "polish", "pl": return "polish" + case "latin", "la": + return "latin" case "english", "en": return "english" default: @@ -190,6 +192,8 @@ func (c Config) SiglaLang() string { switch c.SiglaStyle { case "polish": return "pl" + case "latin": + return "la" case "english": return "en" default: @@ -270,7 +274,7 @@ func CalendarsDir() (string, error) { return filepath.Join(filepath.Dir(p), "calendars"), nil } -// BooksPath returns the path to the optional user books.toml (in the same +// 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". func BooksPath() (string, error) { @@ -278,22 +282,74 @@ func BooksPath() (string, error) { if err != nil { return "", err } - return filepath.Join(filepath.Dir(p), "books.toml"), nil + return filepath.Join(filepath.Dir(p), "books.ini"), nil } -// UserBooksTOML returns the bytes of the optional user books.toml (see -// BooksPath), or nil when it is absent or unreadable -- callers then fall back -// to bible's embedded default table. -func UserBooksTOML() []byte { +// UserBooksINI returns the bytes of the optional user books.ini (see BooksPath), +// or nil when absent/unreadable -- callers then fall back to bible's embedded +// default table. A former books.toml at the same location is converted to +// books.ini once, on first read (the old file is kept). +func UserBooksINI() []byte { p, err := BooksPath() if err != nil { return nil } - b, err := os.ReadFile(p) + if b, err := os.ReadFile(p); err == nil { + return b + } + tomlPath := filepath.Join(filepath.Dir(p), "books.toml") + data, err := os.ReadFile(tomlPath) if err != nil { return nil } - return b + out := booksTOMLtoINI(data) + if out == nil { + return nil + } + _ = os.WriteFile(p, out, 0o644) + fmt.Fprintf(os.Stderr, "lectio: migrated %s -> books.ini (old file kept)\n", tomlPath) + return out +} + +// booksTOMLtoINI converts a former books.toml (dialect -> book -> [forms]) into +// books.ini. Returns nil if the TOML is unparseable. +func booksTOMLtoINI(data []byte) []byte { + var raw map[string]map[string][]string + if err := toml.Unmarshal(data, &raw); err != nil { + return nil + } + var b strings.Builder + b.WriteString("# lectio book names & abbreviations, per dialect (migrated from books.toml).\n") + emit := func(d string) { + books := raw[d] + if books == nil { + return + } + fmt.Fprintf(&b, "\n[%s]\n", d) + keys := make([]string, 0, len(books)) + for k := range books { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fmt.Fprintf(&b, "%s = %s\n", k, strings.Join(books[k], ", ")) + } + } + seen := map[string]bool{"en": true, "pl": true, "la": true} + emit("en") + emit("pl") + emit("la") + others := make([]string, 0) + for d := range raw { + if !seen[d] { + others = append(others, d) + } + } + sort.Strings(others) + for _, d := range others { + emit(d) + } + return []byte(b.String()) } // seedIfMissing writes the embedded default config to path if nothing is -- cgit v1.3