henkan 0.3.3 → 0.4.1

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 (58) hide show
  1. package/dist/index.cjs.js +146 -144
  2. package/dist/index.cjs.js.map +3 -3
  3. package/dist/index.mjs +144 -140
  4. package/dist/index.mjs.map +2 -2
  5. package/dist/types/types.d.ts +16 -12
  6. package/dist/types/types.d.ts.map +1 -1
  7. package/dist/types/utils.d.ts +8 -8
  8. package/dist/types/utils.d.ts.map +1 -1
  9. package/docs/api/functions/capitalizeString.md +1 -1
  10. package/docs/api/functions/convertJMdict.md +3 -3
  11. package/docs/api/functions/convertKanjiDic.md +1 -1
  12. package/docs/api/functions/convertKradFile.md +4 -4
  13. package/docs/api/functions/convertRadkFile.md +4 -4
  14. package/docs/api/functions/convertTanakaCorpus.md +1 -1
  15. package/docs/api/functions/generateAnkiNote.md +1 -1
  16. package/docs/api/functions/generateAnkiNotesFile.md +1 -1
  17. package/docs/api/functions/getKanji.md +1 -1
  18. package/docs/api/functions/getKanjiExtended.md +1 -1
  19. package/docs/api/functions/getWord.md +1 -1
  20. package/docs/api/functions/isStringArray.md +1 -1
  21. package/docs/api/functions/isValidArray.md +1 -1
  22. package/docs/api/functions/isValidArrayWithFirstElement.md +1 -1
  23. package/docs/api/functions/makeSSML.md +1 -1
  24. package/docs/api/functions/shuffleArray.md +1 -1
  25. package/docs/api/functions/synthesizeSpeech.md +1 -1
  26. package/docs/api/interfaces/DictKanji.md +5 -5
  27. package/docs/api/interfaces/DictKanjiForm.md +4 -4
  28. package/docs/api/interfaces/DictKanjiMisc.md +5 -5
  29. package/docs/api/interfaces/DictKanjiReading.md +3 -3
  30. package/docs/api/interfaces/DictKanjiReadingMeaning.md +3 -3
  31. package/docs/api/interfaces/DictKanjiReadingMeaningGroup.md +3 -3
  32. package/docs/api/interfaces/DictKanjiWithRadicals.md +3 -3
  33. package/docs/api/interfaces/DictMeaning.md +11 -11
  34. package/docs/api/interfaces/DictRadical.md +4 -4
  35. package/docs/api/interfaces/DictReading.md +5 -5
  36. package/docs/api/interfaces/DictWord.md +29 -19
  37. package/docs/api/interfaces/ExamplePart.md +7 -7
  38. package/docs/api/interfaces/Grammar.md +15 -15
  39. package/docs/api/interfaces/GrammarMeaning.md +3 -3
  40. package/docs/api/interfaces/Kana.md +11 -11
  41. package/docs/api/interfaces/Kanji.md +22 -22
  42. package/docs/api/interfaces/KanjiComponent.md +3 -3
  43. package/docs/api/interfaces/KanjiForm.md +4 -4
  44. package/docs/api/interfaces/NoteAndTag.md +3 -3
  45. package/docs/api/interfaces/Phrase.md +4 -4
  46. package/docs/api/interfaces/Radical.md +16 -16
  47. package/docs/api/interfaces/Reading.md +5 -5
  48. package/docs/api/interfaces/ResultEntry.md +7 -7
  49. package/docs/api/interfaces/TanakaExample.md +17 -7
  50. package/docs/api/interfaces/Translation.md +3 -3
  51. package/docs/api/interfaces/UsefulRegExps.md +9 -9
  52. package/docs/api/interfaces/Word.md +18 -18
  53. package/docs/api/type-aliases/Dict.md +1 -1
  54. package/docs/api/type-aliases/DictName.md +1 -1
  55. package/docs/api/type-aliases/EntryType.md +1 -1
  56. package/docs/api/type-aliases/JLPT.md +1 -1
  57. package/docs/api/type-aliases/Result.md +1 -1
  58. package/package.json +6 -6
