Skip to content

afrikaans: the engine and normalizer constants move to the manifest - #756

Merged
christopherthompson81 merged 2 commits into
mainfrom
afrikaans-manifest
Aug 8, 2026
Merged

afrikaans: the engine and normalizer constants move to the manifest#756
christopherthompson81 merged 2 commits into
mainfrom
afrikaans-manifest

Conversation

@christopherthompson81

Copy link
Copy Markdown
Owner

Both Afrikaans files carried significant data in code.

afrikaans.ts — final devoicing, unstressed vowel reduction, the ⟨c⟩-softening letters, the stress-shifting loan suffixes, the unstressed-prefix IPA.
normalize.ts — ordinals 1–19, the multi-dot and single-dot abbreviations, the letter names, the word-acronym and spelled-out-run lists, the phonotactic onset/coda sets.

Each lands with the reasoning it already had — including the corpus evidence in normalize.ts, which is the part most worth keeping next to the data.

Two duplications fell out

  • The same six unstressed prefixes lived in three places: the stress regex, PREFIX_IPA, and the manifest's morphology.prefixUnstressed that the shared Germanic compound engine reads. One source now, and the engine throws if the IPA keys and the morphology list diverge (verified the throw fires).
  • The vowel class was spelled inline twice and had already drifted from vowelLetters — missing ⟨ö⟩, which the diacritic table maps to [ø]. Inert today (Afrikaans emits no stress, and ⟨ö⟩ only ever occurs beside another vowel, so the group regex merged it either way), but one edit from mattering. Both regexes are derived now.

⚠ One accident preserved on purpose

The ⟨c⟩ test was "eiyêéè".includes(w[i + 1] ?? "") — and includes("") is true, so a word-final ⟨c⟩ took the soft [s] by accident. Moving the list to the manifest turns that into a Set, which is false — silently flipping 38 of 1500 probe words to [k].

Kept byte-identical, with the accident documented in place. The af referee covers neither reading, so which is correct needs its own evidence rather than arriving as a refactor's side effect. Worth a follow-up; not this PR.

Verification

1513 cases — 1500 random words over the full Afrikaans alphabet, the ordinal table, and 13 normalizer strings — run against main: 0 differ. af referee unchanged at 1658/2220 (74.7%). npm run ci green — 228 files / 3139 tests. Manifest headers checked (non-vacuously).

Spot-checked the behaviour the dot-bound comment exists to protect: 'n Chinese skip still survives intact.

🤖 Generated with Claude Code

https://claude.ai/code/session_012Lc3WnUgogC7okV7n53vjr

afrikaans.ts: final devoicing, unstressed reduction, the ⟨c⟩-softening letters, the stress-shifting loan
suffixes, and the unstressed-prefix IPA.
normalize.ts: ordinals 1-19, the multi-dot and single-dot abbreviations, the letter names, the word
acronyms and spelled-out runs, and the phonotactic onset/coda lists.

Two duplications fell out, which is the usual reason this work is worth doing:

- THE SAME SIX UNSTRESSED PREFIXES lived in three places — the stress regex, PREFIX_IPA, and the
  manifest's morphology.prefixUnstressed that the shared Germanic compound engine reads. One source now,
  with a throw if the IPA keys and the morphology list ever diverge.
- THE VOWEL CLASS was spelled inline twice and had already DRIFTED from vowelLetters: it was missing ⟨ö⟩,
  which the diacritic table maps to [ø]. Inert today (Afrikaans emits no stress, and ⟨ö⟩ only occurs
  beside another vowel so the group regex merged it either way) but one edit from mattering. Both regexes
  are now built from the manifest.

⚠ ONE ACCIDENT PRESERVED DELIBERATELY. The ⟨c⟩ test was `"eiyêéè".includes(w[i+1] ?? "")`, and
includes("") is TRUE — so a WORD-FINAL ⟨c⟩ took the soft [s] by accident. A Set turns that false, which
would have flipped 38 of 1500 probe words to [k]. Kept identical, with the accident documented; the af
referee covers neither reading, so it needs its own evidence rather than a refactor's side effect.

