Hi Pablo,
As I said previously, here is a continuation of my experiments around
lemmatization and glossaries.
As I mentioned in my previous message, there is an important distinction
between building a glossary for an entire chapter or book, and
supporting the kind of pedagogical work done by teachers such as
Geoffrey Steadman, who present texts by short sections, with vocabulary
and commentary laid out page by page. (I did not try to count the exact
number of lines in each excerpt he treats, nor the size of the
corresponding vocabularies.)
Constructing a glossary per page (or per section, or even per paragraph)
raises a specific problem: the mechanism must be able to /forget/ what
was collected for the previous unit, reset itself, and then rebuild a
new vocabulary list for the next unit — while also avoiding duplicates
within each unit and preserving order of appearance. In other words, the
workflow becomes: collect => print => reset => repeat
This is quite different from classical indexes, which are designed for
global accumulation across a whole document. In practice, this is where
I found it difficult to rely solely on standard register/index
mechanisms. They naturally assume long-range memory, whereas a
pedagogical glossary needs short-term, local memory. My current
experimental solution uses a small Lua layer (see below the MWE).
Very briefly, the Lua code does three simple things:
1. it maintains a small local “bucket” (a table) where each marked word
is stored under a key (the lemma), together with its displayed form
and short gloss;
2. it keeps a separate list to preserve the order of first appearance
and to avoid duplicates within the same section;
3. when the end of a section is reached, TeX asks Lua to print the
collected entries, and then Lua is explicitly reset so that the next
section starts from zero.
TeX remains responsible for typography and line numbering; Lua is only
used as a temporary memory that can be cleared on demand.
So, at least from these small experiments, it seems to me that
section-level reading glossaries are not just a technical variation on
indexes: they correspond to a different editorial logic, centered on
locality and frequent resets.
This is still very much exploratory on my side, but I thought the
concrete MWE might help clarify where the conceptual difficulty lies. Of
course, we can still improve the presentation of the glossary (in two
columns), add lemmatization, etc.
Best regards,
JP
Here is the MWE :
% steadman-demo-localgloss.tex
% ------------------------------------------------------------
% Source (Grec): Aristote, Nicomachean Ethics I.3 (1094b–1095a),
% as presented in Geoffrey Steadman’s pedagogical PDF:
% "Aristotle’s Nicomachean Ethics Book I: Greek Text with Facing
Vocabulary and Commentary"
% (G. Steadman, 2025-01 PDF provided by
%
https://geoffreysteadman.com/wp-content/uploads/2025/01/arist.nicobk1.beta10jan25.pdf).
%
% Objectif : texte grec numéroté + glossaire local collecté avec Lua.
% ------------------------------------------------------------
% --- Langues
\setuplanguage[fr][patterns={fr},spacing=french]
% Grec ancien : patterns "agr" (et éventuellement "gr" en secours)
\setuplanguage[agr][patterns={agr}]
% (Optionnel) si tu veux aussi pouvoir utiliser le grec moderne :
% \setuplanguage[gr][patterns={gr}]
% --- Police : Libertinus (polytonique OK) + fallback grec
\definefontfamily[mainface][serif][Libertinus Serif]
\definefallbackfamily[mainface][serif][GFS
Didot][preset=range:greek,force=yes]
\setupbodyfont[mainface,11pt]
% --- Mise en page
\setuplayout[
topspace=14mm,
backspace=18mm,
width=middle,
header=10mm,
footer=12mm,
height=middle,
]
\setupalign[hz,hanging]
\setuptolerance[verytolerant]
% ------------------------------------------------------------
% Lua: local glossary bucket
% - keeps first occurrence order
% - avoids duplicates by key
% - stores list of line numbers (unique)
% ------------------------------------------------------------
\startluacode
moduledata = moduledata or {}
moduledata.localgloss = moduledata.localgloss or {}
local function newbucket()
return { map = {}, order = {} }
end
moduledata.localgloss.bucket = moduledata.localgloss.bucket or newbucket()
local function reset(bucket)
bucket.map, bucket.order = {}, {}
end
local function addline(lines, ln)
-- keep unique line numbers, preserve insertion order
for i=1,#lines do
if lines[i] == ln then return end
end
lines[#lines+1] = ln
end
local function add(bucket, key, form, gloss, lineno)
if not key or key == "" then return end
if not bucket.map[key] then
bucket.map[key] = { form=form, gloss=gloss, lines={} }
bucket.order[#bucket.order+1] = key
end
if lineno and lineno ~= "" then
addline(bucket.map[key].lines, lineno)
end
end
local function joinlines(lines)
if #lines == 0 then return "" end
return table.concat(lines, ", ")
end
local function print(bucket)
local context = context
if #bucket.order == 0 then
context("\\noindent{\\tfxx\\it (no entries)}\\par")
return
end
context("\\startitemize[packed,joinedup]\\tfxx\n")
for i=1,#bucket.order do
local k = bucket.order[i]
local v = bucket.map[k]
context("\\item {\\bf ")
context(v.form)
context("} — ")
context(v.gloss)
local ln = joinlines(v.lines)
if ln ~= "" then
context("\\hfill{\\tfxx[")
context(ln)
context("]}")
end
context("\n")
end
context("\\stopitemize\n")
end
function moduledata.localgloss.reset()
reset(moduledata.localgloss.bucket)
end
function moduledata.localgloss.add(key, form, gloss, lineno)
add(moduledata.localgloss.bucket, key, form, gloss, lineno)
end
function moduledata.localgloss.print()
print(moduledata.localgloss.bucket)
end
\stopluacode
% ------------------------------------------------------------
% Du côté de ConTeXt: numbered lines we control (stable)
% ------------------------------------------------------------
\newcount\LineNo
\unexpanded\def\ResetLines{\global\LineNo=0}
% Print one logical line with a left line number:
\unexpanded\def\Line#1{%
\global\advance\LineNo by 1
\noindent
\llap{\tfxx\the\LineNo\quad}#1\par
}
% Add a word to glossary (key, form, gloss) with current line number:
\unexpanded\def\G#1#2#3{%
#2%
\ctxlua{moduledata.localgloss.add([==[#1]==],[==[#2]==],[==[#3]==],[==[\the\LineNo]==])}%
}
\unexpanded\def\LocalGlossary{%
\blank[big]\hrule\blank[small]
{\bf Glossaire local (par ordre d'apparition)}\par
\ctxlua{moduledata.localgloss.print()}%
}
\unexpanded\def\ResetLocalGlossary{%
\ctxlua{moduledata.localgloss.reset()}%
}
% ------------------------------------------------------------
% Document
% ------------------------------------------------------------
\starttext
\chapter{Aristotle, Nicomachean Ethics I.3 (1094b): numbered text + Lua
glossary}
{\tfx\it
Text excerpt: Aristotle, \emph{Nicomachean Ethics} I.3 (1094b–1095a).
Pedagogical model and vocabulary layout inspired by Geoffrey Steadman’s PDF
(\emph{Aristotle’s Nicomachean Ethics Book I}, 2025-01).
}\par
\blank[medium]
\ResetLines
\ResetLocalGlossary
% --- Greek passage, broken into stable "edition-like" lines.
% Glossary calls: \G{key}{form}{gloss}
% (Key can be normalized/lemmatized; form is what prints in the text.)
\Line{οὖν \G{μέθοδος}{μέθοδος}{inquiry, investigation, method}
τούτων \G{ἐφίημι}{ἐφίεται}{aim at (middle; takes gen.)},
πολιτικὴ τις οὖσα.}
\Line{Λέγοιτο δ᾽ ἂν \G{ἱκανῶς}{ἱκανῶς}{adequately, sufficiently},
εἰ κατὰ τὴν \G{ὑποκείμενος}{ὑποκειμένην}{underlying, assumed}
\G{ὕλη}{ὕλην}{matter, material}
\G{διασαφέω}{διασαφηθείη}{be made clear, be explained}.}
\Line{τὸ γὰρ \G{ἀκριβής}{ἀκριβὲς}{exact, precise}
οὐχ ὁμοίως ἐν ἅπασι τοῖς \G{λόγος}{λόγοις}{accounts, arguments}
\G{ἐπιζητητέος}{ἐπιζητητέον}{to be sought}.}
\Line{ὥσπερ οὐδ᾽ ἐν τοῖς \G{δημιουργέω}{δημιουργουμένοις}{things made by
art, fabricated}.}
\Line{τὰ δὲ \G{καλός}{καλὰ}{noble, fine}
καὶ τὰ \G{δίκαιος}{δίκαια}{just things},
περὶ ὧν ἡ πολιτικὴ \G{σκοπέω}{σκοπεῖται}{examines, considers},
πολλὴν ἔχει \G{διαφορά}{διαφορὰν}{difference}
καὶ \G{πλάνη}{πλάνην}{wandering, fluctuation}.}
\Line{ὥστε δοκεῖν \G{νόμος}{νόμῳ}{by law, by convention}
μόνον εἶναι, \G{φύσις}{φύσει}{by nature}
δὲ μή.}
\Line{τοιαύτην δέ τινα \G{πλάνη}{πλάνην}{fluctuation}
ἔχει καὶ τἀγαθὰ διὰ τὸ πολλοῖς συμβαίνειν \G{βλάβη}{βλάβας}{harms}
ἀπ᾽ αὐτῶν.}
\Line{ἤδη γάρ τινες \G{ἀπόλλυμι}{ἀπώλοντο}{perished}
διὰ \G{πλοῦτος}{πλοῦτον}{wealth},
ἕτεροι δὲ δι᾽ \G{ἀνδρεία}{ἀνδρείαν}{courage}.}
\Line{\G{ἀγαπητός}{ἀγαπητὸν}{desired, desirable}
οὖν περὶ τοιούτων καὶ ἐκ τοιούτων λέγοντας
\G{παχυλῶς}{παχυλῶς}{roughly, coarsely}
καὶ \G{τύπος}{τύπῳ}{in outline, as a sketch}
τἀληθὲς \G{ἀληθής}{ἀληθές}{true}
\G{ἐνδείκνυμι}{ἐνδείκνυσθαι}{to point out, show}.}
\Line{καὶ περὶ τῶν ὡς ἐπὶ τὸ πολὺ καὶ ἐκ τοιούτων λέγοντας
τοιαῦτα καὶ \G{συμπεραίνω}{συμπεραίνεσθαι}{to conclude}.}
\Line{τὸν αὐτὸν δὴ τρόπον καὶ \G{ἀποδέχομαι}{ἀποδέχεσθαι}{to accept}
χρεὼν ἕκαστα τῶν λεγομένων.}
\Line{\G{πεπαιδευμένος}{πεπαιδευμένου}{educated person}
γάρ ἐστιν ἐπὶ \G{τοσοῦτος}{τοσοῦτον}{so far, to such an extent}
τἀκριβὲς \G{ἐπιζητέω}{ἐπιζητεῖν}{to seek}
καθ᾽ ἕκαστον \G{γένος}{γένος}{kind, class}.}
\Line{ἐφ᾽ ὅσον ἡ τοῦ \G{πρᾶγμα}{πράγματος}{matter, affair}
\G{φύσις}{φύσις}{nature}
\G{ἐπιδέχομαι}{ἐπιδέχεται}{admits, allows}.}
\Line{παραπλήσιον γὰρ φαίνεται \G{μαθηματικός}{μαθηματικοῦ}{mathematician}
τε \G{πιθανολογέω}{πιθανολογοῦντος}{using probable arguments}
ἀποδέχεσθαι,}
\Line{καὶ \G{ῥητορικός}{ῥητορικὸν}{rhetorician}
\G{ἀπόδειξις}{ἀποδείξεις}{proofs}
\G{ἀπαιτέω}{ἀπαιτεῖν}{to demand} (sc. from him).}
\Line{ἕκαστος δὲ \G{κρίνω}{κρίνει}{judges}
καλῶς ἃ \G{γιγνώσκω}{γινώσκει}{knows},
καὶ τούτων ἐστὶν ἀγαθὸς \G{κριτής}{κριτής}{judge}.}
% --- Glossary at end of unit:
\LocalGlossary
\stoptext
Le 31/01/2026 à 00:17, Jean-Pierre Delange via ntg-context a écrit :
Hi Pablo,
I don’t claim to have a definitive answer here — I’ve simply been
experimenting over the past months, trying to understand what is
structurally involved, and I thought a small MWE prototype might help
clarify things.
What I currently have is very simple. In the running text, each
annotated word sends its information into one of several note channels
(for example: /critical/, /explanatory/, /translation/).
These notes are not printed immediately as footnotes; instead they are
stored (|location=text|). At the end of the textual unit, they are
printed under explicit headings:
*
Critique
*
Explicatif
*
Traduction
So the headings themselves are just empty containers: what fills them
is whatever has been collected earlier via |\critnote|, |\explnote|,
|\transnote| (or small wrapper macros around them).
Conceptually, this gives something like:
*
the main text = reading flow
*
several parallel annotation streams = editorial layers
*
a final “apparatus” that replays each stream separately
From there, moving toward a glossary seems to be only one extra step:
instead of (or in addition to) sending content into notes, the same
data can also be sent into a small Lua bucket (key → lemma + gloss),
reset per block, and printed as a local vocabulary list. That gives a
“collect → print → reset → repeat” pattern, close to the glossary
developed by Steadman.
What strikes me, while experimenting, is the specific difficulty of
/short textual units/ (chapters, sections, passages). In that case the
glossary is not global by nature: it is local, temporary, and tightly
coupled to the immediately preceding text. This feels quite different
from classical registers, which assume long-range accumulation across
a whole document. So my tentative impression (very provisional) is
that these local reading aids solve a different problem than global
indexes:
*
ordered by appearance,
*
scoped to a block,
*
frequently reset,
*
aimed at immediate pedagogical use.
Once this distinction is made, it becomes easier to see why standard
registers don’t quite fit this use case, and why a lightweight Lua
layer may help — though I’m still very much in exploration mode here.
See below the MWE file I’m playing with (I will deliver another MWE
later with a glossary extracted with Lua code).
Best,
JP
% notes-critiques-example-v2.tex
% Grec 1 colonne — notes multi-niveaux stockées (location=text)
% puis imprimées sous rubriques via \placenotes
\mainlanguage[agr]
\setuppapersize[A4]
\definefontfamily[mainface][serif][Libertinus Serif]
\setupbodyfont[mainface,11pt]
\setuplayout[
topspace=1.5cm,
backspace=2cm,
width=middle,
header=1cm,
footer=1.2cm,
height=middle,
]
\setupalign[hz,hanging]
\setuptolerance[verytolerant]
% --- Trois séries de notes
\definenote[critnote]
\definenote[explnote]
\definenote[transnote]
% IMPORTANT : on STOCKE les notes au fil du texte (pas en bas de page)
% pour pouvoir les imprimer ensuite en fin d'unité.
\setupnote[critnote] [location=text, number=no, style=\tfxx]
\setupnote[explnote] [location=text, number=no, style=\tfxx]
\setupnote[transnote][location=text, number=no, style=\tfxx]
% --- Macros : forme (#1), lemme (#2), glose (#3)
\unexpanded\def\Gcrit#1#2#3{#1\critnote{\emph{#1} (\emph{#2}).~#3}}
\unexpanded\def\Gexpl#1#2#3{#1\explnote{\emph{#1} (\emph{#2}).~#3}}
\unexpanded\def\Gtrans#1#2#3{#1\transnote{\emph{#1} (\emph{#2}).~#3}}
% --- Apparat final (fin d'unité) : ICI les rubriques se remplissent
\unexpanded\def\ApparatNotes{%
\blank[big]\hrule\blank[small]
{\bf Critique}\par
\placenotes[critnote]
\blank[small]
{\bf Explicatif}\par
\placenotes[explnote]
\blank[small]
{\bf Traduction}\par
\placenotes[transnote]
}
\starttext
\chapter{Exemple — Rubriques "critique", "explicatif", "traduction"}
τὰ \Gexpl{ὁμώνυμα}{ὁμώνυμος}{Même nom, mais définition (λόγος)
différente.}
λέγεται, ὅταν τὸ \Gexpl{ὄνομα}{ὄνομα}{Le “nom” au sens de signifiant
commun.} μόνον κοινόν ᾖ,
ὁ δὲ τῆς \Gexpl{οὐσίας}{οὐσία}{Ici : “essence / être” selon le
contexte ; terme philosophiquement chargé.}
\Gexpl{λόγος}{λόγος}{Souvent : formule définitionnelle (pas “raison”
au sens moderne).}
ἕτερος.
\blank[line]
τὰ δὲ \Gexpl{συνώνυμα}{συνώνυμος}{Même nom et même définition ;
opposition structurante avec ὁμώνυμα.}
ὧν καὶ τὸ ὄνομα κοινόν, καὶ ὁ τῆς οὐσίας λόγος ὁ αὐτός·
τὰ δὲ \Gexpl{παρώνυμα}{παρώνυμος}{Dérivés : mots formés à partir d’un
autre (nom → adjectif, etc.).}
ἀπὸ τῶν ὀνομάτων μετωνομασμένα.
% --- Critique (leçon / édition)
\Gcrit{ἕτερος}{ἕτερος}{Variante de leçon selon les éditions : à
harmoniser avec l’édition de référence.}
\Gcrit{μετωνομασμένα}{μετονομάζω}{Graphie/accents parfois divergents :
vérifier la normalisation polytonique adoptée.}
% --- Traduction (français)
\Gtrans{ὁμώνυμα}{ὁμώνυμος}{Souvent “homonymes”, mais la logique est
celle des “équivoques” (nom commun, définition différente).}
\Gtrans{λόγος}{λόγος}{Ici : “définition” ; ailleurs : “raison”,
“discours”, “compte”.}
\Gtrans{παρώνυμα}{παρώνυμος}{“Paronymes” est technique ; “dérivés” est
plus lisible selon le public.}
% --- Impression en fin d'unité : rubriques remplies
\ApparatNotes
\stoptext
Le 30/01/2026 à 18:45, Pablo Rodriguez via ntg-context a écrit :
On 1/30/26 10:47, Mikael Sundqvist wrote:
Hi Pablo,
Not sure I understand what you are after with the notes. Does this do
what you want?
Hi Mikael,
I‘m afraid I only get the same as with my sample.
I would like to have invidiual notes synced, such as with these body texts:
\definecolumnset
[parallel]
[n=1,
align={lesswidows,lessclubs,lessbroken,lessorphans}]
\definesubcolumnset[parallel][L][1]
\definesubcolumnset[parallel][R][2]
\startsetups balance:example
\balancetopskip \strutht
\balancebottomskip \strutdp
\stopsetups
\starttext
\page[even]
\startcolumnset[parallel]
\dorecurse{50}
{\startsubcolumnset[L]
\samplefile{knuth}
\stopsubcolumnset
\startsubcolumnset[R]
\samplefile{zapf}
\stopsubcolumnset
\flushsubcolumnsets[spread]}
\stopcolumnset
\stoptext
I wonder wether this is possible.
Many thanks for your help,
Pablo
___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the
Wiki!
maillist :[email protected]
/https://mailman.ntg.nl/mailman3/lists/ntg-context.ntg.nl
webpage :https://www.pragma-ade.nl /https://context.aanhet.net (mirror)
archive :https://github.com/contextgarden/context
wiki :https://wiki.contextgarden.net
___________________________________________________________________________________
___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the
Wiki!
maillist :[email protected]
/https://mailman.ntg.nl/mailman3/lists/ntg-context.ntg.nl
webpage :https://www.pragma-ade.nl /https://context.aanhet.net (mirror)
archive :https://github.com/contextgarden/context
wiki :https://wiki.contextgarden.net
___________________________________________________________________________________
___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the
Wiki!
maillist : [email protected] /
https://mailman.ntg.nl/mailman3/lists/ntg-context.ntg.nl
webpage : https://www.pragma-ade.nl / https://context.aanhet.net (mirror)
archive : https://github.com/contextgarden/context
wiki : https://wiki.contextgarden.net
___________________________________________________________________________________