From 8148a8679dd0210c9c943e72e2d039c811c3c9c4 Mon Sep 17 00:00:00 2001 From: divisey <18656007202@163.com> Date: Thu, 12 Sep 2024 10:40:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20update=20dicts=20(#1062)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- enjoy/assets/styles/mdict-theme.css | 23 +++ enjoy/package.json | 1 + enjoy/src/i18n/en.json | 2 +- enjoy/src/i18n/zh-CN.json | 2 +- enjoy/src/main/mdict.ts | 143 ++++++++++++++++ enjoy/src/main/window.ts | 2 + enjoy/src/preload.ts | 8 + .../dict-settings/dict-import-button.tsx | 23 ++- .../dict-settings/installed-dict-list.tsx | 110 ++++++------- .../widgets/lookup/dict-lookup-result.tsx | 15 +- enjoy/src/renderer/context/dict-provider.tsx | 154 +++++++++++------- enjoy/src/renderer/lib/dict.ts | 10 +- enjoy/src/types/enjoy-app.d.ts | 6 + enjoy/src/types/index.d.ts | 14 +- enjoy/src/types/mdict.d.ts | 6 + yarn.lock | 113 ++++++++++++- 16 files changed, 492 insertions(+), 140 deletions(-) create mode 100644 enjoy/assets/styles/mdict-theme.css create mode 100644 enjoy/src/main/mdict.ts create mode 100644 enjoy/src/types/mdict.d.ts diff --git a/enjoy/assets/styles/mdict-theme.css b/enjoy/assets/styles/mdict-theme.css new file mode 100644 index 00000000..9537dd1e --- /dev/null +++ b/enjoy/assets/styles/mdict-theme.css @@ -0,0 +1,23 @@ +html { + --background-color: #fff; + --text-color: #505050; + --white: #fff; + --black: #000; + + padding-right: 4px; + background: var(--background-color); +} + +::-webkit-scrollbar { + width: 6px; + background-color: var(--background-color); +} + +::-webkit-scrollbar-track { + background-color: var(--background-color); +} + +::-webkit-scrollbar-thumb { + border-radius: 6px; + background-color: #cccccc; +} diff --git a/enjoy/package.json b/enjoy/package.json index dc588061..0473aece 100644 --- a/enjoy/package.json +++ b/enjoy/package.json @@ -89,6 +89,7 @@ }, "dependencies": { "@andrkrn/ffprobe-static": "^5.2.0", + "@divisey/js-mdict": "^5.0.0", "@electron-forge/publisher-s3": "^7.4.0", "@hookform/resolvers": "^3.9.0", "@langchain/community": "^0.2.32", diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index 3dd3eb92..fd6fb2a4 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -783,7 +783,7 @@ "selectDir": "Select Folder", "selectFile": "Select File", "importMdictFile": "Import the original dictionary file", - "mdictFileTip": "Directly import .mdx .mdd format files (.mdx files are required, .mdd files are optional and can have multiple), but there may be problems with style and usability.", + "mdictFileTip": "Directly import .mdx .mdd format files (.mdx files are required, .mdd files are optional and can have multiple).", "authorizationExpired": "Your authorization has expired. Please login again.", "selectUser": "Select user", "export": "Export", diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index 4ce392c8..4209d514 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -783,7 +783,7 @@ "selectDir": "选择文件夹", "selectFile": "选择文件", "importMdictFile": "导入原词典文件", - "mdictFileTip": "直接导入 .mdx .mdd 格式的文件 (.mdx 文件是必须的,.mdd 文件是可选的且可以有多个),不过样式和可用性可能存在问题。", + "mdictFileTip": "直接导入 .mdx .mdd 格式的文件 (.mdx 文件是必须的,.mdd 文件是可选的且可以有多个)。", "authorizationExpired": "您的登录授权已过期,请重新登录。", "selectUser": "选择用户", "export": "导出", diff --git a/enjoy/src/main/mdict.ts b/enjoy/src/main/mdict.ts new file mode 100644 index 00000000..8eb017c0 --- /dev/null +++ b/enjoy/src/main/mdict.ts @@ -0,0 +1,143 @@ +import log from "@main/logger"; +import path from "path"; +import fs from "fs-extra"; +import { ipcMain } from "electron"; +import { LRUCache } from "lru-cache"; +import { Mdict as MdictReader } from "@divisey/js-mdict"; +import { hashFile } from "@/main/utils"; +import settings from "./settings"; + +const logger = log.scope("dict"); + +export class MDictHandler { + private cache = new LRUCache({ max: 20 }); + private mdx: MdictReader; + private mdds: Record; + private currentDictHash: string; + + addition = + ''; + + get dictsPath() { + const _path = path.join(settings.libraryPath(), "dictionaries"); + fs.ensureDirSync(_path); + + return _path; + } + + async import(pathes: string[]) { + const mdxs = pathes.filter((_path) => _path.match(/\.mdx$/)); + const mdds = pathes.filter((_path) => _path.match(/\.mdd$/)); + + if (mdxs.length > 1) { + throw new Error("Multi mdx file found, only one is allowed"); + } + + if (mdxs.length < 1) { + throw new Error("No mdx file found"); + } + + const mdx = mdxs[0]; + const hash = await hashFile(mdx, { algo: "md5" }); + + for (let _path of pathes) { + await fs.copy( + _path, + path.join(this.dictsPath, hash, path.basename(_path)) + ); + } + + return { + title: path.basename(mdx, ".mdx"), + mdx, + mdds, + hash, + }; + } + + async remove(mdict: MDict) { + await fs.remove(path.join(this.dictsPath, mdict.hash)); + } + + lookup(word: string, mdict: MDict): string { + if (mdict.hash !== this.currentDictHash) { + this.mdx = new MdictReader(mdict.mdx); + } + + let result = this.mdx.lookup(word)?.definition ?? null; + + if (result?.startsWith("@@@LINK=")) { + return this.lookup(result.substring(8), mdict); + } + + return result ? `${this.addition}${result}` : null; + } + + getResource(key: string, mdict: MDict) { + const cacheKey = `${mdict.hash}-${key}`; + const cachedValue = this.cache.get(cacheKey); + + if (cachedValue) { + return cachedValue; + } + + if (mdict.hash !== this.currentDictHash) { + this.mdds = {}; + } + + if (key.match(/\.(css|js)$/)) { + const _path = path.join(this.dictsPath, mdict.hash, key); + + if (fs.existsSync(_path)) { + const data = fs.readFileSync(_path, { encoding: "base64" }); + + this.cache.set(cacheKey, data); + return data; + } + } + + try { + for (let _path of mdict.mdds) { + if (!this.mdds[_path]) { + this.mdds[_path] = new MdictReader(_path); + } + + const parsedKey = + "\\" + key.replace(/(^[/\\])|([/]$)/, "").replace(/\//g, "\\"); + const data = this.mdds[_path].locate(parsedKey); + + if (data) { + this.cache.set(cacheKey, data.definition); + return data.definition; + } else { + return ""; + } + } + } catch (err) { + logger.error(`Failed to read resource ${key}`, err); + return ""; + } + } + + registerIpcHandlers() { + ipcMain.handle("mdict-import", async (_event, pathes: string[]) => + this.import(pathes) + ); + + ipcMain.handle("mdict-remove", async (_event, mdict: MDict) => + this.remove(mdict) + ); + + ipcMain.handle("mdict-lookup", async (_event, word: string, mdict: MDict) => + this.lookup(word, mdict) + ); + + ipcMain.handle( + "mdict-read-file", + async (_event, word: string, mdict: MDict) => + this.getResource(word, mdict) + ); + } +} + +export default new MDictHandler(); diff --git a/enjoy/src/main/window.ts b/enjoy/src/main/window.ts index 4c86247a..fb6dcdd4 100644 --- a/enjoy/src/main/window.ts +++ b/enjoy/src/main/window.ts @@ -23,6 +23,7 @@ import url from "url"; import echogarden from "./echogarden"; import camdict from "./camdict"; import dict from "./dict"; +import mdict from "./mdict"; import decompresser from "./decompresser"; import { UserSetting } from "@main/db/models"; @@ -53,6 +54,7 @@ main.init = async () => { camdict.registerIpcHandlers(); dict.registerIpcHandlers(); + mdict.registerIpcHandlers(); // Prepare Settings settings.registerIpcHandlers(); diff --git a/enjoy/src/preload.ts b/enjoy/src/preload.ts index 136f7f0b..69e9ee70 100644 --- a/enjoy/src/preload.ts +++ b/enjoy/src/preload.ts @@ -249,6 +249,14 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", { return ipcRenderer.invoke("camdict-lookup", word); }, }, + mdict: { + remove: (dict: Dict) => ipcRenderer.invoke("mdict-remove", dict), + getResource: (key: string, dict: Dict) => + ipcRenderer.invoke("mdict-read-file", key, dict), + lookup: (word: string, dict: Dict) => + ipcRenderer.invoke("mdict-lookup", word, dict), + import: (pathes: string[]) => ipcRenderer.invoke("mdict-import", pathes), + }, dict: { getDicts: () => ipcRenderer.invoke("dict-list"), remove: (dict: Dict) => ipcRenderer.invoke("dict-remove", dict), diff --git a/enjoy/src/renderer/components/preferences/dict-settings/dict-import-button.tsx b/enjoy/src/renderer/components/preferences/dict-settings/dict-import-button.tsx index c4d5552d..4b8567ed 100644 --- a/enjoy/src/renderer/components/preferences/dict-settings/dict-import-button.tsx +++ b/enjoy/src/renderer/components/preferences/dict-settings/dict-import-button.tsx @@ -16,7 +16,7 @@ import { t } from "i18next"; import { LoaderIcon } from "lucide-react"; export const DictImportButton = () => { - const { reload } = useContext(DictProviderContext); + const { reload, importMDict } = useContext(DictProviderContext); const { EnjoyApp } = useContext(AppSettingsProviderContext); const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); @@ -49,8 +49,21 @@ export const DictImportButton = () => { const handleOriginDictImport = async () => { const pathes = await EnjoyApp.dialog.showOpenDialog({ title: t("selectMdictFileOrDirTitle"), - properties: ["multiSelections", "openFile", "openDirectory"], + properties: ["multiSelections", "openFile"], }); + + if (!pathes[0]) return; + setLoading(true); + + try { + const mdict = await EnjoyApp.mdict.import(pathes); + await importMDict(mdict); + setOpen(false); + } catch (err) { + toast.error(err.message); + } + + setLoading(false); }; return ( @@ -97,7 +110,7 @@ export const DictImportButton = () => { - {/*
+
{t("importMdictFile")}
@@ -106,9 +119,9 @@ export const DictImportButton = () => {
-
*/} +
)} diff --git a/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx b/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx index 91ff0e13..8aec83d7 100644 --- a/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx +++ b/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx @@ -57,7 +57,7 @@ export const InstalledDictList = function () { ))} {installedDicts.map((item) => ( - + ))} ); @@ -79,14 +79,14 @@ const DecompressDictItem = function ({ task }: { task: DecompressTask }) { ); }; -const InstalledDictItem = function ({ dict }: { dict: Dict }) { +const InstalledDictItem = function ({ dict }: { dict: DictItem }) { const { EnjoyApp } = useContext(AppSettingsProviderContext); - const { settings, setDefault, reload, remove, removed } = + const { settings, setDefault, reload, remove } = useContext(DictProviderContext); const [removing, setRemoving] = useState(false); useEffect(() => { - if (settings.removing?.find((v) => v === dict.name)) { + if (settings.removing?.find((v) => v === dict.value)) { handleRemove(); } }, []); @@ -113,9 +113,7 @@ const InstalledDictItem = function ({ dict }: { dict: Dict }) { setRemoving(true); try { - remove(dict); - await EnjoyApp.dict.remove(dict); - removed(dict); + await remove(dict); toast.success(t("dictRemoved")); } catch (err) { toast.error(err.message); @@ -135,64 +133,62 @@ const InstalledDictItem = function ({ dict }: { dict: Dict }) { ); } - if (dict.state === "installed") { - return ( -
- - - - - - - {t("removeDictTitle")} - - {t("removeDictDescription")} - - - - {t("cancel")} - - - - - - + return ( +
+ + + + + + + {t("removeDictTitle")} + + {t("removeDictDescription")} + + + + {t("cancel")} + + + + + + - {settings.default === dict.name ? ( - - ) : ( - - )} -
- ); - } + {settings.default === dict.value ? ( + + ) : ( + + )} +
+ ); } return (
- {dict.title} - {settings.default === dict.name && ( + {dict.text} + {settings.default === dict.value && ( {t("default")} diff --git a/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx b/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx index 8c95eac2..c10ea779 100644 --- a/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx +++ b/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx @@ -32,8 +32,7 @@ export function DictLookupResult({ }) { const { colorScheme } = useContext(ThemeProviderContext); const initialContent = ``; - const { EnjoyApp } = useContext(AppSettingsProviderContext); - const { currentDict } = useContext(DictProviderContext); + const { currentDict, lookup } = useContext(DictProviderContext); const [definition, setDefinition] = useState(""); const [looking, setLooking] = useState(false); const [notFound, setNotFound] = useState(false); @@ -42,11 +41,11 @@ export function DictLookupResult({ useEffect(() => { if (currentDict && word) { - lookup(); + handleLookup(); } }, [currentDict, word]); - async function lookup() { + async function handleLookup() { revoke(); setLooking(true); @@ -56,8 +55,7 @@ export function DictLookupResult({ const _word = word.trim().indexOf(" ") > -1 ? word : word.toLowerCase(); - EnjoyApp.dict - .lookup(_word, currentDict) + lookup(_word, currentDict) .then((result) => { if (!result) { setNotFound(true); @@ -134,8 +132,7 @@ export const DictLookupResultInner = ({ onJump?: (v: string) => void; onResize?: (v: number) => void; }) => { - const { EnjoyApp } = useContext(AppSettingsProviderContext); - const { currentDict } = useContext(DictProviderContext); + const { currentDict, getResource } = useContext(DictProviderContext); const { document: innerDocument } = useFrame(); const [html, setHtml] = useState(""); const [hash, setHash] = useState(""); @@ -178,7 +175,7 @@ export const DictLookupResultInner = ({ }; const handleReadResource = async (key: string) => { - return EnjoyApp.dict.getResource(key, currentDict); + return getResource(key, currentDict); }; const normalizer = new DictDefinitionNormalizer({ diff --git a/enjoy/src/renderer/context/dict-provider.tsx b/enjoy/src/renderer/context/dict-provider.tsx index 933db12a..7cfc5b67 100644 --- a/enjoy/src/renderer/context/dict-provider.tsx +++ b/enjoy/src/renderer/context/dict-provider.tsx @@ -8,35 +8,38 @@ import { UserSettingKeyEnum } from "@/types/enums"; type DictProviderState = { settings: DictSettingType; - dicts: Dict[]; - installedDicts: Dict[]; - dictSelectItems: { text: string; value: string }[]; + installedDicts: DictItem[]; + dictSelectItems: DictItem[]; reload?: () => void; - remove?: (v: Dict) => void; - removed?: (v: Dict) => void; - currentDict?: Dict | null; + importMDict?: (mdict: MDict) => Promise; + lookup?: (word: string, dict: DictItem) => Promise; + getResource?: (key: string, dict: DictItem) => Promise; + remove?: (v: DictItem) => Promise; + currentDict?: DictItem | null; currentDictValue?: string; handleSetCurrentDict?: (v: string) => void; - setDefault?: (v: Dict) => Promise; + setDefault?: (v: DictItem) => Promise; }; const AIDict = { + type: "preset" as DictType, text: t("aiLookup"), value: "ai", }; const CamDict = { + type: "preset" as DictType, text: t("cambridgeDictionary"), value: "cambridge", }; const initialState: DictProviderState = { - dicts: [], installedDicts: [], dictSelectItems: [AIDict], settings: { default: "", removing: [], + mdicts: [], }, }; @@ -49,47 +52,50 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => { const [settings, setSettings] = useState({ default: "", removing: [], + mdicts: [], }); const [currentDictValue, setCurrentDictValue] = useState(""); - const [currentDict, setCurrentDict] = useState(); + const [currentDict, setCurrentDict] = useState(); const { state: dbState } = useContext(DbProviderContext); - const availableDicts = useMemo( - () => - dicts.filter((dict) => { - return ( - dict.state === "installed" && - !settings.removing?.find((v) => v === dict.name) - ); - }), - [dicts, settings] - ); + const installedDicts = useMemo(() => { + const _dicts = dicts + .filter((dict) => dict.state === "installed") + .map((dict) => ({ + type: "dict" as DictType, + text: dict.title, + value: dict.name, + })); + const _mdicts = settings.mdicts.map((mdict) => ({ + type: "mdict" as DictType, + text: mdict.title, + value: mdict.hash, + })); + + return [..._dicts, ..._mdicts]; + }, [dicts, settings]); + + const availableDicts = useMemo(() => { + return installedDicts.filter( + ({ value }) => !settings.removing.find((v) => v === value) + ); + }, [installedDicts, settings]); const dictSelectItems = useMemo(() => { const presets = learningLanguage.startsWith("en") ? [CamDict, AIDict] : [AIDict]; - return [ - ...presets, - ...availableDicts.map((item) => ({ - text: item.title, - value: item.name, - })), - ]; + return [...presets, ...availableDicts]; }, [availableDicts, learningLanguage]); - const installedDicts = useMemo(() => { - return dicts.filter((dict) => dict.state === "installed"); - }, [dicts]); - useEffect(() => { const defaultDict = availableDicts.find( - (dict) => dict.name === settings.default + (dict) => dict.value === settings.default ); if (defaultDict) { - handleSetCurrentDict(defaultDict.name); + handleSetCurrentDict(defaultDict.value); } else { setCurrentDictValue( learningLanguage.startsWith("en") ? CamDict.value : AIDict.value @@ -110,6 +116,12 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => { }); }; + const updateSettings = async (_settings: DictSettingType) => { + return EnjoyApp.userSettings + .set(UserSettingKeyEnum.DICTS, _settings) + .then(() => setSettings(_settings)); + }; + const fetchDicts = async () => { return EnjoyApp.dict.getDicts().then((dicts) => { setDicts(dicts); @@ -119,46 +131,78 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => { const handleSetCurrentDict = (value: string) => { setCurrentDictValue(value); - const dict = dicts.find((dict) => dict.name === value); + const dict = availableDicts.find((dict) => dict.value === value); if (dict) setCurrentDict(dict); }; - const setDefault = async (dict: Dict | null) => { - const _settings = { ...settings, default: dict?.name ?? "" }; - - EnjoyApp.userSettings - .set(UserSettingKeyEnum.DICTS, _settings) - .then(() => setSettings(_settings)); + const setDefault = async (dict: DictItem | null) => { + updateSettings({ ...settings, default: dict?.value ?? "" }); }; - const remove = (dict: Dict) => { - if (!settings.removing?.find((name) => dict.name === name)) { - const removing = [...(settings.removing ?? []), dict.name]; - const _settings = { ...settings, removing }; + const remove = async (dict: DictItem) => { + const isRemoving = settings.removing?.find((value) => dict.value === value); + if (isRemoving) return; - EnjoyApp.userSettings - .set(UserSettingKeyEnum.DICTS, _settings) - .then(() => setSettings(_settings)); + await updateSettings({ + ...settings, + removing: [...(settings.removing ?? []), dict.value], + }); + + if (dict.type === "dict") { + const _dict = dicts.find(({ name }) => name === dict.value); + await EnjoyApp.dict.remove(_dict); + } else if (dict.type === "mdict") { + const _dict = settings.mdicts.find(({ hash }) => hash === dict.value); + await EnjoyApp.mdict.remove(_dict); + } + + await updateSettings({ + ...settings, + mdicts: settings.mdicts?.filter(({ hash }) => dict.value !== hash) ?? [], + removing: + settings.removing?.filter((value) => value !== dict.value) ?? [], + }); + }; + + const lookup = async (word: string, dict: DictItem) => { + if (dict.type === "dict") { + const _dict = dicts.find(({ name }) => name === dict.value); + return EnjoyApp.dict.lookup(word, _dict); + } else if (dict.type === "mdict") { + const _dict = settings.mdicts.find(({ hash }) => hash === dict.value); + return EnjoyApp.mdict.lookup(word, _dict); + } else { + return null; } }; - const removed = (dict: Dict) => { - const removing = - settings.removing?.filter((name) => name !== dict.name) ?? []; - const _settings = { ...settings, removing }; + const getResource = async (key: string, dict: DictItem) => { + if (dict.type === "dict") { + const _dict = dicts.find(({ name }) => name === dict.value); + return EnjoyApp.dict.getResource(key, _dict); + } else if (dict.type === "mdict") { + const _dict = settings.mdicts.find(({ hash }) => hash === dict.value); + return EnjoyApp.mdict.getResource(key, _dict); + } else { + return null; + } + }; - EnjoyApp.userSettings - .set(UserSettingKeyEnum.DICTS, _settings) - .then(() => setSettings(_settings)); + const importMDict = async (mdict: MDict) => { + const mdicts = settings.mdicts.filter(({ hash }) => hash !== mdict.hash); + const _settings = { ...settings, mdicts: [mdict, ...mdicts] }; + + await updateSettings(_settings); }; return ( { const $link = this.$(link); - const data = await this.onReadResource($link.attr("href")); + const href = $link.attr("href"); + + if (href.startsWith("/assets/styles")) { + return; + } + + const data = await this.onReadResource(href); const url = await this.createUrl(MIME["css"], data); $link.replaceWith( diff --git a/enjoy/src/types/enjoy-app.d.ts b/enjoy/src/types/enjoy-app.d.ts index da12301c..48f3b9dd 100644 --- a/enjoy/src/types/enjoy-app.d.ts +++ b/enjoy/src/types/enjoy-app.d.ts @@ -148,6 +148,12 @@ type EnjoyAppType = { camdict: { lookup: (word: string) => Promise; }; + mdict: { + remove: (mdict: MDict) => Promise; + getResource: (key: string, mdict: MDict) => Promise; + lookup: (word: string, mdict: MDict) => Promise; + import: (pathes: string[]) => Promise; + }; dict: { getDicts: () => Promise; remove: (dict: Dict) => Promise; diff --git a/enjoy/src/types/index.d.ts b/enjoy/src/types/index.d.ts index 5637ef3f..15c10ddd 100644 --- a/enjoy/src/types/index.d.ts +++ b/enjoy/src/types/index.d.ts @@ -213,16 +213,16 @@ type RecorderConfigType = { sampleSize: number; }; -type DictSettingItem = { - name: string; - path: string; - description: string; - isKeyCaseSensitive: string; - title: string; - resources: string[]; +type DictType = "dict" | "mdict" | "preset"; + +type DictItem = { + type: DictType; + text: string; + value: string; }; type DictSettingType = { default: string; removing: string[]; + mdicts: MDict[]; }; diff --git a/enjoy/src/types/mdict.d.ts b/enjoy/src/types/mdict.d.ts new file mode 100644 index 00000000..349e2770 --- /dev/null +++ b/enjoy/src/types/mdict.d.ts @@ -0,0 +1,6 @@ +type MDict = { + title: string; + mdx: string; + mdds: string[]; + hash: string; +}; diff --git a/yarn.lock b/yarn.lock index 11ebc608..c9bc1164 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1667,6 +1667,20 @@ __metadata: languageName: node linkType: hard +"@divisey/js-mdict@npm:^5.0.0": + version: 5.0.0 + resolution: "@divisey/js-mdict@npm:5.0.0" + dependencies: + "@xmldom/xmldom": "npm:^0.8.6" + bl: "npm:^2.2.1" + buffer-to-arraybuffer: "npm:0.0.6" + lemmatizer: "npm:0.0.1" + levenshtein-edit-distance: "npm:^3.0.1" + read-chunk: "npm:^3.2.0" + checksum: 10c0/7c6ee53bd5b160d918b236cbb411cddb363b68b54496f1f7d24cd29e1cb33c3a9075b90cbfb075563fb14865f973d85e753486ee3f0705f3c1e643a97c2780e4 + languageName: node + linkType: hard + "@docsearch/css@npm:3.6.1, @docsearch/css@npm:^3.6.1": version: 3.6.1 resolution: "@docsearch/css@npm:3.6.1" @@ -9175,7 +9189,7 @@ __metadata: languageName: node linkType: hard -"@xmldom/xmldom@npm:^0.8.8": +"@xmldom/xmldom@npm:^0.8.6, @xmldom/xmldom@npm:^0.8.8": version: 0.8.10 resolution: "@xmldom/xmldom@npm:0.8.10" checksum: 10c0/c7647c442502720182b0d65b17d45d2d95317c1c8c497626fe524bda79b4fb768a9aa4fae2da919f308e7abcff7d67c058b102a9d641097e9a57f0b80187851f @@ -9966,6 +9980,16 @@ __metadata: languageName: node linkType: hard +"bl@npm:^2.2.1": + version: 2.2.1 + resolution: "bl@npm:2.2.1" + dependencies: + readable-stream: "npm:^2.3.5" + safe-buffer: "npm:^5.1.1" + checksum: 10c0/37481260f1661755253b6205fcdd64b6d852147aaf61628947d2193fcdb78e625ee061dae0094ac16e7c7f10b12c90110fb2b08826815d47a28f9628bebb5a8f + languageName: node + linkType: hard + "bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -10120,6 +10144,13 @@ __metadata: languageName: node linkType: hard +"buffer-to-arraybuffer@npm:0.0.6": + version: 0.0.6 + resolution: "buffer-to-arraybuffer@npm:0.0.6" + checksum: 10c0/fcfca8c9e1c465c33a22173f1e981aa564a7669c8098153b0c35f80a2b479243751e249468a89c197e2b19198da0c7fd1af27bc03b2af59fea5e9602ea811ccd + languageName: node + linkType: hard + "buffer@npm:5.6.0": version: 5.6.0 resolution: "buffer@npm:5.6.0" @@ -12751,6 +12782,31 @@ __metadata: languageName: node linkType: hard +"en-inflectors@npm:^1.0.11, en-inflectors@npm:^1.0.7": + version: 1.0.12 + resolution: "en-inflectors@npm:1.0.12" + dependencies: + en-stemmer: "npm:^1.0.2" + checksum: 10c0/c399cee8e9d03879730f91e2f20f0c9c4efd01b1bd4f8e713c04862a61b5e383c07c2a7e4e3633bcb3bfe987e0f680fd9c6cb83435082751ac7faa2b31639c97 + languageName: node + linkType: hard + +"en-lexicon@npm:^1.0.10": + version: 1.0.11 + resolution: "en-lexicon@npm:1.0.11" + dependencies: + en-inflectors: "npm:^1.0.7" + checksum: 10c0/6356feab9a9043e1c94e58988ab047a1afae0d291c5f98fe7ddf119408a9f50ebfa9e7cd790e0e970cff3fc49c0baedd546b0891cee532e9b47916bb3c0c4794 + languageName: node + linkType: hard + +"en-stemmer@npm:^1.0.2, en-stemmer@npm:^1.0.3": + version: 1.0.3 + resolution: "en-stemmer@npm:1.0.3" + checksum: 10c0/f82a4d7ed3bb3e5f28c0b7722092038d068980e001a1ae0d79697180a984b05030d2e4acc2ce89b451183936f3a9bde7ea8b4195d8b31e3ed8643599f6902951 + languageName: node + linkType: hard + "encode-utf8@npm:^1.0.3": version: 1.0.3 resolution: "encode-utf8@npm:1.0.3" @@ -12808,6 +12864,7 @@ __metadata: resolution: "enjoy@workspace:enjoy" dependencies: "@andrkrn/ffprobe-static": "npm:^5.2.0" + "@divisey/js-mdict": "npm:^5.0.0" "@electron-forge/cli": "npm:^7.4.0" "@electron-forge/maker-deb": "npm:^7.4.0" "@electron-forge/maker-dmg": "npm:^7.4.0" @@ -17117,6 +17174,26 @@ __metadata: languageName: node linkType: hard +"lemmatizer@npm:0.0.1": + version: 0.0.1 + resolution: "lemmatizer@npm:0.0.1" + dependencies: + en-inflectors: "npm:^1.0.11" + en-lexicon: "npm:^1.0.10" + en-stemmer: "npm:^1.0.3" + checksum: 10c0/7a9741ea1c915878c3c266b099c1b25b27063ba55e9076a6a3e50dd7b9d4f058a0475c1a32dfc3b87541fe4b349e20ea8f7be5f52d8454490230f89447d90ae8 + languageName: node + linkType: hard + +"levenshtein-edit-distance@npm:^3.0.1": + version: 3.0.1 + resolution: "levenshtein-edit-distance@npm:3.0.1" + bin: + levenshtein-edit-distance: cli.js + checksum: 10c0/7d443d4f76bc89d2ccea49ebb6e32d0fa9a003c3511a21d5749d9af17b5bbcf92b9c2f513662535093bae465101bdfb1e7f1f067de4d3f8f9c1fab1b480728ff + languageName: node + linkType: hard + "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -19844,7 +19921,7 @@ __metadata: languageName: node linkType: hard -"p-try@npm:^2.0.0": +"p-try@npm:^2.0.0, p-try@npm:^2.1.0": version: 2.2.0 resolution: "p-try@npm:2.2.0" checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f @@ -20326,6 +20403,13 @@ __metadata: languageName: node linkType: hard +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 10c0/6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf + languageName: node + linkType: hard + "pirates@npm:^4.0.1": version: 4.0.6 resolution: "pirates@npm:4.0.6" @@ -21580,6 +21664,16 @@ __metadata: languageName: node linkType: hard +"read-chunk@npm:^3.2.0": + version: 3.2.0 + resolution: "read-chunk@npm:3.2.0" + dependencies: + pify: "npm:^4.0.1" + with-open-file: "npm:^0.1.6" + checksum: 10c0/cf62522dc85a96d54f1e0886cadcdfdd491bdcd250408695c8d63cea2eae5b1a31da8317c9a58a095ee9b15c67e84e0bed1a917c820bcccf1c74749a9fcad797 + languageName: node + linkType: hard + "read-pkg-up@npm:^2.0.0": version: 2.0.0 resolution: "read-pkg-up@npm:2.0.0" @@ -21624,7 +21718,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -22181,7 +22275,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 @@ -25362,6 +25456,17 @@ __metadata: languageName: node linkType: hard +"with-open-file@npm:^0.1.6": + version: 0.1.7 + resolution: "with-open-file@npm:0.1.7" + dependencies: + p-finally: "npm:^1.0.0" + p-try: "npm:^2.1.0" + pify: "npm:^4.0.1" + checksum: 10c0/28a0c67718f0b9f14669636d5a69fa66bc87a4bdbc5913a245b68f0cbd0c96a3f83559147d647dc30332f3eee76688924654587cc58e4d5b944ef04155f9fe36 + languageName: node + linkType: hard + "wkx@npm:^0.5.0": version: 0.5.0 resolution: "wkx@npm:0.5.0"