Skip to content

Upgrade to LibLouis 3.39.0 and carry the full table metadata - #26

Merged
Menelion merged 3 commits into
masterfrom
feature/liblouis-3.39
Sep 2, 2026
Merged

Menelion merged 3 commits into
masterfrom
feature/liblouis-3.39

Conversation

@Menelion

@Menelion Menelion commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Upgrades the bundled LibLouis to 3.39.0 and reworks translation table metadata to carry what LibLouis actually declares. Breaking API changes — this is a 3.0.0, not the 2.1.0 the upgrade alone would have been.

LibLouis 3.39.0

liblouis.dll rebuilt from the 3.39.0 tag for Windows x64 with UCS-4 (UTF-32) widechars, and all bundled tables refreshed: 478 files, up from 472. New: en-nz-g1.utb and en-nz-g2.ctb (English as used in New Zealand, following Braille Authority of New Zealand Aotearoa Trust policy), ht-g1.utb with ht-frenchcompatibility.uti (Haitian Creole), ru-math.uti (Russian mathematical braille), fr-bfu-comp6-puncs-math.uti. None removed. The release also brings an overhauled Portuguese grade 1 table, better back-translation for the Bulgarian, Italian and Biblical Hebrew tables, and 5000+ new English grade 3 hyphenation patterns.

The two Dutch tables generated from m4 templates are carried over from 3.38.0: their .in templates are unchanged since that tag and the expansion rule is a bare m4 -G with no defines, so the output is identical.

Two of the three nmake patches this build needed in 3.38.0 are gone — the missing stdbool.h include and the strncasecmp shim were fixed upstream, which also gained a Windows NMAKE CI job. One remains, and it is an upstream bug worth reporting separately: windows/include/config.h defines PACKAGE_VERSION as "liblouis-3.39.0" where autotools yields a bare "3.39.0", so an unpatched nmake build returns a version string lou_version() callers cannot parse.

Why the metadata rework

TranslationTable described a table as having one language and one type. LibLouis does not work that way — the manual states plainly that "the same key may appear multiple times in a table":

  • 22 tables declare several languages. he-IL.utb (Israeli braille) declares Hebrew, Arabic and English, and was discoverable only as Hebrew. ancient-languages-us.utb declares thirty-six.
  • 8 tables declare two types. The Swedish and Elfdalian 8-dot tables are both computer and literary; only the first survived.
  • Whole fields were discarded: region, grade, system, variant, version, locale, unicode-range and #-index-name were never read.
  • Two tables were missing from tables.json entirely. The jsonifier matched metadata lines by substring, so the word "language" inside the display names of ancient-languages-us.utb and ancient-languages-borger.utb shadowed their real language fields and both were filtered out as having no language. Fixed in Read the full table metadata, and every value of a repeated key liblouis-jsonify-tables#1, which this depends on.

What changed

Languages and TableTypes are lists, replacing the scalar Language and TableType. IndexName, Region, Grade, System, Variant, Version, Locale and UnicodeRange are new. Grade is a string because 1.2, 1.3, 1.4 and 1.5 are real grades.

language and region are RFC 4647 extended language ranges, not plain codes — he-IL.utb declares region: *-IL, IPA.utb declares language: *-fonipa. The new LanguageRange class implements the extended filtering of RFC 4647 §3.3.2, the rule LibLouis itself applies in lou_findTable, and FindByLanguage now matches that way instead of comparing strings. So FindByLanguage("en-GB") finds the tables declaring plain en, and FindByLanguage("ar") finds Israeli braille. Matching is asymmetric: a bare akk does not find a table declaring only akk-Latn.

TranslationTable becomes a sealed record rather than a readonly record struct. A record's synthesized equality compares list members by reference, so keeping the struct would have quietly dropped the value equality the type documents; Equals and GetHashCode are written out and compare the lists element by element.

Also added: TableCollection.FindByRegion(), FindComputer(), FindByGrade(); TranslationTable.MatchesLanguage(), MatchesRegion(), IsOfType(), IsGrade().

ListLanguages() gains curated English names for the codes .NET cannot name — jpa, oar, obm, xdm among the ones the multi-language tables bring in — and a test now fails if any bundled language would reach a UI as a bare code.

Migrating from 2.x

  • table.Language == "en" → table.MatchesLanguage("en")
  • table.TableType → table.TableTypes, or table.IsOfType(...) / IsLiteraryBraille()
  • FindByFileName(...) returns a nullable reference now, so table.Value.DisplayName → table.DisplayName
  • Positional construction → object initializer; FileName and DisplayName are required

Full notes in CHANGELOG.md.

Verification

