这是一个翻译框架,其主要特点是高度可配置性和高度可扩展性。
作为一个翻译框架,它有很多优点:
- 支持多种翻译引擎,比如 ChatGPT, Bing, Google, DeepL, 有道词典/近义词, StarDict, LibreTranslate 等
- 丰富的渲染方式,可以渲染到 Buffer, Posframe, Overlay, Kill Ring 等。支持流式渲染
- 可以灵活获取需要翻译的内容和语言,内置的 Taker 有丰富的选项适配不同场景
- 支持单词翻译,也支持句子翻译,并支持多段落翻译。可同时使用多个引擎,将多个段落,翻译为多种语言
- 支持不同的 HTTP 后端 (url.el, curl),请求纯异步和无阻塞,体验良好。支持为不同请求设定不同代理
- 基于 eieio (CLOS) 实现,各种组件,允许用户灵活搭配、自由扩展
当然,这不仅是一个翻译框架。它非常灵活,可以轻松扩展到任意文本转换的场合 (Text to Text):
- 比如内置的 Text Utility,集成了文本的加密解密、求取哈希、生成二维码等功能
- 比如可以扩展为 ChatGPT 的客户端 (TODO)
首先,需要通过 MELPA 或者其他途径下载并加载本包。
对于最基本的使用,添加如下代码到配置文件:
(setq gt-langs '(en zh))
(setq gt-default-translator (gt-translator :engines (gt-youdao-dict-engine)))
;; 上述配置的意思是,初始化默认翻译器,让其在 en 和 zh 之间通过有道词典进行翻译,
;; 结果将显示在 Echo Area 中
然后选中某段文本,执行命令 gt-do-translate
即可。
当然,也可以为翻译器指定更多选项,比如:
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'buffer :pick 'paragraph) ; 配置拾取器
:engines (list (gt-bing-engine) (gt-youdao-dict-engine)) ; 指定多引擎
:render (gt-buffer-render))) ; 配置渲染器
;; 上述配置的意思是,初始化默认翻译器,让其将当前 buffer 中的所有段落交给 Bing 和有道进行翻译,
;; 并将结果通过一个新的 Buffer 渲染出来
(setq gt-default-translator
(gt-translator
:taker (gt-taker :langs '(en zh) :text 'word :prompt t)
:engines (list (gt-stardict-engine :exact t)
(gt-youdao-dict-engine)
(gt-youdao-suggest-engine))
:render (gt-buffer-render)))
;; 上面配置适用于中英单词互译,初始化翻译器,让其翻译当前或选中单词,使用 StarDict 和有道进行翻译,
;; 结果使用新的 Buffer 渲染出来
除了通过 gt-default-translator
设定默认翻译器,也可以通过 gt-preset-translators
配置若干预设翻译器。
如果 gt-default-translator
为 nil,那么 gt-preset-translators
中的第一个将会被作为默认翻译器。
定义预设翻译器的语法如下:
(setq gt-preset-translators
`((ts-1 . ,(gt-translator
:taker (gt-taker :langs '(en zh) :text 'word)
:engines (gt-bing-engine)
:render (gt-overlay-render)))
(ts-2 . ,(gt-translator
:taker (gt-taker :langs '(en fr ru) :text 'sentence)
:engines (gt-google-engine)
:render (gt-insert-render)))
(ts-3 . ,(gt-translator
:taker (gt-taker :langs '(en zh) :text 'buffer
:pick 'word :pick-pred (lambda (w) (length> w 6)))
:engines (gt-google-engine)
:render (gt-overlay-render :type 'help-echo)))))
上面预设了三个翻译器:
- ts-1: 使用 Bing 在 en 和 zh 间进行翻译,翻译的是光标附近的单词或选中的文本,结果将以 overlay 的方式显示在当前位置
- ts-2: 使用 Google 在 en, fr 和 ru 间进行翻译,翻译的是光标附近的句子或选中的文本,结果将插入当前 buffer
- ts-3: 使用 Google 翻译 buffer 中所有长度大于 6 的单词,将鼠标放到被翻译的单词上后,翻译结果将以 popup 方式显示
随后就可以通过命令 gt-do-translate
进行翻译,并可通过 gt-do-setup
在不同翻译器间进行切换。
强烈建议:
安装 curl 程序和 plz.el 包。之后请求将会通过 curl 进行发送,效果比内置的 url.el 好很多!
请通过 M-x customize-group go-translate
查看更多配置选项,通过阅读之后的章节了解更多配置细节。
翻译框架最核心的组件是翻译器,即 gt-translator
, 它主要包含如下组件:
gt-taker
组件: 用来捕获用户的输入,包括文本和要翻译的语言gt-engine
组件: 用于将 taker 捕获的内容翻译成对应的目标文本gt-render
组件: 用于将 engine 翻译的结果聚合起来,并输出到用户
翻译的基本流程 [输入] -> [翻译/转换] -> [输出]
, 分别对应以上 [Taker] -> [Engine] -> [Render]
三种组件。
对翻译器执行 gt-start
方法,就会按照基本流程完成一次完整的翻译。
因此,配置的实质就是创建一个翻译器实例,并按需求为其指定不同的组件:
;; 通过 :taker :engines 和 :render 指定组件; 通过 gt-start 方法执行翻译
(gt-start (gt-translator :taker ... :engines ... :render ...))
;; gt-do-translate 命令使用 gt-default-translator 指向的翻译器执行翻译任务
(setq gt-default-translator (gt-translator :taker ... :engines ... :render ..))
(call-interactively #'gt-do-translate)
因此,完善配置之前,需要对组件进行进一步了解。
slot | 介绍 | 值 |
---|---|---|
text | 初始文本 | 字符串或返回字符串的一个函数,也可以是 ‘buffer ‘word ‘paragraph ‘sentence 等 symbol |
langs | 要翻译的语言 | 列表,比如 ‘(en zh), ‘(en ru it fr),如果为空,则采用变量 gt-langs 的值 |
prompt | 交互式确认 | 如果为 t 则通过 Minibuffer 确认,如果为 ‘buffer 则通过打开一个新 buffer 进行确认 |
pick | 从文本中挑选段落、句子或单词 | 进行挑选的函数,或者 ‘word ‘paragraph ‘sentence 等 symbol |
pick-pred | 用于过滤 pick 到的文字 | 传入字符串,输出布尔类型 |
then | take 之后要执行的逻辑,钩子 | 一个以当前 translator 为参数的函数。可以对 take 到的内容进行最后一步修改 |
if | 过滤 | 函数或字面量表达式,用于根据输入的内容决定 taker 是否适用于当前翻译任务 |
当前只内置了一个 Taker 实现,它可以适用大多数场景:
通过 text 决定初始文本,通过 langs 判定翻译语言,通过 prompt 进行确认,通过 pick 从中摘取某些段落、句子或单词
如果没有为翻译器指定 Taker 或指定了 Taker 但缺乏选项,将使用下面变量的值作为默认选项:
(setq gt-langs '(en zh)) ; 默认的翻译语言,至少要指定两个语言
(setq gt-taker-text 'word) ; 默认情况下,初始文本是光标下的单词。如果有文本选中,优先使用选中文本
(setq gt-taker-pick 'paragraph) ; 默认情况下,会按照段落标准分割初始文本。如果不想使用多段翻译,将其设置为 nil
(setq gt-taker-prompt nil) ; 默认情况下,没有 prompt 步骤。如果需要,将其设置为 t 或 'buffer
使用 :taker
显式为翻译器指定 Taker。比如,下面创建的 Taker 跟上述的配置是一致的:
(gt-translator :taker (gt-taker))
(gt-translator :taker (gt-taker :langs '(en zh) :text 'word :pick 'paragraph :prompt nil))
(gt-translator :taker (lambda () (gt-taker))) ; 可以是一个函数
;; 也可以是一个 taker 列表,那么返回第一个可用的
;; 结合 :if 判定,如果 taker 没有 :if 则一定是可用的
;; 比如下面的例子: 如果有文字被选中,prompt; 如果 buffer 只读,翻译当前段落; 否则翻译当前行
(gt-translator :taker (list
(gt-taker :prompt t :if 'selection)
(gt-taker :text 'paragraph :if 'read-only)
(gt-taker :text 'line)))
Taker 将使用 text
决定初始翻译内容。如果当前有文本被选中,则选中的文本被采用。否则使用下面规则:
;; 如果是 symbol 使用 thing-at-thing 的逻辑决定初始文本
(gt-translator :taker (gt-taker :text 'word)) ; 当前单词 (默认值)
(gt-translator :taker (gt-taker :text 'buffer)) ; 当前 buffer 的内容
(gt-translator :taker (gt-taker :text 'paragraph)) ; 当前段落的内容
(gt-translator :taker (gt-taker :text t)) ; 交互式选中一个 symbol,之后根据 symbol 选取
;; 如果是一个字符串或返回字符串的函数,则以其作为初始文本
(gt-translator :taker (gt-taker :text "hello world")) ; 固定文本
(gt-translator :taker (gt-taker :text (lambda () (buffer-substring 10 15)))) ; 函数返回值,字符串
(gt-translator :taker (gt-taker :text (lambda () '((10 . 15))))) ; 函数返回值,bounds
Taker 将从 langs
中选取要翻译的语言。默认会结合 gt-lang-rules
里的规则进行判定和选取:
(gt-translator :taker (gt-taker :langs '(en zh))) ; 在中、英之间进行翻译
(gt-translator :taker (gt-taker :langs '(en zh ru))) ; 在中、英、俄之间进行翻译
(setq gt-polyglot-p t) ; 如果将此变量设置为 t,那么将进行多语言翻译,即一次翻译成多语言并聚合输出
通过设定 prompt
让用户对初始文本和翻译语言进行交互式修改和确认:
;; 通过 Minibuffer 的方式进行确认。集成了一些快捷键,不仅可以修改文本,也可以切换语言
(gt-translator :taker (gt-taker :prompt t))
;; 通过打开新 Buffer 的方式进行确认。在某些场合,通过新 Buffer 进行某些调整工作是更合适的
(gt-translator :taker (gt-taker :prompt 'buffer))
最后,会根据 pick
和 pick-pred
对初始文本进行切割和提取。它返回的内容才是最终要被翻译的:
;; pick 可以是类似于 text 的 symbol
(gt-translator :taker (gt-taker ; 翻译整个 buffer 中所有段落
:text 'buffer
:pick 'paragraph))
(gt-translator :taker (gt-taker ; 翻译当前段落中长度大于 6 的单词
:text 'paragraph
:pick 'word :pick-pred (lambda (w) (length> w 6))))
;; pick 也可以是一个函数。下面例子等同于上面,翻译当前段落中长度大于 6 的单词
;; 也可以实现更复杂、更智能的选取逻辑。比如,只选取生词进行翻译
(defun my-get-words-length>-6 (text)
(cl-remove-if-not (lambda (bd) (> (- (cdr bd) (car bd)) 6))
(gt-pick-items-by-thing text 'word)))
(gt-translator :taker (gt-taker :text 'paragraph :pick #'my-get-words-length>-6))
;; 使用 :pick 'fresh-word 实现了“透析翻译”的效果,即结合训练的结果,只显示生僻词
;; 结合 gt-record-words-as-known/unkown 命令将单词标记(训练)为已经掌握的单词
(gt-translator :taker (gt-taker :text 'paragraph :pick 'fresh-word))
slot | 介绍 | 值 |
---|---|---|
parse | 指定解析器 | 解析器或函数 |
cache | 配置缓存 | 如果设为 nil 则为当前 engine 禁用缓存。也可以为不同 engine 指定不同 cacher 或缓存策略 |
stream | 是否开启 stream 模式 | 只有引擎支持流式 API 这个设置才有用。比如,ChatGPT 引擎 |
delimiter | 分隔符 | 如果不为空,则采取「连接-翻译-分割」的翻译策略 |
then | engine 完成后执行的逻辑,钩子 | 一个以当前 task 为参数的函数。可以用于在渲染之前对返回的内容进行最后一步修改 |
if | 过滤 | 函数或字面量表达式,用于根据输入的内容决定当前 engine 是否参与当前翻译任务 |
内置的 Engine 实现有:
gt-deepl-engine
, DeepL 翻译gt-bing-engine
, 微软翻译gt-google-engine/gt-google-rpc-engine
, Google 翻译gt-chatgpt-engine
, 使用 ChatGPT 进行翻译gt-youdao-dict-engine/gt-youdao-suggest-engine
, 有道词典/有道近义词。主要用于中英互译gt-stardict-engine
, StarDict,支持外挂字典,可以用于离线翻译gt-libre-engine
, LibreTranslate, 可以使用网络服务,也可以搭建本地服务
通过 :engines
为翻译器指定引擎。一个翻译器可以有一个或多个引擎,也可以指定一个返回引擎列表的函数:
(gt-translator :engines (gt-google-engine))
(gt-translator :engines (list (gt-google-engine) (gt-deepl-engine) (gt-chatgpt-engine)))
(gt-translator :engines (lambda () (gt-google-engine)))
若引擎存在多个解析器,则可以通过 parse
指定某个从而实现特定解析,比如:
(gt-translator
:engines (list (gt-google-engine :parse (gt-google-parser)) ; 详细结果
(gt-google-engine :parse (gt-google-summary-parser)))) ; 简约结果
可以通过 if
为不同引擎指定不同翻译任务,比如:
(gt-translator
:engines (list (gt-google-engine :if 'word) ; 只有当翻译内容为单词时启用
(gt-bing-engine :if '(and not-word parts)) ; 只有翻译内容不是单词且是多个段落时启用
(gt-deepl-engine :if 'not-word :cache nil) ; 只有翻译内容不是单词时启用; 不缓存
(gt-youdao-dict-engine :if '(or src:zh tgt:zh)) ; 只有翻译中文时启用
(gt-youdao-suggest-engine :if '(and word src:en)))) ; 只有翻译英文单词时启用
可以通过 cache
为不同引擎指定不同的缓存策略:
(gt-translator
:engines (list (gt-youdao-dict-engine) ; 默认缓存机制
(gt-google-engine :cache nil) ; 禁用缓存
(gt-bing-engine :cache 'word) ; 只缓存单词
(gt-deepl-engine :cache (gt-xxx-cacher)))) ; 指定其他缓存器
注意:
如果是多段落或多单词翻译,默认将会采取:
- 先将翻译的内容连成一个字符串,
- 通过一次翻译得到结果,
- 之后再将结果分割开来的翻译策略。
这时传递给 Engine 翻译的文本是一个单独的字符串。
如果将 delimiter 设定为 nil 那么传递给 Engine 的将是一个字符串列表,这时将需要 Engine 有处理列表的能力。
slot | 介绍 | 值 |
---|---|---|
prefix | 定制输出中的 Prefix 内容 | 函数或字符串。定制 Prefix 显示格式。Prefix 是输出结果中的语言、引擎提示的文本 |
then | 渲染完成后执行的逻辑,钩子 | 函数或另一个渲染器。可以将渲染任务传递给下一个渲染器,实现多渲染器输出的效果 |
if | 过滤 | 函数或字面量表达式,用于根据输入的内容决定 render 是否适用于当前翻译任务 |
内置的 Render 实现有:
gt-render
, 默认实现,会将结果输出到 Echo Areagt-buffer-render
, 打开一个在新 Buffer 来渲染结果 (推荐使用)gt-posframe-pop-render
, 在当前位置打开一个 childframe 弹窗来渲染结果gt-posframe-pin-render
, 使用屏幕固定位置的 childframe 窗口来渲染结果gt-insert-render
, 将翻译结果插入到当前 buffer,可设定插入的位置、样式等gt-overlay-render
, 将翻译结果通过 Overlay 的方式进行显示,可设定显示的位置、样式等gt-kill-ring-render
, 将翻译结果保存到 Kill Ring 中gt-alert-render
, 借助 alert 包将结果显示为系统消息
通过 :render
为翻译器配置渲染器。可以通过 :then
将多个渲染器串起来搭配使用:
(gt-translator :render (gt-alert-render))
(gt-translator :render (gt-alert-render :then (gt-kill-ring-render))) ; 以系统消息方式展示,并保存进 kill-ring
(gt-translator :render (lambda () (if buffer-read-only (gt-buffer-render) (gt-insert-render)))) ; 可以指定函数
可以结合 :if
选择使用列表中的第一个可用 render。这可以灵活配置不同情况下 render 的使用。比如:
(gt-translator
:render (list (gt-overlay-render :if 'selection) ; 如果翻译的是选中的文字,那么通过 overlay 方式渲染
(gt-posframe-pop-render :if 'word) ; 如果翻译的是单词,那么通过 posframe 方式渲染
(gt-alert-render :if '(and read-only not-word)) ; 如果翻译的是只读 buffer 中的非单词,那么通过 alert 渲染
(gt-buffer-render))) ; 默认,使用新 buffer 进行渲染
gt-memory-cacher
是内置的缓存实现,功能简单直接。只需要将 gt-cache-p
设置为 t 即可使用。
可以通过 gt-default-cacher
对缓存进行配置或切换其他缓存实现:
(setq gt-default-cacher (gt-memory-cacher :if 'word)) ; 只缓存单词
(setq gt-default-cacher (gt-memory-cacher :if '(or word not-src:zh))) ; 只缓存单词和非中文翻译
(setq gt-default-cacher (gt-xxxxxx-cacher)) ; 使用其他缓存实现
要关闭缓存,可以将总开关 gt-cache-p
设为 nil,也可以单独关闭某引擎的缓存:
(gt-translator :engines (gt-google-engine :cache nil))
可以通过扩展将翻译结果缓存进文件、SQLite 或 Redis 等。但感觉没必要。
某些引擎需要通过网络获取翻译结果,这需要借助 gt-http-client
组件进行网络处理。
默认情况下网络请求是通过 gt-url-http-client
处理的,它的底层是 url.el,效率很低。
组件 gt-plz-http-client
实现了通过 curl 进行 http 请求的目的,效果好很多很多。
可以通过 gt-default-http-client
对请求客户端进行配置。当然,如果检测到系统内已经存在 curl 程序并安装了 plz 包,组件 gt-plz-http-client
将自动设定为默认请求器。
如果想让请求走代理,这样设置:
;; 如果是 gt-url-http-client
(setq gt-default-http-client
(gt-url-http-client :proxies '(("http" . "host:9999") ("https" . "host:9999"))))
;; 如果是 gt-plz-http-client
(setq gt-default-http-client
(gt-plz-http-client :args '("--proxy" "socks5://127.0.0.1:9999")))
若想让不同请求走不同代理,可以仿照下面方式设置:
(setq gt-default-http-client
(lambda (host)
(if (string-match-p "google\\|deepl\\|openai" host)
(gt-plz-http-client :args '("--proxy" "socks5://127.0.0.1:9999"))
(gt-plz-http-client))))
(setq gt-default-http-client
(lambda (host)
(let ((proxy? (string-match-p "google\\|deepl\\|openai" host)))
(if (require 'plz nil t)
(if proxy?
(gt-plz-http-client :args '("--proxy" "socks5://127.0.0.1:9999"))
(gt-plz-http-client))
(if proxy?
(gt-url-http-client :proxies '(("http" . "host:9999") ("https" . "host:9999")))
(gt-url-http-client))))))
如果通过 minibuffer 进行 prompt,那么在 minibuffer 中存在如下快捷键:
C-n
和C-p
切换语言C-l
清空输入C-g
取消翻译
如果通过 buffer 进行 prompt,那么在打开的 buffer 中默认存在如下快捷键:
C-c C-c
提交修改,进行翻译C-c C-k
取消翻译- 也可以切换语言、切换组件,通过 mode-line 获取更多信息
可以通过 pick 的 fresh-word
选项实现只翻译生僻词的目的。基本步骤:
- 配置要使用的翻译器,将 pick 指定为 fresh-word, 比如:
(setq gt-default-translator (gt-translator :taker (gt-taker :text 'paragraph :pick 'fresh-word) :engines (gt-bing-engine) :render (gt-overlay-render :sface nil)))
- 对文本进行翻译,默认会对目标文本中的所有单词进行翻译
- 执行
gt-record-words-as-known
命令,按提示将已经掌握的单词记录在案 - 持续执行 2/3 步骤,训练掌握单词的量。已记录单词作为非生僻词将不会出现在之后的翻译中
- 可执行
gt-record-words-as-unknown
将某单词重新设定为生僻词 - 本功能有很大的优化提升空间。比如换作用数据库记录,统计单词的翻译次数等,按下不提
这是个支持外挂字典的离线翻译引擎。
首先,需要确保你的系统中已经安装了 sdcv:
sudo pacman -S sdcv
另外,需要下载字典文件放入到相关目录。比如下面是在 Linux 下安装朗道字典文件的示例:
mkdir -p ~/.stardict/dic
cd ~/.stardict/dic
wget http://download.huzheng.org/zh_CN/stardict-langdao-ce-gb-2.4.2.tar.bz2
wget http://download.huzheng.org/zh_CN/stardict-langdao-ce-gb-2.4.2.tar.bz2
tar xvf stardict-langdao-ec-gb-2.4.2.tar.bz2
tar xvf stardict-langdao-ce-gb-2.4.2.tar.bz2
sdcv -l
之后,你就可以配置使用此引擎了:
;; 基本配置
(setq gt-default-translator
(gt-translator :engines (gt-stardict-engine)
:render (gt-buffer-render)))
;; 可以指定更多选项
(setq gt-default-translator
(gt-translator :engines (gt-stardict-engine
:dir "~/.stardict/dic" ; 指定数据文件位置
:dict "朗道英汉字典5.0" ; 可以指定具体使用的字典
:exact t) ; do not fuzzy-search, only return exact matches
:render (gt-buffer-render)))
注意: 如果是通过 Buffer-Render 等渲染,可以通过点击字典名或错误提示实现字典切换 (快捷键: C-c C-c
)。
DeepL 需要 auth-key 才能正常使用,首先需要通过官网进行获取。
然后,可以通过下列方法对 auth-key 进行设置:
- 在 engine 定义中直接指定:
(gt-translator :engines (gt-deepl-engine :key "***"))
- 将 auth-key 存进系统的
.authinfo
文件中:machine api.deepl.com login auth-key password ***
ChatGPT 需要 apikey 才能正常使用:
;; 提供 apikey 有很多方式
(setq gt-chatgpt-key "YOUR-KEY")
(gt-chatgpt-engine :key "YOUR_KEY")
(find-file "~/.authinfo") ; machine api.openai.com login apikey password [YOUR_KEY]
;; 其他配置
(setq gt-chatgpt-host "YOUR-HOST")
(setq gt-chatgpt-model "gpt-3.5-turbo")
(setq gt-chatgpt-temperature 0.7)
可以自定义翻译的 prompt。比如:
(setq gt-chatgpt-user-prompt-template
(lambda (text lang)
(format "将文本翻译成 %s 然后将结果复制3份返回。文本如下: \n%s"
(alist-get lang gt-lang-codes) text)))
甚至可以基于其它自定义 Prompt,借助 ChatGPT 完成其他任务。比如下面的命令用于润色句子:
(defun my-command-polish-using-ChatGPT ()
(interactive)
(let ((gt-chatgpt-system-prompt "你是一个优秀的网文写手")
(gt-chatgpt-user-prompt-template (lambda (text _)
(read-string
"Prompt: "
(format "润色句子,要求只返回生成的句子: %s" text)))))
(gt-start (gt-translator
:engines (gt-chatgpt-engine :cache nil)
:render (gt-insert-render)))))
可以通过设置 :stream t
让引擎返回流式 (stream) 结果,即内容一点点返回并输出。下面是例子:
;; 下面配置了三个引擎,第一个是流式的,另外两个是普通的
;; 其中 Buffer Render, Posframe Render 和 Insert Render 可以进行流式渲染
(setq gt-default-translator
(gt-translator :taker (gt-taker :pick nil)
:engines (list (gt-chatgpt-engine :stream t)
(gt-chatgpt-engine :stream nil)
(gt-google-engine))
:render (gt-buffer-render)))
;; 下面配置,将查询的内容一点点插入到 buffer 当前位置
(setq gt-default-translator
(gt-translator :taker (gt-taker :pick nil :prompt t)
:engines (gt-chatgpt-engine :stream t)
:render (gt-insert-render)))
另外,可以通过 gt-do-speak
尝试其语音播报。
打开一个新的 buffer 来展示翻译结果。这是非常通用的一种展示结果的方式。
在弹出的 buffer 中,存在若干快捷键 (可以通过 ?
获取到相关提示),比如:
- 通过
t
切换语言 - 通过
T
切换多语言模式 - 通过
C
清除缓存 - 通过
g
刷新 - 通过
q
退出
另外,通过 y
(命令 gt-do-speak
) 播放语音。可以先选中文本,然后通过 y
只播放选取片段的语音。
这需要这些引擎已经实现了语音播放的功能。另外,在其他任何地方调用 gt-do-speak
命令,将会尝试
使用操作系统本身的 TTS 功能对当前的文本进行语音播报。
可以通过 buffer-name/window-config/split-threshold
等对弹出的窗口进行设定:
(gt-translator :render (gt-buffer-render
:buffer-name "abc"
:window-config '((display-buffer-at-bottom))
:then (lambda (_) (pop-to-buffer "abc"))))
下面是若干使用示例:
;; 捕获光标下的单词或选区,使用 Google 翻译单词,使用 DeepL 翻译句子,使用 Buffer 展示结果
;; 这是非常通用的一种配置方式
(setq gt-default-translator
(gt-translator
:taker (gt-taker :langs '(en zh) :text 'word)
:engines (list (gt-google-engine :if 'word) (gt-deepl-engine :if 'not-word))
:render (gt-buffer-render)))
;; 封装了一个命令,用于将 Buffer 中的多个段落翻译为多种语言,并渲染到新的 Buffer 中
;; 这主要展示了命令的封装,以及多引擎多段落多语言的聚合显示效果
(defun demo-translate-multiple-langs-and-multiple-parts ()
(interactive)
(let ((gt-polyglot-p t)
(translator (gt-translator
:taker (gt-taker :langs '(en zh ru ja) :text 'buffer :pick 'paragraph)
:engines (list (gt-google-engine) (gt-deepl-engine))
:render (gt-buffer-render))))
(gt-start translator)))
需要安装 posframe 之后才能使用。
这两个 Render 的效果跟 gt-buffer-render
类似,只不过它的窗口是浮动的。
快捷键也是一致的,比如 q
表示退出。
可以通过 :frame-params
向 posframe 传递任意需要的参数:
(gt-posframe-pin-render :frame-params (list :border-width 20 :border-color "red"))
将翻译结果插入到当前 buffer。
可以指定如下类型 (type
):
after
, 默认类型,将结果插入到光标之后replace
, 用翻译结果替换被翻译的源文本
如果对默认的输出格式和样式不满意,可以通过如下选项进行调整:
sface
, 翻译完成后,被翻译的源文本的 facerfmt
, 翻译结果的输出格式rface
, 为翻译结果指定特定样式
选项 rfmt
是一个包含控制字符 %s
的字符串,也可以是一个函数:
;; %s 是翻译结果的占位符
(gt-insert-render :rfmt " [%s]")
;; 一个参数,传入的是翻译结果字符串
(gt-insert-render :rfmt (lambda (res) (concat " [" res "]")))
;; 两个参数,则第一个是源文本
(gt-insert-render :rfmt (lambda (stext res)
(if (length< stext 3)
(concat "\n" res)
(propertize res 'face 'font-lock-warning-face)))
:rface 'font-lock-doc-face)
下面是若干使用示例:
;; 按段落进行翻译,将每一段翻译的结果,插入到段落后面
;; 这种配置适合文章的翻译工作。基本流程是: 翻译 -> 修改 -> 保存
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'buffer :pick 'paragraph)
:engines (gt-google-engine)
:render (gt-insert-render :type 'after)))
;; 翻译当前段落,并使用翻译的结果替换掉被翻译的段落
;; 这种配置适合即时聊天等场合。输入文本,翻译得到译文,执行发送
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'paragraph :pick nil)
:engines (gt-google-engine)
:render (gt-insert-render :type 'replace)))
;; 将当前段落中符合条件的单词进行翻译,并将结果插入到单词之后
;; 这种配置方式,可以辅助阅读有生僻字的文章
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'paragraph
:pick 'word
:pick-pred (lambda (w) (length> w 6)))
:engines (gt-google-engine)
:render (gt-insert-render :type 'after
:rfmt " (%s)"
:rface '(:foreground "grey"))))
使用 Overlay 显示翻译结果。
通过 type
设置显示的方式:
after
, 默认类型,将翻译结果显示在源文本后面before
, 将翻译结果显示在源文本前面replace
, 将翻译结果覆盖显示到源文本上面help-echo
, 鼠标移动到源文本上时,翻译结果才弹出显示
它在很多方面跟 gt-insert-render
很像,包括选项:
sface
, 翻译完成后,被翻译的源文本的 facerfmt
, 翻译结果的输出格式rface/rdisp
, 为翻译结果指定特定样式pface/pdisp
, 单独为翻译后的 Prefix (语言、引擎的提示) 设定样式
下面是若干使用示例:
;; 翻译 buffer 中所有段落,将结果通过指定格式显示在原段落之后
;; 这是一种适合阅读 Info, News 等只读内容的配置
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'buffer :pick 'paragraph)
:engines (gt-google-engine)
:render (gt-overlay-render :type 'after
:sface nil
:rface 'font-lock-doc-face)))
;; 将 Buffer 中所有符合条件的单词做标记,当鼠标移上去的时候显示翻译结果
;; 这是一种实用的配置,适合阅读存在某些生僻词的文章
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'buffer :pick 'word :pick-pred (lambda (w) (length> w 5)))
:engines (gt-google-engine)
:render (gt-overlay-render :type 'help-echo)))
;; 也可以将符合条件单词的翻译直接显示在原单词后面
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'buffer :pick 'word :pick-pred (lambda (w) (length> w 5)))
:engines (gt-google-engine)
:render (gt-overlay-render :type 'after
:sface nil
:rfmt "%s"
:rdisp '(space (:width 0.3) raise 0.6)
:rface '(:foreground "grey" :height 0.5))))
;; 使用 Overlay 把翻译的结果直接覆盖到原文之上
;; 对于某篇文章,如果想通过速览的方式获取其大致意思,适合使用这种配置
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'buffer)
:engines (gt-google-engine)
:render (gt-overlay-render :type 'replace)))
派生自 gt-translator
的一个组件,集成了很多文本转换和处理方面的功能。
这展示了本框架的扩展性,它不仅可以应用在翻译方面,其 taker 和 render 具备普适性。
如果要生成二维码,需要在系统中安装 qrencode
程序或通过 MELPA 安装 qrencode
包:
pacman -S qrencode
brew install qrencode
# or in Emacs
M-x package-install qrencode
另外,可以通过扩展 generic 方法 gt-text-util
集成其他想要的功能。
下面是若干使用示例:
;; 默认情况下,通过 completing-read 选择如何进行文本处理
;; 注意:无需为其指定 engines
(setq gt-default-translator
(gt-text-utility :render (gt-buffer-render)))
;; 为当前文本生成二维码 (通过 :langs 指定 utility)
;; 实用的配置,适用于电脑向手机传递文本片段
(setq gt-default-translator
(gt-text-utility
:taker (gt-taker :langs '(qrcode) :pick nil)
:render (gt-buffer-render)))
;; 为 buffer 中的每段文字都生成 TTS 按钮以及其 md5 值
(setq gt-default-translator
(gt-text-utility
:taker (gt-taker :langs '(speak md5) :text 'buffer :pick 'paragraph)
:render (gt-posframe-pin-render)))
组件 gt-taker
, gt-engine
和 gt-render
等都继承了 gt-validator
, 它通过 :if
提供了判定组件可用性的方式,
因此可以大大简化不同场景下 translator 的配置。
槽 :if
的值可以是函数,也可以是内置实现的一些 symbol, 或者通过 and/or 连接起来的 form 列表。
另外, symbol 可以使用 not-
或 no-
为前缀表示反向判定。
部分内置 symbol:
word
翻译的文本是单词src:en
翻译的源语言是英语tgt:en
翻译的目的语言是英语parts
翻译的是分段的文本read-only
当前 buffer 是只读的selection
当前翻译的是选中的文本emacs-lisp-mode
以 mode 结尾,表示须匹配当前模式not-word
orno-word
反向判定,翻译的文本 不是 单词
一个粗糙的配置示例:
;; 对于选中的文本,不分段,并使用 posframe 渲染
;; 对于 Info,翻译当前段落,使用 overlay 显示结果
;; 对于只读文本,翻译整个 buffer 中的生词,并使用 overlay 渲染
;; 对于 Magit commit buffer,将翻译结果插入到光标位置
;; 对于单词,使用 google 引擎翻译; 其他使用 deepl 引擎
(setq gt-default-translator
(gt-translator
:taker (list (gt-taker :pick nil :if 'selection)
(gt-taker :text 'paragraph :if '(Info-mode help-mode))
(gt-taker :text 'buffer :pick 'fresh-word :if 'read-only)
(gt-taker :text 'word))
:engines (list (gt-google-engine :if 'word)
(gt-deepl-engine :if 'no-word))
:render (list (gt-posframe-pop-render :if 'selection)
(gt-overlay-render :if 'read-only)
(gt-insert-render :if (lambda (translator) (member (buffer-name) '("COMMIT_EDITMSG"))))
(gt-alert-render :if '(and xxx-mode (or not-selection (and read-only parts))))
(gt-buffer-render))))
代码基于 eieio (CLOS) 编写,所有的组件都是类,因此几乎每一部分都是可以扩展或替换的。
比如,要实现一个引擎,让它将捕获的文本倒序输出。实现起来很简单:
;; 首先,定义引擎,继承自 gt-engine
(defclass my-reverse-engine (gt-engine)
((delimiter :initform nil)))
;; 其次,为引擎实现 gt-translate 方法
(cl-defmethod gt-translate ((_ my-reverse-engine) task next)
(with-slots (text res) task
(setf res (cl-loop for c in text collect (reverse c)))
(funcall next task)))
;; 最后,配置使用
(setq gt-default-translator (gt-translator :engines (my-reverse-engine)))
比如,想扩展 Taker,让它能够捕获 org mode 中所有的标题。也很简单:
;; [实现] 让 Taker 的 text 支持 org-headline,只需要对方法进行特化
(cl-defmethod gt-thing-at-point ((_ (eql 'org-headline)) (_ (eql 'org-mode)))
(let (bds)
(org-element-map (org-element-parse-buffer) 'headline
(lambda (h)
(save-excursion
(goto-char (org-element-property :begin h))
(skip-chars-forward "* ")
(push (cons (point) (line-end-position)) bds))))))
;; [使用] 通过 :text org-headline 捕获所有 headline; 通过 overlay 展示结果
(setq gt-default-translator (gt-translator
:taker (gt-taker :text 'org-headline)
:engines (gt-google-engine)
:render (gt-overlay-render :rfmt " (%s)" :sface nil)))
如此这般,只要发挥想象,将可以做到很多。
要打开调试,需要将 gt-debug-p
设为 t。之后将能在 *gt-log*
中查看日志内容。
我使用翻译不多,这个框架纯粹是兴趣使然。因为对翻译工作的认知有限,某些功能设置未必合理。 因此若有同学和专业人士提出好的想法和建议,必欣然受之。请不吝赐教,谢谢。