Skip to content

Commit da63df9

Browse files
manifests: the Adlam and Roman-Balochi script tables leave the .ts
The last two whole transliteration tables still living in code — noted in #746, skipped in #748 because moving three sets out of a file whose surrounding table stayed would have been incoherent. balochi gains a "roman" block: Balochi is written in BOTH scripts and the Roman half is a second full g2p (phonemic, where the Arabic half is defective). Vowel letters, long/short, consonants, and the retroflex/postalveolar diacritic maps. The DIACRITIC LOGIC stays in balochi.ts, which is the point of the split. fula gains an "adlam" block: 34 letters plus the combining marks. Two things worth the reader's time are now recorded there — that Adlam marks length and gemination with MARKS rather than doubling (so the transliteration doubles what it just emitted rather than mapping them), and that the marks are written as SURROGATE PAIRS because they are astral and JSON has no \u{…} form, and invisible besides. And the move surfaced a triplicate: Adlam digits were folded in THREE places — a lookup table in fulaAdlam.ts, foldAdlamDigits in numbers.ts, and core/unicode.ts's foldNativeDigits, which has carried the Adlam base since #745. Both copies now defer to the shared one. Pure relocation: 998 generated Adlam/Roman words phonemized against main, 0 differ. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Lc3WnUgogC7okV7n53vjr
1 parent ef681e5 commit da63df9

6 files changed

Lines changed: 89 additions & 31 deletions

File tree

src/languages/balochi/balochi.jsonc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,27 @@
4141
// ⚠ ORTHOGRAPHIC, NOT IPA, AND NOT THE ARABIC-SCRIPT LIST ABOVE. Balochi is written in both scripts;
4242
// phonemizeRoman scans the ROMAN spelling, and these are its vowel letters. "vowelLetters" above is
4343
// the Arabic-script set, for a different scan in the same engine.
44-
"romanVowels": ["a", "e", "i", "o", "u"],
44+
// ⚠ THE ROMAN ORTHOGRAPHY, a SECOND full g2p in the same engine — Balochi is written in both scripts and
45+
// this half is PHONEMIC where the Arabic half is defective, so it resolves to full IPA on its own rather
46+
// than leaning on the lexicon. The two halves share nothing but the language.
47+
"roman": {
48+
// The vowel letters (the scan's own class), then long vs short by diacritic. A MACRON writes length.
49+
// ⚠ ⟨e o⟩ ARE LONG IN BOTH COLUMNS: Balochi has no short /e o/, so an unmarked ⟨e⟩ is still [eː].
50+
"vowelLetters": ["a", "e", "i", "o", "u"],
51+
"long": { "a": "", "e": "", "i": "", "o": "", "u": "" },
52+
"short": { "a": "a", "e": "", "i": "i", "o": "", "u": "u" },
53+
// Base consonants. ⟨t d⟩ are DENTAL [t̪ d̪] — the retroflex series below is derived from them.
54+
"consonants": {
55+
"b": "b", "p": "p", "t": "", "d": "", "k": "k", "g": "ɡ", "q": "k", "f": "f", "v": "v",
56+
"s": "s", "z": "z", "š": "ʃ", "ž": "ʒ", "c": "t͡ʃ", "j": "d͡ʒ", "x": "x", "ġ": "ɣ", "h": "h",
57+
"m": "m", "n": "n", "r": "r", "l": "l", "w": "w", "y": "j"
58+
},
59+
// A DOT BELOW makes the consonant retroflex; a HÁČEK makes it postalveolar. Keyed by the phone the
60+
// base letter already produced (hence "t̪"/"d̪" here, not ⟨t⟩/⟨d⟩) except where the letter is the
61+
// only handle — balochi.ts tries the phone first, then the letter.
62+
"retroflex": { "t̪": "ʈ", "d̪": "ɖ", "r": "ɽ", "n": "ɳ", "s": "ʂ", "l": "ɭ" },
63+
"postalveolar": { "c": "t͡ʃ", "s": "ʃ", "z": "ʒ", "j": "d͡ʒ" }
64+
},
4565

