之前我写的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
知识共享署名-非商业性使用-相同方式共享4.0国际许可协议