/go-translate

Powerful translator on Emacs. Supports multiple translation engines such as Google, Bing, deepL, StarDict, Youdao.

Primary LanguageEmacs LispMIT LicenseMIT

License: MIT MELPA

Go-Translate

This is a translation framework for emacs, and is flexible and powerful.

In addition to Google Translate, it now supports more engines like Google RPC API, Bing, DeepL. You can easily add other translation engines on the basis of the framework.

Very scalable, very flexible, asynchronous request and better user experience.

The reason I rewrite this is that I want to add the new RPC API of Google translation to this plugin. Since adding new API, it's not nice to implement the plugin as a translation framework first.

Then, there is it.

Installation

With MELPA and package.el:

M-x package-install RET go-translate RET

(require 'go-translate)

Or manually download go-translate.el then put into /some-path, then add this to .emacs:

(add-to-list 'load-path "/some-path")
(require 'go-translate)

Basic Usage

Add this to your configuration file:

(require 'go-translate)

(setq gts-translate-list '(("en" "zh")))

;; (setq gts-default-translator (gts-translator :engines (gts-bing-engine)))

(setq gts-default-translator
      (gts-translator
       :picker (gts-prompt-picker)
       :engines (list (gts-bing-engine) (gts-google-engine))
       :render (gts-buffer-render)))

Then use gts-do-translate to start translation.

More configuration

;; your languages pair used to translate
(setq gts-translate-list '(("en" "zh") ("fr" "en")))

;; config the default translator, it will be used by command gts-do-translate
(setq gts-default-translator
      (gts-translator

       :picker ; used to pick source text, from, to. choose one.

       ;;(gts-noprompt-picker)
       ;;(gts-noprompt-picker :texter (gts-whole-buffer-texter))
       (gts-prompt-picker)
       ;;(gts-prompt-picker :single t)
       ;;(gts-prompt-picker :texter (gts-current-or-selection-texter) :single t)

       :engines ; engines, one or more. Provide a parser to give different output.

       (list
        (gts-bing-engine)
        ;;(gts-google-engine)
        ;;(gts-google-rpc-engine)
        ;;(gts-deepl-engine :auth-key [YOUR_AUTH_KEY] :pro nil)
        (gts-google-engine :parser (gts-google-summary-parser))
        ;;(gts-google-engine :parser (gts-google-parser))
        ;;(gts-google-rpc-engine :parser (gts-google-rpc-summary-parser) :url "https://translate.google.com")
        (gts-google-rpc-engine :parser (gts-google-rpc-parser) :url "https://translate.google.com")
        )

       :render ; render, only one, used to consumer the output result. Install posframe yourself when use gts-posframe-xxx

       (gts-buffer-render)
       ;;(gts-posframe-pop-render)
       ;;(gts-posframe-pop-render :backcolor "#333333" :forecolor "#ffffff")
       ;;(gts-posframe-pin-render)
       ;;(gts-posframe-pin-render :position (cons 1200 20))
       ;;(gts-posframe-pin-render :width 80 :height 25 :position (cons 1000 20) :forecolor "#ffffff" :backcolor "#111111")
       ;;(gts-kill-ring-render)

       :splitter ; optional, used to split text into several parts, and the translation result will be a list.

       (gts-paragraph-splitter)
       ))

Slots picker/engines/render/splitter can be a function or lambda, it allows the dynamic initialization of slots while translating. For example, set a separate translation behavior for pdf-tools:

(setq gts-default-translator
      (gts-translator
       :picker
       (lambda ()
         (cond ((equal major-mode 'pdf-view-mode)
                (gts-noprompt-picker :texter (gts-current-or-selection-texter)))
               (t (gts-prompt-picker))))
       :engines
       (lambda ()
         (cond ((equal major-mode 'pdf-view-mode)
                (gts-bing-engine))
               (t (list
                   (gts-bing-engine)
                   (gts-google-engine :parser (gts-google-summary-parser))
                   (gts-google-rpc-engine)))))
       :render
       (lambda ()
         (cond ((equal major-mode 'pdf-view-mode)
                (gts-posframe-pop-render))
               (t (gts-buffer-render))))))

Another example, adopt different translation strategies according to your variable or translate content:

(defvar your-gts-split-enable nil
  "Split translate?")

(setq gts-default-translator
      (gts-translator
       :picker (gts-prompt-picker)
       :splitter (lambda ()
                   (if your-gts-split-enable (gts-paragraph-splitter)))
       :engines (lambda ()
                  (with-slots (text) gts-default-translator
                    (if your-gts-split-enable
                        (gts-deepl-engine :auth-key "xxx")
                      (list
                       (gts-bing-engine)
                       (gts-deepl-engine :auth-key "xxx")
                       (if (string-match-p " " text)
                           (gts-google-rpc-engine)
                         (gts-google-engine))))))
       :render (gts-buffer-render)))

You can look into customize-group - go-translate for more configurations.

Extend your commands

In addition to setup gts-default-translator and direct use gts-do-translate, you can define your own commands.

Eg, pick directly and use Google RPC API to translate:

(defun my-translate-command-1 ()
  (interactive)
  (gts-translate (gts-translator
                  :picker (gts-noprompt-picker)
                  :engines (gts-google-rpc-engine)
                  :render (gts-buffer-render))))

Eg, pick directly and add the results into kill-ring:

(defun my-translate-command-2 ()
  (interactive)
  (gts-translate (gts-translator
                  :picker (gts-noprompt-picker)
                  :engines (gts-google-rpc-engine)
                  :render (gts-kill-ring-render))))

Eg, pop a childframe to show the translation result:

(defun my-translate-command-3 ()
  (interactive)
  (gts-translate (gts-translator
                  :picker (gts-prompt-picker)
                  :engines (gts-google-rpc-engine)
                  :render (gts-posframe-pop-render))))

Eg, show multiple engines's result (Google/DeepL) in a pin childframe:

(defun my-translate-command-4 ()
  (interactive)
  (gts-translate (gts-translator
                  :picker (gts-prompt-picker)
                  :engines (list (gts-google-rpc-engine :parser (gts-google-rpc-summary-parser)) (gts-deepl-engine))
                  :render (gts-kill-ring-render))))

To avoid the cost of creating objects every time you call a command, you can define your command this way:

;; predefine
(defvar my-translator-n
  (gts-translator :picker .. :engines .. :render ..))

;; reference
(defun my-translate-command-n ()
  (interactive)
  (gts-translate my-translator-n)

Compose yourself. Whatever you like.

Builtin Components

The command gts-do-translate will take gts-default-translator as the default translator.

gts-buffer-render

By default, it use gts-buffer-render to display translation results. In the result buffer, there are several shortcut keys:

  • h show help
  • g refresh q exit
  • x exchanges source language and target language and refresh the translation
  • M-n and M-p, switch to the next/prev available translation direction, and refresh
  • y to speak the current selection or word. You should have mplayer/mpv installed, or on Windows it will fallback to use powershell to do the tts job.
  • C clear all caches in gts-default-cacher

gts-posframe-pop-render/gts-posframe-pin-render

Use childframe to show the results.

You should install posframe from MELPA first if you want to use these renders.

gts-posframe-pop-render will pop a childframe in current position to show the results. The frame will be disappeared by any user action, except when you focus into it. When you focus into the frame, you can use all keybindings that like in gts-buffer-render.

gts-posframe-pin-render will pin a childframe to service for the translation result. It is draggable and resizable, and you can change the position/color/etc as you wish.

gts-prompt-picker/gts-noprompt-picker

By default, translator uses gts-prompt-picker to pick the source text and translation from/to languages.

gts-prompt-picker uses a texter to decide the initial source text, the default texter is gts-current-or-selection-texter, it takes the currently selected text or word-at-point as the default source text.

In the pop-up read-from-minibuffer interface triggled by gts-prompt-picker, you can use C-l to clear the input, and fire the translation with Return. Also, you can use C-n and C-p to switch translation directions. These directions are those configured in gts-translate-list above.

The gts-noprompt-picker is another choice if you don't like the prompting style's picking. It will automately take the text from texter and choose a suitable from/to, then translate directly.

gts-paragraph-splitter

Make mix-source-and-target-style translation possible. This is a basic implement to split source text to several parts.

Extend your components

Extending components is easy and happy, go and have a try!

You can replace almost everything. logger/cacher/http-client, picker/texter/render and engine...

For example, you want to insert the results directly into the buffer, create your own render like below.

Define class and override methods:

;; A class
(defclass your-render (gts-render) ())

;; A method
(cl-defmethod gts-out ((_ your-render) task)
  (deactivate-mark)
  (with-slots (err parsed) task
    (if err (user-error "%s" err))
    (insert (if (listp parsed) (string-join parsed "\n\n") parsed))))

Use them in your translator:

(defun my-translate-command-5 ()
  (interactive)
  (gts-translate (gts-translator
                  :picker (gts-noprompt-picker)
                  :engines (gts-google-rpc-engine)
                  :render (your-render) ; yeap!
                  )))

Of course, it's relatively easy to build a new translation engine too:

  • create a class from gts-engine, implement gts-translate/gts-tts
  • create a class from gts-parser, implement gts-parse

For example, if you can't stand the poor performance of url.el, you can implement your own gts-http-client with curl, etc.

Miscellaneous

More documentation will be added later.