4666
// NUMBERS — Balochi Arabic spellings; the canonical IPA is resolved through the cross-script lexicon
4767
// (balochi-lexicon.tsv now carries these headwords with their full vowels — the defective abjad cannot recover

src/languages/balochi/balochi.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ interface BalochiDef {
2121
consonants: Record<string, string>;
2222
vowels: Record<string, string>;
2323
vowelLetters: readonly string[];
24-
romanVowels: readonly string[];
24+
roman: {
25+
vowelLetters: readonly string[];
26+
long: Record<string, string>;
27+
short: Record<string, string>;
28+
consonants: Record<string, string>;
29+
retroflex: Record<string, string>;
30+
postalveolar: Record<string, string>;
31+
};
2532
numbers: BalNumbersDef;
2633
clausePunctuation: Record<string, string>;
2734
}
@@ -70,15 +77,14 @@ export function phonemizeArabic(word: string): string {
7077
}
7178

7279
// ── Roman-script g2p (phonemic orthography → full IPA) ────────────────────────────────────────────────────────
73-
const R_VOWEL = new Set(DEF.romanVowels); // the ROMAN-script vowel letters (balochi.jsonc)
74-
const R_LONG: Record<string, string> = { a: "aː", e: "eː", i: "iː", o: "oː", u: "uː" };
75-
const R_SHORT: Record<string, string> = { a: "a", e: "eː", i: "i", o: "oː", u: "u" }; // e,o have no short counterpart
76-
const R_CONS: Record<string, string> = {
77-
b: "b", p: "p", t: "t̪", d: "d̪", k: "k", g: "ɡ", q: "k", f: "f", v: "v", s: "s", z: "z",
78-
š: "ʃ", ž: "ʒ", c: "t͡ʃ", j: "d͡ʒ", x: "x", ġ: "ɣ", h: "h", m: "m", n: "n", r: "r", l: "l", w: "w", y: "j",
79-
};
80-
const RETRO: Record<string, string> = { "t̪": "ʈ", "d̪": "ɖ", r: "ɽ", n: "ɳ", s: "ʂ", l: "ɭ" };
81-
const POSTALV: Record<string, string> = { c: "t͡ʃ", s: "ʃ", z: "ʒ", j: "d͡ʒ" };
80+
// The Roman half's tables (balochi.jsonc `roman`). The diacritic LOGIC — which mark reaches for which
81+
// table, and that a macron may be combining or precomposed — is the scan below.
82+
const R_VOWEL = new Set(DEF.roman.vowelLetters);
83+
const R_LONG = DEF.roman.long;
84+
const R_SHORT = DEF.roman.short;
85+
const R_CONS = DEF.roman.consonants;
86+
const RETRO = DEF.roman.retroflex;
87+
const POSTALV = DEF.roman.postalveolar;
8288
const MACRON = "̄", HACEK = "̌", DOTBELOW = "̣";
8389

