This is a translation framework on Emacs, with high configurability and extensibility.
As a translation framework, it offers many advantages:
- Supports multiple translation engines, including ChatGPT, Bing, Google, DeepL, YoudaoDict, StarDict, LibreTranslate and more.
- Rich rendering components, such as rendering to Buffer, Posframe, Overlay, Kill Ring, and others. With stream output support.
- Flexible retrieval of content and language for translation, with the help of the built-in Taker component.
- Support for word and sentence translation, as well as translation of multiple paragraphs. It can use multiple engines concurrently to translate multiple paragraphs into multiple languages.
- Support for different HTTP backends (url.el, curl) with asynchronous and non-blocking requests, providing a smooth user experience. Support independent proxy configs.
- Implemented based on eieio (CLOS), allowing users to flexibly configure and extend the various components.
It’s more than just a translation framework.It’s flexible, and can easily be extended to various Text-to-Text conversion scenarios:
- For example, the built-in Text-Utility component integrates text encryption/decryption, hashing, QR code generation, etc.
- For example, it can be extended as a client for ChatGPT (TODO)
First, you need to download and load this package via MELPA or other ways.
For the most basic use, add the following code to the configuration file:
(setq gt-langs '(en fr))
(setq gt-default-translator (gt-translator :engines (gt-google-engine)))
;; This configuration means:
;; Initialize the default translator, let it translate between en and fr via Google Translate,
;; and the result will be displayed in the Echo Area.
Then select a certain text, and start translation with command gt-do-translate
.
That’s it.
Of course, it is possible to specify more options for the translator, such as:
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'buffer :pick 'paragraph) ; config the Taker
:engines (list (gt-bing-engine) (gt-google-engine)) ; specify the Engines
:render (gt-buffer-render))) ; config the Render
;; This configuration means:
;; Initialize the default translator, let it send all paragraphs in the buffer to Bing and Google,
;; and output the results with a new Buffer.
Except config default translator with gt-default-translator
, you can define several preset translators with gt-preset-translators
.
The first translator in gt-preset-translators
will be used as the default one if gt-default-translator
is nil.
The preset translators are defined like this:
(setq gt-preset-translators
`((ts-1 . ,(gt-translator
:taker (gt-taker :langs '(en fr) :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 fr) :text 'buffer
:pick 'word :pick-pred (lambda (w) (length> w 6)))
:engines (gt-google-engine)
:render (gt-overlay-render :type 'help-echo)))))
This configuration presets three translators:
- ts-1: translate word or selected region near the cursor between
en
andfr
via Bing, display the translated result with Overlay - ts-2: translate sentence or selected region near the cursor between
en
,fr
andru
via Google, insert the translated result into current buffer - ts-3: translate all words with length more than 6 in buffer between
en
andfr
via Google, display the translated result with help echo
Then, translate with command gt-do-translate
and switch between preset translators with command gt-do-setup
.
highly recommended:
Install the curl program and the plz.el package. The request will then be sent through curl, which is much better than the built-in url.el!
See more configuration options via M-x customize-group go-translate
, and read the following chapters for more configuration details.
The core component of the translation framework is gt-translator
, which contains the following components:
gt-taker
: used to capture user input, including text and languages to be translatedgt-engine
: used to translate the content captured by the taker into the corresponding target textgt-render
: used to aggregate results from engines and output them to the user
The flow of translation is [Input] -> [Translate/Transform] -> [Output]
, corresponding to the components [Taker] -> [Engine] -> [Render]
above.
Executing the method gt-start
on the translator will complete a full translation flow.
Therefore, the essence of configuration is to create a translator instance and specify different components according to needs:
;; specify components with ':taker' ':engines' and ':render'; start translation with 'gt-start'
(gt-start (gt-translator :taker ... :engines ... :render ...))
;; command 'gt-do-translate' use the translator defined in 'gt-default-translator' to do its job
(setq gt-default-translator (gt-translator :taker ... :engines ... :render ..))
(call-interactively #'gt-do-translate)
Therefore, one needs to understand these components first for better configuration.
slot | desc | value |
---|---|---|
text | Initial text | String or a function that returns a string, it can also be symbol like ‘buffer ‘word ‘paragraph ‘sentence etc |
langs | Translate languages | List as ‘(en fr), ‘(en ru it fr), if empty, use the value of gt-langs instead |
prompt | Interactive Confirm | If t, confirm by minibuffer. If ‘buffer, confirm by opening a new buffer |
pick | Pick paragraphs, sentences or words from initial text | Function or a symbol like ‘word ‘paragraph ‘sentence etc |
pick-pred | Used to filter the text picked | Pass in a string and output a Boolean type |
then | The logic to be executed after take. Hook | A function that takes the current translator as argument. The final modification can be made to the content captured by Taker |
if | Validate | Function or literal symbol, used to determine whether taker is available for current translation task |
Currently there is only one built-in Taker implementation, which can be used in most scenarios:
Determine the initial text with 'text', determine the translation languages with 'langs', confirm with 'prompt', and extract certain paragraphs, sentences, or words with 'pick'.
If no Taker is specified or if Taker is specified but lacks options, the values of the following variables will be used as default:
(setq gt-langs '(en fr)) ; Default translation languages, at least two must be specified
(setq gt-taker-text 'word) ; By default, the initial text is the word under the cursor. If there is active region, the selected text will be used first
(setq gt-taker-pick 'paragraph) ; By default, the initial text will be split by paragraphs. If you don't want to use multi-parts translation, set it to nil
(setq gt-taker-prompt nil) ; By default, there is no confirm step. Set it to t or 'buffer if needed
It’s better to use :taker
to explicitly specify a Taker for the translator:
(gt-translator :taker (gt-taker))
(gt-translator :taker (gt-taker :langs '(en fr) :text 'word :pick 'paragraph :prompt nil))
(gt-translator :taker (lambda () (gt-taker))) ; a function
(gt-translator :taker (list ; a list, use the first available one
(gt-taker :prompt t :if 'selection)
(gt-taker :text 'paragraph :if 'read-only)
(gt-taker :text 'line)))
Taker will use text
to determine the initial text. If there is active region, the selected text is taken. Otherwise use the following rules:
;; It can be a symbol, then use logic like 'thing-at-thing' to take the text
(gt-translator :taker (gt-taker :text 'word)) ; current word (default)
(gt-translator :taker (gt-taker :text 'buffer)) ; current buffer
(gt-translator :taker (gt-taker :text 'paragraph)) ; current paragraph
(gt-translator :taker (gt-taker :text t)) ; interactively choose a symbol, then take by the symbol
;; If it's a string or a function that returns a string, use it as the initial text
(gt-translator :taker (gt-taker :text "hello world")) ; just the string
(gt-translator :taker (gt-taker :text (lambda () (buffer-substring 10 15)))) ; the returned string
(gt-translator :taker (gt-taker :text (lambda () '((10 . 15))))) ; the returned bounds
Taker determine the languages to translate from langs
in the help of gt-lang-rules
:
(gt-translator :taker (gt-taker :langs '(en fr))) ; between English and French
(gt-translator :taker (gt-taker :langs '(en fr ru))) ; between English, French and Russian
(setq gt-polyglot-p t) ; If this is t, then multilingual translation will be performed, i.e., translated into multiple languages at once and the output aggregated
By setting prompt
to allow the user to modify and confirm the initial text and languages interactively:
;; Confirm by minibuffer
(gt-translator :taker (gt-taker :prompt t))
;; Confirm by new buffer
(gt-translator :taker (gt-taker :prompt 'buffer))
Finally, the initial text is cut and filtered based on pick
and pick-pred
. The content it returns is what will ultimately be translated:
;; It can be a symbol like those used by text slot
(gt-translator :taker (gt-taker ; translate all paragraphs in the buffer
:text 'buffer
:pick 'paragraph))
(gt-translator :taker (gt-taker ; translate all words longer than 6 in the paragraph
:text 'paragraph
:pick 'word :pick-pred (lambda (w) (length> w 6))))
;; It can be a function. The following example is also translating words longer than 6 in current paragraph.
;; More complex and intelligent pick logic can be implemented
(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))
;; Use ':pick 'fresh-word' to pick unknown word only for translation
;; With commands 'gt-record-words-as-known/unknown' to add word to known/unknown list
(gt-translator :taker (gt-taker :text 'paragraph :pick 'fresh-word))
slot | desc | value |
---|---|---|
parse | Specify parser | A parser or a function |
cache | Configure cache | If set to nil, cache is disabled for the current engine. You can also specify different cachers or cache strategies for different engines |
stream | Whether turn on stream query | Boolean. Works only when engines support stream, for example ChatGPT engine. |
delimiter | Delimiter | If not empty, the translation strategy of “join-translate-split” will be adopted |
then | The logic to be executed after the engine is completed. Hook | A function that takes current task as argument. Can be used to make final modifications to the translate result before rendering |
if | Filter | Function or literal symbol, used to determine whether the current engine should work for current translation task |
The built-in Engine implementations are:
gt-deepl-engine
, DeepL Translategt-bing-engine
, Bing Translategt-google-engine/gt-google-rpc-engine
, Google Translategt-chatgpt-engine
, translate with ChatGPTgt-youdao-dict-engine/gt-youdao-suggest-engine
, 有道翻译,有道近义词gt-stardict-engine
, StarDict,for offline translategt-libre-engine
, LibreTranslate, support both online and offline translate
Specify engines for translator via :engines
. A translator can have one or more engines, or you can specify a function that returns the 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)))
If a engine has multiple parsers, you can specify one through parse
to achieve specific parsing, such as:
(gt-translator :engines
(list (gt-google-engine :parse (gt-google-parser)) ; detail results
(gt-google-engine :parse (gt-google-summary-parser)))) ; brief results
You can use if
to filter the engines for current translation task. For example:
(gt-translator :engines
(list (gt-google-engine :if 'word) ; Enabled only when translating a word
(gt-bing-engine :if '(and not-word parts)) ; Enabled only when translating single part sentence
(gt-deepl-engine :if 'not-word :cache nil) ; Enabled only when translating sentence; disable cache
(gt-youdao-dict-engine :if '(or src:fr tgt:fr)))) ; Enabled only when translating French
You can specify different caching policies for different engines with cache
:
(gt-translator :engines
(list (gt-youdao-dict-engine) ; use default cacher
(gt-google-engine :cache nil) ; disable cache
(gt-bing-engine :cache 'word) ; cache for word only
(gt-deepl-engine :cache (gt-xxx-cacher)))) ; use specify cacher
Notice:
If translate multiple parts text, the default strategy is:
- join the parts into a single string,
- translate the whole string through the engine,
- then split the result into parts.
The text passed to the Engine for translation should be a single string.
If delimiter is set to nil, then a list of strings will be passed to the engine, and the engine should have the ability to process the string list.
slot | desc | value |
---|---|---|
prefix | Customize the Prefix | Override the default Prefix format. Set to nil to disable prefix output |
then | Logic to be executed after rendering is complete. Hook | function or another Render. The rendering task can be passed to the next Render to achieve the effect of multi-renders output |
if | Validate | Function or literal symbol, used to determine whether render is available for current translation task |
The built-in Render implementations:
gt-render
, the default implementation, will output the results to Echo Areagt-buffer-render
, open a new Buffer to render the results (recommended)gt-posframe-pop-render
, open a childframe at the current position to render the resultsgt-posframe-pin-render
, use a childframe window with fixed position on the screen to render the resultsgt-insert-render
, insert the results into current buffergt-overlay-render
, displays the results through Overlaygt-kill-ring-render
, save the results to Kill Ringgt-alert-render
, display results as system notification with the help of alert package
Configure render for translator via :render
. Multiple renders can be chained together with :then
:
(gt-translator :render (gt-alert-render))
(gt-translator :render (gt-alert-render :then (gt-kill-ring-render))) ; display as system notification then save in kill ring
(gt-translator :render (lambda () (if buffer-read-only (gt-buffer-render) (gt-insert-render)))) ; a function return render
The first available render in the list (validate conjunction with :if
) can be used as the final render. For example:
(gt-translator
:render (list (gt-posframe-pop-render :if 'word) ; if current translation text is word, render with posframe
(gt-alert-render :if '(and read-only not-word)) ; if text is not word and buffer is readonly, render with alert
(gt-buffer-render))) ; default, render with new buffer
Component gt-memory-cacher
is the built-in cache implementation. Just set gt-cache-p
to t to use it.
You can configure the cacher or switch to another cacher by setting gt-default-cacher
:
(setq gt-default-cacher (gt-memory-cacher :if 'word)) ; just cache for word
(setq gt-default-cacher (gt-memory-cacher :if '(or word src:en))) ; just cache for word or english
(setq gt-default-cacher (gt-xxxxxx-cacher)) ; use other cacher
Set gt-cache-p
to nil to turn off all caches. Or turn off the cache for engine individually like this:
(gt-translator :engines (gt-google-engine :cache nil))
Translation results can be cached in files, SQLite or Redis through extensions. But maybe it’s unnecessary.
Some engines need to fetch translation results over the network, which requires network processing with the help of the gt-http-client
component.
By default, gt-url-http-client
is used as the http client, which is inefficient.
The component gt-plz-http-client
uses curl
to send the request, which is much better.
Config gt-default-http-client
to switch http client. Or just make sure curl
and plz is exists in your system, then gt-plz-http-client
will be used as the default http client without any other configs.
To send request with proxy, config like this:
;; for gt-url-http-client
(setq gt-default-http-client
(gt-url-http-client :proxies '(("http" . "host:9999") ("https" . "host:9999"))))
;; for gt-plz-http-client
(setq gt-default-http-client
(gt-plz-http-client :args '("--proxy" "socks5://127.0.0.1:9999")))
;; dynamic by host of request url
(setq gt-default-http-client
(lambda (host)
(if (string-match-p "deepl" host)
(gt-plz-http-client :args '("--proxy" "socks5://127.0.0.1:9999"))
(gt-plz-http-client))))
If prompt via minibuffer, the following keys exist in minibuffer:
C-n
andC-p
switch languagesC-l
clear inputC-g
abort translate
If prompt via buffer, the following keys exist in the taking buffer:
C-c C-c
submit translateC-c C-k
abort translate- Other keys like switch languages and components please refer to tips on buffer mode line
This is an offline translation engine that supports plug-in dictionaries.
First, make sure sdcv has been installed on your system:
sudo pacman -S sdcv
In addition, download the dictionary files and put them to the correct location.
After that, configure and use the engine:
;; Basic configuration
(setq gt-default-translator
(gt-translator :engines (gt-stardict-engine)
:render (gt-buffer-render)))
;; More options can be specified
(setq gt-default-translator
(gt-translator :engines (gt-stardict-engine
:dir "~/.stardict/dic" ; specify data file location
:dict "dict-name" ; specify a dict name
:exact t) ; exact, do not fuzzy-search
:render (gt-buffer-render)))
NOTE: If rendering via Buffer-Render etc, you can switch between dictionaries by click dictionary name or error message (or press C-c C-c
on it).
DeepL requires auth-key
to work, please obtained it through the official website.
The auth-key
can then be set in the following ways:
- Specify directly in the engine definition:
(gt-translator :engines (gt-deepl-engine :key "***"))
- Save it in
.authinfo
file of OS:machine api.deepl.com login auth-key password ***
Please obtained the apikey through the official website first.
;; Provide apikey with one of following ways:
(setq gt-chatgpt-key "YOUR-KEY")
(gt-chatgpt-engine :key "YOUR_KEY")
(find-file "~/.authinfo") ; machine api.openai.com login apikey password [YOUR_KEY]
;; Others
(setq gt-chatgpt-model "gpt-3.5-turbo")
(setq gt-chatgpt-temperature 0.7)
Custom the translation prompt as you wish:
(setq gt-chatgpt-user-prompt-template
(lambda (text lang)
(format "Translate text to %s and return the first word. Text is: \n%s"
(alist-get lang gt-lang-codes) text)))
Even can custom the prompt for other tasks. For example, for polish sentence:
(defun my-command-polish-using-ChatGPT ()
(interactive)
(let ((gt-chatgpt-system-prompt "You are a writer")
(gt-chatgpt-user-prompt-template (lambda (text _)
(read-string
"Prompt: "
(format "Polish the sentence below: %s" text)))))
(gt-start (gt-translator
:engines (gt-chatgpt-engine :cache nil)
:render (gt-insert-render)))))
It support streaming output with some renders. Examples:
;; Three engines, one with streaming query, two for normal
;; The streaming result can be output with buffer render, posframe render and 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)))
;; Translate and insert the streaming results to buffer
(setq gt-default-translator
(gt-translator :taker (gt-taker :pick nil :prompt t)
:engines (gt-chatgpt-engine :stream t)
:render (gt-insert-render)))
After all, try text to speech with command gt-do-speak
.
Display the translation results with a new buffer. This is a very general way of displaying results.
In the result buffer, there are many shortcut keys (overview through ?
), such as:
- Switch languages via
t
- Switch multi-language mode via
T
- Clear caches with
C
- Refresh via
g
- Quit via
q
Alternatively, play speech via y
(command gt-do-speak
). If the active region exists, then only
speak current selection content. TTS requires that the engine have implemented gt-speak
method.
Command gt-do-speak
can use anywhere else, then it will try to speak text via TTS service of system.
You can set the buffer window through 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"))))
Here are some usage examples:
;; Capture content under cursor, use Google to translate word, use DeepL to translate sentence, use Buffer to display the results
;; This is a very practical configuration
(setq gt-default-translator
(gt-translator
:taker (gt-taker :langs '(en fr) :text 'word)
:engines (list (gt-google-engine :if 'word) (gt-deepl-engine :if 'not-word))
:render (gt-buffer-render)))
;; A command for translating multiple paragraphs in the Buffer into multiple languages and rendering into new Buffer
;; This shows the use of translation of multi-engines with multi-paragraphs and with multi-languages
(defun demo-translate-multiple-langs-and-multiple-parts ()
(interactive)
(let ((gt-polyglot-p t)
(translator (gt-translator
:taker (gt-taker :langs '(en fr ru) :text 'buffer :pick 'paragraph)
:engines (list (gt-google-engine) (gt-deepl-engine))
:render (gt-buffer-render))))
(gt-start translator)))
You need to install posframe before you use these renders.
The effect of these two Renders is similar to gt-buffer-render
, except that the window is floating.
The shortcut keys are similar too, such as q
to quit.
You can pass any params to posframe-show
with :frame-params
:
(gt-posframe-pin-render :frame-params (list :border-width 20 :border-color "red"))
Insert the translation results into current buffer.
The following types can be specified (type
):
after
, the default type, insert the results after the cursorreplace
, replace the translated source text with the results
If not satisfied with the default output format and style, adjust it with the following options:
sface
, propertize the source text with this face after the translation is completerfmt
, the output format of the translation resultrface
, specify a specific face for the translation results
The option rfmt
is a function or a string containing the control character %s
:
;; %s is a placeholder for translation result
(gt-insert-render :rfmt " [%s]")
;; One argument, that is the translation result
(gt-insert-render :rfmt (lambda (res) (concat " [" res "]")))
;; Two arguments, the first one is the source text
(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)
Here are some usage examples:
;; Translate by paragraph and insert each result at the end of source paragraph
;; This configuration is suitable for translation work. That is: Translate -> Modify -> Save
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'buffer :pick 'paragraph)
:engines (gt-google-engine)
:render (gt-insert-render :type 'after)))
;; Translate the current paragraph and replace it with the translation result
;; This configuration is suitable for scenes such as live chat. Type some text, translate it, and send it
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'paragraph :pick nil)
:engines (gt-google-engine)
:render (gt-insert-render :type 'replace)))
;; Translate specific words in current paragraph and insert the result after each word
;; This configuration can help in reading articles with some words you don't know
(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"))))
Use Overlays to display translation results.
Set the display mode through type
:
after
, the default type, displays the translation results after the source textbefore
, displays the translation results before the source textreplace
, overlays the translation results on top of the source texthelp-echo
, display result only when the mouse is hovered over the source text
It is similar to gt-insert-render
in many ways, including options:
sface
, propertize the source text with this face after the translation is completerfmt
, the output format of the translation resultrface/rdisp
, specify face or display for the translation resultspface/pdisp
, specify face or display for the translation prefix (language and engine prompts)
Here are some usage examples:
;; Translate all paragraphs in buffer and display the results after the original paragraphs in the specified format
;; This is a configuration suitable for reading read-only content such as Info, News, etc.
(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)))
;; Mark all qualified words in the Buffer and display the translation results when hover over them
;; This is a practical configuration, suitable for reading articles that contains unfamiliar words
(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)))
;; Use overlays to overlay the translated results directly on top of the original text
;; Use this configuration for an article to get its general idea quickly
(setq gt-default-translator
(gt-translator
:taker (gt-taker :text 'buffer)
:engines (gt-google-engine)
:render (gt-overlay-render :type 'replace)))
It is flexible, even something like real-time translation can be implement with the help of hook or timer.
Derived from gt-translator
, integrates a lot of text conversion and processing features.
This demonstrates the extensibility of the framework, shows that it can be used not only for translation.
To generate QR code for text, need to install the qrencode
program or qrencode
package first:
pacman -S qrencode
brew install qrencode
# or in Emacs
M-x package-install qrencode
In addition, other functionalities can be integrated by extending the generic method gt-text-util
.
Here are some usage examples:
;; By default, interactivelly choose what to do with the text
;; Notice: you should not specify any engine for it
(setq gt-default-translator
(gt-text-utility :render (gt-buffer-render)))
;; Generate QR Code for current text (specify the `utility' explicitly with :langs)
;; Very practical configuration for sharing text to Mobile phone
(setq gt-default-translator
(gt-text-utility
:taker (gt-taker :langs '(qrcode) :pick nil)
:render (gt-buffer-render)))
;; Output text to speech label and MD5 sum
(setq gt-default-translator
(gt-text-utility
:taker (gt-taker :langs '(speak md5) :text 'buffer :pick 'paragraph)
:render (gt-posframe-pin-render)))
Component gt-taker
, gt-engine
and gt-render
are inherited from gt-validator
, which
provides a way to determine component availability through :if
slot. This greatly simplifies
the configuration of translator for different scenarios.
The value of the slot :if
can be a function, a symbol or a list of forms linked by and/or.
Symbol can be prefixed with not-
or no-
to indicate a reverse determination.
Some symbols built-in:
word
translated text is wordsrc:en
source language is Englishtgt:en
target language is Englishparts
multiple parts text to be translatedread-only
current buffer is read onlyselection
current use region is activeemacs-lisp-mode
suffix with -mode, that match with current modenot-word
orno-word
reverse determination, translated text is not word
One simple config example:
;; for text selected, not pick, render with posframe
;; for buffer Info, translate current paragraph, render with overlay
;; for buffer readonly, translate all fresh word in buffer, render with overlay
;; for Magit commit buffer, insert the translated result into current position
;; for word, translate with google engine; for non-word, use 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))))
The code is based on eieio (CLOS), so almost every component can be extended or replaced.
For example, implement an engine that outputs the captured text in reverse order. It’s easy:
;; First, define the class, inherit from gt-engine
(defclass my-reverse-engine (gt-engine)
((delimiter :initform nil)))
;; Then, implement the method 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)))
;; At last, config and have a try
(setq gt-default-translator (gt-translator :engines (my-reverse-engine)))
For example, extend Taker to let it can capture all headlines in org mode:
;; [implement] make text slot of Taker support '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))))))
;; [usage] config Taker with ':text org-headline' and that's it
(setq gt-default-translator (gt-translator
:taker (gt-taker :text 'org-headline)
:engines (gt-google-engine)
:render (gt-overlay-render :rfmt " (%s)" :sface nil)))
In this way, use your imagination, you can do a lot.
To enable debug, set gt-debug-p
to t, then you will see the logs in buffer *gt-log*
.
Welcome your PRs and sugguestions.