130 tests pass in Debug and Release, including new ones that compile and translate with every table added or at risk in this release, and a guard that GetVersion() reports 3.39.x so a refreshed table set can never ship next to a stale DLL. dotnet pack produces a package carrying all 478 tables. A build with GITHUB_ACTIONS=true (warnings-as-errors, analyzers) is clean.

Merge order

accessmind/liblouis-jsonify-tables#1 is merged, so this is unblocked. The committed tables.json was re-generated from LLJT master after that merge and is byte-identical, so the two are in sync. Next step after this merges is tagging v3.0.0.

Rebuilt liblouis.dll from the 3.39.0 tag for Windows x64 with UCS-4
(UTF-32) widechars, and refreshed the bundled tables: 478 files, up from
472.

New tables: en-nz-g1.utb and en-nz-g2.ctb (English as used in New Zealand,
following Braille Authority of New Zealand Aotearoa Trust policy),
ht-g1.utb with ht-frenchcompatibility.uti (Haitian Creole),
ru-math.uti (Russian mathematical braille), and
fr-bfu-comp6-puncs-math.uti. None removed.

The two Dutch tables generated from m4 templates, nl-chardefs.uti and
nl-NL-g0.utb, are carried over from 3.38.0: their .in templates are
unchanged since that tag and the rule that expands them is a bare "m4 -G"
with no defines, so the output is identical.

The three nmake patches this build needed in 3.38.0 are down to one. The
missing stdbool.h include and the strncasecmp shim were both fixed
upstream in 3.39.0, which also gained a Windows NMAKE CI job. Still
patched locally: windows/include/config.h defines PACKAGE_VERSION as
"liblouis-3.39.0", where autotools yields a bare "3.39.0", so an
unpatched nmake build reports a version lou_version() callers cannot
parse.
TranslationTable described a table with a single language and a single
type. LibLouis does not: the manual states that "the same key may appear
multiple times in a table", and in 3.39.0 twenty-two tables declare
several languages. he-IL.utb (Israeli braille) declares Hebrew, Arabic and
English but was discoverable only as Hebrew; ancient-languages-us.utb
declares thirty-six. Eight tables declare both a computer and a literary
type, of which only the first survived.

Languages and TableTypes are therefore lists, replacing the scalar
Language and TableType. The metadata that was dropped altogether is now
carried too: IndexName, Region, Grade, System, Variant, Version, Locale
and UnicodeRange. Grade is a string because 1.2, 1.3, 1.4 and 1.5 are real
grades.

Two of those fields are RFC 4647 extended language ranges rather than
plain codes -- he-IL.utb declares "region: *-IL", IPA.utb declares
"language: *-fonipa" -- so LanguageRange implements the extended filtering
of RFC 4647 section 3.3.2, the rule LibLouis itself applies in
lou_findTable. FindByLanguage now matches that way instead of comparing
strings, so FindByLanguage("en-GB") finds the tables declaring plain "en",
and FindByLanguage("ar") finds Israeli braille.

TranslationTable becomes a sealed record rather than a readonly record
struct. The synthesized equality of a record compares list members by
reference, so keeping the struct would have quietly dropped the value
equality the type documents; Equals and GetHashCode are written out
instead and compare the lists element by element.

tables.json is regenerated with the updated LLJT, which also restores
ancient-languages-us.utb and ancient-languages-borger.utb: LLJT used to
match metadata lines by substring, so the word "language" in those tables'
display names shadowed their language fields and both were dropped from
the file entirely. 249 tables are now described, up from 244.

ListLanguages gains curated English names for the language codes .NET
cannot name -- jpa, oar, obm and xdm among the ones the multi-language
tables bring in -- and a test now fails if any bundled language would
surface to a UI as a bare code.
FindByLanguage("en-GB") returns every English table, which reads like a
bug and is not one: no LibLouis table narrows its language to en-GB. Every
English table declares plain "language: en" and puts the country in the
separate region field, so the RFC 4647 answer to "what can a British
reader use" really is all of them.

Selecting the specifically British tables means filtering by region, and
the language filter is still needed alongside it -- regions are extended
ranges too, so a bare FindByRegion("en-GB") also matches the two
Greek-for-English-speakers tables, whose region is the broader range "en".

Worth knowing before building a country picker: Unified English Braille
declares no region at all, being an international code rather than a
national one, so a region filter drops the very tables the U.K. has used
since 2011.

Adds a README section, a CHANGELOG note on the surprising case, and two
tests that pin both behaviors down.
@Menelion
Menelion force-pushed the feature/liblouis-3.39 branch from 1695b27 to 2c9717f Compare September 2, 2026 18:08
@Menelion
Menelion merged commit 952fcc1 into master Sep 2, 2026
1 check passed
@Menelion
Menelion deleted the feature/liblouis-3.39 branch September 2, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant