lorniu / go-translate

Translator on Emacs. Supports multiple engines such as Google, Bing, deepL, ChatGPT, StarDict, Youdao and so on.
GNU General Public License v3.0
274 stars 37 forks source link
chatgpt text-utility translation

[[https://melpa.org/#/go-translate][file:https://melpa.org/packages/go-translate-badge.svg]] [[https://stable.melpa.org/#/go-translate][file:https://stable.melpa.org/packages/go-translate-badge.svg]]

This is a translation framework on Emacs, with high configurability and extensibility.

[[README-zh.org][点击查看《中文版文档》]]

As a translation framework, it offers many advantages:

It's more than just a translation framework.It's flexible, and can easily be extended to various Text-to-Text conversion scenarios:

** Basic Usage

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:

+begin_src emacs-lisp

(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.

+end_src

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:

+begin_src emacs-lisp

(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.

+end_src

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:

+begin_src emacs-lisp

(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)))))

+end_src

This configuration presets three translators:

Then, translate with command =gt-do-translate= and switch between preset translators with command =gt-do-setup=.

+begin_quote

highly recommended:

Install the curl program and the [[https://github.com/alphapapa/plz.el][plz.el]] package. The request will then be sent through curl, which is much better than the built-in url.el!

+end_quote

See more configuration options via =M-x customize-group go-translate=, and read the following chapters for more configuration details.

** More configurations

The core component of the translation framework is =gt-translator=, which contains the following components:

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:

+begin_src emacs-lisp

;; 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)

+end_src

Therefore, one needs to understand these components first for better configuration.

*** component =gt-taker= for capturing

| 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:

+begin_src emacs-lisp

(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

+end_src

It's better to use =:taker= to explicitly specify a Taker for the translator:

+begin_src emacs-lisp

(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)))

+end_src

Taker will use =text= to determine the initial text. If there is active region, the selected text is taken. Otherwise use the following rules:

+begin_src emacs-lisp

;; 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

+end_src

Taker determine the languages to translate from =langs= in the help of =gt-lang-rules=:

+begin_src emacs-lisp

(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

+end_src

By setting =prompt= to allow the user to modify and confirm the initial text and languages interactively:

+begin_src emacs-lisp

;; Confirm by minibuffer (gt-translator :taker (gt-taker :prompt t))

;; Confirm by new buffer (gt-translator :taker (gt-taker :prompt 'buffer))

+end_src

Finally, the initial text is cut and filtered based on =pick= and =pick-pred=. The content it returns is what will ultimately be translated:

+begin_src emacs-lisp

;; 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))

+end_src

*** component =gt-engine= for translating/transforming

| 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:

Specify engines for translator via =:engines=. A translator can have one or more engines, or you can specify a function that returns the engines:

+begin_src emacs-lisp

(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)))

+end_src

If a engine has multiple parsers, you can specify one through =parse= to achieve specific parsing, such as:

+begin_src emacs-lisp

(gt-translator :engines (list (gt-google-engine :parse (gt-google-parser)) ; detail results (gt-google-engine :parse (gt-google-summary-parser)))) ; brief results

+end_src

You can use =if= to filter the engines for current translation task. For example:

+begin_src emacs-lisp

