之前我写的Emacs翻译插件 maple-translate 有一个 sdcv 离线翻译的功能,它使用了
Elisp
解析 StarDict 的字典文件,所以不需要安装其它任何依赖。但是也是因为此原因,首次使用离线翻译需要等待字典文件加载到内存,这不是很友好,此次我将添加 sdcv 二进制文件的支持,使离线翻译能够更加快捷方便
首先是安装 sdcv 二进制文件
└──╼ brew install sdcv └──╼ sdcv --help 用法: sdcv [选项…] words 帮助选项: -h, --help 显示帮助选项 应用程序选项: -v, --version display version information and exit -l, --list-dicts display list of available dictionaries and exit -u, --use-dict=bookname for search use only dictionary with this bookname -n, --non-interactive for use in scripts -j, --json-output print the result formatted as JSON --json print the result formatted as JSON -e, --exact-search do not fuzzy-search for similar words, only return exact matches -0, --utf8-output output must be in utf8 -1, --utf8-input input of sdcv in utf8 -2, --data-dir=path/to/dir use this directory as path to stardict data directory -x, --only-data-dir only use the dictionaries in data-dir, do not search in user and system directories -c, --color colorize the output
然后定义二进制文件的路径,如果 sdcv 未安装,则返回为 nil
(defvar maple-translate-sdcv-program (executable-find "sdcv"))
接着修改之前写好的 maple-translate-sdcv
函数,通过判断 maple-translate-sdcv-program
是否为空采取不同的翻译操作
(defun maple-translate-sdcv(word &optional callback) "Search WORD with sdcv, use async request if CALLBACK non-nil." (if maple-translate-sdcv-program (maple-translate-execute maple-translate-sdcv-program :args (append '("-n" "-x" "-j" "-0" "-1" "-2") (cl-loop for dict in maple-translate-sdcv-dicts collect (expand-file-name (cdr dict) maple-translate-sdcv-dir)) (list word)) :format (maple-translate-sdcv-format) :callback callback) ;; ... ;; 使用ELisp解析并翻译 ))
需要说明的是,由于该函数接收一个 callback
的变量,用于处理异步翻译,如果是同步翻译,可以直接使用 call-process
获取结果
(with-temp-buffer (apply 'call-process "sdcv" nil t nil '("-n" "-x" "-j" "-0" "-1" "-2" "/Users/xxx/.emacs.d/stardict/stardict-lazyworm-ec-2.4.2" "word")) (buffer-string))
但如果是异步翻译,则需要使用 start-process
,再通过监听进程状态在进程结束后再获取翻译结果
(let ((name (format "maple-translate-process %s" ,program))) (set-process-sentinel (apply 'start-process name (format "*%s*" name) ,program ,args) (lambda(process _) (unless (process-live-p process) (with-current-buffer (process-buffer process) (prog1 (funcall ,callback ,format) (kill-buffer (current-buffer))))))))
这里的 buffer 名称也可以通过 (generate-new-buffer " *temp*" t)
生成一个临时 buffer
最后就是翻译结果的展示,由于输出的是多行 json,比如:
[] [{"dict": "懒虫简明英汉词典","word":"word","definition":"\n[wә:d]\nn.\n字, 词, 话, 消息, 诺言, 命令\nvt.\n为...措辞"}]
所以我在解析翻译结果时取了个巧,没有使用 (buffer-string)
而是直接在 buffer 里操作,每次都跳到开始位置,再依次向下移动 n 行,这个 n 即是字典的数量,最后使用 (thing-at-point 'line t)
获取当前行的数据
(defun maple-translate-sdcv-format() "Format result with sdcv output." (let ((results (cl-loop for index from 0 for dicts in maple-translate-sdcv-dicts collect (progn (goto-char (point-min)) (forward-line index) (string-join (cl-loop for child across-ref (json-read-from-string (decode-coding-string (thing-at-point 'line t) 'utf-8)) collect (format "%s: %s" (alist-get 'dict child) (alist-get 'definition child))) "\n\n"))))) (unless (null results) (string-join (cl-remove nil results) "\n\n"))))
最终效果
maple-translate的具体修改可见: dfd0eae