summaryrefslogtreecommitdiff
path: root/lib/naming/lang.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-19 23:48:35 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-19 23:48:35 +0200
commitecadd969e1d96918820a1fdac0cf0d520d96ba06 (patch)
treea06ed1160bfd28ae4578d9445242e48ac9ac3ea4 /lib/naming/lang.ml
parent6762ce46af3cb12bc6ae37cda762c5d95add7903 (diff)
parent329d07b49e397cb65ab52e7ca019b47313027136 (diff)
downloadcolitur-ecadd969e1d96918820a1fdac0cf0d520d96ba06.tar.gz
colitur-ecadd969e1d96918820a1fdac0cf0d520d96ba06.zip
feat: naming, localisation and the rebuilt printed output
colitur computed the calendar correctly and could not say what it had computed. A printed ordo read ef-septuagesima-sunday-2 where a reader expects Dominica in Sexagesima, and the wall calendar showed slugs in every cell. lib/naming a language table and a config file, both pure and total, parsing the INI reader Overlay_ini already had lang/ la.ini and en.ini -- 725 names, every one transcribed from the 1962 Missal and citing the line it came from templates the ordo rebuilt as an A5 booklet: one week per page, a table of contents, framed days, a colour swatch; the wall calendar now fills its sheet instead of a quarter of it tools check_citations.py verifies all 400 citations resolve, with 33 self-tests of its own Names resolve through lang -> declared fallback -> the slug, so a partial translation is usable from its first line and the fully degraded case is the old output rather than a blank page. colitur day and colitur readings are BYTE-IDENTICAL to before, verified against main rather than asserted; --lang, --raw and the lang/config subcommands are still to come, and man/colitur-templates.5 still documents the pre-naming view, so writing a custom template needs the source until that lands.
Diffstat (limited to 'lib/naming/lang.ml')
-rw-r--r--lib/naming/lang.ml109
1 files changed, 109 insertions, 0 deletions
diff --git a/lib/naming/lang.ml b/lib/naming/lang.ml
new file mode 100644
index 0000000..147a49b
--- /dev/null
+++ b/lib/naming/lang.ml
@@ -0,0 +1,109 @@
+module OI = Colitur_kernel.Overlay_ini
+
+module SM = Map.Make (String)
+
+type table = string SM.t
+
+type t = {
+ code : string;
+ fallback_code : string option;
+ celebration : table;
+ weekday : table;
+ month : table;
+ season : table;
+ rank : table;
+ colour : table;
+ term : table;
+ chain : t option; (** consulted when this table misses *)
+}
+
+let empty_table = SM.empty
+
+let rec lookup t sel key =
+ match SM.find_opt key (sel t) with
+ | Some v -> Some v
+ | None -> ( match t.chain with Some b -> lookup b sel key | None -> None)
+
+(* A miss returns the KEY, never "". See lang.mli for why. *)
+let get t sel key = match lookup t sel key with Some v -> v | None -> key
+
+let celebration t k = get t (fun x -> x.celebration) k
+let season t k = get t (fun x -> x.season) k
+let rank t k = get t (fun x -> x.rank) k
+let colour t k = get t (fun x -> x.colour) k
+let term t k = get t (fun x -> x.term) k
+
+let weekday_key = [| "sunday"; "monday"; "tuesday"; "wednesday"; "thursday"; "friday"; "saturday" |]
+
+(* weekday/month deliberately do NOT go through [get]: their SM key is a
+ presentation detail (an English day-name word for weekday, a numeral for
+ month), not the value a miss should degrade to. [get]'s generic
+ echo-the-search-key fallback would leak that word ("sunday") out of an
+ empty/raw table instead of the documented numeral -- [month] never showed
+ the bug because its key already equals [string_of_int n], but [weekday]
+ did, and it broke [Lang.raw]'s own identity contract (0 = Sunday, not
+ "sunday"). Falling back to [string_of_int n] directly keeps [month]
+ byte-identical and fixes [weekday]. *)
+let weekday t n =
+ if n < 0 || n > 6 then string_of_int n
+ else match lookup t (fun x -> x.weekday) weekday_key.(n) with Some v -> v | None -> string_of_int n
+
+let month t n =
+ if n < 1 || n > 12 then string_of_int n
+ else match lookup t (fun x -> x.month) (string_of_int n) with Some v -> v | None -> string_of_int n
+
+let code t = t.code
+let fallback_code t = t.fallback_code
+
+let raw =
+ { code = "raw"; fallback_code = None; celebration = empty_table; weekday = empty_table;
+ month = empty_table; season = empty_table; rank = empty_table; colour = empty_table;
+ term = empty_table; chain = None }
+
+let with_fallback t base = { t with chain = Some base }
+
+let of_string text =
+ match OI.parse_sections text with
+ | Error e -> Error e
+ | Ok sections ->
+ (* Merge EVERY section sharing [name], not just the first: a hand-edited
+ 595-entry language file (Tasks 3/4's la.ini) WILL grow duplicate
+ [section] headers as contributors append entries over time -- a
+ second [celebration] block is the natural way to paste in a new
+ batch of names. Taking only the first match (the original
+ [List.find_opt] here) silently dropped every later block; the
+ failure then surfaces as a coverage report claiming those slugs have
+ "no Latin name", with nothing pointing back at the parser. Folding
+ over all matching sections, in file order, keeps this consistent
+ with the existing within-section behaviour below (last [SM.add]
+ wins): a key repeated across two blocks resolves to the later one,
+ exactly what a reader expects when appending to an INI file. *)
+ let find name =
+ List.fold_left
+ (fun m (s : OI.section) ->
+ if s.OI.name = name then List.fold_left (fun m (k, v) -> SM.add k v m) m s.OI.fields else m)
+ empty_table sections
+ in
+ let meta = find "meta" in
+ (match SM.find_opt "lang" meta with
+ | None -> Error "language file has no [meta] lang = <code>"
+ | Some code ->
+ Ok
+ { code;
+ fallback_code = SM.find_opt "fallback" meta;
+ celebration = find "celebration";
+ weekday = find "weekday";
+ month = find "month";
+ season = find "season";
+ rank = find "rank";
+ colour = find "colour";
+ term = find "term";
+ chain = None })
+
+let keys t =
+ let qualify prefix m = SM.bindings m |> List.map (fun (k, v) -> (prefix ^ "." ^ k, v)) in
+ List.concat
+ [ qualify "celebration" t.celebration; qualify "weekday" t.weekday;
+ qualify "month" t.month; qualify "season" t.season; qualify "rank" t.rank;
+ qualify "colour" t.colour; qualify "term" t.term ]
+ |> List.sort compare