package/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ var regexps = {
12
12
  kanji: new RegExp("\\p{Script=Han}+", "u"),
13
13
  scriptSplit: /([\p{sc=Han}]+|[\p{sc=Hiragana}]+|[\p{sc=Katakana}]+|[^\p{sc=Han}\p{sc=Hiragana}\p{sc=Katakana}]+)/u,
14
14
  regExChars: /[-\/\\^$*+?.()|[\]{}]/,
15
- tanakaID: /#ID=\d+_\d+$/,
15
+ tanakaID: /#ID=(?<id>\d+_\d+)$/,
16
16
  tanakaPart: /(?<base>[^()\[\]\{\}\s]+)(?:\((?<reading>[\S]+)\))?(?:\[(?<glossnum>[\S]+)\])?(?:\{(?<inflection>[\S]+)\})?/,
17
17
  tanakaReferenceID: /#(?<entryid>[\d]+)/
18
18
  };
@@ -1168,18 +1168,10 @@ function convertJMdict(xmlString, examples) {
1168
1168
  noent: true,
1169
1169
  recover: false
1170
1170
  });
1171
- const dict = [];
1171
+ let dict = [];
1172
+ const partMatches = /* @__PURE__ */ new Set();
1172
1173
  xml.parseString(dictParsed, (err, result) => {
1173
1174
  if (err) throw err;
1174
- const tanakaParts = examples && examples.length > 0 ? new Set(
1175
- examples.map(
1176
- (example) => example.parts.map((part) => [
1177
- part.baseForm,
1178
- ...part.reading ? [part.reading] : [],
1179
- ...part.referenceID ? [part.referenceID] : []
1180
- ])
1181
- ).flat(2)
1182
- ) : void 0;
1183
1175
  if (result.JMdict && typeof result.JMdict === "object" && isValidArray(result.JMdict.entry))
1184
1176
  for (const entry of result.JMdict.entry) {
1185
1177
  const entryObj = {
@@ -1228,7 +1220,8 @@ function convertJMdict(xmlString, examples) {
1228
1220
  if (readingObj.reading.length > 0)
1229
1221
  entryObj.readings.push(readingObj);
1230
1222
  }
1231
- if (isValidArray(meanings))
1223
+ if (isValidArray(meanings)) {
1224
+ let usuallyInKanaMeanings = 0;
1232
1225
  for (const meaning of meanings) {
1233
1226
  const meaningObj = {};
1234
1227
  if (isStringArray(meaning.pos))
@@ -1254,50 +1247,133 @@ function convertJMdict(xmlString, examples) {
1254
1247
  if (isStringArray(meaning.field))
1255
1248
  meaningObj.fields = meaning.field;
1256
1249
  if (isStringArray(meaning.s_inf)) meaningObj.info = meaning.s_inf;
1257
- if (isStringArray(meaning.misc)) meaningObj.misc = meaning.misc;
1250
+ if (isStringArray(meaning.misc)) {
1251
+ meaningObj.misc = meaning.misc;
1252
+ if (meaningObj.misc && meaningObj.misc.includes(
1253
+ "word usually written using kana alone"
1254
+ ))
1255
+ usuallyInKanaMeanings++;
1256
+ }
1258
1257
  if (isStringArray(meaning.dial))
1259
1258
  meaningObj.dialects = meaning.dial;
1260
1259
  if (meaningObj.partOfSpeech && meaningObj.partOfSpeech.length > 0 || meaningObj.translations && meaningObj.translations.length > 0)
1261
1260
  entryObj.meanings.push(meaningObj);
1262
1261
  }
1262
+ if (entryObj.meanings.length === usuallyInKanaMeanings)
1263
+ entryObj.usuallyInKana = true;
1264
+ }
1263
1265
  if (examples) {
1264
- const readings2 = new Set(
1265
- entryObj.readings.filter(
1266
- (reading) => (!reading.notes || !reading.notes.some(
1267
- (note) => notSearchedForms.has(note)
1268
- )) && (entryObj.isCommon === void 0 || reading.commonness && reading.commonness.length > 0)
1269
- ).map((reading) => reading.reading)
1270
- );
1271
- const kanjiForms2 = entryObj.kanjiForms ? new Set(
1272
- entryObj.kanjiForms.filter(
1273
- (kanjiForm) => (!kanjiForm.notes || !kanjiForm.notes.some(
1274
- (note) => notSearchedForms.has(note)
1275
- )) && (entryObj.isCommon === void 0 || kanjiForm.commonness && kanjiForm.commonness.length > 0)
1276
- ).map((kanjiForm) => kanjiForm.form)
1277
- ) : void 0;
1278
- let existsExample = false;
1279
- if (kanjiForms2 && kanjiForms2.size > 0 && tanakaParts) {
1280
- for (const kf of kanjiForms2)
1281
- if (tanakaParts.has(kf)) {
1282
- existsExample = true;
1283
- break;
1284
- }
1285
- }
1286
- if (!existsExample && readings2.size > 0 && tanakaParts) {
1287
- for (const r of readings2)
1288
- if (tanakaParts.has(r)) {
1289
- existsExample = true;
1290
- break;
1291
- }
1292
- }
1293
- if (!existsExample && tanakaParts && tanakaParts.has(entryObj.id))
1294
- existsExample = true;
1295
- if (existsExample) entryObj.hasPhrases = true;
1266
+ const readings2 = entryObj.readings.filter(
1267
+ (reading) => (!reading.notes || !reading.notes.some(
1268
+ (note) => notSearchedForms.has(note)
1269
+ )) && (entryObj.isCommon === void 0 || reading.commonness && reading.commonness.length > 0)
1270
+ ).map((reading) => reading.reading);
1271
+ const kanjiForms2 = entryObj.kanjiForms ? entryObj.kanjiForms.filter(
1272
+ (kanjiForm) => (!kanjiForm.notes || !kanjiForm.notes.some(
1273
+ (note) => notSearchedForms.has(note)
1274
+ )) && (entryObj.isCommon === void 0 || kanjiForm.commonness && kanjiForm.commonness.length > 0)
1275
+ ).map((kanjiForm) => kanjiForm.form) : void 0;
1276
+ for (const reading of readings2) partMatches.add(reading);
1277
+ if (kanjiForms2)
1278
+ for (const kanjiForm of kanjiForms2) partMatches.add(kanjiForm);
1279
+ partMatches.add(entryObj.id);
1296
1280
  }
1297
1281
  if (entryObj.id.length > 0 && entryObj.readings.length > 0 && entryObj.meanings.length > 0)
1298
1282
  dict.push(entryObj);
1299
1283
  }
1300
1284
  });
1285
+ if (examples && dict.length > 0) {
1286
+ const filteredExamples = examples.filter(
1287
+ (ex) => {
1288
+ const parts = ex.parts.flatMap((part) => [
1289
+ part.baseForm,
1290
+ ...part.reading ? [part.reading] : [],
1291
+ ...part.referenceID ? [part.referenceID] : []
1292
+ ]);
1293
+ for (const part of parts) if (partMatches.has(part)) return true;
1294
+ return false;
1295
+ }
1296
+ );
1297
+ dict = dict.map((entryObj) => {
1298
+ const readings = new Set(
1299
+ entryObj.readings.filter(
1300
+ (reading) => (!reading.notes || !reading.notes.some(
1301
+ (note) => notSearchedForms.has(note)
1302
+ )) && (entryObj.isCommon === void 0 || reading.commonness && reading.commonness.length > 0)
1303
+ ).map((reading) => reading.reading)
1304
+ );
1305
+ const kanjiForms = entryObj.kanjiForms ? new Set(
1306
+ entryObj.kanjiForms.filter(
1307
+ (kanjiForm) => (!kanjiForm.notes || !kanjiForm.notes.some(
1308
+ (note) => notSearchedForms.has(note)
1309
+ )) && (entryObj.isCommon === void 0 || kanjiForm.commonness && kanjiForm.commonness.length > 0)
1310
+ ).map((kanjiForm) => kanjiForm.form)
1311
+ ) : void 0;
1312
+ const kanjiFormExamples = [];
1313
+ const readingMatchingKanjiFormExamples = [];
1314
+ const readingExamples = [];
1315
+ const partParts = /* @__PURE__ */ new Set();
1316
+ for (const example of filteredExamples)
1317
+ for (const part of example.parts) {
1318
+ const readingAsReadingMatch = part.reading !== void 0 && readings.has(part.reading);
1319
+ if (kanjiForms && kanjiForms.size > 0 && kanjiForms.has(part.baseForm)) {
1320
+ if (readingAsReadingMatch) {
1321
+ readingMatchingKanjiFormExamples.push(example);
1322
+ partParts.add(part.baseForm).add(part.reading);
1323
+ } else {
1324
+ kanjiFormExamples.push(example);
1325
+ partParts.add(part.baseForm);
1326
+ }
1327
+ break;
1328
+ }
1329
+ const readingAsBaseFormMatch = readings.has(part.baseForm);
1330
+ const referenceIDMatch = part.referenceID !== void 0 && entryObj.id !== void 0 && part.referenceID === entryObj.id;
1331
+ if (readingAsReadingMatch || readingAsBaseFormMatch || referenceIDMatch) {
1332
+ readingExamples.push(example);
1333
+ if (readingAsReadingMatch) partParts.add(part.reading);
1334
+ if (readingAsBaseFormMatch) partParts.add(part.baseForm);
1335
+ if (referenceIDMatch) partParts.add(part.referenceID);
1336
+ break;
1337
+ }
1338
+ }
1339
+ const exampleSize = readingMatchingKanjiFormExamples.length + kanjiFormExamples.length + readingExamples.length;
1340
+ const includeKanjiFormExamples = readingMatchingKanjiFormExamples.length < Math.max(2, Math.round(exampleSize * 0.05));
1341
+ const includeReadingExamples = entryObj.usuallyInKana === void 0 && includeKanjiFormExamples && readingExamples.length >= Math.max(10, Math.round(exampleSize * 0.15)) || entryObj.usuallyInKana === true && readingExamples.length >= Math.max(2, Math.round(exampleSize * 0.5));
1342
+ let wordExamples = [
1343
+ ...readingMatchingKanjiFormExamples,
1344
+ ...includeKanjiFormExamples ? kanjiFormExamples : [],
1345
+ ...includeReadingExamples ? readingExamples : []
1346
+ ];
1347
+ const glossSpecificExamples = [];
1348
+ const seenPhrases = /* @__PURE__ */ new Set();
1349
+ for (let i = 0; i < entryObj.meanings.length; i++) {
1350
+ outer: for (const example of wordExamples) {
1351
+ if (seenPhrases.has(example.phrase)) continue;
1352
+ for (const part of example.parts)
1353
+ if (part.glossNumber === i + 1 && (partParts.has(part.baseForm) || part.reading && partParts.has(part.reading) || part.referenceID && partParts.has(part.referenceID))) {
1354
+ glossSpecificExamples.push(example);
1355
+ seenPhrases.add(example.phrase);
1356
+ break outer;
1357
+ }
1358
+ }
1359
+ if (glossSpecificExamples.length === 5) break;
1360
+ }
1361
+ if (glossSpecificExamples.length === 5)
1362
+ wordExamples = glossSpecificExamples;
1363
+ else if (glossSpecificExamples.length > 0) {
1364
+ const seenPhrases2 = new Set(
1365
+ glossSpecificExamples.map((ex) => ex.phrase)
1366
+ );
1367
+ wordExamples = [
1368
+ ...glossSpecificExamples,
1369
+ ...wordExamples.filter((ex) => !seenPhrases2.has(ex.phrase)).slice(0, 5 - glossSpecificExamples.length)
1370
+ ];
1371
+ }
1372
+ if (wordExamples.length > 0)
1373
+ entryObj.phraseIDs = (wordExamples.length > 5 ? wordExamples.slice(0, 5) : wordExamples).map((ex) => ex.id);
1374
+ return entryObj;
1375
+ });
1376
+ }
1301
1377
  return dict;
1302
1378
  } catch (err) {
1303
1379
  throw err;
@@ -1391,9 +1467,12 @@ async function convertTanakaCorpus(tanakaString, generateFurigana) {
1391
1467
  let a = tanakaParsed[i];
1392
1468
  let b = tanakaParsed[i + 1];
1393
1469
  if (a && b && a.startsWith("A: ") && b.startsWith("B: ")) {
1394
- a = a.replace("A: ", "").replace(regexps.tanakaID, "");
1470
+ a = a.replace("A: ", "");
1395
1471
  b = b.replace("B: ", "");
1396
- const aParts = a.split(" ");
1472
+ const idMatch = regexps.tanakaID.exec(a);
1473
+ if (!idMatch || !idMatch.groups || !idMatch.groups["id"])
1474
+ throw new Error(`Invalid phrase ID for ${a}`);
1475
+ const aParts = a.replace(regexps.tanakaID, "").split(" ");
1397
1476
  const bParts = b.split(" ").filter((part) => part.trim().length !== 0).map((part) => {
1398
1477
  const partMatches = regexps.tanakaPart.exec(part);
1399
1478
  if (!partMatches || !partMatches.groups || partMatches.length === 0)
@@ -1431,8 +1510,9 @@ async function convertTanakaCorpus(tanakaString, generateFurigana) {
1431
1510
  mode: "furigana"
1432
1511
  });
1433
1512
  tanakaArray.push({
1434
- phrase,
1435
- translation,
1513
+ id: idMatch.groups["id"].trim(),
1514
+ phrase: phrase.trim(),
1515
+ translation: translation.trim(),
1436
1516
  parts: bParts,
1437
1517
  ...furigana ? { furigana } : {}
1438
1518
  });
@@ -1611,7 +1691,6 @@ function getWord(dict, id, kanjiDic, examples, dictWord, noteTypeName, deckPath)
1611
1691
  } : {},
1612
1692
  ...dictReading.commonness && dictReading.commonness.length > 0 ? { common: true } : {}
1613
1693
  }));
1614
- let usuallyInKanaMeanings = 0;
1615
1694
  word.translations = dictWord.meanings.map((dictMeaning) => {
1616
1695
  if (!dictMeaning.translations)
1617
1696
  throw new Error(`No translations for ${dictWord.id}`);
@@ -1664,11 +1743,10 @@ function getWord(dict, id, kanjiDic, examples, dictWord, noteTypeName, deckPath)
1664
1743
  dictMeaning.info,
1665
1744
  (info) => lookupWordNote(info, notes, word.tags, false, info)
1666
1745
  );
1667
- wordAddNoteArray(dictMeaning.misc, (misc) => {
1668
- lookupWordNote(misc, notes, word.tags, false, misc);
1669
- if (misc.toLowerCase() === "word usually written using kana alone")
1670
- usuallyInKanaMeanings++;
1671
- });
1746
+ wordAddNoteArray(
1747
+ dictMeaning.misc,
1748
+ (misc) => lookupWordNote(misc, notes, word.tags, false, misc)
1749
+ );
1672
1750
  for (let i = 0; i < notes.length; i++)
1673
1751
  notes[i] = capitalizeString(notes[i]);
1674
1752
  return {
@@ -1676,8 +1754,7 @@ function getWord(dict, id, kanjiDic, examples, dictWord, noteTypeName, deckPath)
1676
1754
  notes
1677
1755
  };
1678
1756
  });