(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

+end_src

You can specify different caching policies for different engines with =cache=:

+begin_src emacs-lisp

(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

+end_src

+begin_quote

Notice:

If translate multiple parts text, the default strategy is:

  1. join the parts into a single string,
  2. translate the whole string through the engine,
  3. 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.

+end_quote

*** component =gt-render= for rendering

| 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:

Configure render for translator via =:render=. Multiple renders can be chained together with =:then=:

+begin_src emacs-lisp

(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

+end_src

The first available render in the list (validate conjunction with =:if=) can be used as the final render. For example:

+begin_src emacs-lisp

(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

+end_src

** Components (Supplementary Notes) *** gt-memory-cacher (gt-default-cacher)

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=:

+begin_src emacs-lisp

(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

+end_src

Set =gt-cache-p= to nil to turn off all caches. Or turn off the cache for engine individually like this:

+begin_src emacs-lisp

(gt-translator :engines (gt-google-engine :cache nil))

+end_src

+begin_quote

Translation results can be cached in files, SQLite or Redis through extensions. But maybe it's unnecessary.

+end_quote

*** gt-url-http-client/gt-plz-http-client (gt-default-http-client)

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 [[https://github.com/alphapapa/plz.el][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:

+begin_src emacs-lisp

;; 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))))

+end_src

*** gt-taker

If prompt via minibuffer, the following keys exist in minibuffer:

If prompt via buffer, the following keys exist in the taking buffer:

*** gt-stardict-engine

This is an offline translation engine that supports plug-in dictionaries.

First, make sure [[https://github.com/Dushistov/sdcv][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:

+begin_src emacs-lisp

;; 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)))

+end_src

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).

*** gt-deepl-engine

DeepL requires =auth-key= to work, please obtained it through the official website.

The =auth-key= can then be set in the following ways:

  1. Specify directly in the engine definition:

    +begin_example

    (gt-translator :engines (gt-deepl-engine :key "***"))

    +end_example

  2. Save it in =.authinfo= file of OS:

    +begin_example

    machine api.deepl.com login auth-key password ***

    +end_example

*** gt-chatgpt-engine

Please obtained the apikey through the official website first.

+begin_src emacs-lisp

;; 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)

+end_src

Custom the translation prompt as you wish:

+begin_src emacs-lisp

(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)))

+end_src

Even can custom the prompt for other tasks. For example, for polish sentence:

+begin_src emacs-lisp

(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)))))

+end_src

It support streaming output with some renders. Examples:

+begin_src emacs-lisp

;; 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)))

+end_src

After all, try text to speech with command =gt-do-speak=.

*** gt-buffer-render

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:

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=:

+begin_src emacs-lisp

(gt-translator :render (gt-buffer-render :buffer-name "abc" :window-config '((display-buffer-at-bottom)) :then (lambda (_) (pop-to-buffer "abc"))))

+end_src

Here are some usage examples:

+begin_src emacs-lisp

;; 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)))

+end_src

*** gt-posframe-pop-render/gt-posframe-pin-render

You need to install [[https://github.com/tumashu/posframe][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=:

+begin_src emacs-lisp

(gt-posframe-pin-render :frame-params (list :border-width 20 :border-color "red"))

+end_src

*** gt-insert-render

Insert the translation results into current buffer.

The following types can be specified (=type=):

If not satisfied with the default output format and style, adjust it with the following options:

The option =rfmt= is a function or a string containing the control character =%s=:

+begin_src emacs-lisp

;; %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)

+end_src

Here are some usage examples:

+begin_src emacs-lisp

;; 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"))))

+end_src

*** gt-overlay-render

Use Overlays to display translation results.

Set the display mode through =type=:

It is similar to =gt-insert-render= in many ways, including options:

Here are some usage examples:

+begin_src emacs-lisp

;; 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)))

+end_src

It is flexible, even something like real-time translation can be implement with the help of hook or timer.

*** gt-text-utility

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:

+begin_src sh

pacman -S qrencode brew install qrencode

or in Emacs

M-x package-install qrencode

+end_src

In addition, other functionalities can be integrated by extending the generic method =gt-text-util=.

Here are some usage examples:

+begin_src emacs-lisp

;; 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)))

+end_src

*** gt-validator (:if)

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:

One simple config example:

+begin_src emacs-lisp

;; 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))))

+end_src

** Customization and Extension

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:

+begin_src emacs-lisp

;; 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)))

+end_src

For example, extend Taker to let it can capture all headlines in org mode:

+begin_src emacs-lisp

;; [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)))

+end_src

In this way, use your imagination, you can do a lot.

** Miscellaneous

To enable debug, set =gt-debug-p= to t, then you will see the logs in buffer =gt-log=.

Welcome your PRs and sugguestions.