+ {isLoading ? (
+
+ Loading...
+
+ ) : dicts.length === 0 ? (
+
+ No local dictionaries imported yet
+
+ ) : (
+ dicts.map((dict) => (
+
+
+
+ {dict.name}
+
+
+ {dict.extension.toUpperCase()}
+
+
+ this.handleDelete(dict)}
+ >
+ Delete
+
+
+ ))
+ )}
+
+
+ {/* Hidden file input */}
+ {
+ t: (title: string) => string;
+ handleFetchPlugins: () => void;
+}
+
+export interface SettingInfoState {
+ dicts: DictMeta[];
+ isLoading: boolean;
+}
diff --git a/src/containers/settings/pluginSetting/component.tsx b/src/containers/settings/pluginSetting/component.tsx
index 03acab2f..4ab504a5 100644
--- a/src/containers/settings/pluginSetting/component.tsx
+++ b/src/containers/settings/pluginSetting/component.tsx
@@ -201,7 +201,7 @@ class SettingDialog extends React.Component<
{this.props.plugins &&
this.props.plugins
- .filter((item) => item.type !== "ai")
+ .filter((item) => item.type !== "ai" && item.type !== "dictionary")
.map((item) => {
return (
diff --git a/src/store/actions/manager.tsx b/src/store/actions/manager.tsx
index 8b9513a4..4d349887 100644
--- a/src/store/actions/manager.tsx
+++ b/src/store/actions/manager.tsx
@@ -22,6 +22,7 @@ import { azureTTSVoiceList, officialVoiceList } from "../../constants/ttsList";
import { langToName } from "../../utils/common";
import { resetReaderRequest } from "../../utils/request/reader";
import { resetThirdpartyRequest } from "../../utils/request/thirdparty";
+import DictUtil from "../../utils/file/dictUtil";
export function handleBooks(books: BookModel[]) {
return { type: "HANDLE_BOOKS", payload: books };
}
@@ -299,6 +300,29 @@ export function handleFetchPlugins() {
}
pluginList = pluginList.filter((p: PluginModel) => p.type !== "ai");
+ // Load local dictionary plugins from ConfigService
+ const localDictIds = DictUtil.getDictIds();
+ for (const dictId of localDictIds) {
+ const meta = DictUtil.getDictMeta(dictId);
+ console.log(meta, "dict mdeta");
+ if (meta) {
+ let localDictPlugin = new PluginModel(
+ `dict_${dictId}`,
+ "dictionary",
+ meta.name,
+ "dict",
+ "1.0.0",
+ "",
+ { dictId },
+ [],
+ [],
+ "",
+ ""
+ );
+ pluginList.push(localDictPlugin);
+ }
+ }
+
if (ConfigService.getReaderConfig("aiTranslateModel")) {
const modelKey = ConfigService.getReaderConfig("aiTranslateModel");
const entry = ConfigService.getObjectConfig(
@@ -371,6 +395,7 @@ export function handleFetchPlugins() {
pluginList.push(assistPlugin);
}
}
+ console.log(pluginList, "PLUGINLIST");
TokenService.getToken("is_authed").then((value) => {
let isAuthed = value === "yes";
if (
diff --git a/src/utils/file/dictUtil.ts b/src/utils/file/dictUtil.ts
new file mode 100644
index 00000000..e16ff83f
--- /dev/null
+++ b/src/utils/file/dictUtil.ts
@@ -0,0 +1,139 @@
+import { isElectron } from "react-device-detect";
+import { getStorageLocation } from "../common";
+import { ConfigService } from "../../assets/lib/kookit-extra-browser.min";
+import { LocalFileManager } from "./localFile";
+import localforage from "localforage";
+import { Buffer } from "buffer";
+
+declare var window: any;
+
+const DICT_FOLDER = "dict";
+
+export interface DictMeta {
+ id: string;
+ name: string;
+ extension: string;
+}
+
+class DictUtil {
+ /** Save dict file (ArrayBuffer) by id */
+ static async saveDict(
+ id: string,
+ name: string,
+ arrayBuffer: ArrayBuffer
+ ): Promise {
+ const ext = name.split(".").pop()?.toLowerCase() || "mdx";
+ const filename = `${id}.${ext}`;
+
+ if (isElectron) {
+ const fs = window.require("fs");
+ const path = window.require("path");
+ const dir = path.join(getStorageLocation() || "", DICT_FOLDER);
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir, { recursive: true });
+ }
+ fs.writeFileSync(path.join(dir, filename), Buffer.from(arrayBuffer));
+ } else {
+ if (ConfigService.getReaderConfig("isUseLocal") === "yes") {
+ await LocalFileManager.saveFile(filename, arrayBuffer, DICT_FOLDER);
+ } else {
+ await localforage.setItem(`dict_${id}`, arrayBuffer);
+ }
+ }
+ }
+
+ /** Delete dict file by id */
+ static async deleteDict(id: string): Promise {
+ if (isElectron) {
+ const fs = window.require("fs");
+ const path = window.require("path");
+ const dir = path.join(getStorageLocation() || "", DICT_FOLDER);
+ if (!fs.existsSync(dir)) return;
+ const files: string[] = fs.readdirSync(dir);
+ const file = files.find((f) => f.startsWith(id + "."));
+ if (file) {
+ const filePath = path.join(dir, file);
+ if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
+ }
+ } else {
+ if (ConfigService.getReaderConfig("isUseLocal") === "yes") {
+ for (const ext of ["mdx", "mdd"]) {
+ await LocalFileManager.deleteFile(`${id}.${ext}`, DICT_FOLDER).catch(
+ () => {}
+ );
+ }
+ } else {
+ await localforage.removeItem(`dict_${id}`);
+ }
+ }
+ }
+
+ /** Get file path for Electron only */
+ static getDictFilePath(id: string): string | null {
+ if (!isElectron) return null;
+ const fs = window.require("fs");
+ const path = window.require("path");
+ const dir = path.join(getStorageLocation() || "", DICT_FOLDER);
+ if (!fs.existsSync(dir)) return null;
+ const files: string[] = fs.readdirSync(dir);
+ const file = files.find((f) => f.startsWith(id + "."));
+ return file ? path.join(dir, file) : null;
+ }
+
+ /** Look up a word in the local MDX/MDD dictionary */
+ static async lookupWord(id: string, word: string): Promise {
+ if (isElectron) {
+ try {
+ const filePath = this.getDictFilePath(id);
+ if (!filePath) return "";
+ const { MDX } = window.require("js-mdict");
+ const mdict = new MDX(filePath);
+ const result = mdict.lookup(word);
+ if (
+ !result ||
+ result.definition === null ||
+ result.definition === undefined
+ ) {
+ return "";
+ }
+ return String(result.definition);
+ } catch (e) {
+ console.error("Dict lookup error:", e);
+ return "";
+ }
+ } else {
+ // Browser: js-mdict requires file system access; not supported in web mode
+ return "";
+ }
+ }
+
+ /** Save dict metadata */
+ static saveDictMeta(id: string, meta: Omit): void {
+ ConfigService.setObjectConfig(id, { id, ...meta }, "customDicts");
+ }
+
+ /** Get dict metadata */
+ static getDictMeta(id: string): DictMeta | null {
+ return ConfigService.getObjectConfig(id, "customDicts", null);
+ }
+
+ /** Delete dict metadata */
+ static deleteDictMeta(id: string): void {
+ ConfigService.setObjectConfig(id, null, "customDicts");
+ }
+
+ /** Return all stored dict ids */
+ static getDictIds(): string[] {
+ return ConfigService.getAllListConfig("dictList") || [];
+ }
+
+ static addDictId(id: string): void {
+ ConfigService.setListConfig(id, "dictList");
+ }
+
+ static removeDictId(id: string): void {
+ ConfigService.deleteListConfig(id, "dictList");
+ }
+}
+
+export default DictUtil;