narrator-ts 0.0.0 → 0.1.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -3
  3. package/dist/narrator/contour.d.ts +90 -0
  4. package/dist/narrator/contour.js +235 -0
  5. package/dist/narrator/duration.d.ts +39 -0
  6. package/dist/narrator/duration.js +259 -0
  7. package/dist/narrator/frames.d.ts +140 -0
  8. package/dist/narrator/frames.js +428 -0
  9. package/dist/narrator/index.d.ts +15 -0
  10. package/dist/narrator/index.js +13 -0
  11. package/dist/narrator/interpolate.d.ts +183 -0
  12. package/dist/narrator/interpolate.js +467 -0
  13. package/dist/narrator/onset.d.ts +25 -0
  14. package/dist/narrator/onset.js +73 -0
  15. package/dist/narrator/parse.d.ts +67 -0
  16. package/dist/narrator/parse.js +184 -0
  17. package/dist/narrator/prosody.d.ts +355 -0
  18. package/dist/narrator/prosody.js +1121 -0
  19. package/dist/narrator/render.d.ts +64 -0
  20. package/dist/narrator/render.js +273 -0
  21. package/dist/narrator/rewrite.d.ts +55 -0
  22. package/dist/narrator/rewrite.js +155 -0
  23. package/dist/narrator/speak.d.ts +120 -0
  24. package/dist/narrator/speak.js +206 -0
  25. package/dist/narrator/stress.d.ts +36 -0
  26. package/dist/narrator/stress.js +147 -0
  27. package/dist/narrator/voice.d.ts +66 -0
  28. package/dist/narrator/voice.js +78 -0
  29. package/dist/translator/engines.d.ts +37 -0
  30. package/dist/translator/engines.js +26 -0
  31. package/dist/translator/index.d.ts +3 -0
  32. package/dist/translator/index.js +2 -0
  33. package/dist/translator/translate.d.ts +10 -0
  34. package/dist/translator/translate.js +363 -0
  35. package/dist/translator/types.d.ts +70 -0
  36. package/dist/translator/types.js +31 -0
  37. package/package.json +57 -4
  38. package/reference/README.md +168 -0
  39. package/reference/formants.json +18 -0
  40. package/reference/nrl-7948.json +434 -0
  41. package/reference/nrl-table.json +1 -0
  42. package/reference/voice-free.json +11775 -0
  43. package/reference/wordlist.txt +10 -0