8490
/** One Balochi word in the Roman orthography → full IPA. Combining/precomposed macron→long vowel, háček→postalveolar,

src/languages/fula/fula.jsonc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,33 @@
1111
// vowel LENGTHENER doubles the vowel letter it just wrote. These are those letters.
1212
"latinVowels": ["a", "e", "i", "o", "u"],
1313

14+
// ⚠ THE ADLAM FRONT-END (𞤀𞤁𞤂𞤃, U+1E900–1E95F) — the second script Fula is registered for, transliterated
15+
// to the Boko/Latin orthography so the ONE g2p serves both. Adlam is the modern (1989, the Barry
16+
// brothers) phonemic alphabet for Fulfulde/Pular, in wide diaspora and West-African use.
17+
// Keys are the SMALL letters (U+1E922–1E943); the uppercase range folds onto them by a −0x22 offset in
18+
// fulaAdlam.ts, since Adlam case is not phonemic. The last four (va x gb z kp sh) are the loan letters,
19+
// mapped to the same Boko equivalents the Latin engine already uses so the two scripts stay consistent.
20+
"adlam": {
21+
"letters": {
22+
"𞤢": "a", "𞤣": "d", "𞤤": "l", "𞤥": "m", "𞤦": "b", "𞤧": "s", "𞤨": "p", "𞤩": "ɓ", "𞤪": "r", "𞤫": "e",
23+
"𞤬": "f", "𞤭": "i", "𞤮": "o", "𞤯": "ɗ", "𞤰": "ƴ", "𞤱": "w", "𞤲": "n", "𞤳": "k", "𞤴": "y", "𞤵": "u",
24+
"𞤶": "j", "𞤷": "c", "𞤸": "h", "𞤹": "q", "𞤺": "g", "𞤻": "ny", "𞤼": "t", "𞤽": "ŋ", "𞤾": "v", "𞤿": "x",
25+
"𞥀": "gb", "𞥁": "z", "𞥂": "kp", "𞥃": "sh"
26+
},
27+
// ⚠ ADLAM MARKS LENGTH AND GEMINATION WITH COMBINING MARKS, NOT BY DOUBLING — which is why the
28+
// transliteration has to DOUBLE the letter it just wrote rather than map these to anything:
29+
// lengtheners (ALIF U+1E944 / VOWEL LENGTHENER U+1E945) double the preceding VOWEL (𞤢𞥅 → "aa" → aː)
30+
// gemination (U+1E946) double the preceding CONSONANT (𞤦𞥆 → "bb")
31+
// ⚠ WRITTEN AS SURROGATE PAIRS: these are ASTRAL codepoints and JSON has no \u{…} form. They are
32+
// also INVISIBLE combining marks, so a literal would be an unreadable blank in this file.
33+
// U+1E944 ALIF LENGTHENER, U+1E945 VOWEL LENGTHENER.
34+
"lengtheners": ["\uD83A\uDD44", "\uD83A\uDD45"],
35+
"gemination": "\uD83A\uDD46", // U+1E946 GEMINATION MARK
36+
"hamza": "\uD83A\uDD47", // → the glottal ⟨q⟩, which the Latin g2p already reads as [ʔ]
37+
// CONSONANT MODIFIER / GEMINATE CONSONANT MODIFIER / NUKTA — rare foreign-sound marks, dropped.
38+
"drop": ["\uD83A\uDD48", "\uD83A\uDD49", "\uD83A\uDD4A"] // U+1E948–1E94A
39+
},
40+
1441
"script": ["Latin", "Adlam"],
1542
"name": "Fula (Fulfulde)",
1643
"provenance": "Cleanroom, rule-based g2p — Fulfulde Boko/Latin orthography is shallow and near-1:1; validated vs the r12a/Wikipedia references + languagesandnumbers. Sole census provider of the implosives ʄ (ƴ) / ɠ.",

src/languages/fula/fulaAdlam.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,17 @@
1212
* dropped. The extra loan letters (va x gb z kp sh) transliterate to their Boko equivalents — identical to how the
1313
* Latin engine already treats them, so the two scripts stay consistent.
1414
*/
15+
import { foldNativeDigits } from "../../core/unicode.ts";
1516
import { MANIFEST } from "./manifest.ts";
1617