Pure relocation: 1513 cases (random words + ordinals + normalizer strings) against main, 0 differ; af
referee unchanged at 1658/2220 (74.7%).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Lc3WnUgogC7okV7n53vjr
- The parity throw ran at MODULE INIT, and registry.ts imports afrikaans.ts statically — so one typo in
  the Afrikaans manifest would have made the whole package unimportable, taking every other language and
  the entire suite down with it. Moved to test/afrikaans.test.ts, and verified it still fails when the
  two lists diverge rather than trusting that it would.

- The byLen comment claimed longest-first ordering was needed so ⟨siteit⟩ beats ⟨teit⟩. It cannot be:
  both patterns are $-anchored and used only through .test(), so alternation order can't change a
  boolean. Says what it actually is now — cosmetic, kept for readability and for safety if either is
  ever used to capture.

- The preserved ⟨c⟩ accident now points at issue #757, so the intent outlives the comment. franc→frans
  and arc→ars are genuinely wrong; the af referee has no word-final ⟨c⟩ at all, so it needs a
  phonological call rather than a refactor's side effect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Lc3WnUgogC7okV7n53vjr
@christopherthompson81
christopherthompson81 merged commit afa136d into main Aug 8, 2026
2 checks passed
@christopherthompson81
christopherthompson81 deleted the afrikaans-manifest branch August 8, 2026 14:47
christopherthompson81 added a commit that referenced this pull request Aug 8, 2026
The rule is "soft [s] BEFORE a front vowel". Word-finally there is no following vowel, so the soft
branch cannot apply — but it did, because the test was `"eiyêéè".includes(w[i + 1] ?? "")` and
includes("") is TRUE for any string. franc→frans, arc→ars, bloc→blɔs.

#756 preserved that verbatim, correctly: a data move must not change output. Deciding it needed the
phonological call, and the call is that the accident INVERTED the rule rather than extending it — no
following letter means the soft condition is unmet, not met.

⚠ NOT REFEREE-CONFIRMED, and it cannot be: af.wiktionary-af.tsv has no word-final ⟨c⟩ at all — its 20
⟨c⟩ entries are surnames (Coetzee, Terblanche), ⟨ch⟩ words, and the letter name C→sɪə. af is unmoved at
1658/2220, exactly as predicted. The goldens pin the rule being self-consistent, and say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Lc3WnUgogC7okV7n53vjr
christopherthompson81 added a commit that referenced this pull request Aug 8, 2026
* afrikaans: a word-final ⟨c⟩ is [k], not the soft [s] (#757)

The rule is "soft [s] BEFORE a front vowel". Word-finally there is no following vowel, so the soft
branch cannot apply — but it did, because the test was `"eiyêéè".includes(w[i + 1] ?? "")` and
includes("") is TRUE for any string. franc→frans, arc→ars, bloc→blɔs.

#756 preserved that verbatim, correctly: a data move must not change output. Deciding it needed the
phonological call, and the call is that the accident INVERTED the rule rather than extending it — no
following letter means the soft condition is unmet, not met.

⚠ NOT REFEREE-CONFIRMED, and it cannot be: af.wiktionary-af.tsv has no word-final ⟨c⟩ at all — its 20
⟨c⟩ entries are surnames (Coetzee, Terblanche), ⟨ch⟩ words, and the letter name C→sɪə. af is unmoved at
1658/2220, exactly as predicted. The goldens pin the rule being self-consistent, and say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Lc3WnUgogC7okV7n53vjr

* review: the referee DOES hold a word-final ⟨c⟩ — my reason was wrong, the conclusion was not

I wrote that af.wiktionary-af.tsv contains no word-final ⟨c⟩, and that its C→sɪə entry reaches output
through the initialism path. Both false. The entry IS a word-final ⟨c⟩, eval.ts feeds af through
phonemizeWord directly, and the initialism normalizer only fires on runs of two or more letters — so
that entry traverses this exact branch, going s→k with this change.

The conclusion survives: it misses both ways against sɪə, which is why the folded backbone is unmoved
at 1658/2220. But the stated reason would have told the next reader that nothing exercises the branch,
which is the opposite of true. My original grep was case-sensitive and lowercase-only; I then saw the C
entry in later output and explained it away with an assumption I never checked.

Filed the underlying gap as #761: a bare single letter is read as a sound rather than spelled as its
name (B→p too), because the initialism normalizer needs ≥2 letters. Also moved the new test into the
g2p describe block, where it belongs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Lc3WnUgogC7okV7n53vjr

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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