1679
- if (word.translations && word.translations.length === usuallyInKanaMeanings)
1680
- word.usuallyInKana = true;
1757
+ if (dictWord.usuallyInKana === true) word.usuallyInKana = true;
1681
1758
  if (kanjiDic && word.kanjiForms) {
1682
1759
  word.kanji = [];
1683
1760
  for (const kanjiForm of word.kanjiForms)
@@ -1701,89 +1778,16 @@ function getWord(dict, id, kanjiDic, examples, dictWord, noteTypeName, deckPath)
1701
1778
  }
1702
1779
  if (word.kanji.length === 0) delete word.kanji;
1703
1780
  }
1704
- if (examples && dictWord.hasPhrases === true) {
1705
- const readings = new Set(
1706
- word.readings.filter(
1707
- (reading) => (!reading.notes || !reading.notes.some(
1708
- (note) => notSearchedForms.has(note)
1709
- )) && (word.common === void 0 || reading.common === true)
1710
- ).map((reading) => reading.reading)
1711
- );
1712
- const kanjiForms = word.kanjiForms ? new Set(
1713
- word.kanjiForms.filter(
1714
- (kanjiForm) => (!kanjiForm.notes || !kanjiForm.notes.some(
1715
- (note) => notSearchedForms.has(note)
1716
- )) && (word.common === void 0 || kanjiForm.common === true)
1717
- ).map((kanjiForm) => kanjiForm.kanjiForm)
1718
- ) : void 0;
1719
- const kanjiFormExamples = [];
1720
- const readingMatchingKanjiFormExamples = [];
1721
- const readingExamples = [];
1722
- const partParts = /* @__PURE__ */ new Set();
1723
- for (const example of examples)
1724
- for (const part of example.parts) {
1725
- const readingAsReadingMatch = part.reading !== void 0 && readings.has(part.reading);
1726
- if (kanjiForms && kanjiForms.size > 0 && kanjiForms.has(part.baseForm)) {
1727
- if (readingAsReadingMatch) {
1728
- readingMatchingKanjiFormExamples.push(example);
1729
- partParts.add(part.baseForm).add(part.reading);
1730
- } else {
1731
- kanjiFormExamples.push(example);
1732
- partParts.add(part.baseForm);
1733
- }
1734
- break;
1735
- }
1736
- const readingAsBaseFormMatch = readings.has(part.baseForm);
1737
- const referenceIDMatch = part.referenceID !== void 0 && word.id !== void 0 && part.referenceID === word.id;
1738
- if (readingAsReadingMatch || readingAsBaseFormMatch || referenceIDMatch) {
1739
- readingExamples.push(example);
1740
- if (readingAsReadingMatch) partParts.add(part.reading);
1741
- if (readingAsBaseFormMatch) partParts.add(part.baseForm);
1742
- if (referenceIDMatch) partParts.add(part.referenceID);
1743
- break;
1744
- }
1745
- }
1746
- const exampleSize = readingMatchingKanjiFormExamples.length + kanjiFormExamples.length + readingExamples.length;
1747
- const includeKanjiFormExamples = readingMatchingKanjiFormExamples.length < Math.max(2, Math.round(exampleSize * 0.05));
1748
- const includeReadingExamples = word.usuallyInKana === void 0 && includeKanjiFormExamples && readingExamples.length >= Math.max(10, Math.round(exampleSize * 0.15)) || word.usuallyInKana === true && readingExamples.length >= Math.max(2, Math.round(exampleSize * 0.5));
1749
- let wordExamples = [
1750
- ...readingMatchingKanjiFormExamples,
1751
- ...includeKanjiFormExamples ? kanjiFormExamples : [],
1752
- ...includeReadingExamples ? readingExamples : []
1753
- ];
1754
- if (word.translations) {
1755
- const glossSpecificExamples = [];
1756
- const seenPhrases = /* @__PURE__ */ new Set();
1757
- for (let i = 0; i < word.translations.length; i++) {
1758
- outer: for (const example of wordExamples) {
1759
- if (seenPhrases.has(example.phrase)) continue;
1760
- for (const part of example.parts)
1761
- if (part.glossNumber === i + 1 && (partParts.has(part.baseForm) || part.reading && partParts.has(part.reading) || part.referenceID && partParts.has(part.referenceID))) {
1762
- glossSpecificExamples.push(example);
1763
- seenPhrases.add(example.phrase);
1764
- break outer;
1765
- }
1766
- }
1767
- if (glossSpecificExamples.length === 5) break;
1768
- }
1769
- if (glossSpecificExamples.length === 5)
1770
- wordExamples = [...glossSpecificExamples];
1771
- else if (glossSpecificExamples.length > 0) {
1772
- const seenPhrases2 = new Set(
1773
- glossSpecificExamples.map((ex) => ex.phrase)
1774
- );
1775
- wordExamples = [
1776
- ...glossSpecificExamples,
1777
- ...wordExamples.filter((ex) => !seenPhrases2.has(ex.phrase)).slice(0, 5 - glossSpecificExamples.length)
1778
- ];
1779
- }
1780
- }
1781
- if (wordExamples.length > 0)
1782
- word.phrases = (wordExamples.length > 5 ? wordExamples.slice(0, 5) : wordExamples).map((ex) => ({
1783
- phrase: ex.furigana ?? ex.phrase,
1784
- translation: ex.translation,
1785
- originalPhrase: ex.phrase
1786
- }));
1781
+ if (examples && dictWord.phraseIDs && dictWord.phraseIDs.length > 0) {
1782
+ word.phrases = [];
1783
+ const phraseIDs = new Set(dictWord.phraseIDs);
1784
+ for (const ex of examples)
1785
+ if (phraseIDs.has(ex.id))
1786
+ word.phrases.push({
1787
+ phrase: ex.furigana ?? ex.phrase,
1788
+ translation: ex.translation,
1789
+ originalPhrase: ex.phrase
1790
+ });
1787
1791
  }
1788
1792
  return word;
1789
1793
  } else throw new Error(`Word${id ? ` ${id}` : ""} not found`);