lyric-romanizer 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 — 2026-07-25
4
+
5
+ Architecture pass: every piece of per-script knowledge now lives in one table per side of the light/heavy packaging seam, engines are lazy and injectable, and the universal fallback is observable. Public interface changes are **additive**; the two deliberate output-behavior changes are listed below with their rationale.
6
+
7
+ ### Added
8
+
9
+ - **Engine adapters** — `createRomanizer({ engines })` overrides any built-in engine or plugs an adapter for externally-romanized scripts (`arabic`, `hebrew`, `malayalam`, `bengali`, `other`). The library still performs zero network I/O by default; `UnsupportedRomanizationError` is now precisely "no engine for this script".
10
+ - **`RomanizeResult.fallbacks`** — per-line flags, `true` where the script engine failed and the line was universally transliterated as a last resort. Previously this degradation was silent and indistinguishable from success.
11
+ - **`requiresExternalRomanization` on the detector subpath** — pure classification moved to the light, engine-free side; `lyric-romanizer/detector` can now answer the full pre-romanization triage (which script → is it latin → needs external) with zero engine payload. Still re-exported from the main entry.
12
+ - **`RomanizeEngine`, `RomanizeEngineContext`, `ExternalScript`** exported types.
13
+ - **Plain Node ESM support** — `import 'lyric-romanizer'` previously crashed outside bundlers (`@romanize/korean` named-export interop); engines now load through interop-safe dynamic imports.
14
+ - **Lazy engine loading** — every engine loads on first use. Importing the main entry no longer executes any engine package; a Korean-only consumer never loads pinyin-pro or kuroshiro. Failed loads retry on the next call and degrade to the universal fallback (flagged) rather than crashing playback.
15
+
16
+ ### Changed — deliberate output-behavior changes
17
+
18
+ 1. **Latin guard under a pinned script.** `romanizeLines` pins one script for the whole array; lines with ASCII letters and no character of any detectable script are now returned unchanged instead of being fed to the pinned engine. Before: `romanizeLines(['Hello', '世界'])` → `['H e l l o', 'shì jiè']` (pinyin-pro spaces out Latin text). After: `['Hello', 'shì jiè']`. Rationale: the engine cannot romanize what isn't its script; the old output was mangled.
19
+ 2. **Out-of-contract script strings throw.** Untyped callers pinning a string outside `ScriptType` (e.g. `{ script: 'greek' }`) now get `UnsupportedRomanizationError` instead of a silent universal transliteration. This includes names inherited from `Object.prototype`: `{ script: 'toString' }` previously resolved to the inherited method and returned `'[object Undefined]'`. Unaffected: auto-detected scripts (detection only ever returns `ScriptType` members), and empty or letterless lines, which short-circuit to a no-op before any script is consulted — as they always did.
20
+
21
+ All romanized output strings are byte-identical to 0.2.0 apart from those two cases, verified against a golden corpus across every supported script, dialect, preset, and error path. Two further notes for anyone diffing behavior:
22
+
23
+ - `romanizeLine` with an engineless pinned script (e.g. `{ script: 'arabic' }`) still throws even when the line is pure Latin — the engine check precedes the latin guard, matching `romanizeLines`, which rejects such a script before looking at any line.
24
+ - `NON_LATIN_SCRIPT_RE` is now derived from the range table, so its `.source` text changed: ranges are emitted in ascending code-point order, and the adjacent Hebrew and Arabic ranges collapse into one (8 groups → 7). The matching contract is unchanged — `.flags` is still `''` and `.test()` agrees with the old literal for every code point in the BMP, asserted by a test sweep. Only a consumer that snapshots, hashes, or string-compares the pattern text is affected; one that uses it as a matcher is not.
25
+
26
+ ### Fixed
27
+
28
+ - README Quick Start example was impossible: `romanizeLines(['你好世界', 'こんにちは'])` returns `{ script: 'japanese' }` (kana is definitive), not `{ script: 'chinese' }` as documented. The example and the detection-granularity contract are now documented accurately.
29
+ - Documented romanization outputs corrected against the actual engines (in all nine READMEs): Thai `สวัสดี` → `swasdi` (was `sawatdi`), Gurmukhi `ਨਮਸਤੇ` → `namasate` (was `namaste`; standard Punjabi spelling has no virama, so both consonants keep the inherent vowel), and the Kannada sample is now the correctly-spelled `ನಮಸ್ತೇ` → `namaste`. The feature list said "7 Indic scripts"; there are 6.
30
+ - Dead defensive throws in the Sanscript branch (unreachable behind the catch-all fallback) removed; the silent `default:` switch arm is gone — the engine table is total over the script union, so a forgotten engine row is a **compile error** instead of silent garbage.
31
+ - `NON_LATIN_SCRIPT_RE` is now derived from the same per-script range table that drives detection (previously a hand-maintained duplicate with a "keep aligned" comment). Verified byte-for-byte identical across the BMP.
32
+
33
+ ### Internal
34
+
35
+ - Per-script knowledge concentrated: detection ranges + external classification in `SCRIPT_METADATA` (detector side), engine bindings in one total record (romanizer side). Adding a script is one row per side, compiler-enforced.
36
+ - `selectCyrillicPreset` extracted as a pure, unit-tested function (the Ukrainian-detection headline feature previously had zero coverage).
37
+ - Dependencies updated to latest (TypeScript 7, vitest 4.1.10, pinyin-pro 3.28.2). CI, Dependabot (daily, grouped), and a weekly latest-deps canary keep every engine on its newest release.
38
+ - Test suite grew from 19 to 44 tests: injection seam, fallback observability, lazy-init retry, preset selection, latin guard, regex-derivation equivalence, tie-break regression, BMP-range invariant.
39
+ - `pnpm run smoke` (`scripts/smoke.mjs`) exercises the built package under plain Node ESM: every engine's interop chain, the fallback path, both entry points, and an assertion that `dist/detector.js` imports nothing. Runs in CI and in the canary; `pnpm run verify` chains typecheck + build + test + smoke.
40
+ - `package.json` declares `"sideEffects": false` (safe now that engines are lazy) and ships `src` so the published source maps resolve.
package/README.md CHANGED
@@ -3,10 +3,28 @@
3
3
  [![npm version](https://img.shields.io/npm/v/lyric-romanizer.svg)](https://www.npmjs.com/package/lyric-romanizer)
4
4
  [![license](https://img.shields.io/npm/l/lyric-romanizer.svg)](https://github.com/thedavidweng/lyric-romanizer/blob/main/LICENSE)
5
5
 
6
+ [English](https://github.com/thedavidweng/lyric-romanizer#readme) | [日本語](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ja.md) | [中文(简体)](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.zh-CN.md) | [中文(粵語)](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.zh-yue.md) | [한국어](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ko.md) | [Русский](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ru.md) | [हिन्दी](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.hi.md) | [தமிழ்](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ta.md) | [ไทย](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.th.md)
7
+
8
+ > **Philosophy: Don't reinvent the wheel.**
9
+ > This project deliberately avoids building romanization logic from scratch. Instead, it composes best-in-class, community-maintained libraries — one for each script — and focuses on the orchestration layer: script detection, engine routing, dialect handling, and a unified API. Every romanization engine in the dependency list is a dedicated, battle-tested library maintained by domain experts. That's the point.
10
+
6
11
  Script detection and local romanization engine for lyrics. Supports 12 scripts across Japanese, Chinese (Mandarin and Cantonese), Korean, Cyrillic, Indic, Tamil, and Thai — all running locally with zero API calls.
7
12
 
8
13
  Extracted from [Spotify Karaoke](https://github.com/haroldalan/spotify-karaoke). Used by [OpenKara](https://github.com/thedavidweng/openkara).
9
14
 
15
+ ## Features
16
+
17
+ - **Zero API calls** — all romanization runs locally
18
+ - **Auto script detection** — pass in text, get back the detected script
19
+ - **12+ scripts** — Japanese, Chinese, Korean, Cyrillic, 6 Indic scripts, Tamil, Thai
20
+ - **Cantonese support** — Jyutping alongside default Mandarin Pinyin
21
+ - **Lightweight detector subpath** — import only script detection (and external-script classification) without pulling in romanization engines
22
+ - **Lazy engines** — every engine loads on first use; importing the main entry costs nothing until you romanize
23
+ - **Pluggable engines** — override any built-in engine, or plug your own adapter for externally-romanized scripts
24
+ - **Observable fallbacks** — per-line flags tell you when an engine failed and a line was transliterated as a last resort
25
+ - **Ukrainian-aware Cyrillic** — auto-detects Ukrainian-specific characters and applies the correct transliteration preset
26
+ - **Runs in plain Node ESM** — no bundler required
27
+
10
28
  ## Installation
11
29
 
12
30
  ```bash
@@ -29,14 +47,16 @@ import { createRomanizer, detectScript } from 'lyric-romanizer';
29
47
  const romanizer = createRomanizer();
30
48
 
31
49
  // Auto-detect script and romanize
32
- const result = await romanizer.romanizeLines(['你好世界', 'こんにちは']);
33
- // { script: 'chinese', lines: ['nǐ hǎo shì jiè', 'こんにちは'] }
50
+ const result = await romanizer.romanizeLines(['你好世界', '很高兴认识你']);
51
+ // { script: 'chinese', lines: ['nǐ hǎo shì jiè', 'hěn gāo xìng rèn shi nǐ'], fallbacks: [false, false] }
34
52
 
35
53
  // Romanize a single line
36
54
  const line = await romanizer.romanizeLine('안녕하세요');
37
55
  // 'annyeonghaseyo'
38
56
  ```
39
57
 
58
+ > **Detection granularity** — `romanizeLines` detects the dominant script **once across all lines** and pins it for every line (deliberate: kanji-only lines inside a Japanese song must reach the Japanese engine, and any kana in the array pins `japanese`). Calling `romanizeLine` in a loop instead detects **per line** and may route each line to a different engine. Pure-Latin lines under a pinned script are returned unchanged. See [Mixed-script arrays](#mixed-script-arrays).
59
+
40
60
  ## API
41
61
 
42
62
  ### Imports
@@ -52,7 +72,12 @@ import {
52
72
  } from 'lyric-romanizer';
53
73
 
54
74
  // Detector-only subpath — lightweight, no romanization dependencies
55
- import { detectScript, isLatinScript, NON_LATIN_SCRIPT_RE } from 'lyric-romanizer/detector';
75
+ import {
76
+ detectScript,
77
+ isLatinScript,
78
+ requiresExternalRomanization,
79
+ NON_LATIN_SCRIPT_RE,
80
+ } from 'lyric-romanizer/detector';
56
81
  ```
57
82
 
58
83
  ### Types
@@ -63,23 +88,33 @@ type ScriptType =
63
88
  | 'devanagari' | 'gujarati' | 'gurmukhi' | 'telugu'
64
89
  | 'kannada' | 'odia' | 'tamil' | 'malayalam'
65
90
  | 'bengali' | 'arabic' | 'hebrew' | 'thai'
66
- | 'other';
91
+ | 'latin' | 'other';
67
92
 
68
93
  interface Romanizer {
69
94
  romanizeLine(line: string, options?: RomanizeOptions): Promise<string>;
70
95
  romanizeLines(lines: readonly string[], options?: RomanizeOptions): Promise<RomanizeResult>;
71
96
  }
72
97
 
98
+ // `dialect` is only honored for 'chinese'; every other script ignores it.
73
99
  type RomanizeOptions = { script?: ScriptType; dialect?: 'mandarin' | 'cantonese' };
74
- type RomanizeResult = { script: ScriptType; lines: string[] };
75
- type RomanizerOptions = { japaneseDictPath?: string };
76
- ```
77
100
 
78
- ### Functions
101
+ // `fallbacks` is aligned with `lines`: true where the engine failed and the
102
+ // line was universally transliterated as a last resort.
103
+ type RomanizeResult = { script: ScriptType; lines: string[]; fallbacks?: boolean[] };
79
104
 
80
- #### `createRomanizer(options?)`
105
+ // An engine adapter: romanizes one line of its script. Throwing (or
106
+ // rejecting) triggers the universal transliteration fallback.
107
+ type RomanizeEngine = (line: string, context: { dialect: 'mandarin' | 'cantonese' }) => string | Promise<string>;
81
108
 
82
- Factory that returns a `Romanizer` instance. The Kuroshiro engine (Japanese) is lazily initialized on first use and cached.
109
+ type RomanizerOptions = {
110
+ japaneseDictPath?: string;
111
+ engines?: Partial<Record<ScriptType, RomanizeEngine>>;
112
+ };
113
+ ```
114
+
115
+ ### `createRomanizer(options?)`
116
+
117
+ Factory that returns a `Romanizer` instance. Every engine is lazily loaded on first use and cached — a failed load retries on the next call.
83
118
 
84
119
  ```ts
85
120
  const romanizer = createRomanizer();
@@ -90,7 +125,27 @@ const romanizer = createRomanizer({
90
125
  });
91
126
  ```
92
127
 
93
- #### `detectScript(lines)`
128
+ #### Engine adapters
129
+
130
+ `options.engines` overrides the built-in engine for a script — or plugs an engine into a script that has none built in (`arabic`, `hebrew`, `malayalam`, `bengali`, `other`), so `romanizeLines` can handle every script through one interface. By default the library performs **zero network I/O**; plugging a remote adapter is an explicit caller decision.
131
+
132
+ ```ts
133
+ const romanizer = createRomanizer({
134
+ engines: {
135
+ // Bring your own external romanization for scripts without a local engine.
136
+ arabic: async (line) => myTransliterationApi(line),
137
+ // Or replace a built-in engine (e.g. with a fake in tests).
138
+ korean: (line) => myKoreanRomanizer(line),
139
+ },
140
+ });
141
+
142
+ await romanizer.romanizeLines(['مرحبا']);
143
+ // { script: 'arabic', lines: [...], fallbacks: [false] } — no longer throws
144
+ ```
145
+
146
+ Scripts without an engine — built-in or injected — still throw `UnsupportedRomanizationError`.
147
+
148
+ ### `detectScript(lines)`
94
149
 
95
150
  Detects the dominant script in the given text lines. Checks for Japanese kana first (definitive), then scores all other scripts by character count.
96
151
 
@@ -102,7 +157,7 @@ detectScript(['Hello world']); // 'latin'
102
157
  detectScript(['123 ???']); // 'other'
103
158
  ```
104
159
 
105
- #### `isLatinScript(lines)`
160
+ ### `isLatinScript(lines)`
106
161
 
107
162
  Fast check — returns `true` if the text contains only Latin letters (no CJK, Cyrillic, Indic, etc.). Useful for skipping romanization entirely.
108
163
 
@@ -112,9 +167,9 @@ isLatinScript(['안녕하세요']); // false
112
167
  isLatinScript(['♪♪♪']); // false (no letters)
113
168
  ```
114
169
 
115
- #### `requiresExternalRomanization(script)`
170
+ ### `requiresExternalRomanization(script)`
116
171
 
117
- Returns `true` for scripts that cannot be romanized locally and require an external API.
172
+ Returns `true` for scripts that have no built-in engine and require an external API. Importable from the lightweight `lyric-romanizer/detector` subpath, so answering "should I branch to an external service?" costs zero engine payload.
118
173
 
119
174
  ```ts
120
175
  requiresExternalRomanization('chinese'); // false
@@ -122,15 +177,13 @@ requiresExternalRomanization('arabic'); // true
122
177
  requiresExternalRomanization('malayalam'); // true
123
178
  ```
124
179
 
125
- ### `Romanizer` Interface
126
-
127
- #### `romanizer.romanizeLine(line, options?)`
180
+ ### `romanizer.romanizeLine(line, options?)`
128
181
 
129
- Romanizes a single line. If `script` is omitted, it is auto-detected via `detectScript`. Returns the original line unchanged for Latin text or non-letter content.
182
+ Romanizes a single line. If `script` is omitted, it is auto-detected **per line** via `detectScript` — looping over `romanizeLine` may route each line to a different engine, unlike `romanizeLines`, which pins one script for the whole array. Returns the original line unchanged for Latin text or non-letter content.
130
183
 
131
- For Chinese text, the `dialect` option controls the romanization system: `'mandarin'` (default) uses Pinyin, `'cantonese'` uses [Jyutping](https://github.com/CanCLID/to-jyutping).
184
+ For Chinese text, the `dialect` option controls the romanization system: `'mandarin'` (default) uses Pinyin, `'cantonese'` uses [Jyutping](https://github.com/CanCLID/to-jyutping). Other scripts ignore `dialect`.
132
185
 
133
- **Throws** `UnsupportedRomanizationError` for external scripts.
186
+ **Throws** `UnsupportedRomanizationError` for scripts without an engine (built-in or injected).
134
187
 
135
188
  ```ts
136
189
  await romanizer.romanizeLine('你好世界');
@@ -149,21 +202,21 @@ await romanizer.romanizeLine('مرحبا');
149
202
  // throws UnsupportedRomanizationError { script: 'arabic' }
150
203
  ```
151
204
 
152
- #### `romanizer.romanizeLines(lines, options?)`
205
+ ### `romanizer.romanizeLines(lines, options?)`
153
206
 
154
- Romanizes multiple lines in parallel. Returns the detected script and romanized lines.
207
+ Romanizes multiple lines in parallel. Detects the dominant script **once across all lines** and pins it for every line (see [Mixed-script arrays](#mixed-script-arrays)). Returns the script, the romanized lines, and per-line `fallbacks` flags — `true` where the engine failed and the line was universally transliterated as a last resort.
155
208
 
156
209
  ```ts
157
- const { script, lines } = await romanizer.romanizeLines([
210
+ const { script, lines, fallbacks } = await romanizer.romanizeLines([
158
211
  'สวัสดี',
159
212
  'ชาวโลก',
160
213
  ]);
161
- // { script: 'thai', lines: ['sawatdi', 'chaolok'] }
214
+ // { script: 'thai', lines: ['swasdi', 'chaolok'], fallbacks: [false, false] }
162
215
  ```
163
216
 
164
217
  ### `UnsupportedRomanizationError`
165
218
 
166
- Thrown when attempting to romanize a script that requires an external API. Has a `script` property for programmatic handling.
219
+ Thrown when attempting to romanize a script that has no engine — built in or injected via `options.engines`. Has a `script` property for programmatic handling.
167
220
 
168
221
  ```ts
169
222
  try {
@@ -190,12 +243,12 @@ try {
190
243
  | Cyrillic | [cyrillic-to-translit-js](https://github.com/greybax/CyrillicToTranslitJS) | `Привет` → `Privet` |
191
244
  | Devanagari | [sanscript](https://github.com/indic-transliteration/sanscript) | `नमस्ते` → `namaste` |
192
245
  | Gujarati | [sanscript](https://github.com/indic-transliteration/sanscript) | `નમસ્તે` → `namaste` |
193
- | Gurmukhi | [sanscript](https://github.com/indic-transliteration/sanscript) | `ਨਮਸਤੇ` → `namaste` |
246
+ | Gurmukhi | [sanscript](https://github.com/indic-transliteration/sanscript) | `ਨਮਸਤੇ` → `namasate` |
194
247
  | Telugu | [sanscript](https://github.com/indic-transliteration/sanscript) | `నమస్తే` → `namaste` |
195
- | Kannada | [sanscript](https://github.com/indic-transliteration/sanscript) | `నಮస్తె` → `namaste` |
248
+ | Kannada | [sanscript](https://github.com/indic-transliteration/sanscript) | `ನಮಸ್ತೇ` → `namaste` |
196
249
  | Odia | [sanscript](https://github.com/indic-transliteration/sanscript) | `ନମସ୍ତେ` → `namaste` |
197
250
  | Tamil | [tamil-romanizer](https://github.com/haroldalan/tamil-romanizer) | `வணக்கம்` → `vanakkam` |
198
- | Thai | [@dehoist/romanize-thai](https://github.com/Dehoist/Open-Source) | `สวัสดี` → `sawatdi` |
251
+ | Thai | [@dehoist/romanize-thai](https://github.com/Dehoist/Open-Source) | `สวัสดี` → `swasdi` |
199
252
 
200
253
  ### External (requires API)
201
254
 
@@ -207,13 +260,26 @@ try {
207
260
  | Hebrew | Google Translate `dt=rm` |
208
261
  | Other | Google Translate `dt=rm` |
209
262
 
210
- Use `requiresExternalRomanization()` to detect these and branch to your preferred API.
263
+ Use `requiresExternalRomanization()` to detect these and branch to your preferred API — or plug the API in once as an [engine adapter](#engine-adapters) and let `romanizeLines` handle every script.
264
+
265
+ ## Script-Specific Notes
266
+
267
+ ### Mixed-script arrays
268
+
269
+ `romanizeLines` pins the **dominant** script of the whole array to every line. This is deliberate: a kanji-only line inside a Japanese song is indistinguishable from Chinese on its own (kanji and hanzi share the same Unicode block), so only whole-array context routes it to the right engine. Consequences worth knowing:
270
+
271
+ - Any kana anywhere in the array pins the whole array to `japanese` — kana is definitive proof.
272
+ - A line of a *different* non-Latin script inside the array is still fed to the pinned engine.
273
+ - Pure-Latin lines (an English chorus inside a CJK song) are **returned unchanged** instead of being fed to the pinned engine.
274
+ - Per-line `fallbacks` flags report when an engine failed and a line degraded to universal transliteration.
275
+
276
+ If you need true per-line engine routing, call `romanizeLine` in a loop — it detects per line.
211
277
 
212
- ## Cyrillic Detection
278
+ ### Cyrillic Detection
213
279
 
214
280
  Cyrillic auto-detects Ukrainian-specific characters (`і`, `ї`, `є`, `ґ`) and applies the Ukrainian transliteration preset. All other Cyrillic text defaults to Russian.
215
281
 
216
- ## Cantonese Support
282
+ ### Cantonese Support
217
283
 
218
284
  Chinese text defaults to Mandarin (Pinyin). Pass `dialect: 'cantonese'` in `RomanizeOptions` to romanize Chinese text to [Jyutping](https://github.com/CanCLID/to-jyutping) instead.
219
285
 
@@ -1,5 +1,79 @@
1
1
  import type { ScriptType } from './types.js';
2
+ declare const SCRIPT_METADATA: {
3
+ readonly japanese: {
4
+ readonly ranges: readonly [readonly [12352, 12543]];
5
+ readonly definitive: true;
6
+ };
7
+ readonly chinese: {
8
+ readonly ranges: readonly [readonly [19968, 40959]];
9
+ };
10
+ readonly korean: {
11
+ readonly ranges: readonly [readonly [44032, 55215]];
12
+ };
13
+ readonly cyrillic: {
14
+ readonly ranges: readonly [readonly [1024, 1279]];
15
+ };
16
+ readonly devanagari: {
17
+ readonly ranges: readonly [readonly [2304, 2431]];
18
+ };
19
+ readonly gujarati: {
20
+ readonly ranges: readonly [readonly [2688, 2815]];
21
+ };
22
+ readonly gurmukhi: {
23
+ readonly ranges: readonly [readonly [2560, 2687]];
24
+ };
25
+ readonly telugu: {
26
+ readonly ranges: readonly [readonly [3072, 3199]];
27
+ };
28
+ readonly kannada: {
29
+ readonly ranges: readonly [readonly [3200, 3327]];
30
+ };
31
+ readonly odia: {
32
+ readonly ranges: readonly [readonly [2816, 2943]];
33
+ };
34
+ readonly tamil: {
35
+ readonly ranges: readonly [readonly [2944, 3071]];
36
+ };
37
+ readonly malayalam: {
38
+ readonly ranges: readonly [readonly [3328, 3455]];
39
+ readonly external: true;
40
+ };
41
+ readonly bengali: {
42
+ readonly ranges: readonly [readonly [2432, 2559]];
43
+ readonly external: true;
44
+ };
45
+ readonly arabic: {
46
+ readonly ranges: readonly [readonly [1536, 1791]];
47
+ readonly external: true;
48
+ };
49
+ readonly hebrew: {
50
+ readonly ranges: readonly [readonly [1424, 1535]];
51
+ readonly external: true;
52
+ };
53
+ readonly thai: {
54
+ readonly ranges: readonly [readonly [3584, 3711]];
55
+ };
56
+ readonly latin: {
57
+ readonly ranges: readonly [];
58
+ };
59
+ readonly other: {
60
+ readonly ranges: readonly [];
61
+ readonly external: true;
62
+ };
63
+ };
64
+ /**
65
+ * Scripts classified as external in SCRIPT_METADATA. The engine table in
66
+ * romanizer.ts excludes exactly these members, so reclassifying a script here
67
+ * surfaces as a compile error there.
68
+ */
69
+ export type ExternalScript = {
70
+ [K in ScriptType]: (typeof SCRIPT_METADATA)[K] extends {
71
+ external: true;
72
+ } ? K : never;
73
+ }[ScriptType];
2
74
  export declare const NON_LATIN_SCRIPT_RE: RegExp;
3
75
  export declare function isLatinScript(lines: readonly string[]): boolean;
4
76
  export declare function detectScript(lines: readonly string[]): ScriptType;
77
+ export declare function requiresExternalRomanization(script: ScriptType): boolean;
78
+ export {};
5
79
  //# sourceMappingURL=detector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAwB7C,eAAO,MAAM,mBAAmB,QAC8E,CAAC;AAE/G,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAG/D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAcjE"}
1
+ {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAwB7C,QAAA,MAAM,eAAe;;iBAGP,MAAM;iBAAsB,UAAU;;;iBACvC,MAAM;;;iBACP,MAAM;;;iBACJ,MAAM;;;iBACJ,MAAM;;;iBACR,MAAM;;;iBACN,MAAM;;;iBACR,MAAM;;;iBACL,MAAM;;;iBACT,MAAM;;;iBACL,MAAM;;;iBACF,MAAM;iBAAsB,QAAQ;;;iBACtC,MAAM;iBAAsB,QAAQ;;;iBACrC,MAAM;iBAAsB,QAAQ;;;iBACpC,MAAM;iBAAsB,QAAQ;;;iBACtC,MAAM;;;iBACL,MAAM;;;iBACN,MAAM;iBAAM,QAAQ;;CACoB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;KAC1B,CAAC,IAAI,UAAU,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,CAAC,GAAG,KAAK;CACtF,CAAC,UAAU,CAAC,CAAC;AAwCd,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AAEF,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAG/D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAejE;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAMxE"}
package/dist/detector.js CHANGED
@@ -1,33 +1,75 @@
1
1
  const LETTER_RE = /\p{L}/u;
2
- const SCRIPT_SCORES = [
3
- ['chinese', /[\u4E00-\u9FFF]/g],
4
- ['korean', /[\uAC00-\uD7AF]/g],
5
- ['cyrillic', /[\u0400-\u04FF]/g],
6
- ['devanagari', /[\u0900-\u097F]/g],
7
- ['gujarati', /[\u0A80-\u0AFF]/g],
8
- ['gurmukhi', /[\u0A00-\u0A7F]/g],
9
- ['telugu', /[\u0C00-\u0C7F]/g],
10
- ['kannada', /[\u0C80-\u0CFF]/g],
11
- ['odia', /[\u0B00-\u0B7F]/g],
12
- ['tamil', /[\u0B80-\u0BFF]/g],
13
- ['malayalam', /[\u0D00-\u0D7F]/g],
14
- ['bengali', /[\u0980-\u09FF]/g],
15
- ['arabic', /[\u0600-\u06FF]/g],
16
- ['hebrew', /[\u0590-\u05FF]/g],
17
- ['thai', /[\u0E00-\u0E7F]/g],
18
- ];
19
- // Keep this range aligned with extension content script behavior for the
20
- // "latin fast path" check used during mode switching.
21
- export const NON_LATIN_SCRIPT_RE = /[\u3040-\u30FF\u4E00-\u9FFF\uAC00-\uD7AF\u0400-\u04FF\u0900-\u0D7F\u0600-\u06FF\u0590-\u05FF\u0E00-\u0E7F]/;
2
+ // Single source of truth for per-script knowledge on the light (engine-free)
3
+ // side of the packaging seam: detection ranges and local/external
4
+ // classification. SCRIPT_SCORES, NON_LATIN_SCRIPT_RE, and
5
+ // requiresExternalRomanization are all derived from this table, and the
6
+ // engine table in romanizer.ts is compile-time checked against the
7
+ // classification, so adding a script here is the single detector-side edit.
8
+ //
9
+ // Entry order is load-bearing: it is the tie-break priority of detectScript —
10
+ // on an equal character count, the earlier entry wins.
11
+ const SCRIPT_METADATA = {
12
+ // Kana is definitive: kanji shares the Han block with chinese, so any kana
13
+ // must decide 'japanese' before Han scoring runs.
14
+ japanese: { ranges: [[0x3040, 0x30ff]], definitive: true },
15
+ chinese: { ranges: [[0x4e00, 0x9fff]] },
16
+ korean: { ranges: [[0xac00, 0xd7af]] },
17
+ cyrillic: { ranges: [[0x0400, 0x04ff]] },
18
+ devanagari: { ranges: [[0x0900, 0x097f]] },
19
+ gujarati: { ranges: [[0x0a80, 0x0aff]] },
20
+ gurmukhi: { ranges: [[0x0a00, 0x0a7f]] },
21
+ telugu: { ranges: [[0x0c00, 0x0c7f]] },
22
+ kannada: { ranges: [[0x0c80, 0x0cff]] },
23
+ odia: { ranges: [[0x0b00, 0x0b7f]] },
24
+ tamil: { ranges: [[0x0b80, 0x0bff]] },
25
+ malayalam: { ranges: [[0x0d00, 0x0d7f]], external: true },
26
+ bengali: { ranges: [[0x0980, 0x09ff]], external: true },
27
+ arabic: { ranges: [[0x0600, 0x06ff]], external: true },
28
+ hebrew: { ranges: [[0x0590, 0x05ff]], external: true },
29
+ thai: { ranges: [[0x0e00, 0x0e7f]] },
30
+ latin: { ranges: [] },
31
+ other: { ranges: [], external: true },
32
+ };
33
+ function escapeCodePoint(codePoint) {
34
+ return `\\u${codePoint.toString(16).toUpperCase().padStart(4, '0')}`;
35
+ }
36
+ function characterClass(ranges) {
37
+ return ranges
38
+ .map(([start, end]) => (start === end ? escapeCodePoint(start) : `${escapeCodePoint(start)}-${escapeCodePoint(end)}`))
39
+ .join('');
40
+ }
41
+ function mergeRanges(ranges) {
42
+ const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
43
+ const merged = [];
44
+ for (const [start, end] of sorted) {
45
+ const last = merged[merged.length - 1];
46
+ if (last && start <= last[1] + 1) {
47
+ last[1] = Math.max(last[1], end);
48
+ }
49
+ else {
50
+ merged.push([start, end]);
51
+ }
52
+ }
53
+ return merged;
54
+ }
55
+ const METADATA_ENTRIES = Object.entries(SCRIPT_METADATA);
56
+ const DEFINITIVE_SCRIPTS = METADATA_ENTRIES.filter(([, meta]) => meta.definitive).map(([script, meta]) => [script, new RegExp(`[${characterClass(meta.ranges)}]`)]);
57
+ const SCRIPT_SCORES = METADATA_ENTRIES.filter(([, meta]) => meta.ranges.length > 0 && !meta.definitive).map(([script, meta]) => [script, new RegExp(`[${characterClass(meta.ranges)}]`, 'g')]);
58
+ // Derived union of every detectable range. Also consumed by the extension
59
+ // content script's "latin fast path" during mode switching — deriving it from
60
+ // SCRIPT_METADATA keeps it aligned with detection structurally instead of by
61
+ // comment.
62
+ export const NON_LATIN_SCRIPT_RE = new RegExp(`[${characterClass(mergeRanges(METADATA_ENTRIES.flatMap(([, meta]) => meta.ranges)))}]`);
22
63
  export function isLatinScript(lines) {
23
64
  const text = lines.join('');
24
65
  return !NON_LATIN_SCRIPT_RE.test(text) && LETTER_RE.test(text);
25
66
  }
26
67
  export function detectScript(lines) {
27
68
  const text = lines.join('');
28
- // Japanese: any kana is definitive and must be checked before CJK scoring.
29
- if (/[\u3040-\u30FF]/.test(text))
30
- return 'japanese';
69
+ for (const [script, pattern] of DEFINITIVE_SCRIPTS) {
70
+ if (pattern.test(text))
71
+ return script;
72
+ }
31
73
  let best = ['other', 0];
32
74
  for (const [script, pattern] of SCRIPT_SCORES) {
33
75
  const score = (text.match(pattern) ?? []).length;
@@ -38,4 +80,12 @@ export function detectScript(lines) {
38
80
  return best[0];
39
81
  return LETTER_RE.test(text) ? 'latin' : 'other';
40
82
  }
83
+ export function requiresExternalRomanization(script) {
84
+ // Own-property check so an out-of-contract string from untyped callers
85
+ // (including inherited names like 'toString') returns false rather than
86
+ // resolving up the prototype chain.
87
+ if (!Object.hasOwn(SCRIPT_METADATA, script))
88
+ return false;
89
+ return SCRIPT_METADATA[script].external === true;
90
+ }
41
91
  //# sourceMappingURL=detector.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"detector.js","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAEA,MAAM,SAAS,GAAG,QAAQ,CAAC;AAE3B,MAAM,aAAa,GAAyC;IAC1D,CAAC,SAAS,EAAE,kBAAkB,CAAC;IAC/B,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IAC9B,CAAC,UAAU,EAAE,kBAAkB,CAAC;IAChC,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAClC,CAAC,UAAU,EAAE,kBAAkB,CAAC;IAChC,CAAC,UAAU,EAAE,kBAAkB,CAAC;IAChC,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IAC9B,CAAC,SAAS,EAAE,kBAAkB,CAAC;IAC/B,CAAC,MAAM,EAAE,kBAAkB,CAAC;IAC5B,CAAC,OAAO,EAAE,kBAAkB,CAAC;IAC7B,CAAC,WAAW,EAAE,kBAAkB,CAAC;IACjC,CAAC,SAAS,EAAE,kBAAkB,CAAC;IAC/B,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IAC9B,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IAC9B,CAAC,MAAM,EAAE,kBAAkB,CAAC;CAC7B,CAAC;AAEF,yEAAyE;AACzE,sDAAsD;AACtD,MAAM,CAAC,MAAM,mBAAmB,GAC9B,4GAA4G,CAAC;AAE/G,MAAM,UAAU,aAAa,CAAC,KAAwB;IACpD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5B,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAE5B,2EAA2E;IAC3E,IAAI,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC;IAEpD,IAAI,IAAI,GAAkC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACvD,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,aAAa,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACjD,IAAI,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC;YAAE,IAAI,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9C,CAAC;IAED,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;IAChC,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;AAClD,CAAC"}
1
+ {"version":3,"file":"detector.js","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { detectScript, isLatinScript, NON_LATIN_SCRIPT_RE } from './detector.js';
2
- export { createRomanizer, requiresExternalRomanization } from './romanizer.js';
3
- export { UnsupportedRomanizationError, type RomanizeOptions, type RomanizeResult, type Romanizer, type RomanizerOptions, type ScriptType, } from './types.js';
1
+ export { detectScript, isLatinScript, requiresExternalRomanization, NON_LATIN_SCRIPT_RE, type ExternalScript, } from './detector.js';
2
+ export { createRomanizer } from './romanizer.js';
3
+ export { UnsupportedRomanizationError, type RomanizeEngine, type RomanizeEngineContext, type RomanizeOptions, type RomanizeResult, type Romanizer, type RomanizerOptions, type ScriptType, } from './types.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EACL,4BAA4B,EAC5B,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,4BAA4B,EAC5B,mBAAmB,EACnB,KAAK,cAAc,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,4BAA4B,EAC5B,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { detectScript, isLatinScript, NON_LATIN_SCRIPT_RE } from './detector.js';
2
- export { createRomanizer, requiresExternalRomanization } from './romanizer.js';
1
+ export { detectScript, isLatinScript, requiresExternalRomanization, NON_LATIN_SCRIPT_RE, } from './detector.js';
2
+ export { createRomanizer } from './romanizer.js';
3
3
  export { UnsupportedRomanizationError, } from './types.js';
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EACL,4BAA4B,GAM7B,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,4BAA4B,EAC5B,mBAAmB,GAEpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,4BAA4B,GAQ7B,MAAM,YAAY,CAAC"}
@@ -1,4 +1,9 @@
1
- import type { Romanizer, RomanizerOptions, ScriptType } from './types.js';
2
- export declare function requiresExternalRomanization(script: ScriptType): boolean;
1
+ import type { Romanizer, RomanizerOptions } from './types.js';
2
+ /**
3
+ * Ukrainian-specific characters pick the Ukrainian preset; all other Cyrillic
4
+ * romanizes as Russian. Applied per line, so a mixed-language song switches
5
+ * preset line by line.
6
+ */
7
+ export declare function selectCyrillicPreset(line: string): 'ru' | 'uk';
3
8
  export declare function createRomanizer(options?: RomanizerOptions): Romanizer;
4
9
  //# sourceMappingURL=romanizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"romanizer.d.ts","sourceRoot":"","sources":["../src/romanizer.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAGV,SAAS,EACT,gBAAgB,EAChB,UAAU,EACX,MAAM,YAAY,CAAC;AAwBpB,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAExE;AA+GD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAErE"}
1
+ {"version":3,"file":"romanizer.d.ts","sourceRoot":"","sources":["../src/romanizer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAKV,SAAS,EACT,gBAAgB,EAEjB,MAAM,YAAY,CAAC;AA4EpB;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE9D;AA+ID,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAErE"}