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