@@ -0,0 +1,363 @@
1
+ import { engineFor } from './engines.js';
2
+ import { CLASS } from './types.js';
3
+ /**
4
+ * English text to narrator phonemes — a port of translator.library's
5
+ * Translate(), following the 33.2 disassembly rather than the published
6
+ * description of the NRL rules, because the two differ in places that matter.
7
+ *
8
+ * Addresses in comments are hunk offsets into translator.library 33.2
9
+ * (`11997e3c`); see research/01-translator.md.
10
+ */
11
+ const SPACE = 0x20;
12
+ const HASH = 0x23;
13
+ const DEL = 0x7f;
14
+ const RBRACKET = 0x5d;
15
+ /** Longest word the library will buffer, from `cmpi.w #$64,D3` at 0x552. */
16
+ const MAX_WORD = 100;
17
+ /** The word buffer is 27 longwords of spaces pushed at 0x13e. */
18
+ const WORD_BUF = 108;
19
+ /** Returned when a word exceeds MAX_WORD (`moveq #-3,D1` at 0x590). */
20
+ const ERR_WORD_TOO_LONG = -3;
21
+ /** Letters the suffix and long-U wildcards compare against by name. */
22
+ const CH = { D: 0x44, E: 0x45, G: 0x47, I: 0x49, L: 0x4c, N: 0x4e, R: 0x52, S: 0x53, Y: 0x59 };
23
+ /**
24
+ * Fold one input character the way the buffer filler does (0x522-0x54c).
25
+ *
26
+ * The range test is `cmpi.b #$20,D1` followed by **bge** — a *signed* branch.
27
+ * A byte of 0x80 or more is negative as a signed byte, so it fails the test
28
+ * and becomes a space, exactly like a control character. That is why the
29
+ * class table only ever needs its 128 real entries: no high byte reaches it.
30
+ */
31
+ function normalise(c) {
32
+ if (c >= 0x80)
33
+ return SPACE; // signed bge at 0x526
34
+ if (c < 0x20)
35
+ return SPACE;
36
+ if (c === DEL)
37
+ return SPACE;
38
+ if (c >= 0x61 && c <= 0x7a)
39
+ return c - 0x20; // to upper
40
+ if (c === RBRACKET)
41
+ return SPACE; // ']' can't appear in a rule
42
+ return c;
43
+ }
44
+ export function translate(text, tables, outSize = 4096) {
45
+ const traits = engineFor(tables.version);
46
+ const classOf = (c) => c >= 0 && c < tables.classes.length ? tables.classes[c] : 0;
47
+ const input = [];
48
+ for (let i = 0; i < text.length; i++)
49
+ input.push(text.charCodeAt(i) & 0xff);
50
+ let inPos = 0;
51
+ let remaining = input.length; // frame+0x82
52
+ const out = [];
53
+ let wordStart = 0; // frame+0x10, start of this word's output
54
+ let stressPending = false; // frame+0x14 bit 0
55
+ let overflowed = false;
56
+ const word = new Uint8Array(WORD_BUF).fill(SPACE);
57
+ let pos = 0; // A2, index into `word`
58
+ let written = 0; // D3
59
+ const emit = (c) => {
60
+ if (out.length >= outSize - 1) {
61
+ overflowed = true;
62
+ return;
63
+ }
64
+ out.push(c);
65
+ };
66
+ // ---------------------------------------------------------------- 0x4fc
67
+ /**
68
+ * Fill the buffer with the next word. Returns an error code, or 0.
69
+ *
70
+ * The buffer is laid out as [carried char][space][word...]: matching starts
71
+ * at index 2, so a rule's left context can see a space at word start. The
72
+ * filler deliberately over-reads one character past the trailing space and
73
+ * un-reads it from the input (0x58a), carrying it into the next buffer.
74
+ */
75
+ const fillWord = () => {
76
+ // 0x50e, and only here: the loop's back-edges all target 0x514, so the
77
+ // remaining-input test is not repeated. Re-testing it each pass emits the
78
+ // end sentinel in the middle of a word that consumes the last character.
79
+ if (remaining <= 0) {
80
+ emit(HASH);
81
+ written = 0;
82
+ return 0;
83
+ }
84
+ let a2 = 0;
85
+ word[a2] = word[a2 + written];
86
+ a2++;
87
+ word[a2++] = SPACE;
88
+ let d0 = 0;
89
+ let d1 = 0;
90
+ written = 0;
91
+ for (;;) {
92
+ d0 = d1;
93
+ d1 = input[inPos++] ?? 0;
94
+ if (d1 === 0)
95
+ remaining = 1; // 0x51a: NUL forces the last pass
96
+ d1 = normalise(d1);
97
+ written++;
98
+ // 0x590 puts -3 in D1 and returns. The main loop branches on D3, not
99
+ // D1, so an over-long word does not abort: the buffer simply keeps the
100
+ // first 100 characters and matching carries on. D1 is then overwritten
101
+ // with 0 on the normal exit path (0x1a8), so the code rarely surfaces.
102
+ if (written > MAX_WORD)
103
+ return ERR_WORD_TOO_LONG;
104
+ word[a2++] = d1;
105
+ if (d0 === SPACE) { // 0x55a: stop one past the space...
106
+ inPos--; // 0x58a: ...and un-read it
107
+ written--;
108
+ return 0;
109
+ }
110
+ remaining--;
111
+ if (remaining === 0) { // 0x564 -> 0x594
112
+ word[a2++] = SPACE;
113
+ word[a2++] = SPACE;
114
+ return 0;
115
+ }
116
+ if (d1 === HASH && d0 === HASH) { // 0x566: '##' ends translation
117
+ if (written === 2) { // 0x572
118
+ emit(HASH);
119
+ written = 0;
120
+ return 0;
121
+ }
122
+ inPos--; // 0x580
123
+ word[a2 - 2] = SPACE;
124
+ written--;
125
+ inPos--;
126
+ written--;
127
+ return 0;
128
+ }
129
+ }
130
+ };
131
+ // ---------------------------------------------------------------- 0x59e
132
+ /**
133
+ * Insert a stress mark into the word just emitted.
134
+ *
135
+ * Only when the previous rule left a mark pending, the word is at least
136
+ * three characters, and it contains no stress digit already — a rule that
137
+ * supplied its own digit wins, which is why `Amiga` keeps its `IY3` rather
138
+ * than gaining a `4` on the leading `AH`.
139
+ */
140
+ const stressPass = () => {
141
+ // Every exit path runs 0x608, which advances the word-start marker —
142
+ // including the not-pending branch at 0x5a4. Returning early without it
143
+ // makes the next word's stress land on this one.
144
+ if (!stressPending) {
145
+ wordStart = out.length;
146
+ return;
147
+ }
148
+ if (out.length - wordStart < 3) {
149
+ wordStart = out.length;
150
+ return;
151
+ } // 0x5b2
152
+ for (let i = wordStart; i < out.length; i++) { // 0x5bc
153
+ if (classOf(out[i]) & CLASS.DIGIT) {
154
+ wordStart = out.length;
155
+ return;
156
+ }
157
+ }
158
+ for (let i = wordStart; i + 1 < out.length; i++) { // 0x5da
159
+ const pair = String.fromCharCode(out[i], out[i + 1]);
160
+ if (tables.vowels.includes(pair)) {
161
+ out.splice(i + 2, 0, 0x34); // '4'
162
+ break;
163
+ }
164
+ }
165
+ wordStart = out.length;
166
+ };
167
+ // ---------------------------------------------------------------- 0x3be
168
+ /**
169
+ * Read the next character of the word buffer and advance.
170
+ * `dir` +1 walks left (predecrement), -1 walks right (postincrement).
171
+ */
172
+ const step = (dir) => (dir < 0 ? word[pos++] : word[--pos]) ?? SPACE;
173
+ /** The same, when only the character's class matters. */
174
+ const stepClass = (dir) => classOf(step(dir));
175
+ // ---------------------------------------------------------------- 0x398
176
+ /**
177
+ * Apply one wildcard. Returns true on match.
178
+ *
179
+ * The handlers are in jump-table order (hunk 0x60e). Two asymmetries are
180
+ * deliberate and reproduced: `*` does not step back after over-consuming,
181
+ * while `:` and `_` do; and `%` never checks that its `?NG` branch actually
182
+ * begins with an I, so any letter followed by NG at a word end counts as
183
+ * the suffix.
184
+ */
185
+ const wildcard = (wc, dir) => {
186
+ switch (wc) {
187
+ case '#': return (stepClass(dir) & CLASS.VOWEL) !== 0;
188
+ case '*': {
189
+ if (!(stepClass(dir) & CLASS.CONSONANT))
190
+ return false;
191
+ while (stepClass(dir) & CLASS.CONSONANT) { /* consume; no step back */ }
192
+ return true;
193
+ }
194
+ case '.': {
195
+ const c = stepClass(dir);
196
+ return (c & CLASS.VOICED) !== 0 && (c & CLASS.CONSONANT) !== 0;
197
+ }
198
+ case '$': {
199
+ if (!(stepClass(dir) & CLASS.CONSONANT))
200
+ return false;
201
+ const c = step(dir);
202
+ return c === CH.I || c === CH.E;
203
+ }
204
+ case '%': { // 0x422
205
+ // A suffix only counts when it ends the word. `-ES`/`-ED`/`-ELY`
206
+ // check that directly (0x47a); `-ER` and `-ING` allow a trailing S
207
+ // first (0x46a), and ING falls through into the very same code.
208
+ const endsWord = () => (stepClass(dir) & CLASS.LETTER) === 0; // 0x47a
209
+ // 0x46a — the trailing-S branch exists only from 31.7 on; engines.ts.
210
+ const afterRorIng = () => {
211
+ if (!traits.suffixAllowsTrailingS)
212
+ return endsWord();
213
+ const c = step(dir);
214
+ if (!(classOf(c) & CLASS.LETTER))
215
+ return true;
216
+ if (c !== CH.S)
217
+ return false;
218
+ return endsWord();
219
+ };
220
+ if (step(dir) === CH.E) {
221
+ const c = step(dir);
222
+ if (!(classOf(c) & CLASS.LETTER))
223
+ return true; // bare E at word end
224
+ if (c === CH.S || c === CH.D)
225
+ return endsWord();
226
+ if (c === CH.R)
227
+ return afterRorIng();
228
+ if (c === CH.L)
229
+ return step(dir) === CH.Y ? endsWord() : false;
230
+ return false;
231
+ }
232
+ // 0x456: the leading letter is never checked, so anything followed by
233
+ // NG at a word end counts as -ING. Reproduced deliberately.
234
+ if (step(dir) !== CH.N)
235
+ return false;
236
+ if (step(dir) !== CH.G)
237
+ return false;
238
+ return afterRorIng();
239
+ }
240
+ case '&': return (stepClass(dir) & CLASS.SIBILANT) !== 0;
241
+ case '@': return (stepClass(dir) & CLASS.AFFECTS_U) !== 0;
242
+ case '^': return (stepClass(dir) & CLASS.CONSONANT) !== 0;
243
+ case '+': return (stepClass(dir) & CLASS.FRONT_VOWEL) !== 0;
244
+ case ':': {
245
+ while (stepClass(dir) & CLASS.CONSONANT) { /* consume */ }
246
+ pos += dir; // 0x4c2 step back
247
+ return true;
248
+ }
249
+ case '?': return (stepClass(dir) & CLASS.DIGIT) !== 0;
250
+ case '_': {
251
+ while (stepClass(dir) & CLASS.DIGIT) { /* consume */ }
252
+ pos += dir; // 0x4de step back
253
+ return true;
254
+ }
255
+ default: { // ' ' — 0x4e6
256
+ return (stepClass(dir) & CLASS.LETTER) === 0;
257
+ }
258
+ }
259
+ };
260
+ /** Match a context pattern. Left runs right-to-left from `pos`. */
261
+ const matchContext = (pattern, dir) => {
262
+ const order = dir > 0
263
+ ? [...pattern].reverse() // left context is scanned backwards
264
+ : [...pattern];
265
+ for (const ch of order) {
266
+ if (classOf(ch.charCodeAt(0)) & CLASS.WILDCARD) {
267
+ if (!wildcard(ch, dir))
268
+ return false;
269
+ }
270
+ else {
271
+ if (dir > 0) {
272
+ if (word[--pos] !== ch.charCodeAt(0))
273
+ return false;
274
+ }
275
+ else {
276
+ if (word[pos++] !== ch.charCodeAt(0))
277
+ return false;
278
+ }
279
+ }
280
+ }
281
+ return true;
282
+ };
283
+ // ---------------------------------------------------------------- 0x23a
284
+ /** Bucket 0-25 for A-Z, 26 for digits, 27 for anything else. */
285
+ const bucketFor = (c) => {
286
+ const cl = classOf(c);
287
+ let k = c;
288
+ if (!(cl & CLASS.LETTER))
289
+ k = cl & CLASS.DIGIT ? 0x5b : 0x5c;
290
+ return k - 0x41;
291
+ };
292
+ const tryRules = () => {
293
+ const bucket = tables.buckets[bucketFor(word[pos])];
294
+ if (!bucket)
295
+ return false;
296
+ for (const [left, match, right, outStr, term] of bucket) {
297
+ const save = pos;
298
+ let ok = true;
299
+ for (let i = 0; i < match.length; i++) { // 0x266, literal
300
+ if (word[pos + i] !== match.charCodeAt(i)) {
301
+ ok = false;
302
+ break;
303
+ }
304
+ }
305
+ if (!ok)
306
+ continue;
307
+ pos = save;
308
+ if (!matchContext(left, +1)) {
309
+ pos = save;
310
+ continue;
311
+ }
312
+ pos = save + match.length;
313
+ if (!matchContext(right, -1)) {
314
+ pos = save;
315
+ continue;
316
+ }
317
+ pos = save + match.length;
318
+ for (let i = 0; i < outStr.length; i++)
319
+ emit(outStr.charCodeAt(i));
320
+ // 0x334: a rule whose output ends in a letter leaves a stress mark
321
+ // pending, unless the rule was stored with a backtick terminator.
322
+ const last = out.length ? out[out.length - 1] : 0;
323
+ if (classOf(last) & CLASS.LETTER)
324
+ stressPending = term !== '`';
325
+ return true;
326
+ }
327
+ return false;
328
+ };
329
+ // ---------------------------------------------------------------- 0x17a
330
+ let rc = 0;
331
+ for (;;) {
332
+ if (out.length && out[out.length - 1] === HASH)
333
+ break; // 0x17a
334
+ if (pos === 0 || word[pos - 1] === SPACE) { // 0x184
335
+ const err = fillWord();
336
+ stressPass();
337
+ if (written < 0) {
338
+ rc = err;
339
+ break;
340
+ } // 0x19a tests D3, and returns D1
341
+ if (written === 0)
342
+ break; // 0x19c
343
+ pos = 2;
344
+ }
345
+ if (!tryRules())
346
+ pos++; // no rule matched: don't spin on this character
347
+ if (overflowed) {
348
+ rc = -(input.length - inPos);
349
+ break;
350
+ }
351
+ }
352
+ stressPass(); // 0x1aa
353
+ if (out.length && out[out.length - 1] === HASH)
354
+ out.pop(); // 0x1ae
355
+ return { phonemes: String.fromCharCode(...out), rc };
356
+ }
357
+ /** Convenience wrapper around a fixed table set. */
358
+ export function createTranslator(tables) {
359
+ return {
360
+ version: tables.version,
361
+ translate: (text, outSize) => translate(text, tables, outSize),
362
+ };
363
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * A rule from the letter-to-sound table: `left[match]right=out<term>`.
3
+ *
4
+ * `term` is the raw terminator byte the rule was stored with, `\` or a
5
+ * backtick. It is not decoration — see CLASS.LETTER and the stress pass in
6
+ * translate.ts.
7
+ */
8
+ export interface Rule {
9
+ left: string;
10
+ match: string;
11
+ right: string;
12
+ out: string;
13
+ term: string;
14
+ }
15
+ /** Everything the matcher needs, extracted from one translator.library build. */
16
+ export interface TranslatorTables {
17
+ version: string;
18
+ source: string;
19
+ /** 128 entries, one per character code, of CLASS bit flags. */
20
+ classes: number[];
21
+ /** The pattern metacharacters, in dispatch order. */
22
+ wildcards: string;
23
+ /** The fifteen two-character vowel phonemes the stress pass looks for. */
24
+ vowels: string[];
25
+ /**
26
+ * 28 buckets: 0-25 for A-Z, 26 for digits, 27 for everything else.
27
+ * Order within a bucket decides which rule wins, so it is load-bearing.
28
+ */
29
+ buckets: Array<Array<[string, string, string, string, string]>>;
30
+ }
31
+ /**
32
+ * Character class bits, read from the table at hunk 0x642.
33
+ *
34
+ * Bits 9 and 12-15 are never set in any shipped build. The names come from
35
+ * which wildcard handler tests each bit (jump table at hunk 0x60e), not from
36
+ * guesswork about what the letters have in common.
37
+ */
38
+ export declare const CLASS: {
39
+ /** Not alphanumeric. */
40
+ readonly PUNCT: number;
41
+ /** `0`-`9`; tested by `?` and `_`, and by the stress pass. */
42
+ readonly DIGIT: number;
43
+ /** `@` — a consonant that changes a following long U: D J L N R S T Z. */
44
+ readonly AFFECTS_U: number;
45
+ /** `.` — voiced consonant: B D G J L M N R V W Z. */
46
+ readonly VOICED: number;
47
+ /** `&` — sibilant: C G J S X Z. */
48
+ readonly SIBILANT: number;
49
+ /** `^`, `*` and `:` — a consonant. */
50
+ readonly CONSONANT: number;
51
+ /** `#` — a vowel: A E I O U Y. */
52
+ readonly VOWEL: number;
53
+ /** Any letter. Gates whether a rule's output can take a stress mark. */
54
+ readonly LETTER: number;
55
+ /** `+` — a front vowel: E I Y. */
56
+ readonly FRONT_VOWEL: number;
57
+ /** The character is itself a pattern metacharacter. */
58
+ readonly WILDCARD: number;
59
+ /** A word or sentence delimiter. */
60
+ readonly DELIMITER: number;
61
+ };
62
+ export interface TranslateResult {
63
+ /** The phoneme string, as narrator.device expects it. */
64
+ phonemes: string;
65
+ /**
66
+ * 0 on success, or minus the number of input characters not consumed when
67
+ * the output buffer ran out — the library's own convention.
68
+ */
69
+ rc: number;
70
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Character class bits, read from the table at hunk 0x642.
3
+ *
4
+ * Bits 9 and 12-15 are never set in any shipped build. The names come from
5
+ * which wildcard handler tests each bit (jump table at hunk 0x60e), not from
6
+ * guesswork about what the letters have in common.
7
+ */
8
+ export const CLASS = {
9
+ /** Not alphanumeric. */
10
+ PUNCT: 1 << 0,
11
+ /** `0`-`9`; tested by `?` and `_`, and by the stress pass. */
12
+ DIGIT: 1 << 1,
13
+ /** `@` — a consonant that changes a following long U: D J L N R S T Z. */
14
+ AFFECTS_U: 1 << 2,
15
+ /** `.` — voiced consonant: B D G J L M N R V W Z. */
16
+ VOICED: 1 << 3,
17
+ /** `&` — sibilant: C G J S X Z. */
18
+ SIBILANT: 1 << 4,
19
+ /** `^`, `*` and `:` — a consonant. */
20
+ CONSONANT: 1 << 5,
21
+ /** `#` — a vowel: A E I O U Y. */
22
+ VOWEL: 1 << 6,
23
+ /** Any letter. Gates whether a rule's output can take a stress mark. */
24
+ LETTER: 1 << 7,
25
+ /** `+` — a front vowel: E I Y. */
26
+ FRONT_VOWEL: 1 << 8,
27
+ /** The character is itself a pattern metacharacter. */
28
+ WILDCARD: 1 << 10,
29
+ /** A word or sentence delimiter. */
30
+ DELIMITER: 1 << 11,
31
+ };
package/package.json CHANGED
@@ -1,9 +1,62 @@
1
1
  {
2
2
  "name": "narrator-ts",
3
- "version": "0.0.0",
4
- "description": "Placeholder to claim the name so trusted publishing can be configured. See 0.1.0 and later.",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript reimplementation of the Amiga narrator.device and translator.library",
5
+ "type": "module",
5
6
  "license": "MIT",
6
- "repository": { "type": "git", "url": "git+https://github.com/bitplane/narrator-ts.git" },
7
+ "author": "Gareth Davidson <gaz@bitplane.net>",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/bitplane/narrator-ts.git"
11
+ },
12
+ "bugs": "https://github.com/bitplane/narrator-ts/issues",
7
13
  "homepage": "https://github.com/bitplane/narrator-ts#readme",
8
- "private": false
14
+ "keywords": [
15
+ "amiga",
16
+ "speech-synthesis",
17
+ "text-to-speech",
18
+ "tts",
19
+ "narrator",
20
+ "translator",
21
+ "formant",
22
+ "phonemes",
23
+ "retrocomputing"
24
+ ],
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "scripts": {
29
+ "build": "tsc -p tsconfig.json",
30
+ "prepublishOnly": "npm run build && npm test",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest",
33
+ "say": "vite-node tools/say.ts --",
34
+ "oracle:build": "bash tools/fetch-musashi.sh && make -C tools/oracle"
35
+ },
36
+ "devDependencies": {
37
+ "typescript": "^5.7.2",
38
+ "vitest": "^2.1.8",
39
+ "@types/node": "^22.10.2"
40
+ },
41
+ "main": "./dist/narrator/index.js",
42
+ "types": "./dist/narrator/index.d.ts",
43
+ "exports": {
44
+ ".": {
45
+ "types": "./dist/narrator/index.d.ts",
46
+ "default": "./dist/narrator/index.js"
47
+ },
48
+ "./translator": {
49
+ "types": "./dist/translator/index.d.ts",
50
+ "default": "./dist/translator/index.js"
51
+ },
52
+ "./reference/nrl-table.json": "./reference/nrl-table.json",
53
+ "./reference/voice-free.json": "./reference/voice-free.json",
54
+ "./package.json": "./package.json"
55
+ },
56
+ "files": [
57
+ "dist",
58
+ "reference",
59
+ "README.md",
60
+ "LICENSE"
61
+ ]
9
62
  }