summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--README.md26
-rw-r--r--man/colitur-config.5308
-rw-r--r--man/colitur-templates.5562
4 files changed, 698 insertions, 203 deletions
diff --git a/Makefile b/Makefile
index c96113f..6ba81ba 100644
--- a/Makefile
+++ b/Makefile
@@ -120,13 +120,14 @@ install: build ## install binary, calendar data, language tables, templates, sch
@mkdir -p $(MAN5DIR)
install -m 644 man/colitur-overlay.5 $(MAN5DIR)/colitur-overlay.5
install -m 644 man/colitur-templates.5 $(MAN5DIR)/colitur-templates.5
+ install -m 644 man/colitur-config.5 $(MAN5DIR)/colitur-config.5
@echo "installed $(BINDIR)/$(COLITUR), data in $(PREFIX)/share/colitur/{ef,lang,templates,schema}, man pages in $(MANDIR) and $(MAN5DIR)"
@command -v $(COLITUR) >/dev/null 2>&1 || \
echo "note: $(BINDIR) is not on PATH -- add it, or run $(BINDIR)/$(COLITUR) directly"
uninstall: ## remove everything install put into PREFIX
-$(DUNE) dune uninstall --prefix $(PREFIX)
- rm -f $(MANDIR)/colitur.1 $(MAN5DIR)/colitur-overlay.5 $(MAN5DIR)/colitur-templates.5
+ rm -f $(MANDIR)/colitur.1 $(MAN5DIR)/colitur-overlay.5 $(MAN5DIR)/colitur-templates.5 $(MAN5DIR)/colitur-config.5
@echo "removed $(COLITUR) from $(PREFIX)"
reinstall: uninstall install ## uninstall then install (the installed copy is a snapshot, not a link)
@@ -135,11 +136,13 @@ man: ## preview the man pages
man -l man/colitur.1
man -l man/colitur-overlay.5
man -l man/colitur-templates.5
+ man -l man/colitur-config.5
doc: ## lint the man pages (groff warnings; silence means clean)
groff -man -Tutf8 -ww -z man/colitur.1
groff -man -Tutf8 -ww -z man/colitur-overlay.5
groff -man -Tutf8 -ww -z man/colitur-templates.5
+ groff -man -Tutf8 -ww -z man/colitur-config.5
fmt: ## format the OCaml sources
$(DUNE) dune build @fmt --auto-promote
diff --git a/README.md b/README.md
index 5914734..affb91f 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,32 @@ See `colitur-templates(5)` for the template format (syntax, escaping, the
full field reference) and `colitur(1)` for `emit`, `table`/`render` and
`publish` in full.
+## Language
+
+Every command's output is names, not slugs, by default (`--raw` restores
+the bare-slug form). Start a new translation from the shipped Latin table,
+edit a couple of entries, check it, and render with it -- every line below
+is real, not transcribed:
+
+```sh
+$ dune exec colitur -- lang --dump la > my-lang.ini
+$ sed -i \
+ -e 's/^ef-circumcision = .*/ef-circumcision = My Own Circumcision Text/' \
+ -e 's/^ef-epiphany = .*/ef-epiphany = My Own Epiphany Text/' \
+ my-lang.ini
+$ dune exec colitur -- lang --check my-lang.ini
+my-lang.ini: 725 of 725 celebrations named, 0 missing, 0 unknown
+$ dune exec colitur -- day 2026 --lang ./my-lang.ini | head -1
+2026-01-01 thursday christmastide - ef-circumcision class-1 white My Own Circumcision Text
+```
+
+`--check` reports what a language file is still missing, and rejects any
+entry naming a slug that does not exist (a typo, otherwise silently dead).
+`colitur lang --list` shows what this build can find; `colitur config
+--show` shows the effective `--lang` (and every other setting) plus where
+it came from. See `colitur(1)`'s `NAMING` section and `colitur-config(5)`
+for both in full.
+
## License
AGPL-3.0-or-later. See `LICENSE`.
diff --git a/man/colitur-config.5 b/man/colitur-config.5
new file mode 100644
index 0000000..8f21028
--- /dev/null
+++ b/man/colitur-config.5
@@ -0,0 +1,308 @@
+.TH COLITUR\-CONFIG 5 "2026" "colitur" "File Formats"
+.SH NAME
+colitur\-config \- default settings file for colitur(1)
+.SH SYNOPSIS
+.I ~/.config/colitur/config.ini
+.SH DESCRIPTION
+A
+.B colitur
+config file supplies DEFAULTS for four settings \(em
+.IR lang ", " overlay ", " template " and " format
+\(em so a command line does not have to repeat
+.B \-\-lang
+.IR en ,
+.B \-\-overlay
+.I ~/calendars/diocese.sexp
+or
+.B \-\-template
+.I booklet.tex
+on every invocation.
+.PP
+A config file is entirely
+.BR OPTIONAL .
+With none present, every command behaves exactly as it does without this
+feature, except that display names still resolve through the built\-in
+default language
+.RI ( la ).
+.SH LOCATION
+.RS
+.nf
+$XDG_CONFIG_HOME/colitur/config.ini if $XDG_CONFIG_HOME is set and non\-blank
+$HOME/.config/colitur/config.ini otherwise
+.fi
+.RE
+.PP
+If neither variable is usable (no
+.BR HOME ),
+there is no config path at all, and colitur behaves exactly as if no config
+file existed \(em this is not an error.
+.SH PRECEDENCE
+For every one of the four settings, in order:
+.RS
+.nf
+1. the command\-line flag (\-\-lang, \-\-overlay, \-\-template, \-\-format)
+2. the config file's own value
+3. the built\-in default
+.fi
+.RE
+.PP
+.BR "colitur config \-\-show" ,
+described below, reports EXACTLY this resolution for the flags given on
+that same command line \(em it is not a separate calculation that could
+disagree with what a real command actually does; both go through the same
+resolver.
+.PP
+.IR lang ", " template " and " format
+are each a single scalar value: config supplies one only when the
+corresponding flag is
+.B entirely absent
+from the command line, never merged or overridden field\-by\-field with a
+partial flag value.
+.I overlay
+is a
+.B list
+(it is repeatable on the command line, and overlays apply in order, last
+writer wins) and follows the identical rule at the list level: config's
+.I overlay
+lines are used only when
+.B zero
+.B \-\-overlay
+flags were given; a single
+.B \-\-overlay
+on the command line replaces the config list entirely rather than adding to
+it. This keeps the direction of precedence uniform across all four settings
+\(em flag beats config beats default \(em rather than inventing a merge
+order nothing else in this file documents.
+.SH FORMAT
+An INI file, read by the same section reader
+.BR colitur\-overlay (5)'s
+flat INI form and colitur's own language files use: section names in
+.RB \(lq [ name ] \(rq ,
+.RB \(lq key " = " value \(rq
+lines,
+.B ;
+and
+.B #
+comments, blank lines ignored. No continuations, no quoting.
+.PP
+Every setting lives in a section named
+.BR defaults .
+A section named anything else is
+.B unrecognised
+\(em including a plain typo such as
+.B [deafults]
+\(em and is warned about, not silently dropped (see
+.B UNKNOWN KEYS AND SECTIONS
+below).
+.PP
+If
+.B [defaults]
+appears more than once, every occurrence is merged, not only the first: a
+hand\-edited config file will naturally grow a second
+.B [defaults]
+block as a user appends a line later, and only reading the first would
+silently ignore it. Within one key, or across two blocks, the
+.B LAST
+occurrence wins \(em the natural reading of "the bottom line is the one
+that took effect" for a file edited by hand over time. A repeated
+.B overlay
+key is the one exception: it
+.B accumulates
+(one entry per line), because a user legitimately has more than one local
+overlay.
+.SH SETTINGS
+.TP
+.B lang
+A language CODE (e.g.
+.BR la ", " en )
+or a file path, exactly as
+.B \-\-lang
+accepts it. Default
+.BR la .
+See
+.BR colitur (1)'s
+own
+.B NAMING
+section for how a CODE resolves to a file, and what an unknown language
+does (a hard error, on every command, never a silent fallback).
+.TP
+.B overlay
+A local calendar file, applied on top of the shipped universal one, exactly
+as
+.B \-\-overlay
+accepts it. Repeatable \(em one
+.B overlay
+.B =
+line per file, applied in the order they appear in the config file. No
+default (an empty list).
+.TP
+.B template
+A template file path, exactly as
+.B \-\-template
+accepts it. Relevant only to
+.BR table " and " render ,
+which require a template either from this setting or from
+.BR \-\-template ;
+no default.
+.PP
+.B Neither
+.I overlay
+.B nor
+.I template
+.B is shell\-expanded.
+A value is read as a literal string and handed straight to the filesystem
+\(em there is no shell here to turn a leading
+.B ~
+into
+.IR $HOME .
+.RS
+.nf
+
+$ printf '[defaults]\enoverlay = ~/calendars/diocese.sexp\en' > config.ini
+$ XDG_CONFIG_HOME=. colitur day 2026
+colitur: failed to load ~/calendars/diocese.sexp: ~/calendars/diocese.sexp: No such file or directory
+.fi
+.RE
+.PP
+Write the full path instead
+.RI ( $HOME /calendars/diocese.sexp,
+which the SHELL expands when the config file is CREATED, e.g. via
+.BR "echo \(dqoverlay = $HOME/calendars/diocese.sexp\(dq >> config.ini" ,
+not a bare
+.BR ~ / ... " written literally into the file)."
+.TP
+.B format
+One of
+.BR csv ", " json ", " sexp ", " xml " or " ics ,
+exactly as
+.B \-\-format
+accepts it. Relevant only to
+.BR emit ,
+which requires a format either from this setting or from
+.BR \-\-format ;
+no default.
+.SH UNKNOWN KEYS AND SECTIONS
+A key inside
+.B [defaults]
+that is none of the four settings above, and a section that is not named
+.BR defaults ,
+are each reported \(em on standard error, prefixed with the config file's
+own path \(em and then
+.BR ignored .
+Neither is fatal on its own. This is deliberate: a config file written for
+a newer
+.B colitur
+must still work on an older one that does not yet recognise every setting,
+but silently discarding a line the user wrote is exactly how a setting they
+believe is active quietly does nothing. The two are reported separately,
+worded differently, so a misspelled
+.B SECTION
+(the whole block silently doing nothing) reads differently from a
+misspelled
+.B KEY
+inside an otherwise\-recognised one:
+.RS
+.nf
+
+colitur: ~/.config/colitur/config.ini: unknown setting "langauge" (ignored)
+colitur: ~/.config/colitur/config.ini: unknown section [deafults] (ignored)
+.fi
+.RE
+.PP
+A config file that
+.B EXISTS
+but fails to
+.B parse
+at all \(em an unterminated
+.RB \(lq [ \(rq ,
+a
+.B key = value
+line before any section header \(em is a different case and is
+.BR FATAL :
+exit
+.BR 2 ,
+naming the file and the reason, on
+.B every
+command, not only ones that read
+.IR lang / overlay / template / format .
+The config file is loaded once, unconditionally, before any command
+dispatches \(em a config the user wrote and colitur cannot honour at all is
+not something to silently carry on past, even for a command
+(
+.B \-\-help
+included) that would not otherwise have consulted it.
+.SH SHOWING THE RESOLVED CONFIGURATION
+.BI "colitur config " \-\-show
+prints the config file's own path and whether it exists, then every one of
+the four settings with its resolved value and where that value came from:
+.BR flag ", " config " or " default .
+There is deliberately no separate "provenance" function elsewhere in the
+program \(em the same resolver that decides what
+.B colitur day
+actually uses is what
+.B \-\-show
+reports, so the two cannot disagree.
+.PP
+Flags given on the SAME
+.B config \-\-show
+command line preview exactly what they would resolve to on any other
+command \(em
+.B config
+is the one command that does NOT refuse
+.BR \-\-lang / \-\-overlay / \-\-template / \-\-format
+as having no effect on it, because previewing their resolution
+.I is
+its effect.
+.I overlay
+has no single scalar value, so it is listed one line per effective entry
+instead of one line total.
+.SH WORKED EXAMPLE
+Reproducible against the shipped example overlay \(em every literal below is
+a real run, not a transcription. The full, absolute path is written into the
+file (see the shell\-expansion note above \(em a bare
+.B ~
+would fail to load):
+.RS
+.nf
+$ mkdir \-p ~/.config/colitur
+$ cat > ~/.config/colitur/config.ini <<EOF
+[defaults]
+lang = en
+overlay = $HOME/.local/share/colitur/examples/diocesan\-example.sexp
+EOF
+$ colitur config \-\-show
+config file: /home/user/.config/colitur/config.ini (exists)
+lang en (config)
+template (none) (default)
+format (none) (default)
+overlay /home/user/.local/share/colitur/examples/diocesan\-example.sexp (config)
+$ colitur day 2026 | head \-1
+2026\-01\-01 thursday christmastide \- ef\-circumcision class\-1 white The Octave Day of the Nativity
+.fi
+.RE
+.PP
+.B day
+above used
+.I en
+and the diocesan example overlay with no flags at all, because neither was
+given on the command line; a real run with
+.B \-\-lang la
+or
+.B \-\-overlay
+would override each independently, per\-invocation, without editing the
+file.
+.SH SEE ALSO
+.BR colitur (1)
+for
+.BR \-\-lang / \-\-raw ,
+.BR "colitur lang" ,
+and the
+.B NAMING
+section in full.
+.PP
+.BR colitur\-overlay (5)
+for the overlay file format
+.I overlay
+names.
+.SH LICENSE
+AGPL\-3.0\-or\-later.
diff --git a/man/colitur-templates.5 b/man/colitur-templates.5
index 2f42d68..06ad583 100644
--- a/man/colitur-templates.5
+++ b/man/colitur-templates.5
@@ -107,9 +107,6 @@ like an
.BR colitur\-overlay (5)
file, and is never allowed to crash the program that reads it.
.SH SCOPE AND LOOKUP
-.B This section documents a real hazard, not a theoretical one \(em it has
-.B produced wrong output during this program's own development.
-.PP
Scope is a stack. Rendering starts with the whole view (the year) as the one
entry on the stack; each
.B {{#section}}
@@ -131,79 +128,46 @@ could never reach
.BR {{year}} ,
which lives only on the outermost object.
.PP
-.B The hazard: a DOTTED PATH that resolves only PART WAY inward falls back
-.B WHOLESALE to an outer scope of the SAME NAME.
-A bare key inside the immediately enclosing section is not at risk this way
-\(em it either resolves right there or fails outright (see
-.B num
-below); it is specifically a dotted path, one step of which is missing from
-the inner object, that abandons the whole path and restarts the lookup one
-level out. Falling back outward means a name that exists at
-.I both
-levels never fails and never warns \(em it just silently resolves to the
-.I outer
-one, because the inner object's own absence of that key is indistinguishable
-from "look further out" and "this key does not apply here". Two collisions
-are known to exist in the shipped view model:
-.TP
+.B A hazard this section used to document here no longer exists, and is
+.B recorded as removed rather than silently dropped from this page.
+Before the view model's naming rework,
.B name
-Both a
+was an object keyed by language (e.g.
+.BR la " and " en ),
+carried on both a
.B month
and a
-.B day
-carry a
-.I name
-field (each an object keyed by language, e.g.
-.BR la " and " en ).
-Written naively, when
-.B {{#days}}
-is nested inside
-.BR {{#months}} ,
-a bare
+.BR day .
+A dotted path that resolved only part way inward \(em a bare
.B {{name.la}}
-does
-.I not
-resolve to the day's own Latin name. It resolves to the
-.I enclosing month's
-Latin name, because the day's own
-.I name
-object either has no
-.B la
-key (an unnamed day) or the dotted path fails partway and the WHOLE path
-falls back to the outer scope, which does have one. This is not a corner
-case: on an ordinary month, most days carry no Latin name at all (only named
-sanctoral days do), so the naive form renders the
+on a day with no Latin name of its own \(em fell back WHOLESALE to the
+enclosing
+.BR month 's
+own
+.B name.la
+instead of failing, because the lookup rule above cannot distinguish "this
+key does not apply here" from "look further out": on an ordinary month, most
+days carried no Latin name at all, so the naive form silently printed the
.I month's
-name on nearly every day \(em in a per\-month booklet, dozens of wrong
-lines; in a month grid (whose
-.B {{#weeks}}
-is itself only ever reachable through
-.BR {{#months}} ,
-since
-.I weeks
-is a field of
-.IR month ,
-never a top\-level list) EVERY cell reads the month's own name.
-.PP
-.B The collision needs a month actually on the scope stack to fire.
-Iterating the TOP\-LEVEL, flat
-.B days
-list directly (
-.B {{#days}}...{{/days}}
-at the outermost level, never passing through
+own name on nearly every day.
+.B name
+is now a PLAIN STRING, always fully resolved before the template ever sees
+it (see
+.B VIEW MODEL
+below) \(em there is no dotted path left for a partial match to climb out
+of, so this specific hazard is unrepresentable, not merely avoided by
+convention. A bare
+.B {{name}}
+inside
+.B {{#days}}
+always reads that day's own name, nested under
.B {{#months}}
-first) puts no month object anywhere on the stack, so the naive form does
-.I not
-silently substitute the wrong answer there \(em it silently resolves to
-nothing, exactly as any other absent key would, because there is no
-outer scope left to climb to. This is precisely what makes the hazard easy
-to miss: an author who tries the naive form against the flat list first
-sees the unnamed days come out empty, reasonably concludes the form is
-safe, and then hits the real collision the moment the identical fields are
-read from inside
-.BR {{#months}} ,
-which is what every shipped template that produces a grid or a per\-month
-booklet actually does.
+or not, full stop.
+.PP
+.B One collision of the same underlying shape still exists in the shipped
+.B view model, because it involves two PLAIN (non\-dotted) fields that
+.B happen to share a name, which the fallback rule above cannot help with
+.B either:
.TP
.B num
Both a
@@ -216,46 +180,77 @@ field. Inside
.BR {{#weeks}} ,
a bare
.B {{num}}
-is the week's own number, correctly \(em but only because nothing between
-the week and the day currently redefines it. A template that reaches
+is the week's own ordinal (1, 2, 3, ...) within its month, correctly \(em
+but only because nothing between the week and wherever
+.B {{num}}
+is actually written currently redefines it. A
+.B day
+object has no
.I num
-from any scope where the immediately enclosing section does not itself
-carry it will silently climb to whichever ancestor does, and that may not be
-the one the author meant.
-.PP
-.B The safe idiom.
-Push the object you actually want onto the scope stack yourself, with a
-.B {{#name}}
-section, before reading its fields \(em then a bare field inside that
-section can only resolve against the object you just pushed, or fail
-outright and fall through to an inverted fallback you write explicitly:
+field of its own at all, so a bare
+.B {{num}}
+written inside
+.B {{#days}}
+climbs straight past the day to the enclosing
+.BR week 's
+own
+.IR num ,
+not the month's, even where a template author reaching for "the month
+number" from deep inside a day cell might expect otherwise. Verified live,
+not merely reasoned about (a template iterating
+.BR {{#months}}{{#weeks}}{{#days}} ,
+printing the enclosing week's own
+.I num
+beside the month's own
+.IR month_num ,
+both carried on every day and week object for exactly this reason \(em see
+.B VIEW MODEL
+below):
.RS
.nf
-{{#name}}{{la}}{{^la}}{{slug}}{{/la}}{{/name}}
+.B colitur table \-\-year 2027 \-\-template num\-hazard.txt
+week 1 of month 1
+ day\-scope num: 1
+week 2 of month 1
+ day\-scope num: 2
.fi
.RE
-This opens the day's own
-.I name
-object (never the month's \(em a
-.B {{#section}}
-always resolves its OWN path from the point it appears, which for
-.B {{#name}}
-written inside
-.B {{#days}}
-is the day's
-.IR name ,
-shadowing the month's identically\-named field exactly as intended), reads
-.B la
-from it, and falls back to the day's own
-.B slug
-only when
-.B la
-is genuinely absent from
-.I that
-object \(em never when it is merely absent from an ancestor. Every shipped
-template that prints a day's name uses exactly this idiom; none uses the
-bare dotted form.
+The "day\-scope num" line is reading a bare
+.B {{num}}
+from inside
+.BR {{#days}} .
+It changes from week to week, proving it is the enclosing
+.BR week 's
+own ordinal, not the constant
+.B 1
+a reader expecting "month number" might assume.
+.PP
+.B The safe idiom, when a template genuinely needs a number from an
+.B ancestor further out than the immediately enclosing section:
+use the field the view already carries FOR that purpose rather than a bare,
+climbing
+.BR {{num}} .
+A
+.B week
+object already carries
+.B month_num
+(and
+.BR month_name )
+precisely because the engine has no
+.RB \(lq "../" \(rq
+parent\-path syntax to reach the enclosing month any other way \(em see
+.B week
+under
+.B VIEW MODEL
+below. There is no equivalent shortcut for reaching a
+.B week
+object's own
+.I num
+from inside
+.BR {{#days}} ,
+because no shipped template needs one; a template that does should carry it
+down explicitly the same way, rather than relying on the climb.
.SH HOST\-LANGUAGE COMMENTS
.B The engine has no awareness of the target language's own comment syntax.
A
@@ -438,6 +433,12 @@ for the rite's own scope and limitations (readings cover only the Epistle
and Gospel; the votive Office of the Blessed Virgin Mary on Saturday does not
yet select among its five seasonal Masses).
.SH VIEW MODEL
+.B Every field below is verified against
+.I lib/render/view.ml
+.B and
+.IR schema/day\-v1.json ,
+.B not transcribed from memory or from an earlier version of this page.
+.PP
The value a template renders against is built once per
.B colitur table
/
@@ -447,6 +448,7 @@ The value a template renders against is built once per
invocation, from the same resolved calendar
.B colitur emit
uses, and is shaped for two artefacts from one model: a flat booklet (the
+top\-level
.B days
list, one entry per day of the requested year) and a month grid (the
.B months
@@ -457,6 +459,28 @@ cells so every row has exactly seven). This is the same shape published at
.IR schema/day\-v1.json ,
described here in prose; the JSON Schema is the machine\-checked contract and
this page is its worked explanation.
+.PP
+Every localised field below (marked
+.RI \(lq "in the active language" \(rq )
+resolves through whichever language
+.B \-\-lang
+selected, default
+.IR la ;
+under
+.BR \-\-raw ,
+each equals its own unlocalised counterpart (a
+.B name
+equals its
+.BR slug ,
+a
+.B rank_name
+equals its
+.BR rank ,
+and so on) rather than being blank \(em see
+.BR colitur (1)'s
+own
+.B NAMING
+section.
.SS Top level
.TP
.B rite
@@ -466,6 +490,32 @@ The rite identifier, currently always the string
.B year
The civil year requested, as a four\-digit string.
.TP
+.B term
+A fixed, closed vocabulary of strings in the active language, so a
+translated booklet needs no template edit for its own boilerplate words.
+Object keys:
+.BR ordo ", " contents ", " epistle ", " lesson ", " gospel ", "
+.BR commemoration " and " week .
+Referenced as
+.BR {{term.ordo}} ,
+.BR {{term.epistle}} ,
+and so on.
+.TP
+.B weekday_headings
+A list of exactly seven objects, Sunday first, each
+.RB { name ", " last },
+for a localised grid header row \(em
+.B name
+is the weekday's own name in the active language,
+.B last
+is true on the seventh (Saturday) entry, the same
+.RB \(lq "unless this is the last one" \(rq
+flag the
+.B day
+object's own
+.B last
+field below provides for a week's row of cells.
+.TP
.B months
A list of twelve
.B month
@@ -483,14 +533,9 @@ template iterates over directly, without going through
The month number, 1 through 12, as a string.
.TP
.B name
-An object keyed by language (currently
-.B la
-and
-.BR en ),
-each value the month's own name in that language (e.g.
-.RB \(lq Ianuarius \(rq
-/
-.RB \(lq January \(rq ).
+The month's own resolved display name in the active language (e.g.
+.RB \(lq Ianuarius \(rq ),
+a plain string.
.TP
.B days
This month's own
@@ -512,7 +557,39 @@ The week's ordinal within its month (1, 2, 3, ...), as a string. This is
.I not
a liturgical week number \(em see
.B day \(-> week
-below for that.
+below for that, and
+.B SCOPE AND LOOKUP
+above for the
+.B num
+collision this field and the enclosing month's own
+.B num
+share.
+.TP
+.B month_num
+.TQ
+.B month_name
+The enclosing month's own
+.B num
+and
+.BR name ,
+carried onto every week because the engine has no
+.RB \(lq "../" \(rq
+parent\-path syntax \(em a nested
+.B {{month_num}}
+or
+.B {{name}}
+read from inside
+.B {{#weeks}}
+would otherwise be unable to reach the month at all (a bare
+.B {{name}}
+here would in fact resolve to the WEEK's enclosing DAY's name once one is
+pushed, not the month's, since
+.I week
+itself carries no
+.B name
+field of its own \(em carrying
+.B month_name
+explicitly is what avoids relying on that climb).
.TP
.B days
Exactly seven
@@ -556,8 +633,13 @@ seven entries); a template checks this, not
emptiness, to decide whether to render a cell's contents.
.TP
.B season
-The liturgical season's own string name (e.g.
-.BR paschaltide ", " lent ).
+The liturgical season's own unlocalised string key (e.g.
+.BR paschaltide ", " lent ),
+stable across every language \(em unaffected by
+.BR \-\-lang / \-\-raw .
+.TP
+.B season_name
+The season's own resolved display name, in the active language.
.TP
.B week
The liturgical week number within the season, as a string, or the empty
@@ -578,30 +660,60 @@ key of its own at all, only
so there is nothing for it to shadow).
.TP
.B slug
-The observed celebration's stable identifier (e.g.
-.BR ef\-easter\-sunday ).
+The observed celebration's stable, unlocalised identifier (e.g.
+.BR ef\-easter\-sunday ),
+unaffected by
+.BR \-\-lang / \-\-raw \(em
+the machine key a template or a downstream script keys off, distinct from
+.B name
+below.
.TP
.B name
-An object keyed by language, the observed celebration's own name in each
-language colitur's data supplies one for. Frequently has no
-.B la
-or
-.B en
-key at all (most temporal days, most sanctoral entries in the shipped data)
-\(em see
-.B SCOPE AND LOOKUP
-above for the resulting month\-name collision and its safe idiom.
+The observed celebration's resolved display name, in the active language
+\(em a PLAIN STRING, not an object keyed by language. Under
+.B \-\-raw
+this equals
+.B slug
+exactly, which is what makes
+.B \-\-raw
+output byte\-stable; a real language's own table names essentially every
+slug the engine can produce (verified: 725 of 725 over the window colitur's
+own coverage test measures), so in ordinary operation
+.B name
+differs from
+.B slug
+on nearly every day, not only the ones with a proper name in the historical
+sense.
+.TP
+.B weekday
+The day's own weekday name in the active language (e.g.
+.RB \(lq "Feria V" \(rq ,
+.RB \(lq Sabbatum \(rq ), matching
+.BR dow 's
+numbering (0 = Sunday).
.TP
.B rank
-The observed celebration's class, as the kernel's own string (e.g.
-.BR class\-1 ).
-There is deliberately no separate, localized rank label: the kernel carries
-no per\-language rank names to draw one from.
+The observed celebration's class, as the kernel's own unlocalised string
+key (e.g.
+.BR class\-1 ),
+unaffected by
+.BR \-\-lang / \-\-raw .
+.TP
+.B rank_name
+The rank's own resolved display name, in the active language (e.g.
+.RB \(lq "I classis" \(rq ).
.TP
.B colour
-The observed celebration's liturgical colour, lowercase (one of
+The observed celebration's liturgical colour, lowercase, unlocalised (one of
.BR white ", " red ", " green ", " violet ", " rose ", " black ,
-or the empty string).
+or the empty string), unaffected by
+.BR \-\-lang / \-\-raw .
+.TP
+.B colour_name
+The colour's own resolved display name, in the active language (e.g.
+.RB \(lq albus \(rq
+for
+.BR white ).
.TP
.BR is_white ", " is_red ", " is_green ", " is_violet ", " is_rose ", " is_black
Six booleans, exactly one true (matching
@@ -614,16 +726,17 @@ is no expression evaluation, so this is the only way a template branches on
colour at all.
.TP
.B subject
-Whose feast this is, lowercase (one of
+Whose feast this is, lowercase, unlocalised (one of
.BR lord ", " bvm ", " saint ", " temporal ).
.TP
.B comms
A list of commemoration objects admitted on this day, each carrying
-.BR slug ,
-.B name
-(an object keyed by language, same shape as the day's own
-.BR name ),
-and
+.BR slug " (unlocalised), " name
+(the SAME resolved\-string shape as the day's own
+.BR name ,
+through the identical language table \(em a commemoration's slug is drawn
+from the same sanctoral/temporal pool as the observed day's, not a second
+vocabulary), and
.B privileged
(boolean: true for a privileged commemoration under RG 109, which survives
even where an ordinary one would be capped out). Empty list on a day with no
@@ -634,14 +747,18 @@ cell.
A list of at most one object, present when a feast impeded elsewhere was
transferred onto THIS day (RG 96\(en98); carries the transferred
celebration's own
-.BR slug .
+.B slug
+only \(em
+.I not
+a resolved
+.BR name .
Empty list when nothing transferred in.
.TP
.B transferred_out
A list of objects, one per celebration that would have fallen on this day
but was displaced and moved to a later date; each carries
.B slug
-and
+(only, as above) and
.B to
(the ISO\-8601 date it was moved to). Empty on the ordinary day.
.TP
@@ -649,6 +766,9 @@ and
The Epistle/Lesson reading citation (e.g.
.RB \(lq "Heb 1:1\-12" \(rq ),
never scripture text \(em a reference only. Empty string when none resolved.
+Unaffected by
+.BR \-\-lang / \-\-raw :
+a citation is a reference, not a display name.
.TP
.B gospel
The Gospel reading citation, same shape as
@@ -666,16 +786,15 @@ an eighth, empty column and
.B pdflatex
would reject the file outright.
.SH A WORKED MINIMAL TEMPLATE
+.B Every line below is pasted from a real run, not written by hand.
A plain\-text booklet, days nested inside months \(em the shape every
-shipped template actually uses, and the shape the
-.B SCOPE AND LOOKUP
-hazard needs to fire \(em using the safe name idiom from that section:
+shipped template actually uses:
.RS
.nf
{{rite}} {{year}}
{{#months}}{{#days}}
-{{iso}} {{#name}}{{la}}{{^la}}{{slug}}{{/la}}{{/name}} {{colour}}{{#comms}} +{{slug}}{{/comms}}
+{{iso}} {{weekday}} {{name}} {{colour_name}}{{#comms}} +{{name}}{{/comms}}
{{/days}}{{/months}}
.fi
.RE
@@ -693,89 +812,119 @@ one):
.B colitur table \-\-year 2026 \-\-template minimal.txt | head \-7
ef 2026
-2026\-01\-01 ef\-circumcision white
+2026\-01\-01 Feria V In Octava Nativitatis Domini albus
-2026\-01\-02 ef\-christmas\-1\-friday white
+2026\-01\-02 Feria VI Feria VI ante Epiphaniam albus
-2026\-01\-03 Officium sanctae Mariae in sabbato white
+2026\-01\-03 Sabbatum Officium sanctae Mariae in sabbato albus
.fi
.RE
.PP
-The second data line shows the fallback firing: 2 January carries no Latin
+Every line names a real celebration in Latin \(em there is no unnamed\-day
+fallback to demonstrate here the way an earlier version of this page showed
+one: the shipped
+.I la
+table names every slug the engine can produce (725 of 725 over the window
+its own coverage test measures), so
.B name
-in the shipped data, so
-.B {{^la}}
-supplies
-.B {{slug}}
-instead. The third shows the non\-fallback case: 3 January
-.I does
-carry a Latin name (the votive Office of the Blessed Virgin Mary on
-Saturday), and the idiom prints it correctly.
-.PP
-Now swap only the interpolation \(em the safe idiom above becomes the
-naive, unsafe
-.B {{name.la}} ,
-nesting left exactly as it was:
+is never the bare
+.B slug
+in ordinary operation.
+.B \-\-raw
+is what produces the bare\-slug form instead, on demand, not a gap in the
+data:
.RS
.nf
-{{rite}} {{year}}
-{{#months}}{{#days}}
-{{iso}} {{name.la}} {{colour}}
-{{/days}}{{/months}}
+.B colitur table \-\-year 2026 \-\-template minimal.txt \-\-raw | head \-3
+ef 2026
+
+2026\-01\-01 4 ef\-circumcision white
.fi
.RE
.PP
-Rendered against the identical year, same extension and flavour:
+.RB \(lq 4 \(rq
+above is
+.BR weekday 's
+own raw form: with no language table at all,
+.I weekday
+falls back to the numeral
+.BR dow
+itself carries (Thursday, 1 January 2026), not a slug\-like string \(em
+there is no bare English or Latin word for a weekday to echo back the way a
+missing celebration name echoes its own
+.BR slug .
+.PP
+The
+.B num
+collision from
+.B SCOPE AND LOOKUP
+above, reproduced here in full \(em a grid\-shaped template, a week nested
+inside a month, a day nested inside the week:
.RS
.nf
-.B colitur table \-\-year 2026 \-\-template minimal\-unsafe.txt | head \-7
-ef 2026
-
-2026\-01\-01 Ianuarius white
-
-2026\-01\-02 Ianuarius white
+{{#months}}{{#weeks}}week {{num}} of month {{month_num}}
+{{#days}} day\-scope num: {{num}}
+{{/days}}{{/weeks}}{{/months}}
+.fi
+.RE
+.RS
+.nf
-2026\-01\-03 Officium sanctae Mariae in sabbato white
+.B colitur table \-\-year 2027 \-\-template num\-hazard.txt | head \-9
+week 1 of month 1
+ day\-scope num: 1
+ day\-scope num: 1
+ day\-scope num: 1
+ day\-scope num: 1
+ day\-scope num: 1
+ day\-scope num: 1
+ day\-scope num: 1
+week 2 of month 1
.fi
.RE
.PP
-The collision fires for real on the first two lines: with a month on the
-scope stack, the day's own missing
-.B la
-key falls back all the way out to the ENCLOSING MONTH's own
-.RB \(lq Ianuarius \(rq
-rather than failing. The third line is untouched, because 3 January's own
-.I name
-object genuinely has a
-.B la
-key, so the dotted path resolves without ever needing to fall back.
-.PP
-.B This is specific to nesting, and that is the point.
-The identical naive
-.B {{name.la}}
-written against the TOP\-LEVEL, flat
-.B days
-list (no
-.B {{#months}}
-wrapping it) does
-.I not
-print
-.RB \(lq Ianuarius \(rq
-anywhere \(em there is no month on the stack to fall back to, so it prints
-nothing on an unnamed day instead, silently but not wrongly. See
+Every
+.RB \(lq "day\-scope num" \(rq
+line inside week 1 reads
+.BR 1 ,
+and every one inside week 2 (not shown above, but the next seven lines of
+the same run) reads
+.BR 2 :
+a bare
+.B {{num}}
+written inside
+.B {{#days}}
+is reading the enclosing
+.BR week 's
+own ordinal, silently, because
+.I day
+itself carries no
+.I num
+field to shadow it with. Nothing here is wrong \(em the lookup rule is
+doing exactly what
.B SCOPE AND LOOKUP
-above for why testing the naive form only against a flat list is exactly
-how this hazard goes unnoticed until a template is later nested under
-.BR {{#months}} .
+documents \(em but a template author who expected "the month number" from
+that bare
+.B {{num}}
+would be reading the wrong field with no warning at all. Compare
+.B {{month_num}}
+in the same template, which stays
+.B 1
+throughout January regardless of which week or day it is read from,
+because it is carried explicitly rather than reached by climbing.
.SH SEE ALSO
.BR colitur (1)
for
.BR table ", " render " and " publish ,
-and for the five
+for the five
.B emit
-formats that share this same view model.
+formats that share this same view model, and for
+.BR \-\-lang / \-\-raw
+and the
+.B NAMING
+section that controls every localised field this page documents.
.PP
.BR colitur\-overlay (5)
for the local\-calendar file format that supplies the celebrations a
@@ -784,5 +933,14 @@ template's
and
.B slug
fields can carry.
+.PP
+.BR colitur\-config (5)
+for setting a default
+.B \-\-template
+so
+.B table
+/
+.B render
+need not repeat it on every invocation.
.SH LICENSE
AGPL\-3.0\-or\-later.