17-
// Adlam SMALL LETTER (U+1E922–1E943) → Fula Boko/Latin. Uppercase (U+1E900–1E921) folds here by the −0x22 offset.
18-
const ADLAM: Record<string, string> = {
19-
"𞤢": "a", "𞤣": "d", "𞤤": "l", "𞤥": "m", "𞤦": "b", "𞤧": "s", "𞤨": "p", "𞤩": "ɓ", "𞤪": "r", "𞤫": "e",
20-
"𞤬": "f", "𞤭": "i", "𞤮": "o", "𞤯": "ɗ", "𞤰": "ƴ", "𞤱": "w", "𞤲": "n", "𞤳": "k", "𞤴": "y", "𞤵": "u",
21-
"𞤶": "j", "𞤷": "c", "𞤸": "h", "𞤹": "q", "𞤺": "g", "𞤻": "ny", "𞤼": "t", "𞤽": "ŋ", "𞤾": "v", "𞤿": "x",
22-
"𞥀": "gb", "𞥁": "z", "𞥂": "kp", "𞥃": "sh",
23-
};
24-
const DIGITS: Record<string, string> = { "𞥐": "0", "𞥑": "1", "𞥒": "2", "𞥓": "3", "𞥔": "4", "𞥕": "5", "𞥖": "6", "𞥗": "7", "𞥘": "8", "𞥙": "9" };
25-
const LENGTHENER = new Set(["\u{1E944}", "\u{1E945}"]); // ALIF / VOWEL LENGTHENER → double the preceding vowel
26-
const GEMINATION = "\u{1E946}"; // GEMINATION MARK → double the preceding consonant
27-
const HAMZA = "\u{1E947}"; // → the glottal ⟨q⟩ ([ʔ] in the g2p)
28-
const DROP = new Set(["\u{1E948}", "\u{1E949}", "\u{1E94A}"]); // CONSONANT MODIFIER / GEMINATE MOD / NUKTA (foreign)
18+
// The Adlam tables (fula.jsonc `adlam`): letter → Boko/Latin, and the combining marks. The TRANSLITERATION
19+
// LOGIC — case folding, and that a lengthener/gemination mark doubles what was just emitted rather than
20+
// mapping to anything — is the scan below.
21+
const ADLAM = MANIFEST.adlam.letters;
22+
const LENGTHENER = new Set(MANIFEST.adlam.lengtheners);
23+
const GEMINATION = MANIFEST.adlam.gemination;
24+
const HAMZA = MANIFEST.adlam.hamza;
25+
const DROP = new Set(MANIFEST.adlam.drop);
2926
const VOWELS = new Set(MANIFEST.latinVowels); // the LATIN spelling vowels (fula.jsonc)
3027

3128
/** Is any character of `s` in the Adlam block (U+1E900–1E95F)? */
@@ -50,7 +47,7 @@ export function adlamToLatin(word: string): string {
5047
if (ch === GEMINATION) { out += lastBase; continue; }
5148
if (ch === HAMZA) { out += "q"; lastBase = "q"; continue; }
5249
if (DROP.has(ch)) continue;
53-
const lat = ADLAM[ch] ?? DIGITS[ch];
50+
const lat = ADLAM[ch] ?? foldNativeDigits(ch);
5451
if (lat !== undefined) { out += lat; lastBase = lat; } else out += raw; // pass unknown through
5552
}
5653
return out;

src/languages/fula/manifest.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ export interface FulaManifest {
1010
rules: [string, string, boolean][];
1111
/** The LATIN spelling vowels the Adlam lengthener doubles; not IPA. */
1212
latinVowels: readonly string[];
13+
/** The Adlam front-end: letter → Boko/Latin, plus the combining marks. */
14+
adlam: {
15+
letters: Record<string, string>;
16+
lengtheners: readonly string[];
17+
gemination: string;
18+
hamza: string;
19+
drop: readonly string[];
20+
};
1321
clausePunctuation: Record<string, string>;
1422
}
1523

src/languages/fula/numbers.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ export function numberToWords(n: number): string {
102102
return r === 0 ? head : `${head} ${E} ${numberToWords(r)}`;
103103
}
104104

105-
/** Adlam digits 𞥐–𞥙 (U+1E950–1E959) → ASCII, so the number branch serves both registered scripts. */
106-
export function foldAdlamDigits(s: string): string {
107-
return [...s].map((ch) => {
108-
const c = ch.codePointAt(0)!;
109-
return c >= 0x1e950 && c <= 0x1e959 ? String(c - 0x1e950) : ch;
110-
}).join("");
111-
}
105+
/**
106+
* Adlam digits 𞥐–𞥙 (U+1E950–1E959) → ASCII, so the number branch serves both registered scripts.
107+
* Re-exported from the shared fold rather than re-implemented: core/unicode.ts carries the Adlam base in
108+
* NATIVE_DIGIT_BASES, and this file had a third copy of the same arithmetic (fulaAdlam.ts had a fourth,
109+
* as a lookup table).
110+
*/
111+
export { foldNativeDigits as foldAdlamDigits } from "../../core/unicode.ts";

0 commit comments

Comments
 (0)