]> git.bundesbrandschatzamt.de Git - emacsconfig.git/commitdiff
update go-translate
authorAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Sat, 21 Dec 2024 15:38:14 +0000 (16:38 +0100)
committerAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Sat, 21 Dec 2024 15:38:14 +0000 (16:38 +0100)
Signed-off-by: Andreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
42 files changed:
bba.org
elpa/go-translate-20221211.1327/go-translate-autoloads.el [deleted file]
elpa/go-translate-20221211.1327/go-translate-pkg.el [deleted file]
elpa/go-translate-20221211.1327/go-translate.el [deleted file]
elpa/go-translate-20221211.1327/gts-core.el [deleted file]
elpa/go-translate-20221211.1327/gts-engine-bing.el [deleted file]
elpa/go-translate-20221211.1327/gts-engine-deepl.el [deleted file]
elpa/go-translate-20221211.1327/gts-engine-google-rpc.el [deleted file]
elpa/go-translate-20221211.1327/gts-engine-google.el [deleted file]
elpa/go-translate-20221211.1327/gts-engine-stardict.el [deleted file]
elpa/go-translate-20221211.1327/gts-engine-youdao.el [deleted file]
elpa/go-translate-20221211.1327/gts-faces.el [deleted file]
elpa/go-translate-20221211.1327/gts-implements.el [deleted file]
elpa/go-translate-20230503.303/go-translate-autoloads.el [deleted file]
elpa/go-translate-20230503.303/go-translate-pkg.el [deleted file]
elpa/go-translate-20230503.303/go-translate.el [deleted file]
elpa/go-translate-20230503.303/gts-core.el [deleted file]
elpa/go-translate-20230503.303/gts-engine-bing.el [deleted file]
elpa/go-translate-20230503.303/gts-engine-deepl.el [deleted file]
elpa/go-translate-20230503.303/gts-engine-google-rpc.el [deleted file]
elpa/go-translate-20230503.303/gts-engine-google.el [deleted file]
elpa/go-translate-20230503.303/gts-engine-stardict.el [deleted file]
elpa/go-translate-20230503.303/gts-engine-youdao.el [deleted file]
elpa/go-translate-20230503.303/gts-faces.el [deleted file]
elpa/go-translate-20240802.131/go-translate-autoloads.el [new file with mode: 0644]
elpa/go-translate-20240802.131/go-translate-pkg.el [new file with mode: 0644]
elpa/go-translate-20240802.131/go-translate.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-core.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-engine-bing.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-engine-chatgpt.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-engine-deepl.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-engine-echo.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-engine-google-rpc.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-engine-google.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-engine-libre.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-engine-stardict.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-engine-youdao.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-extension.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-faces.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-httpx.el [new file with mode: 0644]
elpa/go-translate-20240802.131/gt-text-utility.el [new file with mode: 0644]
elpa/graphql-mode-20230411.1943/graphql-mode-autoloads.el [deleted file]

diff --git a/bba.org b/bba.org
index fa96e49753d598760c084df88b10f61f7c8df409..29b8ed6bfb67a79dae2d1ae4668a04a36d6a6a15 100644 (file)
--- a/bba.org
+++ b/bba.org
@@ -954,15 +954,12 @@ Use our default socket files
 #+begin_src emacs-lisp
   (require 'go-translate)
 
-  (setq gts-translate-list '(("en" "de")))
+  (setq gt-translate-list '(("en" "de")))
 
-  ;; (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)))
+  (setq gt-default-translator
+        (gt-translator
+         :engines (list (gt-bing-engine) (gt-google-engine))
+         :render (gt-buffer-render)))
 #+end_src
 
 * [[http://www.mew.org][mew]] email
diff --git a/elpa/go-translate-20221211.1327/go-translate-autoloads.el b/elpa/go-translate-20221211.1327/go-translate-autoloads.el
deleted file mode 100644 (file)
index 358b41e..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-;;; go-translate-autoloads.el --- automatically extracted autoloads  -*- lexical-binding: t -*-
-;;
-;;; Code:
-
-(add-to-list 'load-path (directory-file-name
-                         (or (file-name-directory #$) (car load-path))))
-
-\f
-;;;### (autoloads nil "go-translate" "go-translate.el" (0 0 0 0))
-;;; Generated autoloads from go-translate.el
-
-(autoload 'gts-do-translate "go-translate" "\
-Do the translate" t nil)
-
-(register-definition-prefixes "go-translate" '("gts-default-translator"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-core" "gts-core.el" (0 0 0 0))
-;;; Generated autoloads from gts-core.el
-
-(register-definition-prefixes "gts-core" '("gts-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-bing" "gts-engine-bing.el" (0 0
-;;;;;;  0 0))
-;;; Generated autoloads from gts-engine-bing.el
-
-(register-definition-prefixes "gts-engine-bing" '("gts-bing-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-deepl" "gts-engine-deepl.el" (0
-;;;;;;  0 0 0))
-;;; Generated autoloads from gts-engine-deepl.el
-
-(register-definition-prefixes "gts-engine-deepl" '("gts-deepl-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-google" "gts-engine-google.el"
-;;;;;;  (0 0 0 0))
-;;; Generated autoloads from gts-engine-google.el
-
-(register-definition-prefixes "gts-engine-google" '("gts-google-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-google-rpc" "gts-engine-google-rpc.el"
-;;;;;;  (0 0 0 0))
-;;; Generated autoloads from gts-engine-google-rpc.el
-
-(register-definition-prefixes "gts-engine-google-rpc" '("gts-google-rpc-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-stardict" "gts-engine-stardict.el"
-;;;;;;  (0 0 0 0))
-;;; Generated autoloads from gts-engine-stardict.el
-
-(register-definition-prefixes "gts-engine-stardict" '("gts-stardict-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-youdao" "gts-engine-youdao.el"
-;;;;;;  (0 0 0 0))
-;;; Generated autoloads from gts-engine-youdao.el
-
-(register-definition-prefixes "gts-engine-youdao" '("gts-youdao-dict-e"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-faces" "gts-faces.el" (0 0 0 0))
-;;; Generated autoloads from gts-faces.el
-
-(register-definition-prefixes "gts-faces" '("gts-p"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-implements" "gts-implements.el" (0 0 0
-;;;;;;  0))
-;;; Generated autoloads from gts-implements.el
-
-(register-definition-prefixes "gts-implements" '("gts-"))
-
-;;;***
-\f
-;;;### (autoloads nil nil ("go-translate-pkg.el") (0 0 0 0))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; go-translate-autoloads.el ends here
diff --git a/elpa/go-translate-20221211.1327/go-translate-pkg.el b/elpa/go-translate-20221211.1327/go-translate-pkg.el
deleted file mode 100644 (file)
index 19c19ea..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-(define-package "go-translate" "20221211.1327" "Translation framework supports multiple engines such as Google/Bing/DeepL"
-  '((emacs "27.1"))
-  :commit "65d5139f202320b71e6b8b03d752d02b7c0b8c6f" :authors
-  '(("lorniu" . "lorniuNOSPAMgmail.com"))
-  :maintainer
-  '("lorniu" . "lorniuNOSPAMgmail.com")
-  :keywords
-  '("convenience")
-  :url "https://github.com/lorniu/go-translate")
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
diff --git a/elpa/go-translate-20221211.1327/go-translate.el b/elpa/go-translate-20221211.1327/go-translate.el
deleted file mode 100644 (file)
index 100ef59..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-;;; go-translate.el --- Translation framework supports multiple engines such as Google/Bing/DeepL -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-
-;; Author: lorniu <lorniuNOSPAMgmail.com>
-;; URL: https://github.com/lorniu/go-translate
-;; Package-Requires: ((emacs "27.1"))
-;; Keywords: convenience
-;; SPDX-License-Identifier: MIT
-;; Version: 2.0
-
-;;; Commentary:
-
-;; To be the most powerful translator on Emacs. Supports multiple translation engines such as Google, Bing, deepL.
-
-;; First, Install it via MELPA or download from github. Make sure this is on your `load-path'.
-
-;; Then, add following lines to your `.emacs':
-
-;;   (require 'go-translate)
-;;   (setq gts-translate-list '(("en" "zh")))
-;;   (setq gts-default-translator
-;;        (gts-translator
-;;         :picker (gts-prompt-picker)
-;;         :engines (list (gts-google-engine) (gts-google-rpc-engine))
-;;         :render (gts-buffer-render)))
-
-;; And start your translate with command `gts-do-translate'.
-
-;;; Code:
-
-(require 'gts-core)
-(require 'gts-implements)
-(require 'gts-engine-bing)
-(require 'gts-engine-google)
-(require 'gts-engine-google-rpc)
-(require 'gts-engine-deepl)
-(require 'gts-engine-stardict)
-(require 'gts-engine-youdao)
-
-\f
-;;; Commands
-
-(defvar gts-default-translator
-  (gts-translator :picker
-                  (gts-prompt-picker)
-                  ;;(gts-noprompt-picker)
-                  ;;(gts-noprompt-picker :texter (gts-whole-buffer-texter))
-
-                  :engines
-                  (list
-                   (gts-bing-engine)
-                   (gts-google-engine :parser (gts-google-summary-parser))
-                   (gts-google-rpc-engine)
-
-                   ;;(gts-google-engine)
-                   ;;(gts-google-rpc-engine)
-                   ;;(gts-deepl-engine :auth-key "2e20bade-88e9-02f3-169f-ab3c445d7984:fx" :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))
-                   )
-
-                  :render
-                  (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 nil
-                  ;; (gts-paragraph-splitter)
-                  ))
-
-;;;###autoload
-(defun gts-do-translate ()
-  "Do the translate"
-  (interactive)
-  (gts-translate gts-default-translator))
-
-
-(provide 'go-translate)
-
-;;; go-translate.el ends here
diff --git a/elpa/go-translate-20221211.1327/gts-core.el b/elpa/go-translate-20221211.1327/gts-core.el
deleted file mode 100644 (file)
index 69f25f0..0000000
+++ /dev/null
@@ -1,667 +0,0 @@
-;;; gts-core.el --- Translate engines and utils -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; The core components and utils of the translator
-
-;;; Code:
-
-(require 'cl-lib)
-(require 'subr-x)
-(require 'eieio)
-
-(defgroup go-translate nil
-  "Translate Framework for Emacs, asynchronous and flexible."
-  :group 'external
-  :prefix 'gts-)
-
-(defvar gts-debug-p nil)
-
-\f
-;;; Utils
-
-(defun gts-aref-for (vector &rest ns)
-  "Recursively find the element in VECTOR. NS is indexes, as the path."
-  (while ns
-    (setq vector (aref vector (pop ns))))
-  vector)
-
-(defun gts-format-params (data)
-  "Format alist DATA to k=v style string."
-  (cond ((null data) nil)
-        ((stringp data) data)
-        (t (mapconcat (lambda (arg)
-                        (format "%s=%s"
-                                (url-hexify-string (format "%s" (car arg)))
-                                (url-hexify-string (format "%s" (cdr arg)))))
-                      data "&"))))
-
-(defun gts-ensure-list (object)
-  "Return OBJECT as a list."
-  (if (listp object) object (list object)))
-
-(defun gts-ensure-plain (o)
-  "Return O or the result of its execution."
-  (if (functionp o) (funcall o) o))
-
-\f
-;;; Components
-
-;; logger/http-client/cacher
-
-(defclass gts-logger () ()
-  "Used to log the messages."
-  :abstract t)
-
-(defclass gts-http-client () ()
-  "Used to send a request."
-  :abstract t)
-
-(defclass gts-cacher ()
-  ((caches  :initform nil)) ; (key . (str . timestamp))
-  "Used to cache the translate results."
-  :abstract t)
-
-;; translator/task
-
-(defclass gts-translator ()
-  ((picker     :initarg :picker   :documentation "`gts-picker' object or a function return it" :initform nil)
-   (engines    :initarg :engines  :documentation "a list of `gts-engine' objects or a function return them" :initform nil)
-   (render     :initarg :render   :documentation "`gts-render' object or a function return it" :initform nil)
-   (splitter   :initarg :splitter :documentation "`gts-splitter' object or a function return it" :initform nil)
-
-   (text       :initarg :text     :documentation "Text to be translated")
-   (sptext     :initarg :sptext   :documentation "List of strings splitted according `gts-splitter'")
-   (path       :initarg :path     :documentation "From/To language cons")
-
-   (plan-cnt   :initform 0        :documentation "count of tasks in a translation")
-   (task-queue :initform nil      :documentation "translation tasks.")
-   (state      :initform 0        :documentation "Inner state of the translator:
-0: new translator without any tasks,
-1: tasks add finished, but not running,
-2: prepare render finished, and begin to running,
-3: all tasks running finished,
-4: rendered done, all over.")
-   (version    :initform nil      :documentation "Used to distinguish between different translations")))
-
-(defclass gts-task ()
-  ((id         :initform (gensym))
-
-   (text       :initarg :text :documentation "Text that sent to engines. It maybe contains delimiters")
-   (from       :initarg :from)
-   (to         :initarg :to)
-
-   (raw        :initform nil :documentation "raw result responsed by the http-client")
-   (parsed     :initform nil :initarg :parsed :documentation "result parsed by parser, string or list")
-   (err        :initform nil :initarg :err    :documentation "error info")
-   (meta       :initform nil :initarg :meta   :documentation "extra info passed from parser to render. tbeg/tend")
-
-   (engine     :initarg :engine     :initform nil)
-   (render     :initarg :render     :initform nil)
-   (translator :initarg :translator :initform nil)))
-
-;; picker/texter
-
-(defclass gts-picker ()
-  ((single   :initarg :single :initform nil)
-   (texter   :initarg :texter))
-  "Used to pick the translation source text and from/to lang."
-  :abstract t)
-
-(defclass gts-texter () ()
-  "Used to get the initial translation text.
-Current word under cursor? Selection region? Whole line? Whole buffer? Others?
-You can implements your rules.")
-
-;; splitter
-
-(defclass gts-splitter () ()
-  "Used to split the translation text by pragraph or other rules."
-  :abstract t)
-
-;; engine/parser
-
-(defclass gts-engine ()
-  ((tag       :initarg :tag    :documentation "Used to display as name")
-   (parser    :initarg :parser :documentation "Used to parse the response result"))
-  :abstract t)
-
-(defclass gts-parser ()
-  ((tag  :initarg :tag :initform nil)))
-
-;; render
-
-(defclass gts-render () ())
-
-;; generic methods
-
-(cl-defgeneric gts-translate (translator-or-engine &rest args)
-  "Start a translation for TRANSLATOR-OR-ENGINE.
-ARGS should be text/path or task/callback.")
-
-;; silence byte-compiler
-(eieio-declare-slots task-queue text from to)
-
-\f
-;;; Logger
-
-(cl-defgeneric gts-log (logger tag message)
-  "Used to record the messages.")
-
-(defvar gts-default-logger nil "The default logger.")
-
-(defmacro gts-do-log (tag &rest messages)
-  (declare (indent 1))
-  `(when gts-debug-p
-     (if gts-default-logger
-         (cl-loop with ms = nil
-                  for message in (list ,@messages)
-                  if message do (cl-loop for m in (split-string message "\n") do (push m ms))
-                  finally (cl-loop
-                           for m in (nreverse ms)
-                           do (gts-log gts-default-logger (format "%s" ,tag) m)))
-       (error "Make sure `gts-default-logger' is available. eg:\n
- (setq gts-default-logger (gts-buffer-logger))\n\n\n"))))
-
-\f
-;;; Cacher
-
-(defcustom gts-cache-enable t
-  "Enable the cacher if this is t."
-  :type 'boolean
-  :group 'go-translate)
-
-(defcustom gts-cache-expired-factor (* 30 60)
-  "Cache alive time based on this.
-Make word live longer time than sentence."
-  :type 'integer
-  :group 'go-translate)
-
-(defvar gts-default-cacher nil "The default cacher.")
-
-(cl-defgeneric gts-cache-get (cacher task)
-  "Get item for TASK from CACHER.")
-
-(cl-defgeneric gts-cache-set (cacher task result)
-  "Add RESULT fro TASK to CACHER.")
-
-(cl-defmethod gts-cache-key ((_ gts-cacher) task)
-  (with-slots (text from to engine) task
-    (sha1 (format "%s-%s-%s-%s"
-                  text from to
-                  (eieio-object-class-name engine)))))
-
-(cl-defmethod gts-clear-expired ((cacher gts-cacher))
-  (with-slots (caches expired) cacher
-    (cl-delete-if (lambda (c) (> (time-to-seconds) (cddr c))) caches)))
-
-(cl-defmethod gts-clear-all ((cacher gts-cacher))
-  (oset cacher caches nil))
-
-\f
-;;; Http Client
-
-(cl-defgeneric gts-request (http-client url &key done fail data headers)
-  "Use HTTP-CLIENT to request a URL with DATA.
-When success execute CALLBACK, or execute ERRORBACK."
-  (:method (&rest _) (user-error "Method `gts-request' is not implement")))
-
-(defvar gts-default-http-client nil
-  "The default http client used to send a request.")
-
-(cl-defun gts-do-request (url &key done fail data headers)
-  "Helper for `gts-request'.
-DONE is a callback based on the responsed result buffer.
-FAIL is the callback for any error occured in request or parse."
-  (if (and gts-default-http-client
-           (eieio-object-p gts-default-http-client)
-           (object-of-class-p gts-default-http-client 'gts-http-client))
-      (let ((tag (format "%s" (eieio-object-class-name gts-default-http-client)))
-            (data (gts-format-params data)))
-        (gts-do-log tag
-          (format "Start! (%s)" url)
-          (if headers (format "    HEADER: %s" headers))
-          (if data (format "    DATA:   %s" data)))
-        (gts-request gts-default-http-client url
-                     :headers headers
-                     :data data
-                     :done (lambda ()
-                             (gts-do-log tag (format "DONE! (%s)" url))
-                             (condition-case err
-                                 (funcall done)
-                               (error
-                                (gts-do-log tag (format "FAIL in callback! (%s) %s" url err))
-                                (funcall fail err))))
-                     :fail (lambda (status)
-                             (gts-do-log tag (format "FAIL! (%s) %s" url status))
-                             (funcall fail status))))
-    (funcall fail "Make sure `gts-default-http-client' is available. eg:\n
- (setq gts-default-http-client (gts-url-http-client))\n\n\n")))
-
-\f
-;;; Picker/Texter
-
-(cl-defgeneric gts-pick (picker)
-  "Get the source text and translate from/to path from PICKER.")
-
-(cl-defgeneric gts-path (picker text)
-  "Use to get the translation path for TEXT from PICKER.")
-
-(cl-defgeneric gts-next-path (picker text path &optional backwardp)
-  "Get the next available path.")
-
-(cl-defgeneric gts-text (texter)
-  "Get the init translation source text via TEXTER.")
-
-(defcustom gts-translate-list nil
-  "Setup your translate languages. Just like:
-
-Single:
-
- (setq gts-translate-list \\='((\"en\" \"zh\")))
-
-Or multiple:
-
- (setq gts-translate-list \\='((\"en\" \"zh\") (\"en\" \"fr\")))
-
-The picker will give the translate from/to pair according this."
-  :type 'alist
-  :group 'go-translate)
-
-(defcustom gts-picker-lang-match-alist
-  (list
-   (cons "en"  "^[-a-zA-Z0-9,.;_ ]+$")
-   (cons "zh"  "\\cc")
-   (cons "ja"  (lambda (text) (string-match-p "\\cj" text)))
-   (cons "ru" "\\cy"))
-  "Setup language match rules.
-
-Used to pick the right language for current translate text.
-
-Key should be the language, value should be a regexp string or function."
-  :type 'alist
-  :group 'go-translate)
-
-(defvar gts-picker-last-path nil)
-
-(defun gts-picker-lang-matcher (lang)
-  "Judge source, sure case, yes:no:unknown.
-choose path, from main, then extra path match."
-  (let ((p (cdr (cl-find-if
-                 (lambda (item) (string-match-p (format "^%s$" (car item)) lang))
-                 gts-picker-lang-match-alist))))
-    (if (stringp p) (lambda (text) (string-match-p p text)) p)))
-
-(cl-defmethod gts-all-paths ((picker gts-picker))
-  (when (or (null gts-translate-list) (not (consp (car gts-translate-list))))
-    (user-error "Please make sure you set the avaiable `gts-translate-list'. eg:\n
- (setq gts-translate-list '((\"en\" \"zh\") (\"en\" \"ja\"))\n\n"))
-  (let (paths)
-    (when gts-picker-last-path
-      (cl-pushnew gts-picker-last-path paths))
-    (cl-loop with candidates = (if (oref picker single)
-                                   (list (car gts-translate-list))
-                                 gts-translate-list)
-             for (lang1 . lang2) in candidates
-             for lang2 = (if (consp lang2) (car lang2) lang2)
-             do (progn
-                  (cl-pushnew (cons lang1 lang2) paths :test 'equal)
-                  (cl-pushnew (cons lang2 lang1) paths :test 'equal)))
-    (reverse paths)))
-
-(cl-defmethod gts-matched-paths ((picker gts-picker) text)
-  (when text
-    (let ((paths (gts-all-paths picker))
-          (text (with-temp-buffer ; clear punctuations
-                  (insert text)
-                  (goto-char (point-min))
-                  (while (re-search-forward "\\s.\\|\n" nil t) (replace-match ""))
-                  (buffer-string))))
-      (cl-loop for path in paths
-               for matcher = (gts-picker-lang-matcher (car path))
-               when (and matcher (funcall matcher text))
-               collect path))))
-
-(cl-defmethod gts-path ((picker gts-picker) text)
-  "Get translate path according TEXT using PICKER."
-  (let ((paths (gts-matched-paths picker text)))
-    (if paths (car paths)
-      (or gts-picker-last-path
-          (car (gts-all-paths picker))))))
-
-(cl-defmethod gts-next-path ((picker gts-picker) text path &optional backwardp)
-  (let (candidates idx)
-    (cl-loop for path in (append (gts-matched-paths picker text) (gts-all-paths picker))
-             do (cl-pushnew path candidates :test 'equal))
-    (setq candidates (reverse candidates))
-    (setq idx (cl-position path candidates :test 'equal))
-    (if backwardp
-        (elt candidates (if (= 0 idx) (- (length candidates) 1) (- idx 1)))
-      (elt candidates (if (= (+ 1 idx) (length candidates)) 0 (+ 1 idx))))))
-
-\f
-;;; Splitter
-
-(cl-defgeneric gts-split (gts-splitter text)
-  "Split TEXT to several parts.
-Return list of string, or nil if split failed.")
-
-\f
-;;; Engine/Parser
-
-(cl-defmethod gts-translate ((engine gts-engine) _task _callback)
-  "Translate and update TASK with parsed result using ENGINE.
-The CALLBACK should contain the render logic."
-  (user-error "Method `gts-translate' of %s is not implement" (eieio-object-class-name engine)))
-
-(cl-defmethod gts-translate :around ((engine gts-engine) task callback)
-  "Add cache and log support."
-  (with-slots (id render translator) task
-    (if-let ((render-name (eieio-object-class-name render))
-             (engine-name (eieio-object-class-name engine))
-             (cache (gts-cache-get gts-default-cacher task)))
-        ;; try cache
-        (let ((engines (gts-get translator 'engines)))
-          (gts-update-raw task cache)
-          (gts-parse (oref engine parser) task)
-          (if (cdr engines) (gts-me-out render task)
-            (gts-out render task))
-          (gts-do-log 'render (format "%s: done with cache!" id)))
-      ;; request from engine
-      (gts-do-log 'engine (format "%s: starting %s..." id engine-name))
-      (cl-call-next-method engine task
-                           (lambda ()
-                             (gts-do-log 'engine (format "%s: %s done!" id engine-name))
-                             (funcall callback)
-                             ;; refresh cache
-                             (with-slots (err raw parsed) task
-                               (unless err
-                                 (gts-cache-set gts-default-cacher task raw))))))))
-
-(cl-defgeneric gts-with-token (engine done fail)
-  (:documentation "Get token for ENGINE, if success then DONE is called.")
-  (declare (indent 1)))
-
-(cl-defgeneric gts-tts (engine text lang)
-  "TTS, speak TEXT with LANG."
-  (:method ((e gts-engine) &rest _)
-           (user-error "No TTS service found on engine `%s'" (oref e tag))))
-
-(cl-defgeneric gts-parse (parser task)
-  (:documentation "Parse the raw result and update the parsed result into TASK.")
-  (:method ((_o gts-parser) task)
-           ;; do nothing
-           (gts-update-parsed task (oref task raw))))
-
-\f
-;;; Render
-
-(cl-defgeneric gts-pre (render translator)
-  (:documentation "Pre-render before request success.
-Do some preparation for the gts-out.")
-  (:method :after ((render gts-render) _)
-           (gts-do-log 'render (format "prepare render with %s finished" (eieio-object-class-name render))))
-  (:method ((_o gts-render) (_t gts-translator)) ()))
-
-(cl-defgeneric gts-out (render task)
-  (:documentation "Render the TASK, default output to *Messages*.")
-  (:method :after ((render gts-render) task)
-           (gts-do-log 'render (format "%s: %s finished" (oref task id) (eieio-object-class-name render))))
-  (:method ((_ gts-render) task)
-           (with-slots (err parsed) task
-             (when (and (not err) (listp parsed))
-               (setq parsed (string-join parsed "\n\f\n")))
-             (setq parsed (string-trim (format "%s" (or err parsed))))
-             (setq parsed (propertize parsed 'face 'font-lock-keyword-face))
-             (message "-+-G-+-T-+-S-+-")
-             (message parsed ))))
-
-(cl-defgeneric gts-me-pre (render translator)
-  (:documentation "Pre-render for TRANSLATOR.
-It used only when multiple engines exists.
-Default dispatch to gts-pre when first pre-render.")
-  (:method :after ((render gts-render) _)
-           (gts-do-log 'render (format "me prepare render with %s finished" (eieio-object-class-name render))))
-  (:method ((render gts-render) translator)
-           (with-slots (task-queue state) translator
-             (when (= state 1)
-               (gts-pre render translator)
-               (setf state 2)))))
-
-(cl-defgeneric gts-me-out (render task)
-  (:documentation "Render TASK with RENDER.
-It used only when multiple engines exists.
-Default dispatch to gts-out with all results concated.")
-  (:method :after ((render gts-render) task)
-           (gts-do-log 'render (format "%s: %s finished" (oref task id) (eieio-object-class-name render))))
-  (:method ((render gts-render) task)
-           (let ((translator (oref task translator)) results)
-             ;; show only when all tasks finished.
-             (when (= (oref translator state) 3)
-               (dolist (task (oref translator task-queue))
-                 (with-slots (err parsed engine) task
-                   (when (and (not err) (listp parsed))
-                     (setq parsed (string-join parsed "\f")))
-                   (push (cons (format "[%s]" (oref engine tag)) (or err parsed)) results)))
-               (setq results (nreverse results))
-               (let* ((newlinep (cl-find-if (lambda (r) (string-match-p "\n" (cdr r))) results))
-                      (concated (mapconcat (lambda (r)
-                                             (if newlinep
-                                                 (concat (propertize (concat (car r) "\n") 'face 'gts-pop-posframe-me-header-2-face)
-                                                         (format "\n%s" (cdr r)))
-                                               (concat (propertize (format "%-7s" (car r)) 'face 'gts-pop-posframe-me-header-face)
-                                                       (format " %s" (cdr r)))))
-                                           results
-                                           (if newlinep "\n\n" "\n"))))
-                 (oset translator state 4)
-                 (gts-out render (gts-task :parsed concated :translator translator)))))))
-
-\f
-;;; TTS
-
-(defcustom gts-tts-speaker (or (executable-find "mpv")
-                               (executable-find "mplayer"))
-  "The program to use to speak the translation.
-
-On Windows, if it is not found, will fallback to use `powershell`
-to do the job. Although it is not perfect, it seems to work."
-  :type 'string
-  :group 'go-translate)
-
-(defcustom gts-tts-try-speak-locally t
-  "If t then will fallback to locally TTS service when engine's TTS failed.
-For example, it will use powershell on Windows to speak the word
-when this is set to t."
-  :type 'boolean
-  :group 'go-translate)
-
-(defvar gts-tts-playing-process nil)
-
-(defun gts-tts-speak-buffer-data ()
-  "Speak the current buffer's data."
-  (gts-tts-try-interrupt-playing-process)
-  (let ((proc (make-process :name (format "gts-tts-process-%s" (+ 1000 (random 1000)))
-                            :command (append (split-string gts-tts-speaker) (list "-"))
-                            :buffer nil
-                            :noquery t
-                            :connection-type 'pipe)))
-    (setq gts-tts-playing-process proc)
-    (process-send-region proc (point-min) (point-max))
-    (if (process-live-p proc) (process-send-eof proc))))
-
-(defun gts-tts-try-interrupt-playing-process ()
-  (when (and gts-tts-playing-process (process-live-p gts-tts-playing-process))
-    (ignore-errors (kill-process gts-tts-playing-process))
-    (setq gts-tts-playing-process nil)))
-
-(defun gts-do-tts (text lang &optional engine)
-  "Speak TEXT in LANG with possible tts service by ENGINE.
-If engine failed, then try locally TTS if `gts-tts-try-speak-locally' is set."
-  (condition-case err
-      (if engine
-          (if gts-tts-speaker
-              (gts-tts engine text lang)
-            (user-error "No mpv/mplayer found"))
-        (user-error "No engine TTS service provided"))
-    (error (if gts-tts-try-speak-locally
-               (cond ((executable-find "powershell")
-                      (let ((cmd (format "$w = New-Object -ComObject SAPI.SpVoice; $w.speak(\\\"%s\\\")" text)))
-                        (shell-command (format "powershell -Command \"& {%s}\""
-                                               (encode-coding-string
-                                                (replace-regexp-in-string "\n" " " cmd)
-                                                (keyboard-coding-system))))))
-                     (t (message "[TTS] %s" (cadr err))))))))
-
-\f
-;;; Translator
-
-(defvar gts-text-delimiter "34587612321123")
-
-(defvar gts-current-command nil "The command invoked by `gts-translate'.")
-
-(cl-defmethod initialize-instance :after ((this gts-translator) &rest _)
-  (unless (gts-ensure-plain (oref this render))
-    (oset this render (gts-render)))
-  (unless (gts-ensure-plain (oref this picker))
-    (oset this picker (gts-noprompt-picker))))
-
-(cl-defmethod gts-get ((this gts-translator) &rest slots)
-  "Return the normalized value of SLOTS in THIS translator."
-  (cl-loop for slot in slots
-           for value =
-           (cl-flet ((ensure (v) (or v (user-error "No %s found in current translator" slot))))
-             (pcase slot
-               ((or 'picker 'render) (ensure (gts-ensure-plain (slot-value this slot))))
-               ('splitter            (gts-ensure-plain (slot-value this slot)))
-               ('engines             (ensure (gts-ensure-list (gts-ensure-plain (slot-value this slot)))))
-               (_                    (slot-value this slot))))
-           collect value into values
-           finally (return (if (cadr slots) (apply #'cl-values values) (car values)))))
-
-(cl-defmethod gts-init ((this gts-translator) text path)
-  "Init all status and tasks in THIS translator."
-  ;; input
-  (unless (and text path)
-    (let ((input (gts-pick (gts-get this 'picker))))
-      (setq text (cl-nth-value 0 input))
-      (setq path (cl-nth-value 1 input))))
-  (oset this text text)
-  (oset this path path)
-  (setq gts-picker-last-path path)
-  (gts-do-log 'translator (format "\nBegin to translate %s" path))
-  (gts-do-log 'text text)
-  (cl-multiple-value-bind (splitter engines render) (gts-get this 'splitter 'engines 'render 'version)
-    ;; reset
-    (oset this state 0)
-    (oset this task-queue nil)
-    (oset this plan-cnt (length engines))
-    (oset this version (time-to-seconds))
-    ;; tasks
-    (let ((splitted (if splitter (gts-split splitter text))))
-      (oset this sptext splitted)
-      (cl-loop for engine in engines
-               for task = (gts-task :translator this
-                                    :engine engine
-                                    :render render
-                                    :from (car path) :to (cdr path)
-                                    :text (if splitted (string-join splitted (concat "\n" gts-text-delimiter "\n")) text))
-               do (gts-add-task this task))
-      (gts-do-log 'text (format "splitted? %s!" (if splitted "Yes" "No"))))))
-
-(cl-defmethod gts-add-task ((this gts-translator) task)
-  "Add a new TASK to THIS translator."
-  (with-slots (id engine render) task
-    (with-slots (text path plan-cnt task-queue state) this
-      (when (= state 0)
-        (setf task-queue (append task-queue (list task)))
-        (gts-do-log 'translator
-          (format "- add task %s: (%s/%s)" id
-                  (eieio-object-class-name engine) (eieio-object-class-name render))))
-      (when (and (= state 0) (= (length task-queue) plan-cnt))
-        (setf state 1)))))
-
-(cl-defmethod gts-done-tasks ((this gts-translator))
-  "Return the finished tasks in THIS translator."
-  (with-slots (task-queue) this
-    (cl-remove-if-not
-     (lambda (task) (or (oref task err) (oref task raw))) task-queue)))
-
-(defun gts-update-raw (task resp &optional error)
-  "Update non-parsed RESP result to TASK.
-If ERROR is not nil, then update task with failure."
-  (declare (indent 1))
-  (with-slots (id raw err translator) task
-    (with-slots (task-queue plan-cnt state) translator
-      (setf raw resp)
-      (setf err error)
-      (when (and (= state 2) (= plan-cnt (length (gts-done-tasks translator))))
-        (setf state 3))
-      (gts-do-log 'translator (format "%s: **responsed** %s" id (or error "success!"))))))
-
-(defun gts-update-parsed (task result &optional metadata filter)
-  "Update TASK with parsed RESULT, update METADATA if exists.
-FILTER the parsed result if necessary, it's a function take parsed result as argument."
-  (declare (indent 1))
-  (with-slots (id raw parsed err meta translator) task
-    (if raw
-        (condition-case er
-            (let ((r (if (or (listp result) (not (gts-get translator 'sptext)))
-                         result
-                       (gts-do-log 'translator
-                         (format "%s: result contains delimiter, so split to list" id))
-                       (split-string result gts-text-delimiter nil "[ \t\n\r]+"))))
-              (setf parsed (if filter (funcall filter r) r))
-              (if metadata (setf meta metadata)))
-          (error (setf err er)))
-      (gts-update-raw task t "Mybe Null Response"))))
-
-(defun gts-render-fail (task error)
-  "Directly render the ERROR message and finish the TASK."
-  (declare (indent 1))
-  (gts-do-log 'translator (format "error? %s" error))
-  (if task
-      (with-slots (translator render) task
-        (let ((engines (gts-get translator 'engines)))
-          (gts-update-raw task nil error)
-          (if (cdr engines)
-              (gts-me-out render task)
-            (gts-out render task))))
-    (signal 'error error)))
-
-(cl-defmethod gts-translate ((this gts-translator) &optional text path)
-  "Fire a translation for THIS translator instance.
-When TEXT and PATH is nil then pick them via `gts-pick'."
-  ;; record this, so can fixup issue in posframe-pop etc. Weird?
-  (setq gts-current-command this-command)
-  ;; initialize
-  (gts-init this text path)
-  (cl-multiple-value-bind (engines render tasks text path)
-      (gts-get this 'engines 'render 'task-queue 'text 'path)
-    (let ((buf (current-buffer)))
-      ;; single engine
-      (if (not (cdr engines))
-          (let ((task (car tasks)))
-            (gts-pre render this)
-            (gts-translate (car engines) task
-                           (lambda ()
-                             (with-current-buffer buf
-                               (gts-out render task)))))
-        ;; multiple engines
-        (gts-me-pre render this)
-        (dolist (task tasks)
-          (condition-case er
-              (gts-translate (oref task engine) task
-                             (lambda ()
-                               (with-current-buffer buf
-                                 (gts-me-out render task))))
-            (error
-             (gts-render-fail task er))))
-        (gts-me-out render (gts-task :text text :from (car path) :to (cdr path) :translator this))))))
-
-
-(provide 'gts-core)
-
-;;; gts-core.el ends here
diff --git a/elpa/go-translate-20221211.1327/gts-engine-bing.el b/elpa/go-translate-20221211.1327/gts-engine-bing.el
deleted file mode 100644 (file)
index 27a1360..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-;;; gts-engine-bing.el --- Microsoft Translate -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; https://bing.com/translator
-
-;;; Code:
-
-(require 'gts-implements)
-
-(defclass gts-bing-parser (gts-parser) ())
-
-(defclass gts-bing-engine (gts-engine)
-  ((tag       :initform "Bing")
-   (base-url  :initform "https://www.bing.com")
-   (sub-url   :initform "/ttranslatev3")
-
-   (tld-url   :initform nil)
-   (ig        :initform nil)
-   (key       :initform nil)
-   (token     :initform nil)
-   (last-time :initform nil)
-   (expired-time :initform (* 30 60))
-
-   (ttsk-url  :initform "/tfetspktok")
-   (tts-url   :initform "https://%s.tts.speech.microsoft.com/cognitiveservices/v1")
-   (tts-tpl   :initform "<speak version='1.0' xml:lang='%s'><voice xml:lang='%s' xml:gender='Female' name='%s'><prosody rate='-20.00%%'>%s</prosody></voice></speak>")
-
-   (parser    :initform (gts-bing-parser))))
-
-\f
-;;; Engine
-
-(defvar gts-bing-extra-langs-mapping '(("zh" . "zh-Hans")))
-
-(defvar gts-bing-token-maybe-invalid nil)
-
-(cl-defmethod gts-get-lang ((_ gts-bing-engine) lang)
-  (or (cdr-safe (assoc lang gts-bing-extra-langs-mapping)) lang))
-
-(cl-defmethod gts-token-available-p ((engine gts-bing-engine))
-  (with-slots (token key ig last-time expired-time) engine
-    (and token key ig last-time
-         (not gts-bing-token-maybe-invalid)
-         (< (- (time-to-seconds) last-time) expired-time))))
-
-(cl-defmethod gts-with-token ((engine gts-bing-engine) done fail)
-  (with-slots (token key ig base-url) engine
-    (if (gts-token-available-p engine)
-        (funcall done)
-      (gts-do-request (concat base-url "/translator")
-                      :done
-                      (lambda ()
-                        (let (key token ig tld)
-                          (re-search-forward "curUrl=.*/\\([a-z]+\\.bing.com\\)")
-                          (setq tld (match-string 1))
-                          (re-search-forward "\"ig\":\"\\([^\"]+\\).*params_RichTranslateHelper = \\[\\([0-9]+\\),\"\\([^\"]+\\)")
-                          (setq ig (match-string 1) key (match-string 2) token (match-string 3))
-                          (oset engine ig ig)
-                          (oset engine key key)
-                          (oset engine token token)
-                          (oset engine last-time (time-to-seconds))
-                          (oset engine tld-url (concat "https://" tld))
-                          (setq gts-bing-token-maybe-invalid nil)
-                          (gts-do-log 'bing (format "url: %s\nkey: %s\ntoken: %s\nig: %s" tld key token ig))
-                          (funcall done)))
-                      :fail fail))))
-
-(cl-defmethod gts-translate ((engine gts-bing-engine) task rendercb)
-  (gts-with-token engine
-    (lambda ()
-      (with-slots (text from to raw) task
-        (with-slots (tld-url sub-url token key ig parser) engine
-          (gts-do-request (format "%s%s?isVertical=1&IG=%s&IID=translator.5022.1" tld-url sub-url ig)
-                          :headers `(("Content-Type" . "application/x-www-form-urlencoded;charset=UTF-8"))
-                          :data `(("fromLang" . ,(gts-get-lang engine from))
-                                  ("to"       . ,(gts-get-lang engine to))
-                                  ("text"     . ,text)
-                                  ("key"      . ,key)
-                                  ("token"    . ,token))
-                          :done (lambda ()
-                                  (gts-update-raw task (buffer-string))
-                                  (gts-parse parser task)
-                                  (funcall rendercb))
-                          :fail (lambda (err)
-                                  (gts-render-fail task
-                                    (cond ((ignore-errors (= (caddar err) 429))
-                                           "[429] Too many requests! Please retry later")
-                                          (t err))))))))
-    (lambda (err)
-      (gts-render-fail task
-        (format "Error occurred when request for token.\n\n%s" err)))))
-
-\f
-;;; TTS
-
-(defvar gts-bing-tts-langs-mapping '(("zh" . ("zh-CN" . "zh-CN-XiaoxiaoNeural"))
-                                     ("en" . ("en-US" . "en-US-AriaNeural"))
-                                     ("fr" . ("fr-CA" . "fr-CA-SylvieNeural"))
-                                     ("de" . ("de-DE" . "de-DE-KatjaNeural"))))
-
-(cl-defmethod gts-tts-payload ((engine gts-bing-engine) lang text)
-  (with-slots (tts-tpl) engine
-    (let (l n (mt (assoc lang gts-bing-tts-langs-mapping)))
-      (if mt (setq l (cadr mt) n (cddr mt))
-        (user-error "Add the mapping of your language into `gts-bing-tts-langs-mapping' :)"))
-      (format tts-tpl l l n (encode-coding-string text 'utf-8)))))
-
-(cl-defmethod gts-tts ((engine gts-bing-engine) text lang)
-  (gts-with-token engine
-    (lambda ()
-      (with-slots (tld-url sub-url token key ig parser ttsk-url tts-url tts-tpl) engine
-        (gts-do-request (format "%s%s?isVertical=1&IG=%s&IID=translator.5022.2" tld-url ttsk-url ig)
-                        :headers '(("content-type" . "application/x-www-form-urlencoded"))
-                        :data `(("token" . ,token) ("key" . ,key))
-                        :done (lambda ()
-                                (let* ((json (json-read))
-                                       (token (cdr (assoc 'token json)))
-                                       (region (cdr (assoc 'region json))))
-                                  (gts-do-log 'bing-tts (format "token: %s\nregion: %s" token region))
-                                  (gts-do-request (format tts-url region)
-                                                  :data (gts-tts-payload engine lang text)
-                                                  :headers `(("content-type" . "application/ssml+xml")
-                                                             ("authorization" . ,(format "Bearer %s" token))
-                                                             ("x-microsoft-outputformat" . "audio-16khz-32kbitrate-mono-mp3"))
-                                                  :done (lambda ()
-                                                          (gts-tts-speak-buffer-data))
-                                                  :fail (lambda (_)
-                                                          (user-error "[BING-TTS] error when play sound")))))
-                        :fail (lambda (err) (user-error "%s" err)))))
-    (lambda (_) (user-error "Failed to get token"))))
-
-\f
-;;; Parser
-
-(cl-defmethod gts-parse ((_ gts-bing-parser) task)
-  (with-temp-buffer
-    (set-buffer-multibyte t)
-    (insert (oref task raw))
-    (decode-coding-region (point-min) (point-max) 'utf-8)
-    (gts-do-log 'bing (string-trim (buffer-string)))
-    (goto-char (point-min))
-    (if-let* ((json (json-read))
-              (result (ignore-errors (cdr (assoc 'text (aref (cdr (assoc 'translations (aref json 0))) 0))))))
-        (gts-update-parsed task result)
-      (setq gts-bing-token-maybe-invalid t) ; refresh token when error occurred
-      (gts-update-raw task nil (buffer-string)))))
-
-
-(provide 'gts-engine-bing)
-
-;;; gts-engine-bing.el ends here
diff --git a/elpa/go-translate-20221211.1327/gts-engine-deepl.el b/elpa/go-translate-20221211.1327/gts-engine-deepl.el
deleted file mode 100644 (file)
index 9b1bdfc..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-;;; gts-engine-deepl.el --- Translate Engine for DeepL -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; https://www.deepl.com
-
-;;; Code:
-
-(require 'gts-implements)
-
-\f
-;;; Components
-
-(defclass gts-deepl-parser (gts-parser) ())
-
-(defclass gts-deepl-engine (gts-engine)
-  ((tag      :initform "DeepL")
-   (pro      :initform nil :initarg :pro :documentation "If your key is PRO version")
-   (free-url :initform "https://api-free.deepl.com")
-   (pro-url  :initform "https://api.deepl.com")
-   (sub-url  :initform "/v2/translate")
-   (auth-key :initform nil :initarg :auth-key)
-   (parser   :initform (gts-deepl-parser))))
-
-(cl-defmethod initialize-instance :after ((engine gts-deepl-engine) &rest _)
-  (unless (oref engine auth-key)
-    (user-error "You should provide a auth-key when instance gts-deepl-engine")))
-
-\f
-;;; Utils
-
-(defcustom gts-deepl-fill-enable t
-  "Controller wheather try to improve the input and output.
-Default behavior is removing excess linebreaks in input for better
-translation effect, and filling the output for better reading experience.
-You can override the behaviors by :around the method `gts-deepl-fill-input'
-or `gts-deepl-fill-output'."
-  :type 'boolean
-  :group 'go-translate)
-
-(cl-defmethod gts-deepl-fill-input (text)
-  "Improve the input TEXT for better translation effect.
-Mainly remove excess linebreaks. I want to skip unfill on comments and codes,
-but don't know how to implement easily. To make it better later, maybe."
-  (if gts-deepl-fill-enable
-      (with-temp-buffer
-        (insert text)
-        (let ((fill-column (point-max)))
-          (fill-region (point-min) (point-max)))
-        (buffer-string))
-    text))
-
-(cl-defmethod gts-deepl-fill-output (text)
-  "Improve the output TEXT for better reading experience.
-Mainly fill the text to suitable length."
-  (if gts-deepl-fill-enable
-      (with-temp-buffer
-        (insert text)
-        (fill-region (point-min) (point-max))
-        (buffer-string))
-    text))
-
-\f
-;;; Engine
-
-(defvar gts-deepl-langs-mapping '(("en" . "EN")
-                                  ("zh" . "ZH")
-                                  ("de" . "DE") ; German
-                                  ("fr" . "FR") ; French
-                                  ("it" . "IT") ; Italian
-                                  ("ja" . "JA") ; Japanese
-                                  ("es" . "ES") ; Spanish
-                                  ("nl" . "NL") ; Dutch
-                                  ("pl" . "PL") ; Polish
-                                  ("pt" . "PT") ; Portuguese (all Portuguese varieties mixed)
-                                  ("ru" . "RU") ; Russian
-                                  ))
-
-(cl-defmethod gts-get-lang ((_ gts-deepl-engine) lang)
-  (let ((mapping (assoc lang gts-deepl-langs-mapping)))
-    (if mapping (cdr mapping)
-      (user-error
-       "Language %s is not supported by DeepL.\nSupported list: %s"
-       lang (mapconcat #'car gts-deepl-langs-mapping ", ")))))
-
-(cl-defmethod gts-gen-url ((engine gts-deepl-engine))
-  (with-slots (free-url pro-url sub-url pro) engine
-    (format "%s%s" (if pro pro-url free-url) sub-url)))
-
-(cl-defmethod gts-translate ((engine gts-deepl-engine) task rendercb)
-  (with-slots (text from to) task
-    (with-slots (auth-key parser) engine
-      (gts-do-request (gts-gen-url engine)
-                      :headers `(("Content-Type"   . "application/x-www-form-urlencoded;charset=UTF-8")
-                                 ("Authorization"  . ,(concat "DeepL-Auth-Key " auth-key)))
-                      :data    `(("text"           . ,(gts-deepl-fill-input text))
-                                 ("source_lang"    . ,(gts-get-lang engine from))
-                                 ("target_lang"    . ,(gts-get-lang engine to)))
-                      :done (lambda ()
-                              (gts-update-raw task (buffer-string))
-                              (gts-parse parser task)
-                              (funcall rendercb))
-                      :fail (lambda (err)
-                              (gts-render-fail task
-                                (if (ignore-errors (= (caddar err) 403))
-                                    "[403] http error, make sure your auth_key is correct"
-                                  err)))))))
-
-\f
-;;; Parser
-
-(cl-defmethod gts-parse ((_ gts-deepl-parser) task)
-  (let* ((json (json-read-from-string (oref task raw)))
-         (str (mapconcat (lambda (r) (cdr (cadr r))) (cdar json) "\n"))
-         (filter (lambda (parsed)
-                   (cl-loop for p in (gts-ensure-list parsed)
-                            collect (gts-deepl-fill-output p) into ps
-                            finally (return (if (cdr ps) ps (car ps)))))))
-    (gts-update-parsed task (decode-coding-string str 'utf-8) nil filter)))
-
-
-(provide 'gts-engine-deepl)
-
-;;; gts-engine-deepl.el ends here
diff --git a/elpa/go-translate-20221211.1327/gts-engine-google-rpc.el b/elpa/go-translate-20221211.1327/gts-engine-google-rpc.el
deleted file mode 100644 (file)
index 8c784c4..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-;;; gts-engine-google-rpc.el --- Google translation with rpc api -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; http://translate.google.com, new RPC API
-
-;;; Code:
-
-(require 'gts-engine-google)
-
-(defcustom gts-google-rpc-base-url "https://translate.google.com"
-  "The base url of Google translate used by google-rpc engine.
-you can customize it according to your country region."
-  :type 'string
-  :group 'go-translate)
-
-\f
-;;; Components
-
-(defclass gts-google-rpc-parser (gts-google-parser)
-  ((tag :initform "Detail")))
-
-(defclass gts-google-rpc-summary-parser (gts-google-summary-parser gts-google-rpc-parser)
-  ((tag :initform "Summary")))
-
-(defclass gts-google-rpc-engine (gts-engine)
-  ((tag       :initform "Google/RPC")
-   (sub-url   :initform "/_/TranslateWebserverUi/data/batchexecute")
-   (parser    :initform (gts-google-rpc-parser))
-
-   (rpc-translate :initform "MkEWBc")
-   (rpc-tts       :initform "jQ1olc")
-   (rpc-sid       :initform "FdrFJe")
-   (rpc-bl        :initform "cfb2h")))
-
-\f
-;;; Engine
-
-(defvar gts-google-rpc-request-headers '(("Content-Type" . "application/x-www-form-urlencoded;charset=UTF-8")))
-
-(cl-defmethod gts-with-token ((engine gts-google-rpc-engine) done fail)
-  (with-slots (sub-url rpc-sid rpc-bl) engine
-    (gts-do-request gts-google-rpc-base-url
-                    :done
-                    (lambda ()
-                      (let* ((f-sid (progn
-                                      (re-search-forward (format "\"%s\":\"\\([^\"]*\\)\"" rpc-sid))
-                                      (match-string 1)))
-                             (bl (progn (re-search-forward (format "\"%s\":\"\\([^\"]*\\)\"" rpc-bl))
-                                        (match-string 1)))
-                             (url-tpl (lambda (rpcid lang)
-                                        (format "%s%s?%s" gts-google-rpc-base-url sub-url
-                                                (gts-format-params
-                                                 `(("rpcids"       . ,rpcid)
-                                                   ("f.sid"        . ,f-sid)
-                                                   ("bl"           . ,bl)
-                                                   ("hl"           . ,lang)
-                                                   ("soc-app"      . 1)
-                                                   ("soc-platform" . 1)
-                                                   ("soc-device"   . 1)
-                                                   ("_reqid"       . ,(+ 1000 (random 9000)))
-                                                   ("rt"           . "c")))))))
-                        (funcall done url-tpl)))
-                    :fail fail)))
-
-(cl-defmethod gts-translate ((engine gts-google-rpc-engine) task rendercb)
-  (gts-with-token engine
-    (lambda (url-tpl)
-      (with-slots (text from to) task
-        (with-slots (rpc-translate parser) engine
-          (gts-do-request (funcall url-tpl rpc-translate to)
-                          :headers gts-google-rpc-request-headers
-                          :data (format
-                                 "f.req=%s&"
-                                 (url-hexify-string
-                                  (let ((outer [[[rpcid inner nil "generic"]]])
-                                        (inner [[text from to t][nil]]))
-                                    (setf (aref (aref inner 0) 0) text)
-                                    (setf (aref (aref inner 0) 1) from)
-                                    (setf (aref (aref inner 0) 2) to)
-                                    (setf (aref (aref (aref outer 0) 0) 0) rpc-translate)
-                                    (setf (aref (aref (aref outer 0) 0) 1) (json-encode inner))
-                                    (json-encode outer))))
-                          :done (lambda ()
-                                  (gts-update-raw task (buffer-string))
-                                  (gts-parse parser task)
-                                  (funcall rendercb))
-                          :fail (lambda (err)
-                                  (gts-render-fail task err))))))
-    (lambda (err)
-      (gts-render-fail task
-        (format "Error occurred when request for token.\n\n%s" err)))))
-
-(cl-defmethod gts-tts ((engine gts-google-rpc-engine) text lang)
-  (gts-with-token engine
-    (lambda (url-tpl)
-      (with-slots (rpc-tts) engine
-        (gts-do-request (funcall url-tpl rpc-tts "en-US")
-                        :headers gts-google-rpc-request-headers
-                        :data (format
-                               "f.req=%s&"
-                               (url-hexify-string
-                                (let ((outer [[[rpcid inner nil "generic"]]])
-                                      (inner [text lang nil nil]))
-                                  (setf (aref inner 0) text)
-                                  (setf (aref inner 1) lang)
-                                  (setf (aref (aref (aref outer 0) 0) 0) rpc-tts)
-                                  (setf (aref (aref (aref outer 0) 0) 1) (json-encode inner))
-                                  (json-encode outer))))
-                        :done (lambda ()
-                                (let (beg end json code)
-                                  (re-search-forward "^[0-9]+$")
-                                  (setq beg (point))
-                                  (re-search-forward "^\\([0-9]+\\)$")
-                                  (setq end (- (point) (length (match-string 1))))
-                                  (setq json (json-read-from-string (string-trim (buffer-substring-no-properties beg end))))
-                                  (when (string= (gts-aref-for json 0 0) "wrb.fr")
-                                    (setq code (aref (json-read-from-string (gts-aref-for json 0 2)) 0))
-                                    (erase-buffer)
-                                    (insert code)
-                                    (base64-decode-region (point-min) (point-max))
-                                    (gts-tts-speak-buffer-data))))
-                        :fail (lambda (err) (user-error "Error when TTS. %s" err)))))
-    (lambda (_) (user-error "Error occurred when request for token"))))
-
-\f
-;;; Parser
-
-(cl-defmethod gts-resp-to-json ((_ gts-google-rpc-parser) resp)
-  "Convert the buffer RESP into JSON format."
-  (condition-case err
-      (with-temp-buffer
-        (let (beg end str json)
-          (insert resp)
-          (goto-char (point-min))
-          (re-search-forward "^[0-9]+$")
-          (setq beg (point))
-          (re-search-forward "^\\([0-9]+\\)$")
-          (setq end (- (point) (length (match-string 1))))
-          (setq str
-                (decode-coding-string
-                 (string-trim (buffer-substring-no-properties beg end))
-                 'utf-8))
-          (setq json (json-read-from-string str))
-          (if (string= (aref (aref json 0) 0) "wrb.fr")
-              (let ((json-str (aref (aref json 0) 2)))
-                (json-read-from-string json-str))
-            (error "No results found"))))
-    (error (user-error "Result conversion error (%s)" err))))
-
-(cl-defmethod gts-result--brief ((_ gts-google-rpc-parser) json)
-  "Get the translation text from JSON."
-  (mapconcat
-   (lambda (item)
-     (if (ignore-errors (aref item 2))
-         (format " %s" (aref item 0))
-       (aref item 0)))
-   (gts-aref-for json 1 0 0 5)
-   ""))
-
-(cl-defmethod gts-result--sphonetic ((_ gts-google-rpc-parser) json)
-  "Get the text phonetic from JSON."
-  (gts-aref-for json 0 0))
-
-(cl-defmethod gts-result--tphonetic ((_ gts-google-rpc-parser) json)
-  "Get the translation phonetic from JSON."
-  (gts-aref-for json 1 0 0 1))
-
-(cl-defmethod gts-result--details ((_ gts-google-rpc-parser) json)
-  "Get the details from JSON.
-Result style: ((noun (a (x y z))) (verb (b (m n o))))."
-  (cl-loop with dts = (ignore-errors (gts-aref-for json 3 5 0))
-           for i across dts
-           collect
-           (cons
-            (aref i 0)
-            (cl-loop for j across (aref i 1)
-                     collect
-                     (cons
-                      (aref j 0)
-                      (cl-loop for k across (aref j 2) collect k))))))
-
-(cl-defmethod gts-result--definitions ((_ gts-google-rpc-parser) json)
-  "Get the definitions from JSON.
-Result style: ((noun (a b)) (verb (c d)))."
-  (cl-loop with defs = (ignore-errors (gts-aref-for json 3 1 0))
-           for i across defs
-           collect
-           (cons
-            (aref i 0)
-            (cl-loop for j across (aref i 1)
-                     collect
-                     (cons (aref j 0)
-                           (ignore-errors (aref j 1)))))))
-
-(cl-defmethod gts-result--suggestion ((_ gts-google-rpc-parser) json)
-  "Get the suggestion from JSON."
-  (ignore-errors
-    (replace-regexp-in-string "<b>\\|</b>\\|<i>\\|</i>" "" (gts-aref-for json 0 1 0 0 1))))
-
-
-(provide 'gts-engine-google-rpc)
-
-;;; gts-engine-google-rpc.el ends here
diff --git a/elpa/go-translate-20221211.1327/gts-engine-google.el b/elpa/go-translate-20221211.1327/gts-engine-google.el
deleted file mode 100644 (file)
index 2ac39ba..0000000
+++ /dev/null
@@ -1,424 +0,0 @@
-;;; gts-engine-google.el --- Google translate module -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; http://translate.google.com
-
-;;; Code:
-
-(require 'gts-implements)
-
-\f
-;;; Components
-
-(defclass gts-google-parser (gts-parser)
-  ((tag :initform "Detail")))
-
-(defclass gts-google-summary-parser (gts-google-parser)
-  ((tag :initform "Summary")))
-
-(defclass gts-google-engine (gts-engine)
-  ((tag                :initform "Google")
-   (base-url           :initform "http://translate.googleapis.com")
-   (sub-url            :initform "/translate_a/single")
-   (token              :initform (cons 430675 2721866130) :initarg token) ; hard code
-   (token-time         :initform t)
-   (token-expired-time :initform (* 30 60))
-   (parser             :initform (gts-google-parser))))
-
-\f
-;;; Engine
-
-(defvar gts-google-request-headers '(("Connection" . "Keep-Alive")))
-
-(cl-defmethod gts-gen-url ((engine gts-google-engine) text from to)
-  "Generate the url with TEXT, FROM and TO. Return a (url text from to) list."
-  (format "%s%s?%s"
-          (oref engine base-url)
-          (oref engine sub-url)
-          (mapconcat (lambda (p)
-                       (format "%s=%s" (url-hexify-string (car p)) (url-hexify-string (cdr p))))
-                     `(("client" . "gtx")
-                       ("ie"     . "UTF-8")
-                       ("oe"     . "UTF-8")
-                       ("dt"     . "bd")
-                       ("dt"     . "ex")
-                       ("dt"     . "ld")
-                       ("dt"     . "md")
-                       ("dt"     . "qc")
-                       ("dt"     . "rw")
-                       ("dt"     . "rm")
-                       ("dt"     . "ss")
-                       ("dt"     . "t")
-                       ("dt"     . "at")
-                       ("pc"     . "1")
-                       ("otf"    . "1")
-                       ("srcrom" . "1")
-                       ("ssel"   . "0")
-                       ("tsel"   . "0")
-                       ("hl"     . ,to)
-                       ("sl"     . ,from)
-                       ("tl"     . ,to)
-                       ("q"      . ,text)
-                       ("tk"     . ,(gts-google-tkk (oref engine token) text)))
-                     "&")))
-
-(cl-defmethod gts-token-available-p ((engine gts-google-engine))
-  (with-slots (token token-time token-expired-time) engine
-    (and token
-         (or (eq token-time t)
-             (and token-time
-                  (<= (float-time (time-subtract (current-time) token-time))
-                      token-expired-time))))))
-
-(cl-defmethod gts-with-token ((engine gts-google-engine) done fail)
-  (with-slots (token token-time base-url) engine
-    (if (gts-token-available-p engine)
-        (funcall done)
-      (gts-do-request base-url
-                      :headers gts-google-request-headers
-                      :done (lambda ()
-                              (let ((tk (progn
-                                          (re-search-forward ",tkk:'\\([0-9]+\\)\\.\\([0-9]+\\)")
-                                          (cons (string-to-number (match-string 1))
-                                                (string-to-number (match-string 2))))))
-                                (setf token tk)
-                                (setf token-time (current-time))
-                                (funcall done)))
-                      :fail fail))))
-
-(cl-defmethod gts-translate ((engine gts-google-engine) task rendercb)
-  (gts-with-token engine
-    (lambda ()
-      (with-slots (text from to) task
-        (gts-do-request (gts-gen-url engine text from to)
-                        :headers gts-google-request-headers
-                        :done (lambda ()
-                                (gts-update-raw task (buffer-string))
-                                (gts-parse (oref engine parser) task)
-                                (funcall rendercb))
-                        :fail (lambda (err)
-                                (gts-render-fail task err)))))
-    (lambda (err)
-      (gts-render-fail task
-        (format "Error when fetching Token-Key, check your network and proxy, or retry later\n\n%s" err)))))
-
-;; tts
-
-(cl-defmethod gts-tts-gen-urls ((engine gts-google-engine) text lang)
-  "Generate the tts urls for TEXT to LANGUAGE."
-  (cl-loop with texts = (gts-tts-text-splitter engine text)
-           for total = (length texts)
-           for index from 0
-           for piece in texts
-           collect
-           (let ((params `(("ie"      . "UTF-8")
-                           ("client"  . "gtx")
-                           ("prev"    . "input")
-                           ("q"       . ,text)
-                           ("tl"      . ,lang)
-                           ("total"   . ,(number-to-string total))
-                           ("idx"     . ,(number-to-string index))
-                           ("textlen" . ,(number-to-string (length piece)))
-                           ("tk"      . ,(gts-google-tkk (oref engine token) piece)))))
-             (format "%s/translate_tts?%s"
-                     (oref engine base-url)
-                     (mapconcat (lambda (p)
-                                  (format "%s=%s"
-                                          (url-hexify-string (car p))
-                                          (url-hexify-string (cdr p))))
-                                params "&")))))
-
-(cl-defmethod gts-tts-text-splitter ((_ gts-google-engine) text)
-  "Split TEXT by maxlen at applicable point for translating.
-Code from `google-translate', maybe improve it someday."
-  (let (result (maxlen 200))
-    (if (or (null maxlen) (<= maxlen 0))
-           (push text result)
-      ;; split long text?
-      (with-temp-buffer
-           (save-excursion (insert text))
-           ;; strategy to split at applicable point
-           ;; 1) fill-region remaining text by maxlen
-           ;; 2) find end of sentence, end of punctuation, word boundary
-           ;; 3) consume from remaining text between start and (2)
-           ;; 4) repeat
-           (let ((fill-column (* maxlen 3))
-                 (sentence-end-double-space nil)
-                 (pos (point-min)))
-             (while (< pos (point-max))
-               (save-restriction
-                 (narrow-to-region pos (point-max))
-                 (fill-region pos (point-max))
-                 (let ((limit (+ pos maxlen)))
-                       (if (>= limit (point-max))
-                           (setq limit (point-max))
-                         (goto-char limit)
-                         ;; try to split at end of sentence
-                         (if (> (backward-sentence) pos)
-                             (setq limit (point))
-                           ;; try to split at end of punctuation
-                           (goto-char limit)
-                           (if (re-search-backward "[,、]" pos t)
-                                   (setq limit (1+ (point))) ; include punctuation
-                             (goto-char limit)
-                             ;; try to split at word boundary
-                             (forward-word-strictly -1)
-                             (when (> (point) pos)
-                                   (setq limit (point))))))
-                       (push (buffer-substring-no-properties pos limit) result)
-                       (goto-char limit)
-                       (setq pos limit)))))))
-    (reverse result)))
-
-(cl-defmethod gts-tts ((engine gts-google-engine) text lang)
-  (let ((urls (gts-tts-gen-urls engine text lang))
-        (speaker (split-string gts-tts-speaker)))
-    (with-temp-message "Speaking..."
-      (gts-tts-try-interrupt-playing-process)
-      (apply #'call-process (car speaker) nil nil nil (append (cdr speaker) urls)))))
-
-\f
-;;; Parser
-
-;; detail-mode, use as default
-
-(cl-defmethod gts-parse ((parser gts-google-parser) task)
-  (let* ((json        (gts-resp-to-json parser (oref task raw)))
-         (brief       (gts-result--brief parser json))
-         (sphonetic   (gts-result--sphonetic parser json))
-         (tphonetic   (gts-result--tphonetic parser json))
-         (details     (gts-result--details parser json))
-         (definitions (gts-result--definitions parser json))
-         (suggestion  (gts-result--suggestion parser json))
-         (suggestionp (> (length suggestion) 0)) tbeg tend)
-    (cl-flet ((phonetic (ph)
-                (if (and (or definitions definitions) (> (length ph) 0))
-                    (propertize (format " [%s]" ph) 'face 'gts-google-buffer-phonetic-face)
-                  ""))
-              (headline (line)
-                (propertize (format "[%s]\n" line) 'face 'gts-google-buffer-headline-face)))
-      (with-temp-buffer
-        ;; suggestion
-        (when suggestionp
-          (insert (propertize "Do you mean:" 'face 'gts-google-buffer-suggestion-desc-face) " "
-                  (propertize suggestion 'face 'gts-google-buffer-suggestion-text-face) "?\n\n"))
-        ;; phonetic & translate
-        (if (or details definitions)
-            (progn
-              (insert (if suggestionp suggestion (oref (oref task translator) text)))
-              (insert (phonetic sphonetic) " ")
-              (setq tbeg (point))
-              (insert (propertize brief 'face 'gts-google-buffer-brief-result-face))
-              (setq tend (point))
-              (insert (phonetic tphonetic) "\n\n"))
-          (insert brief))
-        ;; details
-        (when details
-          (insert (headline "Details"))
-          (cl-loop for (label . items) in details
-                   unless (= 0 (length label))
-                   do (insert (format "\n%s:\n" label))
-                   do (cl-loop with index = 0
-                               for trans in items
-                               do (insert
-                                   (format "%2d. " (cl-incf index))
-                                   (car trans)
-                                   " (" (mapconcat #'identity (cdr trans) ", ")  ")"
-                                   "\n")))
-          (insert "\n"))
-        ;; definitions
-        (when definitions
-          (insert (headline "Definitions"))
-          (cl-loop for (label . items) in definitions
-                   unless (= 0 (length label))
-                   do (insert (format "\n%s:\n" label))
-                   do (cl-loop with index = 0
-                               for (exp . eg) in items
-                               do (insert (format "%2d. " (cl-incf index)) exp)
-                               when (> (length eg) 0)
-                               do (insert
-                                   "\n    > "
-                                   (propertize (or eg "") 'face 'gts-google-buffer-detail-demo-face))
-                               do (insert "\n"))))
-        ;; at last, fill and return
-        (gts-update-parsed task (buffer-string) (list :tbeg tbeg :tend tend))))))
-
-;; summary-mode
-
-(cl-defmethod gts-parse ((parser gts-google-summary-parser) task)
-  (let* ((json (gts-resp-to-json parser (oref task raw)))
-         (result (string-trim (gts-result--brief parser json))))
-    (gts-update-parsed task result (list :tbeg 1 :tend (+ 1 (length result))))))
-
-;; Extract results from response
-
-(cl-defmethod gts-resp-to-json ((_ gts-google-parser) resp)
-  "Convert the buffer RESP into JSON."
-  (condition-case err
-      (json-read-from-string (decode-coding-string resp 'utf-8))
-    (error (user-error "Result conversion error: %s" err))))
-
-(cl-defmethod gts-result--brief ((_ gts-google-parser) json)
-  "Get the translation text from JSON."
-  (mapconcat (lambda (item) (aref item 0)) (aref json 0) ""))
-
-(cl-defmethod gts-result--sphonetic ((_ gts-google-parser) json)
-  "Get the text phonetic from JSON."
-  (mapconcat (lambda (item) (if (> (length item) 3) (aref item 3) "")) (aref json 0) ""))
-
-(cl-defmethod gts-result--tphonetic ((_ gts-google-parser) json)
-  "Get the translation phonetic from JSON."
-  (mapconcat (lambda (item) (if (> (length item) 2) (aref item 2) "")) (aref json 0) ""))
-
-(cl-defmethod gts-result--details ((_ gts-google-parser) json)
-  "Get the details from JSON.
-Result style: ((noun (a (x y z))) (verb (b (m n o))))."
-  (cl-loop for i across (aref json 1)
-           collect
-           (cons
-            (aref i 0)
-            (cl-loop for j across (aref i 2)
-                     collect
-                     (cons
-                      (aref j 0)
-                      (cl-loop for k across (aref j 1) collect k))))))
-
-(cl-defmethod gts-result--definitions ((_ gts-google-parser) json)
-  "Get the definitions from JSON.
-Result style: ((noun (a b)) (verb (c d)))."
-  (cl-loop with defs = (ignore-errors (aref json 12))
-           for i across defs
-           collect
-           (cons
-            (aref i 0)
-            (cl-loop for j across (aref i 1)
-                     collect
-                     (cons
-                      (aref j 0)
-                      (ignore-errors (aref j 2)))))))
-
-(cl-defmethod gts-result--suggestion ((_ gts-google-parser) json)
-  "Get the suggestion from JSON."
-  (let ((info (aref json 7))) (unless (seq-empty-p info) (aref info 1))))
-
-\f
-;;; Token Key algorithm (deprecated)
-
-;; This algorithm is from `google-translate' project.
-;; https://github.com/atykhonov/google-translate/blob/master/google-translate-tk.el
-
-(defvar gts-google-token--bit-v-len 32)
-
-(defun gts-google-token--bit-v-2comp (v)
-  "Return the two's complement of V."
-  (let* ((vc (vconcat v))
-         (len (length vc)))
-    ;; Complement of v
-    (cl-loop for i from 0 below len do
-             (aset vc i (logxor (aref vc i) 1)))
-    ;; vc = complement of v + 1
-    (cl-loop for i downfrom (1- len) to 0
-             do (aset vc i (logxor (aref vc i) 1))
-             when (> (aref vc i) 0) return nil)
-    vc))
-
-(defun gts-google-token--number-to-bit-v (n)
-  "Return a bit vector from N."
-  (if (< n 0) (gts-google-token--bit-v-2comp
-               (gts-google-token--number-to-bit-v (abs n)))
-    (let ((v (make-vector gts-google-token--bit-v-len 0)))
-      (cl-loop for i downfrom (1- gts-google-token--bit-v-len) to 0
-               with q
-               when (< n 1) return nil do
-               (setq q (ffloor (* n 0.5)))
-               (aset v i (floor (- n (* 2.0 q))))
-               (setq n q))
-      v)))
-
-(defun gts-google-token--bit-v-to-number (v)
-  "Return a floating-point number from V."
-  (if (and (> (aref v 0) 0)
-           ;; Exclude [1 0 ... 0]
-           (cl-loop for i from 1 below gts-google-token--bit-v-len
-                    thereis (> (aref v i) 0)))
-      (- (gts-google-token--bit-v-to-number (gts-google-token--bit-v-2comp v)))
-    (funcall (if (> (aref v 0) 0)  #'- #'+)
-             (cl-reduce (lambda (acc e) (+ (* acc 2.0) e))
-                        v :initial-value 0.0))))
-
-(defun gts-google-token--logfn (fn n1 n2)
-  "Helper function for logical FN with N1 and N2."
-  (let ((v1 (gts-google-token--number-to-bit-v n1))
-        (v2 (gts-google-token--number-to-bit-v n2))
-        (v (make-vector gts-google-token--bit-v-len 0)))
-    (cl-loop for i from 0 below gts-google-token--bit-v-len do
-             (aset v i (funcall fn (aref v1 i) (aref v2 i))))
-    (gts-google-token--bit-v-to-number v)))
-
-(defun gts-google-token--logand (n1 n2)
-  "Return a floating-point number from N1 and N2."
-  (gts-google-token--logfn #'logand n1 n2))
-
-(defun gts-google-token--logxor (n1 n2)
-  "Return a floating-point number from N1 and N2."
-  (gts-google-token--logfn #'logxor n1 n2))
-
-(defun gts-google-token--lsh (n d)
-  "Return a floating-point number.
-Shift the bits in N to the left or rihgt D places.
-D is an integer."
-  (let ((v (gts-google-token--number-to-bit-v n))
-        (v-result (make-vector gts-google-token--bit-v-len 0)))
-    (if (< d 0)
-        ;; Shift Right Logical
-        (cl-loop for i from (abs d) below gts-google-token--bit-v-len
-                 for j from 0 do
-                 (aset v-result i (aref v j)))
-      ;; Shift Left Logical
-      (cl-loop for i from d below gts-google-token--bit-v-len
-               for j from 0 do
-               (aset v-result j (aref v i))))
-    (gts-google-token--bit-v-to-number v-result)))
-
-(defun gts-google-token--gen-rl (a b)
-  "Gen rl from A and B."
-  (cl-loop for c from 0 below (- (length b) 2) by 3
-           for d = (aref b (+ c 2)) do
-           (setq d (if (>= d ?a) (- d 87) (- d ?0)))
-           (setq d (if (= (aref b (1+ c)) ?+)
-                       (gts-google-token--lsh a (- d))
-                     (gts-google-token--lsh a d)))
-           (setq a (if (= (aref b c) ?+)
-                       (gts-google-token--logand (+ a d) 4294967295.0)
-                     (gts-google-token--logxor a d))))
-  a)
-
-(defun gts-google-tkk (token text)
-  "Calculate the Token for search TEXT.
-
-It will use the tkk from Google translate page."
-  (let* ((b (car token))
-         (d1 (cdr token))
-         (ub "+-3^+b+-f")
-         (vb "+-a^+6")
-         (a (cl-loop for c across (encode-coding-string text 'utf-8)
-                     for rr = (gts-google-token--gen-rl (+ (or rr b) c) vb)
-                     finally (return rr))))
-    (setq a (gts-google-token--gen-rl a ub))
-    (setq a (gts-google-token--logxor a d1))
-    (if (< a 0) (setq a (+ (gts-google-token--logand a 2147483647.0) 2147483648.0)))
-    (setq a (ffloor (mod a 1e6)))
-    (format "%s.%s"
-            (car (split-string (number-to-string a) "\\."))
-            (car (split-string (number-to-string
-                                (gts-google-token--logxor a b))
-                               "\\.")))))
-
-
-(provide 'gts-engine-google)
-
-;;; gts-engine-google.el ends here
diff --git a/elpa/go-translate-20221211.1327/gts-engine-stardict.el b/elpa/go-translate-20221211.1327/gts-engine-stardict.el
deleted file mode 100644 (file)
index 772a4b2..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-;;; gts-engine-stardict.el --- StarDict -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; Engine for sdcv (StarDict Console Version).
-;;
-;; sdcv is an offline translate software. It can be used when no network.
-;;
-;; Make sure sdcv is installed on your system:
-;;
-;;   sudo pacman -S sdcv
-;;
-;; Then download dictionary data to ~/.stardict/dic or /usr/share/stardict/dic:
-;;
-;;   # For example from http://download.huzheng.org:
-;;
-;;   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
-;;
-;; TODO: Provide more options and a better parser.
-
-;;; Code:
-
-(require 'gts-implements)
-
-(defclass gts-stardict-engine (gts-engine)
-  ((tag      :initform "StarDict")
-   (parser   :initform (gts-parser))))
-
-(defcustom gts-stardict-program "sdcv"
-  "Executable command of sdcv (StarDict Console Version)."
-  :type 'string
-  :group 'go-translate)
-
-(cl-defmethod gts-translate ((engine gts-stardict-engine) task rendercb)
-  (let* ((text (oref task text))
-         (parser (oref engine parser))
-         (result (with-temp-buffer
-                   (require 'ansi-color)
-                   (apply #'call-process gts-stardict-program nil t nil
-                          (list "--non-interactive" "--color" text)) ;--json-output
-                   (if (string-equal (buffer-substring 1 16) "Nothing similar")
-                       "No translation result found, sorry :("
-                     (ansi-color-apply (buffer-string))))))
-    (gts-update-raw task result)
-    (gts-parse parser task)
-    (funcall rendercb)))
-
-
-(provide 'gts-engine-stardict)
-
-;;; gts-engine-stardict.el ends here
diff --git a/elpa/go-translate-20221211.1327/gts-engine-youdao.el b/elpa/go-translate-20221211.1327/gts-engine-youdao.el
deleted file mode 100644 (file)
index 594d345..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-;;; gts-engine-youdao.el --- YouDao Dict -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; 有道词典 (dict.youdao.com), A chinese translation website.
-;;
-;; This engine is only for chinese user.
-;; > 此引擎仅供中文翻译使用
-;;
-;; Simply implement using EWW to do the parsing job for now. I am too lazy to parse and extract the html results.
-;; > 懒得去解析、组装 html,所以暂时直接借用 eww 的解析结果
-;; > 权作应急使用,*也许* 以后会增加更完善的 parser 方案
-
-;;; Code:
-
-(require 'gts-implements)
-
-(defclass gts-youdao-dict-eww-parser (gts-parser)
-  ((tag     :initform "EWW")))
-
-(defclass gts-youdao-dict-engine (gts-engine)
-  ((tag     :initform "有道词典")
-   (url     :initform "https://dict.youdao.com/result?word=%s&lang=%s")
-   (parser  :initform (gts-youdao-dict-eww-parser))))
-
-(cl-defmethod gts-translate ((engine gts-youdao-dict-engine) task rendercb)
-  (with-slots (text from to meta) task
-    (with-slots (url parser) engine
-      (let* ((lang (cond ((string-equal from "zh") to)
-                         ((string-equal to "zh") from)
-                         (t (user-error "只支持中文跟其他语言之间的翻译"))))
-             (url (format url (url-hexify-string text) (url-hexify-string lang))))
-        (setf meta (list :url url))
-        (gts-do-request url
-                        :done (lambda ()
-                                (gts-update-raw task (buffer-string)) ; html format response
-                                (gts-parse parser task)
-                                (funcall rendercb))
-                        :fail (lambda (err)
-                                (gts-render-fail task err)))))))
-
-(cl-defmethod gts-parse ((_ gts-youdao-dict-eww-parser) task)
-  (with-slots (raw meta) task
-    (with-temp-buffer
-      (let ((buf (current-buffer)))
-        (with-temp-buffer
-          (require 'eww)
-          (insert "Content-type: text/html; charset=utf-8\n\n" raw)
-          (eww-render nil (plist-get meta :url) nil buf))
-        (gts-update-parsed task (buffer-string) meta)))))
-
-
-(provide 'gts-engine-youdao)
-
-;;; gts-engine-youdao.el ends here
diff --git a/elpa/go-translate-20221211.1327/gts-faces.el b/elpa/go-translate-20221211.1327/gts-faces.el
deleted file mode 100644 (file)
index 107b4ed..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-;;; gts-faces.el --- Face definitions -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; The faces and colors.
-
-;;; Code:
-
-(require 'gts-core)
-
-(defgroup go-translate-faces nil
-  "The faces for this framework."
-  :group 'go-translate)
-
-\f
-;;; Common
-
-(defface gts-logger-buffer-tag-face '((t :inherit font-lock-comment-face))
-  "Used in the logger buffer tag keyword."
-  :group 'go-translate-faces)
-
-(defface gts-logger-buffer-timestamp-face '((t :foreground "grey"))
-  "Used in the logger buffer timestamp keyword."
-  :group 'go-translate-faces)
-
-(defface gts-render-buffer-header-line-lang-face '((t :inherit font-lock-keyword-face))
-  "Used in the buffer's header-line-format, from/to."
-  :group 'go-translate-faces)
-
-(defface gts-render-buffer-header-line-desc-face '((t :inherit font-lock-variable-name-face))
-  "Used in the buffer's header-line-format, description."
-  :group 'go-translate-faces)
-
-(defface gts-render-buffer-error-face '((t :inherit font-lock-warning-face))
-  "Used in the buffer's error text."
-  :group 'go-translate-faces)
-
-(defface gts-render-buffer-source-face '((t :inherit font-lock-string-face))
-  "Used in the buffer's source."
-  :group 'go-translate-faces)
-
-(defface gts-render-buffer-me-header-backgroud-face '((t :background "slategray" :foreground "white" :extend t))
-  "Used in the buffer's multiple engine separator."
-  :group 'go-translate-faces)
-
-\f
-;;; Google Buffer Render
-
-(defface gts-google-buffer-headline-face '((t :inherit font-lock-function-name-face :weight bold))
-  "Propertize the headline in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-phonetic-face '((t :inherit font-lock-string-face :slant normal))
-  "Propertize the phonetic in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-suggestion-desc-face '((t :inherit font-lock-warning-face))
-  "Propertize the suggestion description in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-suggestion-text-face '((t :slant italic :underline t))
-  "Propertize the phonetic text in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-brief-result-face '((t :weight bold))
-  "Propertize the brief result in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-source-face '((t :inherit font-lock-string-face))
-  "Propertize the source in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-detail-demo-face '((t :inherit font-lock-doc-face))
-  "Propertize the detail demo in buffer rendering."
-  :group 'go-translate-faces)
-
-\f
-;;; Posframe Render
-
-(defcustom gts-pop-posframe-forecolor "white"
-  "Default foreground color of pop posframe."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defcustom gts-pop-posframe-backcolor "black"
-  "Default background color of pop posframe."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defcustom gts-pin-posframe-forecolor nil
-  "Default foreground color of pin posframe."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defcustom gts-pin-posframe-backcolor nil
-  "Default background color of pin posframe."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defcustom gts-pin-posframe-bdcolor "#000000"
-  "Default border color of pin posframe."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defcustom gts-pin-posframe-fringe-color nil
-  "Used in the posframe pip buffer fringe color."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defface gts-pop-posframe-me-header-face '((t :foreground "grey"))
-  "Used in the posframe pop buffer engine header."
-  :group 'go-translate-faces)
-
-(defface gts-pop-posframe-me-header-2-face '((t (:foreground "#666666" :underline t :extend t)))
-  "Used in the posframe pop buffer engine header."
-  :group 'go-translate-faces)
-
-
-(provide 'gts-faces)
-
-;;; gts-faces.el ends here
diff --git a/elpa/go-translate-20221211.1327/gts-implements.el b/elpa/go-translate-20221211.1327/gts-implements.el
deleted file mode 100644 (file)
index fe60a22..0000000
+++ /dev/null
@@ -1,647 +0,0 @@
-;;; gts-implements.el --- Component implements -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; Builtin implements of basic components
-
-;;; Code:
-
-(require 'cl-lib)
-(require 'url)
-(require 'json)
-(require 'gts-core)
-(require 'gts-faces)
-
-\f
-;;; [Logger] Log all to a standalone buffer
-
-(defclass gts-buffer-logger (gts-logger)
-  ((buffer :initarg buf :initform nil))) ; dont set :initarg to buffer, it will make an error on emacsv27.
-
-(defvar gts-logger-buffer-name " *gts-logger*")
-
-(cl-defmethod gts-log ((logger gts-buffer-logger) tag msg)
-  (with-current-buffer (get-buffer-create (or (oref logger buffer) gts-logger-buffer-name))
-    (goto-char (point-max))
-    (if (or (= (length msg) 0) (string= msg "\n"))
-        (insert "")
-      (insert
-       (propertize (cl-subseq (format "%-.1f" (time-to-seconds)) 6) 'face 'gts-logger-buffer-timestamp-face)
-       " "
-       (propertize (concat "[" tag "] ") 'face 'gts-logger-buffer-tag-face))
-      (insert msg))
-    (insert "\n")))
-
-(setq gts-default-logger (gts-buffer-logger))
-
-\f
-;;; [Cacher] Cache in the memory
-
-(defclass gts-memory-cacher (gts-cacher) ()
-  "Cache in the memory.")
-
-(cl-defmethod gts-cache-get ((cacher gts-memory-cacher) task)
-  (when gts-cache-enable
-    (with-slots (caches expired) cacher
-      (let ((key (gts-cache-key cacher task)))
-        (when-let ((cache (assoc key caches)))
-          (when (> (cddr cache) (time-to-seconds))
-            (gts-do-log 'memory-cacher (format "%s: get from cache %s (%s)" (oref task id) key (length caches)))
-            (cadr cache)))))))
-
-(cl-defmethod gts-cache-set ((cacher gts-memory-cacher) task result)
-  (when gts-cache-enable
-    (with-slots (caches expired) cacher
-      (let* ((key (gts-cache-key cacher task))
-             (cache (assoc key caches))
-             (text (oref task text))
-             (etime
-              ;; sentence with shorter expired time, but word with longer.
-              (if (string-match-p "[[:space:]\n]" text)
-                  (+ (time-to-seconds) gts-cache-expired-factor)
-                (+ (time-to-seconds) (* 100 gts-cache-expired-factor)))))
-        (if cache
-            (progn
-              (setf (cadr cache) result)
-              (setf (cddr cache) etime)
-              (gts-do-log 'memory-cacher (format "%s: update cache %s (%s)" (oref task id) key (length caches))))
-          (oset cacher caches (cons (cons key (cons result etime)) caches))
-          (gts-do-log 'memory-cacher (format "%s: add to cache %s (%s)" (oref task id) key (length caches))))
-        (gts-clear-expired cacher)))))
-
-(setq gts-default-cacher (gts-memory-cacher))
-
-\f
-;;; [Http Client] implement with builtin `url.el'.
-;; This will be used as the default http client.
-
-(defclass gts-url-http-client (gts-http-client) ())
-
-(defcustom gts-user-agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36"
-  "The user agent used when send a request."
-  :type 'string
-  :group 'go-translate)
-
-(defvar url-http-end-of-headers)
-
-(cl-defmethod gts-request ((_ gts-url-http-client) url &key done fail data headers)
-  "Request URL with DATA and HEADERS asynchronous.
-Execute DONE when success, or FAIL when failed."
-  (cl-assert (and url done fail))
-  (let ((url-debug gts-debug-p)
-        (url-user-agent gts-user-agent)
-        (url-request-extra-headers headers)
-        (url-request-method (if data "POST" "GET"))
-        (url-request-data data))
-    (url-retrieve url (lambda (status)
-                        (unwind-protect
-                            (if-let ((err (cond
-                                           ((eq (car status) :error) (cadr status))
-                                           ((null url-http-end-of-headers) "Nothing responsed from server"))))
-                                (funcall fail err)
-                              (when done
-                                (delete-region (point-min) url-http-end-of-headers)
-                                (funcall done)))
-                          (kill-buffer)))
-                  nil t)))
-
-(setq gts-default-http-client (gts-url-http-client)) ; use this as default
-
-\f
-;;; [Render] buffer render
-
-(defclass gts-buffer-render (gts-render) ())
-
-(defcustom gts-split-width-threshold 80
-  "Threshold width for window horizontal split."
-  :group 'go-translate
-  :type '(choice
-          (const :tag "Disable" nil)
-          (integer :tag "Threshold")))
-
-(defcustom gts-buffer-follow-p nil
-  "If t then pop to the result window after translation."
-  :type 'boolean
-  :group 'go-translate)
-
-(defcustom gts-buffer-name "*Go-Translate*"
-  "The name of translation result buffer."
-  :type 'string
-  :group 'go-translate)
-
-(defcustom gts-buffer-window-config nil
-  "Window configuration used by the result buffer window.
-
-For example, set to:
-
- \\='((display-buffer-reuse-window display-buffer-in-side-window)
-   (side . right))
-
-will force opening in right side window."
-  :type 'list
-  :group 'go-translate)
-
-(defcustom gts-buffer-render-task-header-function #'gts-buffer-render-task-header
-  "Function used to render the task header when in multiple engines's query."
-  :type 'function
-  :group 'go-translate)
-
-(defvar-local gts-buffer-translator nil)
-(defvar-local gts-buffer-keybinding-messages nil)
-(defvar-local gts-buffer-local-map nil)
-
-(cl-defmacro gts-buffer-set-key ((key &optional desc) form)
-  "Helper for define KEY in buffer."
-  (declare (indent 1))
-  `(progn (define-key gts-buffer-local-map
-                      (kbd ,key)
-                      ,(if (equal 'function (car form)) `,form `(lambda () (interactive) ,form)))
-          (when ,desc
-            (cl-delete ,key gts-buffer-keybinding-messages :key #'car :test #'string=)
-            (push (cons ,key ,desc) gts-buffer-keybinding-messages))))
-
-(defun gts-buffer-init-header-line (path &optional desc)
-  "Setup header line format.
-It will show the basic information of the translation,
-including PATH and other DESC."
-  (setq header-line-format
-        (list
-         " "
-         "[" (propertize (car path) 'face 'gts-render-buffer-header-line-lang-face) "]"
-         (if desc (concat " ― " (propertize desc 'face 'gts-render-buffer-header-line-desc-face)) "")
-         " → "
-         "[" (propertize (cdr path) 'face 'gts-render-buffer-header-line-lang-face) "]"
-         "   (" (propertize "h" 'face 'font-lock-type-face) " for help)"
-         "          "
-         "Loading...")))
-
-(defun gts-buffer-change-header-line-state (type)
-  "Update header line according TYPE."
-  (cl-case type
-    (done
-     (when (string-equal (car (last header-line-format)) "Loading...")
-       (setq header-line-format (butlast header-line-format))))))
-
-(defun gts-buffer-display-or-focus-buffer (buffer)
-  "Only display the BUFFER, or display and focus."
-  (with-current-buffer buffer
-    (if gts-buffer-follow-p (pop-to-buffer buffer)
-      (display-buffer buffer))))
-
-(defun gts-buffer-ensure-a-blank-line-at-beginning (buffer)
-  "Ensure a blank line at beginning of BUFFER."
-  (with-current-buffer buffer
-    (let ((inhibit-read-only t))
-      (save-excursion
-        (let ((m (mark-marker)))
-          (goto-char (point-min))
-          (unless (equal (char-after) ?\n)
-            (insert "\n"))
-          (when (ignore-errors (= (marker-position m) 1))
-            (set-marker m (point))))
-        (set-buffer-modified-p nil)))))
-
-(defun gts-childframe-of-buffer (&optional buffer)
-  "Get the childframe of BUFFER if it exists, or return nil."
-  (let ((cf (window-frame (get-buffer-window buffer t))))
-    (when (frame-parameter cf 'parent-frame) cf)))
-
-(defun gts-buffer-render-task-header (engine-tag parser-tag)
-  (concat (propertize
-           (format "%s %s\n" engine-tag (if parser-tag (format "(%s) " parser-tag) ""))
-           'face 'gts-render-buffer-me-header-backgroud-face)))
-
-(defun gts-render-buffer-prepare (buffer translator)
-  (with-current-buffer (get-buffer-create buffer)
-    (let ((inhibit-read-only t)
-          (engines (gts-get translator 'engines)))
-      (with-slots (text path) translator
-        ;; setup
-        (deactivate-mark)
-        (visual-line-mode -1)
-        (font-lock-mode 1)
-        (setq-local cursor-type 'hbar)
-        (setq-local cursor-in-non-selected-windows nil)
-        (setq-local gts-buffer-translator translator)
-        ;; headline
-        (gts-buffer-init-header-line path (unless (cdr engines) (oref (car engines) tag)))
-        ;; source text
-        (erase-buffer)
-        (unless (gts-childframe-of-buffer (current-buffer)) (insert "\n")) ; except childframe
-        (insert text)
-        ;; keybinds
-        (setq gts-buffer-local-map (make-sparse-keymap))
-        (gts-buffer-set-key ("x" "Reverse Path")
-          (gts-translate translator text (cons (cdr path) (car path))))
-        (gts-buffer-set-key ("M-p" "Prev Path")
-          (gts-translate translator text (gts-next-path (gts-get translator 'picker) text path t)))
-        (gts-buffer-set-key ("M-n" "Next Path")
-          (gts-translate translator text (gts-next-path (gts-get translator 'picker) text path)))
-        (gts-buffer-set-key ("g" "Refresh")
-          (gts-translate translator text path))
-        (gts-buffer-set-key ("y" "TTS")
-          (if-let ((task (get-text-property (point) 'task)))
-              (gts-do-tts text (car path) (oref task engine))
-            (message "[TTS] No engine found at point")))
-        (gts-buffer-set-key ("O" "Open Externally")
-          (when-let ((task (get-text-property (point) 'task)))
-            (let* ((meta (oref task meta)) (url (plist-get meta :url)))
-              (cond (url (browse-url url) (message "Opening %s... Done!" url))
-                    (t (message "Don't know how to open externally for this."))))))
-        (gts-buffer-set-key ("C" "Clean Cache")
-          (gts-clear-all gts-default-cacher))
-        (gts-buffer-set-key ("t" "Toggle-Follow")
-          (let ((state (if (setq gts-buffer-follow-p (not gts-buffer-follow-p)) "allowed" "disabled")))
-            (message "Now, buffer following %s." state)))
-        (gts-buffer-set-key ("q" "Quit") #'kill-buffer-and-window)
-        (gts-buffer-set-key ("p") #'previous-line)
-        (gts-buffer-set-key ("n") #'next-line)
-        (gts-buffer-set-key ("C-g")
-          (unwind-protect (gts-tts-try-interrupt-playing-process)
-            (keyboard-quit)))
-        (gts-buffer-set-key ("h")
-          (message (mapconcat
-                    (lambda (kd) (concat (propertize (car kd) 'face 'font-lock-keyword-face) ": " (cdr kd)))
-                    (reverse gts-buffer-keybinding-messages) " ")))
-        (gts-buffer-set-key ("C-x C-q")
-          (progn (read-only-mode -1)
-                 (use-local-map nil)
-                 (local-set-key (kbd "C-x C-q")
-                                (lambda ()
-                                  (interactive)
-                                  (read-only-mode 1)
-                                  (use-local-map gts-buffer-local-map)))))
-        (read-only-mode 1)
-        (use-local-map gts-buffer-local-map)
-        ;; execute the hook if exists
-        (run-hooks 'gts-after-buffer-prepared-hook)))))
-
-(defun gts-render-buffer (buffer task)
-  "For single engine translation."
-  (when-let ((inhibit-read-only t)
-             (buf (get-buffer buffer)))
-    (with-current-buffer buf
-      (with-slots (err parsed meta engine translator) task
-        ;; error
-        (if err
-            (progn
-              (goto-char (point-max))
-              (insert (propertize (format "\n\n%s" err) 'face 'gts-render-buffer-error-face)))
-          (erase-buffer)
-          (cond
-           ;; content (non-split)
-           ((stringp parsed)
-            (let ((tbeg (plist-get meta :tbeg))
-                  (tend (plist-get meta :tend)))
-              (unless tbeg
-                (insert (propertize (oref translator text) 'face 'gts-render-buffer-source-face) "\n\n"))
-              (unless tbeg (setq tbeg (point)))
-              (insert parsed)
-              (unless tend (setq tend (point)))
-              ;; try set mark in beginning of the translate result,
-              ;; and set currsor position in end of the translate result,
-              ;; so you can quick select the translate result with C-x C-x.
-              (when (not (gts-childframe-of-buffer buf)) ; when not childframe
-                (push-mark tend 'nomsg)
-                (goto-char tbeg)
-                (set-window-point (get-buffer-window buf t) tbeg))))
-           ;; content (split)
-           (t (cl-loop for idx from 0
-                       for src in (gts-get translator 'sptext)
-                       for tar in parsed
-                       concat (concat (propertize src 'face 'gts-render-buffer-source-face) "\n\n" tar "\n\n") into res
-                       finally (insert res)))))
-        ;; update states
-        (set-buffer-modified-p nil)
-        (gts-buffer-change-header-line-state 'done)
-        (put-text-property (point-min) (point-max) 'task task)
-        ;; execute the hook if exists
-        (run-hooks 'gts-after-buffer-render-hook))
-      buf)))
-
-(defun gts-render-buffer-multi-engines (buffer task)
-  "For multiple engines translation."
-  (when-let ((inhibit-read-only t)
-             (buf (get-buffer buffer)))
-    (with-slots (from to translator) task
-      (with-slots (text task-queue version) translator
-        (with-current-buffer buf
-          (when (equal (oref gts-buffer-translator version) version)
-            (erase-buffer)
-            (insert (propertize (format "\n%s\n\n" text) 'face 'gts-render-buffer-source-face))
-            ;; content
-            (save-excursion
-              (dolist (task task-queue)
-                (with-slots (err parsed meta engine) task
-                  (let* ((result (if (or err (stringp parsed)) parsed
-                                   (string-join parsed "\n\n")))
-                         (header (funcall gts-buffer-render-task-header-function
-                                          (oref engine tag)
-                                          (oref (oref engine parser) tag)))
-                         (content (cond
-                                   (err
-                                    (concat header (propertize (format "\n%s\n\n" err) 'face 'gts-render-buffer-error-face)))
-                                   ((string-empty-p result)
-                                    (concat header "\nLoading...\n\n"))
-                                   (t (concat header "\n" (string-trim result) "\n\n")))))
-                    (put-text-property 0 (length content) 'task task content)
-                    (insert content)))))
-            ;; states
-            (set-buffer-modified-p nil)
-            ;; all tasks finished
-            (when (= (oref translator state) 3)
-              ;; cursor
-              (unless (gts-childframe-of-buffer buf)
-                (set-window-point (get-buffer-window buf t) (save-excursion (forward-line 2) (point))))
-              ;; state
-              (gts-buffer-change-header-line-state 'done))
-            ;; execute the hook if exists
-            (run-hooks 'gts-after-buffer-multiple-render-hook)))
-        buf))))
-
-(cl-defmethod gts-pre ((_ gts-buffer-render) translator)
-  ;; init and setup
-  (gts-render-buffer-prepare gts-buffer-name translator)
-  ;; display
-  (let ((split-width-threshold (or gts-split-width-threshold split-width-threshold)))
-    (display-buffer gts-buffer-name gts-buffer-window-config)))
-
-(cl-defmethod gts-out ((_ gts-buffer-render) task)
-  ;; render & display
-  (when-let ((buf (gts-render-buffer gts-buffer-name task)))
-    (gts-buffer-ensure-a-blank-line-at-beginning buf)
-    (gts-buffer-display-or-focus-buffer buf)))
-
-(cl-defmethod gts-me-out ((_ gts-buffer-render) task)
-  ;; render & display
-  (when-let ((buf (gts-render-buffer-multi-engines gts-buffer-name task)))
-    (when (= (oref (oref task translator) state) 3)
-      (gts-buffer-display-or-focus-buffer buf))))
-
-\f
-;;; [Render] Child-Frame Render (Popup Mode)
-;; implements via package Posframe, you should install it before use this
-
-(defclass gts-posframe-pop-render (gts-render)
-  ((width       :initarg :width        :initform 100)
-   (height      :initarg :height       :initform 15)
-   (forecolor   :initarg :forecolor    :initform nil)
-   (backcolor   :initarg :backcolor    :initform nil)
-   (padding     :initarg :padding      :initform 12))
-  "Pop up a childframe to show the result.
-The frame will disappear when do do anything but focus in it.
-Manually close the frame with `q'.")
-
-(require 'posframe nil t)
-(declare-function posframe-show "ext:posframe.el" t t)
-(declare-function posframe-delete "ext:posframe.el" t t)
-(declare-function posframe-hide "ext:posframe.el" t t)
-(declare-function posframe-refresh "ext:posframe.el" t t)
-(declare-function posframe-poshandler-frame-top-right-corner "ext:posframe.el" t t)
-
-(defcustom gts-posframe-pop-render-buffer " *GTS-Pop-Posframe*"
-  "Buffer name of Pop Posframe."
-  :type 'string
-  :group 'go-translate)
-
-(defvar gts-posframe-pop-render-timeout 30)
-(defvar gts-posframe-pop-render-poshandler nil)
-
-(defun gts-posframe-init-header-line (from to)
-  (setq header-line-format
-        (list "[" (propertize from 'face 'gts-render-buffer-header-line-lang-face) "]"
-              " → "
-              "[" (propertize to 'face 'gts-render-buffer-header-line-lang-face) "]")))
-
-(defun gts-posframe-render-auto-close-handler ()
-  "Close the pop-up posframe window."
-  (interactive)
-  (unless (or (and gts-current-command
-                   (member this-command (list gts-current-command #'exit-minibuffer)))
-              (and gts-posframe-pop-render-buffer
-                   (string= (buffer-name) gts-posframe-pop-render-buffer)))
-    (ignore-errors (posframe-delete gts-posframe-pop-render-buffer))
-    (remove-hook 'post-command-hook #'gts-posframe-render-auto-close-handler)))
-
-(cl-defmethod gts-pre ((render gts-posframe-pop-render) translator)
-  (with-slots (width height forecolor backcolor padding) render
-    (let ((inhibit-read-only t)
-          (buf gts-posframe-pop-render-buffer))
-      (posframe-show buf
-                     :string "Loading..."
-                     :timeout gts-posframe-pop-render-timeout
-                     :max-width width
-                     :max-height height
-                     :foreground-color (or forecolor gts-pop-posframe-forecolor)
-                     :background-color (or backcolor gts-pop-posframe-backcolor)
-                     :internal-border-width padding
-                     :internal-border-color (or backcolor gts-pop-posframe-backcolor)
-                     :accept-focus t
-                     :position (point)
-                     :poshandler gts-posframe-pop-render-poshandler)
-      ;; render
-      (gts-render-buffer-prepare buf translator)
-      (posframe-refresh buf)
-      ;; setup
-      (with-current-buffer buf
-        (gts-buffer-set-key ("q" "Close") (posframe-delete buf))))))
-
-(cl-defmethod gts-out ((_ gts-posframe-pop-render) task)
-  ;; render & refresh
-  (when-let ((buf (gts-render-buffer gts-posframe-pop-render-buffer task)))
-    (posframe-refresh buf)
-    (add-hook 'post-command-hook #'gts-posframe-render-auto-close-handler)))
-
-(cl-defmethod initialize-instance :after ((_ gts-posframe-pop-render) &rest _)
-  (unless (featurep 'posframe)
-    (user-error "To use `gts-posframe-render', you should install and load package `posframe' first")))
-
-\f
-;;; [Render] Child-Frame Render (Pin Mode)
-
-(defclass gts-posframe-pin-render (gts-posframe-pop-render)
-  ((width       :initarg :width        :initform 60)
-   (height      :initarg :height       :initform 20)
-   (padding     :initarg :padding      :initform 8)
-   (bd-width    :initarg :bd-width     :initform 1)
-   (bd-color    :initarg :bd-color     :initform nil)
-   (backcolor   :initarg :backcolor    :initform nil)
-   (fri-color   :initarg :fringe-color :initform nil)
-   (position    :initarg :position     :initform nil))
-  "Pin the childframe in a fixed position to display the translate result.
-The childframe will not close, until you kill it with `q'.
-Other operations in the childframe buffer, just like in 'gts-buffer-render'.")
-
-(defcustom gts-posframe-pin-render-buffer " *GTS-Pin-Posframe*"
-  "Buffer name of Pin Posframe."
-  :type 'string
-  :group 'go-translate)
-
-(defvar gts-posframe-pin-render-frame nil)
-(defvar gts-posframe-pin-render-poshandler #'posframe-poshandler-frame-top-right-corner)
-
-(cl-defmethod gts-pre ((render gts-posframe-pin-render) translator)
-  ;; create/show frame
-  (if (and (get-buffer gts-posframe-pin-render-buffer) gts-posframe-pin-render-frame)
-      (make-frame-visible gts-posframe-pin-render-frame)
-    (with-slots (width height min-width min-height bd-width forecolor backcolor bd-color padding position) render
-      (setq gts-posframe-pin-render-frame
-            (let ((inhibit-read-only t))
-              (posframe-show gts-posframe-pin-render-buffer
-                             :string "\nLoading..."
-                             :width width
-                             :height height
-                             :min-width width
-                             :min-height height
-                             :foreground-color (or forecolor gts-pin-posframe-forecolor)
-                             :background-color (or backcolor gts-pin-posframe-backcolor)
-                             :internal-border-width bd-width
-                             :border-color (or bd-color gts-pin-posframe-bdcolor)
-                             :left-fringe padding
-                             :right-fringe padding
-                             :refresh nil
-                             :accept-focus t
-                             :respect-header-line t
-                             :position position
-                             :poshandler (unless position gts-posframe-pin-render-poshandler)))))
-    (set-frame-parameter gts-posframe-pin-render-frame 'drag-internal-border t)
-    (set-frame-parameter gts-posframe-pin-render-frame 'drag-with-header-line t)
-    (when-let ((color (or (oref render fri-color) gts-pin-posframe-fringe-color)))
-      (set-face-background 'fringe color  gts-posframe-pin-render-frame)))
-  ;; render
-  (gts-render-buffer-prepare gts-posframe-pin-render-buffer translator)
-  (gts-buffer-ensure-a-blank-line-at-beginning gts-posframe-pin-render-buffer)
-  ;; setup
-  (with-current-buffer gts-posframe-pin-render-buffer
-    (gts-buffer-set-key ("q" "Hide") (posframe-hide gts-posframe-pin-render-buffer))
-    (gts-buffer-set-key ("Q" "Close") (posframe-delete gts-posframe-pin-render-buffer))))
-
-(cl-defmethod gts-out ((_ gts-posframe-pin-render) task)
-  ;; render & refresh
-  (when-let ((buf (gts-render-buffer gts-posframe-pin-render-buffer task)))
-    (gts-buffer-ensure-a-blank-line-at-beginning buf)))
-
-(cl-defmethod gts-me-out ((_ gts-posframe-pin-render) task)
-  ;; render & refresh
-  (gts-render-buffer-multi-engines gts-posframe-pin-render-buffer task))
-
-\f
-;;; [Render] kill-ring render
-
-(defclass gts-kill-ring-render (gts-render) ())
-
-(cl-defmethod gts-out ((_ gts-kill-ring-render) task)
-  (deactivate-mark)
-  (with-slots (err parsed) task
-    (if err
-        (user-error "%s" err)
-      (kill-new (if (listp parsed) (string-join parsed "\n\n") parsed))
-      (message "Translate result already in the kill ring."))))
-
-\f
-;;; [Texter] take current-at-point-or-selection as source text
-
-(defclass gts-current-or-selection-texter (gts-texter) ())
-
-(cl-defmethod gts-text ((_ gts-current-or-selection-texter))
-  (cond ((eq major-mode 'pdf-view-mode)
-         (if (pdf-view-active-region-p)
-             (car (pdf-view-active-region-text))))
-        ((use-region-p)
-         (string-trim (buffer-substring-no-properties (region-beginning) (region-end))))
-        (t (current-word t t))))
-
-;; silence!
-(declare-function pdf-view-active-region-p "ext:pdf-view.el" t t)
-(declare-function pdf-view-active-region-text "ext:pdf-view.el" t t)
-
-\f
-;;; [Texter] take whole-buffer for as source text
-
-(defclass gts-whole-buffer-texter (gts-texter) ())
-
-(cl-defmethod gts-text ((_ gts-whole-buffer-texter))
-  (buffer-substring-no-properties (point-min) (point-max)))
-
-\f
-;;; [Picker] noprompt picker
-
-(defclass gts-noprompt-picker (gts-picker)
-  ((texter :initarg :texter :initform (gts-current-or-selection-texter))))
-
-(defvar gts-picker-current-picker nil)
-(defvar gts-picker-current-text nil)
-(defvar gts-picker-current-path nil)
-
-(cl-defmethod gts-pick ((picker gts-noprompt-picker))
-  (let ((text (gts-text (oref picker texter))))
-    (when (= 0 (length (if text (string-trim text) "")))
-      (user-error "Make sure there is any word at point, or selection exists"))
-    (let ((path (gts-path picker text)))
-      (setq gts-picker-current-path path)
-      (cl-values text path))))
-
-\f
-;;; [Picker] prompt, and flexible
-
-(defclass gts-prompt-picker (gts-picker)
-  ((texter :initarg :texter :initform (gts-current-or-selection-texter))))
-
-(defvar gts-prompt-for-translate-keymap
-  (let ((map (make-sparse-keymap)))
-    (set-keymap-parent map minibuffer-local-map)
-    (define-key map "\C-g" #'top-level)
-    (define-key map "\C-n" #'gts-prompt-picker-next-path)
-    (define-key map "\C-p" (lambda () (interactive) (gts-prompt-picker-next-path t)))
-    (define-key map "\C-l" #'delete-minibuffer-contents)
-    (define-key map [C-return] (lambda () (interactive) (exit-minibuffer)))
-    map)
-  "Minibuffer keymap used when prompt user input.")
-
-(defun gts-prompt-picker-next-path (&optional backwardp)
-  "Pick next available translate path.
-If BACKWARDP is t, then pick the previous one."
-  (interactive "P")
-  (let ((p (gts-next-path gts-picker-current-picker
-                          gts-picker-current-text
-                          gts-picker-current-path
-                          backwardp)))
-    (setq gts-picker-current-path p)
-    (gts-picker-prompt-pick (minibuffer-contents) p)
-    (exit-minibuffer)))
-
-(defun gts-picker-prompt-pick (&optional text path)
-  (setq gts-picker-current-path path)
-  (let* ((enable-recursive-minibuffers t)
-         (minibuffer-allow-text-properties t)
-         (prompt (concat (concat "[" (car path) " > " (cdr path) "] ") "Text: "))
-         (text (read-from-minibuffer prompt text gts-prompt-for-translate-keymap)))
-    (setq gts-picker-current-text text)
-    (when (zerop (length (string-trim text)))
-      (user-error "Text should not be null"))
-    (with-minibuffer-selected-window
-      (exit-minibuffer))))
-
-(cl-defmethod gts-pick ((picker gts-prompt-picker))
-  (setq gts-picker-current-picker picker)
-  (let* ((text (gts-text (oref picker texter)))
-         (path (gts-path picker text)))
-    (gts-picker-prompt-pick text path)
-    (cl-values gts-picker-current-text gts-picker-current-path)))
-
-\f
-;;; [Splitter] split text by paragraph
-
-(defclass gts-paragraph-splitter (gts-splitter) ())
-
-(cl-defmethod gts-split ((_ gts-paragraph-splitter) text)
-  (setq text (split-string text "\n\n" t))
-  (if (and (listp text) (cadr text)) text))
-
-
-(provide 'gts-implements)
-
-;;; gts-implements.el ends here
diff --git a/elpa/go-translate-20230503.303/go-translate-autoloads.el b/elpa/go-translate-20230503.303/go-translate-autoloads.el
deleted file mode 100644 (file)
index 358b41e..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-;;; go-translate-autoloads.el --- automatically extracted autoloads  -*- lexical-binding: t -*-
-;;
-;;; Code:
-
-(add-to-list 'load-path (directory-file-name
-                         (or (file-name-directory #$) (car load-path))))
-
-\f
-;;;### (autoloads nil "go-translate" "go-translate.el" (0 0 0 0))
-;;; Generated autoloads from go-translate.el
-
-(autoload 'gts-do-translate "go-translate" "\
-Do the translate" t nil)
-
-(register-definition-prefixes "go-translate" '("gts-default-translator"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-core" "gts-core.el" (0 0 0 0))
-;;; Generated autoloads from gts-core.el
-
-(register-definition-prefixes "gts-core" '("gts-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-bing" "gts-engine-bing.el" (0 0
-;;;;;;  0 0))
-;;; Generated autoloads from gts-engine-bing.el
-
-(register-definition-prefixes "gts-engine-bing" '("gts-bing-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-deepl" "gts-engine-deepl.el" (0
-;;;;;;  0 0 0))
-;;; Generated autoloads from gts-engine-deepl.el
-
-(register-definition-prefixes "gts-engine-deepl" '("gts-deepl-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-google" "gts-engine-google.el"
-;;;;;;  (0 0 0 0))
-;;; Generated autoloads from gts-engine-google.el
-
-(register-definition-prefixes "gts-engine-google" '("gts-google-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-google-rpc" "gts-engine-google-rpc.el"
-;;;;;;  (0 0 0 0))
-;;; Generated autoloads from gts-engine-google-rpc.el
-
-(register-definition-prefixes "gts-engine-google-rpc" '("gts-google-rpc-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-stardict" "gts-engine-stardict.el"
-;;;;;;  (0 0 0 0))
-;;; Generated autoloads from gts-engine-stardict.el
-
-(register-definition-prefixes "gts-engine-stardict" '("gts-stardict-"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-engine-youdao" "gts-engine-youdao.el"
-;;;;;;  (0 0 0 0))
-;;; Generated autoloads from gts-engine-youdao.el
-
-(register-definition-prefixes "gts-engine-youdao" '("gts-youdao-dict-e"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-faces" "gts-faces.el" (0 0 0 0))
-;;; Generated autoloads from gts-faces.el
-
-(register-definition-prefixes "gts-faces" '("gts-p"))
-
-;;;***
-\f
-;;;### (autoloads nil "gts-implements" "gts-implements.el" (0 0 0
-;;;;;;  0))
-;;; Generated autoloads from gts-implements.el
-
-(register-definition-prefixes "gts-implements" '("gts-"))
-
-;;;***
-\f
-;;;### (autoloads nil nil ("go-translate-pkg.el") (0 0 0 0))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; go-translate-autoloads.el ends here
diff --git a/elpa/go-translate-20230503.303/go-translate-pkg.el b/elpa/go-translate-20230503.303/go-translate-pkg.el
deleted file mode 100644 (file)
index d7d1cec..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-(define-package "go-translate" "20230503.303" "Translation framework supports multiple engines such as Google/Bing/DeepL"
-  '((emacs "27.1"))
-  :commit "59506f69503cca65291fa5ccd0a326e716338c23" :authors
-  '(("lorniu" . "lorniuNOSPAMgmail.com"))
-  :maintainers
-  '(("lorniu" . "lorniuNOSPAMgmail.com"))
-  :maintainer
-  '("lorniu" . "lorniuNOSPAMgmail.com")
-  :keywords
-  '("convenience")
-  :url "https://github.com/lorniu/go-translate")
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
diff --git a/elpa/go-translate-20230503.303/go-translate.el b/elpa/go-translate-20230503.303/go-translate.el
deleted file mode 100644 (file)
index 100ef59..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-;;; go-translate.el --- Translation framework supports multiple engines such as Google/Bing/DeepL -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-
-;; Author: lorniu <lorniuNOSPAMgmail.com>
-;; URL: https://github.com/lorniu/go-translate
-;; Package-Requires: ((emacs "27.1"))
-;; Keywords: convenience
-;; SPDX-License-Identifier: MIT
-;; Version: 2.0
-
-;;; Commentary:
-
-;; To be the most powerful translator on Emacs. Supports multiple translation engines such as Google, Bing, deepL.
-
-;; First, Install it via MELPA or download from github. Make sure this is on your `load-path'.
-
-;; Then, add following lines to your `.emacs':
-
-;;   (require 'go-translate)
-;;   (setq gts-translate-list '(("en" "zh")))
-;;   (setq gts-default-translator
-;;        (gts-translator
-;;         :picker (gts-prompt-picker)
-;;         :engines (list (gts-google-engine) (gts-google-rpc-engine))
-;;         :render (gts-buffer-render)))
-
-;; And start your translate with command `gts-do-translate'.
-
-;;; Code:
-
-(require 'gts-core)
-(require 'gts-implements)
-(require 'gts-engine-bing)
-(require 'gts-engine-google)
-(require 'gts-engine-google-rpc)
-(require 'gts-engine-deepl)
-(require 'gts-engine-stardict)
-(require 'gts-engine-youdao)
-
-\f
-;;; Commands
-
-(defvar gts-default-translator
-  (gts-translator :picker
-                  (gts-prompt-picker)
-                  ;;(gts-noprompt-picker)
-                  ;;(gts-noprompt-picker :texter (gts-whole-buffer-texter))
-
-                  :engines
-                  (list
-                   (gts-bing-engine)
-                   (gts-google-engine :parser (gts-google-summary-parser))
-                   (gts-google-rpc-engine)
-
-                   ;;(gts-google-engine)
-                   ;;(gts-google-rpc-engine)
-                   ;;(gts-deepl-engine :auth-key "2e20bade-88e9-02f3-169f-ab3c445d7984:fx" :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))
-                   )
-
-                  :render
-                  (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 nil
-                  ;; (gts-paragraph-splitter)
-                  ))
-
-;;;###autoload
-(defun gts-do-translate ()
-  "Do the translate"
-  (interactive)
-  (gts-translate gts-default-translator))
-
-
-(provide 'go-translate)
-
-;;; go-translate.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-core.el b/elpa/go-translate-20230503.303/gts-core.el
deleted file mode 100644 (file)
index 998c3df..0000000
+++ /dev/null
@@ -1,673 +0,0 @@
-;;; gts-core.el --- Translate engines and utils -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; The core components and utils of the translator
-
-;;; Code:
-
-(require 'cl-lib)
-(require 'subr-x)
-(require 'eieio)
-
-(defgroup go-translate nil
-  "Translate Framework for Emacs, asynchronous and flexible."
-  :group 'external
-  :prefix 'gts-)
-
-(defvar gts-debug-p nil)
-
-\f
-;;; Utils
-
-(defun gts-aref-for (vector &rest ns)
-  "Recursively find the element in VECTOR. NS is indexes, as the path."
-  (while ns
-    (setq vector (aref vector (pop ns))))
-  vector)
-
-(defun gts-format-params (data)
-  "Format alist DATA to k=v style string."
-  (cond ((null data) nil)
-        ((stringp data) data)
-        (t (mapconcat (lambda (arg)
-                        (format "%s=%s"
-                                (url-hexify-string (format "%s" (car arg)))
-                                (url-hexify-string (format "%s" (cdr arg)))))
-                      data "&"))))
-
-(defun gts-ensure-list (object)
-  "Return OBJECT as a list."
-  (if (listp object) object (list object)))
-
-(defun gts-ensure-plain (o)
-  "Return O or the result of its execution."
-  (if (functionp o) (funcall o) o))
-
-\f
-;;; Components
-
-;; logger/http-client/cacher
-
-(defclass gts-logger () ()
-  "Used to log the messages."
-  :abstract t)
-
-(defclass gts-http-client () ()
-  "Used to send a request."
-  :abstract t)
-
-(defclass gts-cacher ()
-  ((caches  :initform nil)) ; (key . (str . timestamp))
-  "Used to cache the translate results."
-  :abstract t)
-
-;; translator/task
-
-(defclass gts-translator ()
-  ((picker     :initarg :picker   :documentation "`gts-picker' object or a function return it" :initform nil)
-   (engines    :initarg :engines  :documentation "a list of `gts-engine' objects or a function return them" :initform nil)
-   (render     :initarg :render   :documentation "`gts-render' object or a function return it" :initform nil)
-   (splitter   :initarg :splitter :documentation "`gts-splitter' object or a function return it" :initform nil)
-
-   (text       :initarg :text     :documentation "Text to be translated")
-   (sptext     :initarg :sptext   :documentation "List of strings splitted according `gts-splitter'")
-   (path       :initarg :path     :documentation "From/To language cons")
-
-   (plan-cnt   :initform 0        :documentation "count of tasks in a translation")
-   (task-queue :initform nil      :documentation "translation tasks.")
-   (state      :initform 0        :documentation "Inner state of the translator:
-0: new translator without any tasks,
-1: tasks add finished, but not running,
-2: prepare render finished, and begin to running,
-3: all tasks running finished,
-4: rendered done, all over.")
-   (version    :initform nil      :documentation "Used to distinguish between different translations")))
-
-(defclass gts-task ()
-  ((id         :initform (gensym))
-
-   (text       :initarg :text :documentation "Text that sent to engines. It maybe contains delimiters")
-   (from       :initarg :from)
-   (to         :initarg :to)
-
-   (raw        :initform nil :documentation "raw result responsed by the http-client")
-   (parsed     :initform nil :initarg :parsed :documentation "result parsed by parser, string or list")
-   (err        :initform nil :initarg :err    :documentation "error info")
-   (meta       :initform nil :initarg :meta   :documentation "extra info passed from parser to render. tbeg/tend")
-
-   (engine     :initarg :engine     :initform nil)
-   (render     :initarg :render     :initform nil)
-   (translator :initarg :translator :initform nil)))
-
-;; picker/texter
-
-(defclass gts-picker ()
-  ((single   :initarg :single :initform nil)
-   (texter   :initarg :texter))
-  "Used to pick the translation source text and from/to lang."
-  :abstract t)
-
-(defclass gts-texter () ()
-  "Used to get the initial translation text.
-Current word under cursor? Selection region? Whole line? Whole buffer? Others?
-You can implements your rules.")
-
-;; splitter
-
-(defclass gts-splitter () ()
-  "Used to split the translation text by pragraph or other rules."
-  :abstract t)
-
-;; engine/parser
-
-(defclass gts-engine ()
-  ((tag       :initarg :tag    :documentation "Used to display as name")
-   (parser    :initarg :parser :documentation "Used to parse the response result"))
-  :abstract t)
-
-(defclass gts-parser ()
-  ((tag  :initarg :tag :initform nil)))
-
-;; render
-
-(defclass gts-render () ())
-
-;; generic methods
-
-(cl-defgeneric gts-translate (translator-or-engine &rest args)
-  "Start a translation for TRANSLATOR-OR-ENGINE.
-ARGS should be text/path or task/callback.")
-
-;; silence byte-compiler
-(eieio-declare-slots task-queue text from to)
-
-\f
-;;; Logger
-
-(cl-defgeneric gts-log (logger tag message)
-  "Used to record the messages.")
-
-(defvar gts-default-logger nil "The default logger.")
-
-(defmacro gts-do-log (tag &rest messages)
-  (declare (indent 1))
-  `(when gts-debug-p
-     (if gts-default-logger
-         (cl-loop with ms = nil
-                  for message in (list ,@messages)
-                  if message do (cl-loop for m in (split-string message "\n") do (push m ms))
-                  finally (cl-loop
-                           for m in (nreverse ms)
-                           do (gts-log gts-default-logger (format "%s" ,tag) m)))
-       (error "Make sure `gts-default-logger' is available. eg:\n
- (setq gts-default-logger (gts-buffer-logger))\n\n\n"))))
-
-\f
-;;; Cacher
-
-(defcustom gts-cache-enable t
-  "Enable the cacher if this is t."
-  :type 'boolean
-  :group 'go-translate)
-
-(defcustom gts-cache-expired-factor (* 30 60)
-  "Cache alive time based on this.
-Make word live longer time than sentence."
-  :type 'integer
-  :group 'go-translate)
-
-(defvar gts-default-cacher nil "The default cacher.")
-
-(cl-defgeneric gts-cache-get (cacher task)
-  "Get item for TASK from CACHER.")
-
-(cl-defgeneric gts-cache-set (cacher task result)
-  "Add RESULT fro TASK to CACHER.")
-
-(cl-defmethod gts-cache-key ((_ gts-cacher) task)
-  (with-slots (text from to engine) task
-    (sha1 (format "%s-%s-%s-%s"
-                  text from to
-                  (eieio-object-class-name engine)))))
-
-(cl-defmethod gts-clear-expired ((cacher gts-cacher))
-  (with-slots (caches expired) cacher
-    (cl-delete-if (lambda (c) (> (time-to-seconds) (cddr c))) caches)))
-
-(cl-defmethod gts-clear-all ((cacher gts-cacher))
-  (oset cacher caches nil))
-
-\f
-;;; Http Client
-
-(cl-defgeneric gts-request (http-client url &key done fail data headers)
-  "Use HTTP-CLIENT to request a URL with DATA.
-When success execute CALLBACK, or execute ERRORBACK."
-  (:method (&rest _) (user-error "Method `gts-request' is not implement")))
-
-(defvar gts-default-http-client nil
-  "The default http client used to send a request.")
-
-(cl-defun gts-do-request (url &key done fail data headers)
-  "Helper for `gts-request'.
-DONE is a callback based on the responsed result buffer.
-FAIL is the callback for any error occured in request or parse."
-  (if (and gts-default-http-client
-           (eieio-object-p gts-default-http-client)
-           (object-of-class-p gts-default-http-client 'gts-http-client))
-      (let ((tag (format "%s" (eieio-object-class-name gts-default-http-client)))
-            (data (gts-format-params data)))
-        (gts-do-log tag
-          (format "Start! (%s)" url)
-          (if headers (format "    HEADER: %s" headers))
-          (if data (format "    DATA:   %s" data)))
-        (gts-request gts-default-http-client url
-                     :headers headers
-                     :data data
-                     :done (lambda ()
-                             (gts-do-log tag (format "DONE! (%s)" url))
-                             (condition-case err
-                                 (funcall done)
-                               (error
-                                (gts-do-log tag (format "FAIL in callback! (%s) %s" url err))
-                                (funcall fail err))))
-                     :fail (lambda (status)
-                             (gts-do-log tag (format "FAIL! (%s) %s" url status))
-                             (funcall fail status))))
-    (funcall fail "Make sure `gts-default-http-client' is available. eg:\n
- (setq gts-default-http-client (gts-url-http-client))\n\n\n")))
-
-\f
-;;; Picker/Texter
-
-(cl-defgeneric gts-pick (picker)
-  "Get the source text and translate from/to path from PICKER.")
-
-(cl-defgeneric gts-path (picker text)
-  "Use to get the translation path for TEXT from PICKER.")
-
-(cl-defgeneric gts-next-path (picker text path &optional backwardp)
-  "Get the next available path.")
-
-(cl-defgeneric gts-text (texter)
-  "Get the init translation source text via TEXTER.")
-
-(defvar gts-picker-last-path nil)
-
-(defcustom gts-translate-list nil
-  "Setup your translate languages. Just like:
-
-Single:
-
- (setq gts-translate-list \\='((\"en\" \"zh\")))
-
-Or multiple:
-
- (setq gts-translate-list \\='((\"en\" \"zh\") (\"en\" \"fr\")))
-
-The picker will give the translate from/to pair according this."
-  :type 'alist
-  :group 'go-translate)
-
-(defcustom gts-picker-lang-match-alist
-  (list
-   (cons "zh"  "\\cc")
-   (cons "ja"  (lambda (text) (string-match-p "\\cj" text)))
-   (cons "ru" "\\cy"))
-  "Setup language match rules.
-Used to pick the right language for current translate text.
-Key should be the language, value should be a regexp string or function.
-Picker choose path according this first. If non-match, choose other paths."
-  :type 'alist
-  :group 'go-translate)
-
-(defun gts-picker-filter-paths (paths text)
-  "Filter PATHS according TEXT.
-The rules are provided by `gts-picker-lang-match-alist'."
-  (when text
-    (cl-loop with text = (with-temp-buffer ; clear punctuations
-                           (insert text)
-                           (goto-char (point-min))
-                           (while (re-search-forward "\\s.\\|\n" nil t) (replace-match ""))
-                           (buffer-string))
-             for path in paths
-             for matcher = (let ((p (cdr (cl-find-if ; get match rule
-                                          (lambda (item) (string-match-p (format "^%s$" (car item)) (car path)))
-                                          gts-picker-lang-match-alist))))
-                             (if (stringp p) (lambda (text) (string-match-p p text)) p))
-             when (and matcher (funcall matcher text))
-             collect path)))
-
-(cl-defmethod gts-paths ((picker gts-picker) &optional keep-order)
-  "All paths can be picked by PICKER.
-When KEEP-ORDER is not nil then do not try to push last path to the first place."
-  (when (or (null gts-translate-list) (not (consp (car gts-translate-list))))
-    (user-error "Please make sure you set the avaiable `gts-translate-list'. eg:\n
- (setq gts-translate-list '((\"en\" \"zh\") (\"en\" \"ja\"))\n\n"))
-  (let (paths)
-    (cl-loop for (lang1 . lang2) in gts-translate-list
-             do (let ((lang2 (if (consp lang2) (car lang2) lang2)))
-                  (cl-pushnew (cons lang1 lang2) paths :test #'equal)
-                  (unless (oref picker single) (cl-pushnew (cons lang2 lang1) paths :test #'equal))))
-    (setq paths (nreverse paths))
-    (if (and (not keep-order)
-             gts-picker-last-path (member gts-picker-last-path paths))
-        (cons gts-picker-last-path (remove gts-picker-last-path paths))
-      paths)))
-
-(cl-defmethod gts-path ((picker gts-picker) text)
-  "Default path for TEXT used by PICKER.
-Return the first path matching TEXT. If no path matches, return the non-nil
-`gts-picker-last-path', then the first available path."
-  (if-let* ((paths (gts-paths picker))
-            (matched (gts-picker-filter-paths paths text)))
-      (car matched)
-    (or (let ((langs (mapcar #'car gts-picker-lang-match-alist)))
-          (cl-find-if (lambda (p) (not (member (car p) langs))) paths))
-        (car paths))))
-
-(cl-defmethod gts-next-path ((picker gts-picker) text path &optional backwardp)
-  (let (candidates idx (paths (gts-paths picker 'keep-order)))
-    (cl-loop for path in (append (gts-picker-filter-paths paths text) paths)
-             do (cl-pushnew path candidates :test 'equal))
-    (setq candidates (reverse candidates))
-    (setq idx (cl-position path candidates :test 'equal))
-    (if backwardp
-        (elt candidates (if (= 0 idx) (- (length candidates) 1) (- idx 1)))
-      (elt candidates (if (= (+ 1 idx) (length candidates)) 0 (+ 1 idx))))))
-
-\f
-;;; Splitter
-
-(cl-defgeneric gts-split (gts-splitter text)
-  "Split TEXT to several parts.
-Return list of string, or nil if split failed.")
-
-\f
-;;; Engine/Parser
-
-(cl-defmethod gts-translate ((engine gts-engine) _task _callback)
-  "Translate and update TASK with parsed result using ENGINE.
-The CALLBACK should contain the render logic."
-  (user-error "Method `gts-translate' of %s is not implement" (eieio-object-class-name engine)))
-
-(cl-defmethod gts-translate :around ((engine gts-engine) task callback)
-  "Add cache and log support."
-  (with-slots (id render translator) task
-    (if-let ((render-name (eieio-object-class-name render))
-             (engine-name (eieio-object-class-name engine))
-             (cache (gts-cache-get gts-default-cacher task)))
-        ;; try cache
-        (let ((engines (gts-get translator 'engines)))
-          (gts-update-raw task cache)
-          (gts-parse (oref engine parser) task)
-          (if (cdr engines) (gts-me-out render task)
-            (gts-out render task))
-          (gts-do-log 'render (format "%s: done with cache!" id)))
-      ;; request from engine
-      (gts-do-log 'engine (format "%s: starting %s..." id engine-name))
-      (cl-call-next-method engine task
-                           (lambda ()
-                             (gts-do-log 'engine (format "%s: %s done!" id engine-name))
-                             (funcall callback)
-                             ;; refresh cache
-                             (with-slots (err raw parsed) task
-                               (unless err
-                                 (gts-cache-set gts-default-cacher task raw))))))))
-
-(cl-defgeneric gts-with-token (engine done fail)
-  (:documentation "Get token for ENGINE, if success then DONE is called.")
-  (declare (indent 1)))
-
-(cl-defgeneric gts-tts (engine text lang)
-  "TTS, speak TEXT with LANG."
-  (:method ((e gts-engine) &rest _)
-           (user-error "No TTS service found on engine `%s'" (oref e tag))))
-
-(cl-defgeneric gts-parse (parser task)
-  (:documentation "Parse the raw result and update the parsed result into TASK.")
-  (:method ((_o gts-parser) task)
-           ;; do nothing
-           (gts-update-parsed task (oref task raw))))
-
-\f
-;;; Render
-
-(cl-defgeneric gts-pre (render translator)
-  (:documentation "Pre-render before request success.
-Do some preparation for the gts-out.")
-  (:method :after ((render gts-render) _)
-           (gts-do-log 'render (format "prepare render with %s finished" (eieio-object-class-name render))))
-  (:method ((_o gts-render) (_t gts-translator)) ()))
-
-(cl-defgeneric gts-out (render task)
-  (:documentation "Render the TASK, default output to *Messages*.")
-  (:method :after ((render gts-render) task)
-           (gts-do-log 'render (format "%s: %s finished" (oref task id) (eieio-object-class-name render))))
-  (:method ((_ gts-render) task)
-           (with-slots (err parsed) task
-             (when (and (not err) (listp parsed))
-               (setq parsed (string-join parsed "\n\f\n")))
-             (setq parsed (string-trim (format "%s" (or err parsed))))
-             (setq parsed (propertize parsed 'face 'font-lock-keyword-face))
-             (message "-+-G-+-T-+-S-+-")
-             (message parsed ))))
-
-(cl-defgeneric gts-me-pre (render translator)
-  (:documentation "Pre-render for TRANSLATOR.
-It used only when multiple engines exists.
-Default dispatch to gts-pre when first pre-render.")
-  (:method :after ((render gts-render) _)
-           (gts-do-log 'render (format "me prepare render with %s finished" (eieio-object-class-name render))))
-  (:method ((render gts-render) translator)
-           (with-slots (task-queue state) translator
-             (when (= state 1)
-               (gts-pre render translator)
-               (setf state 2)))))
-
-(cl-defgeneric gts-me-out (render task)
-  (:documentation "Render TASK with RENDER.
-It used only when multiple engines exists.
-Default dispatch to gts-out with all results concated.")
-  (:method :after ((render gts-render) task)
-           (gts-do-log 'render (format "%s: %s finished" (oref task id) (eieio-object-class-name render))))
-  (:method ((render gts-render) task)
-           (let ((translator (oref task translator)) results)
-             ;; show only when all tasks finished.
-             (when (= (oref translator state) 3)
-               (dolist (task (oref translator task-queue))
-                 (with-slots (err parsed engine) task
-                   (when (and (not err) (listp parsed))
-                     (setq parsed (string-join parsed "\f")))
-                   (push (cons (format "[%s]" (oref engine tag)) (or err parsed)) results)))
-               (setq results (nreverse results))
-               (let* ((newlinep (cl-find-if (lambda (r) (string-match-p "\n" (cdr r))) results))
-                      (concated (mapconcat (lambda (r)
-                                             (if newlinep
-                                                 (concat (propertize (concat (car r) "\n") 'face 'gts-pop-posframe-me-header-2-face)
-                                                         (format "\n%s" (cdr r)))
-                                               (concat (propertize (format "%-7s" (car r)) 'face 'gts-pop-posframe-me-header-face)
-                                                       (format " %s" (cdr r)))))
-                                           results
-                                           (if newlinep "\n\n" "\n"))))
-                 (oset translator state 4)
-                 (gts-out render (gts-task :parsed concated :translator translator)))))))
-
-\f
-;;; TTS
-
-(defcustom gts-tts-speaker (or (executable-find "mpv")
-                               (executable-find "mplayer"))
-  "The program to use to speak the translation.
-
-On Windows, if it is not found, will fallback to use `powershell`
-to do the job. Although it is not perfect, it seems to work."
-  :type 'string
-  :group 'go-translate)
-
-(defcustom gts-tts-try-speak-locally t
-  "If t then will fallback to locally TTS service when engine's TTS failed.
-For example, it will use powershell on Windows to speak the word
-when this is set to t."
-  :type 'boolean
-  :group 'go-translate)
-
-(defvar gts-tts-playing-process nil)
-
-(defun gts-tts-speak-buffer-data ()
-  "Speak the current buffer's data."
-  (gts-tts-try-interrupt-playing-process)
-  (let ((proc (make-process :name (format "gts-tts-process-%s" (+ 1000 (random 1000)))
-                            :command (append (split-string gts-tts-speaker) (list "-"))
-                            :buffer nil
-                            :noquery t
-                            :connection-type 'pipe)))
-    (setq gts-tts-playing-process proc)
-    (process-send-region proc (point-min) (point-max))
-    (if (process-live-p proc) (process-send-eof proc))))
-
-(defun gts-tts-try-interrupt-playing-process ()
-  (when (and gts-tts-playing-process (process-live-p gts-tts-playing-process))
-    (ignore-errors (kill-process gts-tts-playing-process))
-    (setq gts-tts-playing-process nil)))
-
-(defun gts-do-tts (text lang &optional engine)
-  "Speak TEXT in LANG with possible tts service by ENGINE.
-If engine failed, then try locally TTS if `gts-tts-try-speak-locally' is set."
-  (condition-case err
-      (if engine
-          (if gts-tts-speaker
-              (gts-tts engine text lang)
-            (user-error "No mpv/mplayer found"))
-        (user-error "No engine TTS service provided"))
-    (error (if gts-tts-try-speak-locally
-               (cond ((executable-find "powershell")
-                      (let ((cmd (format "$w = New-Object -ComObject SAPI.SpVoice; $w.speak(\\\"%s\\\")" text)))
-                        (shell-command (format "powershell -Command \"& {%s}\""
-                                               (encode-coding-string
-                                                (replace-regexp-in-string "\n" " " cmd)
-                                                (keyboard-coding-system))))))
-                     (t (message "[TTS] %s" (cadr err))))))))
-
-\f
-;;; Translator
-
-(defvar gts-text-delimiter "34587612321123")
-
-(defvar gts-current-command nil "The command invoked by `gts-translate'.")
-(defvar gts-current-picker nil "Picker used in current translate.")
-(defvar gts-current-engines nil "Engines used in current translate.")
-(defvar gts-current-render nil "Render used in current translate.")
-
-(cl-defmethod initialize-instance :after ((this gts-translator) &rest _)
-  (unless (gts-ensure-plain (oref this render))
-    (oset this render (gts-render)))
-  (unless (gts-ensure-plain (oref this picker))
-    (oset this picker (gts-noprompt-picker))))
-
-(cl-defmethod gts-get ((this gts-translator) &rest slots)
-  "Return the normalized value of SLOTS in THIS translator."
-  (cl-loop for slot in slots
-           for value =
-           (cl-flet ((ensure (v) (or v (user-error "No %s found in current translator" slot))))
-             (pcase slot
-               ('splitter   (gts-ensure-plain (slot-value this slot)))
-               ('picker     (ensure (gts-ensure-plain (or gts-current-picker (slot-value this slot)))))
-               ('engines    (ensure (gts-ensure-list (gts-ensure-plain (or gts-current-engines (slot-value this slot))))))
-               ('render     (ensure (gts-ensure-plain (or gts-current-render (slot-value this slot)))))
-               (_           (slot-value this slot))))
-           collect value into values
-           finally (return (if (cadr slots) (apply #'cl-values values) (car values)))))
-
-(cl-defmethod gts-init ((this gts-translator) text path)
-  "Init all status and tasks in THIS translator."
-  ;; input
-  (unless (and text path)
-    (let ((input (gts-pick (gts-get this 'picker))))
-      (setq text (cl-nth-value 0 input))
-      (setq path (cl-nth-value 1 input))))
-  (oset this text text)
-  (oset this path path)
-  (setq gts-picker-last-path path)
-  (gts-do-log 'translator (format "\nBegin to translate %s" path))
-  (gts-do-log 'text text)
-  (cl-multiple-value-bind (splitter engines render) (gts-get this 'splitter 'engines 'render 'version)
-    ;; reset
-    (oset this state 0)
-    (oset this task-queue nil)
-    (oset this plan-cnt (length engines))
-    (oset this version (time-to-seconds))
-    ;; tasks
-    (let ((splitted (if splitter (gts-split splitter text))))
-      (oset this sptext splitted)
-      (cl-loop for engine in engines
-               for task = (gts-task :translator this
-                                    :engine engine
-                                    :render render
-                                    :from (car path) :to (cdr path)
-                                    :text (if splitted (string-join splitted (concat "\n" gts-text-delimiter "\n")) text))
-               do (gts-add-task this task))
-      (gts-do-log 'text (format "splitted? %s!" (if splitted "Yes" "No"))))))
-
-(cl-defmethod gts-add-task ((this gts-translator) task)
-  "Add a new TASK to THIS translator."
-  (with-slots (id engine render) task
-    (with-slots (text path plan-cnt task-queue state) this
-      (when (= state 0)
-        (setf task-queue (append task-queue (list task)))
-        (gts-do-log 'translator
-          (format "- add task %s: (%s/%s)" id
-                  (eieio-object-class-name engine) (eieio-object-class-name render))))
-      (when (and (= state 0) (= (length task-queue) plan-cnt))
-        (setf state 1)))))
-
-(cl-defmethod gts-done-tasks ((this gts-translator))
-  "Return the finished tasks in THIS translator."
-  (with-slots (task-queue) this
-    (cl-remove-if-not
-     (lambda (task) (or (oref task err) (oref task raw))) task-queue)))
-
-(defun gts-update-raw (task resp &optional error)
-  "Update non-parsed RESP result to TASK.
-If ERROR is not nil, then update task with failure."
-  (declare (indent 1))
-  (with-slots (id raw err translator) task
-    (with-slots (task-queue plan-cnt state) translator
-      (setf raw resp)
-      (setf err error)
-      (when (and (= state 2) (= plan-cnt (length (gts-done-tasks translator))))
-        (setf state 3))
-      (gts-do-log 'translator (format "%s: **responsed** %s" id (or error "success!"))))))
-
-(defun gts-update-parsed (task result &optional metadata filter)
-  "Update TASK with parsed RESULT, update METADATA if exists.
-FILTER the parsed result if necessary, it's a function take parsed result
-as argument."
-  (declare (indent 1))
-  (with-slots (id raw parsed err meta translator) task
-    (if raw
-        (condition-case er
-            (let ((r (if (or (listp result) (not (gts-get translator 'sptext)))
-                         result
-                       (gts-do-log 'translator
-                         (format "%s: result contains delimiter, so split to list" id))
-                       (split-string result gts-text-delimiter nil "[ \t\n\r]+"))))
-              (setf parsed (if filter (funcall filter r) r))
-              (if metadata (setf meta metadata)))
-          (error (setf err er)))
-      (gts-update-raw task t "Mybe Null Response"))))
-
-(defun gts-render-fail (task error)
-  "Directly render the ERROR message and finish the TASK."
-  (declare (indent 1))
-  (gts-do-log 'translator (format "error? %s" error))
-  (if task
-      (with-slots (translator render) task
-        (let ((engines (gts-get translator 'engines)))
-          (gts-update-raw task nil error)
-          (if (cdr engines)
-              (gts-me-out render task)
-            (gts-out render task))))
-    (signal 'error error)))
-
-(cl-defmethod gts-translate ((this gts-translator) &optional text path)
-  "Fire a translation for THIS translator instance.
-When TEXT and PATH is nil then pick them via `gts-pick'."
-  ;; dynamic
-  (setq gts-current-command this-command)
-  (setq gts-current-picker (oref this picker))
-  (setq gts-current-engines (oref this engines))
-  (setq gts-current-render (oref this render))
-  ;; initialize
-  (gts-init this text path)
-  (cl-multiple-value-bind (engines render tasks text path)
-      (gts-get this 'engines 'render 'task-queue 'text 'path)
-    (let ((buf (current-buffer)))
-      ;; single engine
-      (if (not (cdr engines))
-          (let ((task (car tasks)))
-            (gts-pre render this)
-            (gts-translate (car engines) task
-                           (lambda ()
-                             (with-current-buffer buf
-                               (gts-out render task)))))
-        ;; multiple engines
-        (gts-me-pre render this)
-        (dolist (task tasks)
-          (condition-case er
-              (gts-translate (oref task engine) task
-                             (lambda ()
-                               (with-current-buffer buf
-                                 (gts-me-out render task))))
-            (error
-             (gts-render-fail task er))))
-        (gts-me-out render (gts-task :text text :from (car path) :to (cdr path) :translator this))))))
-
-
-(provide 'gts-core)
-
-;;; gts-core.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-bing.el b/elpa/go-translate-20230503.303/gts-engine-bing.el
deleted file mode 100644 (file)
index 202d34b..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-;;; gts-engine-bing.el --- Microsoft Translate -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; https://bing.com/translator
-
-;;; Code:
-
-(require 'gts-implements)
-
-(defclass gts-bing-parser (gts-parser) ())
-
-(defclass gts-bing-engine (gts-engine)
-  ((tag       :initform "Bing")
-   (base-url  :initform "https://www.bing.com")
-   (sub-url   :initform "/ttranslatev3")
-
-   (tld-url   :initform nil)
-   (ig        :initform nil)
-   (key       :initform nil)
-   (token     :initform nil)
-   (last-time :initform nil)
-   (expired-time :initform (* 30 60))
-
-   (ttsk-url  :initform "/tfetspktok")
-   (tts-url   :initform "https://%s.tts.speech.microsoft.com/cognitiveservices/v1")
-   (tts-tpl   :initform "<speak version='1.0' xml:lang='%s'><voice xml:lang='%s' xml:gender='Female' name='%s'><prosody rate='-20.00%%'>%s</prosody></voice></speak>")
-
-   (parser    :initform (gts-bing-parser))))
-
-\f
-;;; Engine
-
-(defvar gts-bing-extra-langs-mapping '(("zh" . "zh-Hans")))
-
-(defvar gts-bing-token-maybe-invalid nil)
-
-(cl-defmethod gts-get-lang ((_ gts-bing-engine) lang)
-  (or (cdr-safe (assoc lang gts-bing-extra-langs-mapping)) lang))
-
-(cl-defmethod gts-token-available-p ((engine gts-bing-engine))
-  (with-slots (token key ig last-time expired-time) engine
-    (and token key ig last-time
-         (not gts-bing-token-maybe-invalid)
-         (< (- (time-to-seconds) last-time) expired-time))))
-
-(cl-defmethod gts-with-token ((engine gts-bing-engine) done fail)
-  (with-slots (token key ig base-url) engine
-    (if (gts-token-available-p engine)
-        (funcall done)
-      (gts-do-request (concat base-url "/translator")
-                      :done
-                      (lambda ()
-                        (let (key token ig tld)
-                          (re-search-forward "curUrl=.*/\\([a-z]+\\.bing.com\\)")
-                          (setq tld (match-string 1))
-                          (re-search-forward "\"ig\":\"\\([^\"]+\\).*params_AbusePreventionHelper = \\[\\([0-9]+\\),\"\\([^\"]+\\)")
-                          (setq ig (match-string 1) key (match-string 2) token (match-string 3))
-                          (oset engine ig ig)
-                          (oset engine key key)
-                          (oset engine token token)
-                          (oset engine last-time (time-to-seconds))
-                          (oset engine tld-url (concat "https://" tld))
-                          (setq gts-bing-token-maybe-invalid nil)
-                          (gts-do-log 'bing (format "url: %s\nkey: %s\ntoken: %s\nig: %s" tld key token ig))
-                          (funcall done)))
-                      :fail fail))))
-
-(cl-defmethod gts-translate ((engine gts-bing-engine) task rendercb)
-  (gts-with-token engine
-    (lambda ()
-      (with-slots (text from to raw) task
-        (with-slots (tld-url sub-url token key ig parser) engine
-          (gts-do-request (format "%s%s?isVertical=1&IG=%s&IID=translator.5022.1" tld-url sub-url ig)
-                          :headers `(("Content-Type" . "application/x-www-form-urlencoded;charset=UTF-8"))
-                          :data `(("fromLang" . ,(gts-get-lang engine from))
-                                  ("to"       . ,(gts-get-lang engine to))
-                                  ("text"     . ,text)
-                                  ("key"      . ,key)
-                                  ("token"    . ,token))
-                          :done (lambda ()
-                                  (gts-update-raw task (buffer-string))
-                                  (gts-parse parser task)
-                                  (funcall rendercb))
-                          :fail (lambda (err)
-                                  (gts-render-fail task
-                                    (cond ((ignore-errors (= (caddar err) 429))
-                                           "[429] Too many requests! Please retry later")
-                                          (t err))))))))
-    (lambda (err)
-      (gts-render-fail task
-        (format "Error occurred when request for token.\n\n%s" err)))))
-
-\f
-;;; TTS
-
-(defvar gts-bing-tts-langs-mapping '(("zh" . ("zh-CN" . "zh-CN-XiaoxiaoNeural"))
-                                     ("en" . ("en-US" . "en-US-AriaNeural"))
-                                     ("fr" . ("fr-CA" . "fr-CA-SylvieNeural"))
-                                     ("de" . ("de-DE" . "de-DE-KatjaNeural"))))
-
-(cl-defmethod gts-tts-payload ((engine gts-bing-engine) lang text)
-  (with-slots (tts-tpl) engine
-    (let (l n (mt (assoc lang gts-bing-tts-langs-mapping)))
-      (if mt (setq l (cadr mt) n (cddr mt))
-        (user-error "Add the mapping of your language into `gts-bing-tts-langs-mapping' :)"))
-      (format tts-tpl l l n (encode-coding-string text 'utf-8)))))
-
-(cl-defmethod gts-tts ((engine gts-bing-engine) text lang)
-  (gts-with-token engine
-    (lambda ()
-      (with-slots (tld-url sub-url token key ig parser ttsk-url tts-url tts-tpl) engine
-        (gts-do-request (format "%s%s?isVertical=1&IG=%s&IID=translator.5022.2" tld-url ttsk-url ig)
-                        :headers '(("content-type" . "application/x-www-form-urlencoded"))
-                        :data `(("token" . ,token) ("key" . ,key))
-                        :done (lambda ()
-                                (let* ((json (json-read))
-                                       (token (cdr (assoc 'token json)))
-                                       (region (cdr (assoc 'region json))))
-                                  (gts-do-log 'bing-tts (format "token: %s\nregion: %s" token region))
-                                  (gts-do-request (format tts-url region)
-                                                  :data (gts-tts-payload engine lang text)
-                                                  :headers `(("content-type" . "application/ssml+xml")
-                                                             ("authorization" . ,(format "Bearer %s" token))
-                                                             ("x-microsoft-outputformat" . "audio-16khz-32kbitrate-mono-mp3"))
-                                                  :done (lambda ()
-                                                          (gts-tts-speak-buffer-data))
-                                                  :fail (lambda (_)
-                                                          (user-error "[BING-TTS] error when play sound")))))
-                        :fail (lambda (err) (user-error "%s" err)))))
-    (lambda (_) (user-error "Failed to get token"))))
-
-\f
-;;; Parser
-
-(cl-defmethod gts-parse ((_ gts-bing-parser) task)
-  (with-temp-buffer
-    (set-buffer-multibyte t)
-    (insert (oref task raw))
-    (decode-coding-region (point-min) (point-max) 'utf-8)
-    (gts-do-log 'bing (string-trim (buffer-string)))
-    (goto-char (point-min))
-    (if-let* ((json (json-read))
-              (result (ignore-errors (cdr (assoc 'text (aref (cdr (assoc 'translations (aref json 0))) 0))))))
-        (gts-update-parsed task result)
-      (setq gts-bing-token-maybe-invalid t) ; refresh token when error occurred
-      (gts-update-raw task nil (buffer-string)))))
-
-
-(provide 'gts-engine-bing)
-
-;;; gts-engine-bing.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-deepl.el b/elpa/go-translate-20230503.303/gts-engine-deepl.el
deleted file mode 100644 (file)
index 9fa0a39..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-;;; gts-engine-deepl.el --- Translate Engine for DeepL -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; https://www.deepl.com
-
-;;; Code:
-
-(require 'gts-implements)
-
-\f
-;;; Components
-
-(defclass gts-deepl-parser (gts-parser) ())
-
-(defclass gts-deepl-engine (gts-engine)
-  ((tag      :initform "DeepL")
-   (pro      :initform nil :initarg :pro :documentation "If your key is PRO version")
-   (free-url :initform "https://api-free.deepl.com")
-   (pro-url  :initform "https://api.deepl.com")
-   (sub-url  :initform "/v2/translate")
-   (auth-key :initform nil :initarg :auth-key)
-   (parser   :initform (gts-deepl-parser))))
-
-(cl-defmethod initialize-instance :after ((engine gts-deepl-engine) &rest _)
-  (unless (oref engine auth-key)
-    (user-error "You should provide a auth-key when instance gts-deepl-engine")))
-
-\f
-;;; Utils
-
-(defcustom gts-deepl-fill-enable t
-  "Controller wheather try to improve the input and output.
-Default behavior is removing excess linebreaks in input for better
-translation effect, and filling the output for better reading experience.
-You can override the behaviors by :around the method `gts-deepl-fill-input'
-or `gts-deepl-fill-output'."
-  :type 'boolean
-  :group 'go-translate)
-
-(cl-defmethod gts-deepl-fill-input (text)
-  "Improve the input TEXT for better translation effect.
-Mainly remove excess linebreaks. I want to skip unfill on comments and codes,
-but don't know how to implement easily. To make it better later, maybe."
-  (if gts-deepl-fill-enable
-      (with-temp-buffer
-        (insert text)
-        (let ((fill-column (* 2 (point-max))))
-          (fill-region (point-min) (point-max)))
-        (buffer-string))
-    text))
-
-(cl-defmethod gts-deepl-fill-output (text)
-  "Improve the output TEXT for better reading experience.
-Mainly fill the text to suitable length."
-  (if gts-deepl-fill-enable
-      (with-temp-buffer
-        (insert text)
-        (fill-region (point-min) (point-max))
-        (buffer-string))
-    text))
-
-\f
-;;; Engine
-
-(defvar gts-deepl-langs-mapping '(("en" . "EN")
-                                  ("zh" . "ZH")
-                                  ("de" . "DE") ; German
-                                  ("fr" . "FR") ; French
-                                  ("it" . "IT") ; Italian
-                                  ("ja" . "JA") ; Japanese
-                                  ("es" . "ES") ; Spanish
-                                  ("nl" . "NL") ; Dutch
-                                  ("pl" . "PL") ; Polish
-                                  ("pt" . "PT") ; Portuguese (all Portuguese varieties mixed)
-                                  ("ru" . "RU") ; Russian
-                                  ))
-
-(cl-defmethod gts-get-lang ((_ gts-deepl-engine) lang)
-  (let ((mapping (assoc lang gts-deepl-langs-mapping)))
-    (if mapping (cdr mapping)
-      (user-error
-       "Language %s is not supported by DeepL.\nSupported list: %s"
-       lang (mapconcat #'car gts-deepl-langs-mapping ", ")))))
-
-(cl-defmethod gts-gen-url ((engine gts-deepl-engine))
-  (with-slots (free-url pro-url sub-url pro) engine
-    (format "%s%s" (if pro pro-url free-url) sub-url)))
-
-(cl-defmethod gts-translate ((engine gts-deepl-engine) task rendercb)
-  (with-slots (text from to) task
-    (with-slots (auth-key parser) engine
-      (gts-do-request (gts-gen-url engine)
-                      :headers `(("Content-Type"   . "application/x-www-form-urlencoded;charset=UTF-8")
-                                 ("Authorization"  . ,(concat "DeepL-Auth-Key " auth-key)))
-                      :data    `(("text"           . ,(gts-deepl-fill-input text))
-                                 ("source_lang"    . ,(gts-get-lang engine from))
-                                 ("target_lang"    . ,(gts-get-lang engine to)))
-                      :done (lambda ()
-                              (gts-update-raw task (buffer-string))
-                              (gts-parse parser task)
-                              (funcall rendercb))
-                      :fail (lambda (err)
-                              (gts-render-fail task
-                                (if (ignore-errors (= (caddar err) 403))
-                                    "[403] http error, make sure your auth_key is correct"
-                                  err)))))))
-
-\f
-;;; Parser
-
-(cl-defmethod gts-parse ((_ gts-deepl-parser) task)
-  (let* ((json (json-read-from-string (oref task raw)))
-         (str (mapconcat (lambda (r) (cdr (cadr r))) (cdar json) "\n"))
-         (filter (lambda (parsed)
-                   (cl-loop for p in (gts-ensure-list parsed)
-                            collect (gts-deepl-fill-output p) into ps
-                            finally (return (if (cdr ps) ps (car ps)))))))
-    (gts-update-parsed task (decode-coding-string str 'utf-8) nil filter)))
-
-
-(provide 'gts-engine-deepl)
-
-;;; gts-engine-deepl.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-google-rpc.el b/elpa/go-translate-20230503.303/gts-engine-google-rpc.el
deleted file mode 100644 (file)
index 8c784c4..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-;;; gts-engine-google-rpc.el --- Google translation with rpc api -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; http://translate.google.com, new RPC API
-
-;;; Code:
-
-(require 'gts-engine-google)
-
-(defcustom gts-google-rpc-base-url "https://translate.google.com"
-  "The base url of Google translate used by google-rpc engine.
-you can customize it according to your country region."
-  :type 'string
-  :group 'go-translate)
-
-\f
-;;; Components
-
-(defclass gts-google-rpc-parser (gts-google-parser)
-  ((tag :initform "Detail")))
-
-(defclass gts-google-rpc-summary-parser (gts-google-summary-parser gts-google-rpc-parser)
-  ((tag :initform "Summary")))
-
-(defclass gts-google-rpc-engine (gts-engine)
-  ((tag       :initform "Google/RPC")
-   (sub-url   :initform "/_/TranslateWebserverUi/data/batchexecute")
-   (parser    :initform (gts-google-rpc-parser))
-
-   (rpc-translate :initform "MkEWBc")
-   (rpc-tts       :initform "jQ1olc")
-   (rpc-sid       :initform "FdrFJe")
-   (rpc-bl        :initform "cfb2h")))
-
-\f
-;;; Engine
-
-(defvar gts-google-rpc-request-headers '(("Content-Type" . "application/x-www-form-urlencoded;charset=UTF-8")))
-
-(cl-defmethod gts-with-token ((engine gts-google-rpc-engine) done fail)
-  (with-slots (sub-url rpc-sid rpc-bl) engine
-    (gts-do-request gts-google-rpc-base-url
-                    :done
-                    (lambda ()
-                      (let* ((f-sid (progn
-                                      (re-search-forward (format "\"%s\":\"\\([^\"]*\\)\"" rpc-sid))
-                                      (match-string 1)))
-                             (bl (progn (re-search-forward (format "\"%s\":\"\\([^\"]*\\)\"" rpc-bl))
-                                        (match-string 1)))
-                             (url-tpl (lambda (rpcid lang)
-                                        (format "%s%s?%s" gts-google-rpc-base-url sub-url
-                                                (gts-format-params
-                                                 `(("rpcids"       . ,rpcid)
-                                                   ("f.sid"        . ,f-sid)
-                                                   ("bl"           . ,bl)
-                                                   ("hl"           . ,lang)
-                                                   ("soc-app"      . 1)
-                                                   ("soc-platform" . 1)
-                                                   ("soc-device"   . 1)
-                                                   ("_reqid"       . ,(+ 1000 (random 9000)))
-                                                   ("rt"           . "c")))))))
-                        (funcall done url-tpl)))
-                    :fail fail)))
-
-(cl-defmethod gts-translate ((engine gts-google-rpc-engine) task rendercb)
-  (gts-with-token engine
-    (lambda (url-tpl)
-      (with-slots (text from to) task
-        (with-slots (rpc-translate parser) engine
-          (gts-do-request (funcall url-tpl rpc-translate to)
-                          :headers gts-google-rpc-request-headers
-                          :data (format
-                                 "f.req=%s&"
-                                 (url-hexify-string
-                                  (let ((outer [[[rpcid inner nil "generic"]]])
-                                        (inner [[text from to t][nil]]))
-                                    (setf (aref (aref inner 0) 0) text)
-                                    (setf (aref (aref inner 0) 1) from)
-                                    (setf (aref (aref inner 0) 2) to)
-                                    (setf (aref (aref (aref outer 0) 0) 0) rpc-translate)
-                                    (setf (aref (aref (aref outer 0) 0) 1) (json-encode inner))
-                                    (json-encode outer))))
-                          :done (lambda ()
-                                  (gts-update-raw task (buffer-string))
-                                  (gts-parse parser task)
-                                  (funcall rendercb))
-                          :fail (lambda (err)
-                                  (gts-render-fail task err))))))
-    (lambda (err)
-      (gts-render-fail task
-        (format "Error occurred when request for token.\n\n%s" err)))))
-
-(cl-defmethod gts-tts ((engine gts-google-rpc-engine) text lang)
-  (gts-with-token engine
-    (lambda (url-tpl)
-      (with-slots (rpc-tts) engine
-        (gts-do-request (funcall url-tpl rpc-tts "en-US")
-                        :headers gts-google-rpc-request-headers
-                        :data (format
-                               "f.req=%s&"
-                               (url-hexify-string
-                                (let ((outer [[[rpcid inner nil "generic"]]])
-                                      (inner [text lang nil nil]))
-                                  (setf (aref inner 0) text)
-                                  (setf (aref inner 1) lang)
-                                  (setf (aref (aref (aref outer 0) 0) 0) rpc-tts)
-                                  (setf (aref (aref (aref outer 0) 0) 1) (json-encode inner))
-                                  (json-encode outer))))
-                        :done (lambda ()
-                                (let (beg end json code)
-                                  (re-search-forward "^[0-9]+$")
-                                  (setq beg (point))
-                                  (re-search-forward "^\\([0-9]+\\)$")
-                                  (setq end (- (point) (length (match-string 1))))
-                                  (setq json (json-read-from-string (string-trim (buffer-substring-no-properties beg end))))
-                                  (when (string= (gts-aref-for json 0 0) "wrb.fr")
-                                    (setq code (aref (json-read-from-string (gts-aref-for json 0 2)) 0))
-                                    (erase-buffer)
-                                    (insert code)
-                                    (base64-decode-region (point-min) (point-max))
-                                    (gts-tts-speak-buffer-data))))
-                        :fail (lambda (err) (user-error "Error when TTS. %s" err)))))
-    (lambda (_) (user-error "Error occurred when request for token"))))
-
-\f
-;;; Parser
-
-(cl-defmethod gts-resp-to-json ((_ gts-google-rpc-parser) resp)
-  "Convert the buffer RESP into JSON format."
-  (condition-case err
-      (with-temp-buffer
-        (let (beg end str json)
-          (insert resp)
-          (goto-char (point-min))
-          (re-search-forward "^[0-9]+$")
-          (setq beg (point))
-          (re-search-forward "^\\([0-9]+\\)$")
-          (setq end (- (point) (length (match-string 1))))
-          (setq str
-                (decode-coding-string
-                 (string-trim (buffer-substring-no-properties beg end))
-                 'utf-8))
-          (setq json (json-read-from-string str))
-          (if (string= (aref (aref json 0) 0) "wrb.fr")
-              (let ((json-str (aref (aref json 0) 2)))
-                (json-read-from-string json-str))
-            (error "No results found"))))
-    (error (user-error "Result conversion error (%s)" err))))
-
-(cl-defmethod gts-result--brief ((_ gts-google-rpc-parser) json)
-  "Get the translation text from JSON."
-  (mapconcat
-   (lambda (item)
-     (if (ignore-errors (aref item 2))
-         (format " %s" (aref item 0))
-       (aref item 0)))
-   (gts-aref-for json 1 0 0 5)
-   ""))
-
-(cl-defmethod gts-result--sphonetic ((_ gts-google-rpc-parser) json)
-  "Get the text phonetic from JSON."
-  (gts-aref-for json 0 0))
-
-(cl-defmethod gts-result--tphonetic ((_ gts-google-rpc-parser) json)
-  "Get the translation phonetic from JSON."
-  (gts-aref-for json 1 0 0 1))
-
-(cl-defmethod gts-result--details ((_ gts-google-rpc-parser) json)
-  "Get the details from JSON.
-Result style: ((noun (a (x y z))) (verb (b (m n o))))."
-  (cl-loop with dts = (ignore-errors (gts-aref-for json 3 5 0))
-           for i across dts
-           collect
-           (cons
-            (aref i 0)
-            (cl-loop for j across (aref i 1)
-                     collect
-                     (cons
-                      (aref j 0)
-                      (cl-loop for k across (aref j 2) collect k))))))
-
-(cl-defmethod gts-result--definitions ((_ gts-google-rpc-parser) json)
-  "Get the definitions from JSON.
-Result style: ((noun (a b)) (verb (c d)))."
-  (cl-loop with defs = (ignore-errors (gts-aref-for json 3 1 0))
-           for i across defs
-           collect
-           (cons
-            (aref i 0)
-            (cl-loop for j across (aref i 1)
-                     collect
-                     (cons (aref j 0)
-                           (ignore-errors (aref j 1)))))))
-
-(cl-defmethod gts-result--suggestion ((_ gts-google-rpc-parser) json)
-  "Get the suggestion from JSON."
-  (ignore-errors
-    (replace-regexp-in-string "<b>\\|</b>\\|<i>\\|</i>" "" (gts-aref-for json 0 1 0 0 1))))
-
-
-(provide 'gts-engine-google-rpc)
-
-;;; gts-engine-google-rpc.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-google.el b/elpa/go-translate-20230503.303/gts-engine-google.el
deleted file mode 100644 (file)
index 2ac39ba..0000000
+++ /dev/null
@@ -1,424 +0,0 @@
-;;; gts-engine-google.el --- Google translate module -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; http://translate.google.com
-
-;;; Code:
-
-(require 'gts-implements)
-
-\f
-;;; Components
-
-(defclass gts-google-parser (gts-parser)
-  ((tag :initform "Detail")))
-
-(defclass gts-google-summary-parser (gts-google-parser)
-  ((tag :initform "Summary")))
-
-(defclass gts-google-engine (gts-engine)
-  ((tag                :initform "Google")
-   (base-url           :initform "http://translate.googleapis.com")
-   (sub-url            :initform "/translate_a/single")
-   (token              :initform (cons 430675 2721866130) :initarg token) ; hard code
-   (token-time         :initform t)
-   (token-expired-time :initform (* 30 60))
-   (parser             :initform (gts-google-parser))))
-
-\f
-;;; Engine
-
-(defvar gts-google-request-headers '(("Connection" . "Keep-Alive")))
-
-(cl-defmethod gts-gen-url ((engine gts-google-engine) text from to)
-  "Generate the url with TEXT, FROM and TO. Return a (url text from to) list."
-  (format "%s%s?%s"
-          (oref engine base-url)
-          (oref engine sub-url)
-          (mapconcat (lambda (p)
-                       (format "%s=%s" (url-hexify-string (car p)) (url-hexify-string (cdr p))))
-                     `(("client" . "gtx")
-                       ("ie"     . "UTF-8")
-                       ("oe"     . "UTF-8")
-                       ("dt"     . "bd")
-                       ("dt"     . "ex")
-                       ("dt"     . "ld")
-                       ("dt"     . "md")
-                       ("dt"     . "qc")
-                       ("dt"     . "rw")
-                       ("dt"     . "rm")
-                       ("dt"     . "ss")
-                       ("dt"     . "t")
-                       ("dt"     . "at")
-                       ("pc"     . "1")
-                       ("otf"    . "1")
-                       ("srcrom" . "1")
-                       ("ssel"   . "0")
-                       ("tsel"   . "0")
-                       ("hl"     . ,to)
-                       ("sl"     . ,from)
-                       ("tl"     . ,to)
-                       ("q"      . ,text)
-                       ("tk"     . ,(gts-google-tkk (oref engine token) text)))
-                     "&")))
-
-(cl-defmethod gts-token-available-p ((engine gts-google-engine))
-  (with-slots (token token-time token-expired-time) engine
-    (and token
-         (or (eq token-time t)
-             (and token-time
-                  (<= (float-time (time-subtract (current-time) token-time))
-                      token-expired-time))))))
-
-(cl-defmethod gts-with-token ((engine gts-google-engine) done fail)
-  (with-slots (token token-time base-url) engine
-    (if (gts-token-available-p engine)
-        (funcall done)
-      (gts-do-request base-url
-                      :headers gts-google-request-headers
-                      :done (lambda ()
-                              (let ((tk (progn
-                                          (re-search-forward ",tkk:'\\([0-9]+\\)\\.\\([0-9]+\\)")
-                                          (cons (string-to-number (match-string 1))
-                                                (string-to-number (match-string 2))))))
-                                (setf token tk)
-                                (setf token-time (current-time))
-                                (funcall done)))
-                      :fail fail))))
-
-(cl-defmethod gts-translate ((engine gts-google-engine) task rendercb)
-  (gts-with-token engine
-    (lambda ()
-      (with-slots (text from to) task
-        (gts-do-request (gts-gen-url engine text from to)
-                        :headers gts-google-request-headers
-                        :done (lambda ()
-                                (gts-update-raw task (buffer-string))
-                                (gts-parse (oref engine parser) task)
-                                (funcall rendercb))
-                        :fail (lambda (err)
-                                (gts-render-fail task err)))))
-    (lambda (err)
-      (gts-render-fail task
-        (format "Error when fetching Token-Key, check your network and proxy, or retry later\n\n%s" err)))))
-
-;; tts
-
-(cl-defmethod gts-tts-gen-urls ((engine gts-google-engine) text lang)
-  "Generate the tts urls for TEXT to LANGUAGE."
-  (cl-loop with texts = (gts-tts-text-splitter engine text)
-           for total = (length texts)
-           for index from 0
-           for piece in texts
-           collect
-           (let ((params `(("ie"      . "UTF-8")
-                           ("client"  . "gtx")
-                           ("prev"    . "input")
-                           ("q"       . ,text)
-                           ("tl"      . ,lang)
-                           ("total"   . ,(number-to-string total))
-                           ("idx"     . ,(number-to-string index))
-                           ("textlen" . ,(number-to-string (length piece)))
-                           ("tk"      . ,(gts-google-tkk (oref engine token) piece)))))
-             (format "%s/translate_tts?%s"
-                     (oref engine base-url)
-                     (mapconcat (lambda (p)
-                                  (format "%s=%s"
-                                          (url-hexify-string (car p))
-                                          (url-hexify-string (cdr p))))
-                                params "&")))))
-
-(cl-defmethod gts-tts-text-splitter ((_ gts-google-engine) text)
-  "Split TEXT by maxlen at applicable point for translating.
-Code from `google-translate', maybe improve it someday."
-  (let (result (maxlen 200))
-    (if (or (null maxlen) (<= maxlen 0))
-           (push text result)
-      ;; split long text?
-      (with-temp-buffer
-           (save-excursion (insert text))
-           ;; strategy to split at applicable point
-           ;; 1) fill-region remaining text by maxlen
-           ;; 2) find end of sentence, end of punctuation, word boundary
-           ;; 3) consume from remaining text between start and (2)
-           ;; 4) repeat
-           (let ((fill-column (* maxlen 3))
-                 (sentence-end-double-space nil)
-                 (pos (point-min)))
-             (while (< pos (point-max))
-               (save-restriction
-                 (narrow-to-region pos (point-max))
-                 (fill-region pos (point-max))
-                 (let ((limit (+ pos maxlen)))
-                       (if (>= limit (point-max))
-                           (setq limit (point-max))
-                         (goto-char limit)
-                         ;; try to split at end of sentence
-                         (if (> (backward-sentence) pos)
-                             (setq limit (point))
-                           ;; try to split at end of punctuation
-                           (goto-char limit)
-                           (if (re-search-backward "[,、]" pos t)
-                                   (setq limit (1+ (point))) ; include punctuation
-                             (goto-char limit)
-                             ;; try to split at word boundary
-                             (forward-word-strictly -1)
-                             (when (> (point) pos)
-                                   (setq limit (point))))))
-                       (push (buffer-substring-no-properties pos limit) result)
-                       (goto-char limit)
-                       (setq pos limit)))))))
-    (reverse result)))
-
-(cl-defmethod gts-tts ((engine gts-google-engine) text lang)
-  (let ((urls (gts-tts-gen-urls engine text lang))
-        (speaker (split-string gts-tts-speaker)))
-    (with-temp-message "Speaking..."
-      (gts-tts-try-interrupt-playing-process)
-      (apply #'call-process (car speaker) nil nil nil (append (cdr speaker) urls)))))
-
-\f
-;;; Parser
-
-;; detail-mode, use as default
-
-(cl-defmethod gts-parse ((parser gts-google-parser) task)
-  (let* ((json        (gts-resp-to-json parser (oref task raw)))
-         (brief       (gts-result--brief parser json))
-         (sphonetic   (gts-result--sphonetic parser json))
-         (tphonetic   (gts-result--tphonetic parser json))
-         (details     (gts-result--details parser json))
-         (definitions (gts-result--definitions parser json))
-         (suggestion  (gts-result--suggestion parser json))
-         (suggestionp (> (length suggestion) 0)) tbeg tend)
-    (cl-flet ((phonetic (ph)
-                (if (and (or definitions definitions) (> (length ph) 0))
-                    (propertize (format " [%s]" ph) 'face 'gts-google-buffer-phonetic-face)
-                  ""))
-              (headline (line)
-                (propertize (format "[%s]\n" line) 'face 'gts-google-buffer-headline-face)))
-      (with-temp-buffer
-        ;; suggestion
-        (when suggestionp
-          (insert (propertize "Do you mean:" 'face 'gts-google-buffer-suggestion-desc-face) " "
-                  (propertize suggestion 'face 'gts-google-buffer-suggestion-text-face) "?\n\n"))
-        ;; phonetic & translate
-        (if (or details definitions)
-            (progn
-              (insert (if suggestionp suggestion (oref (oref task translator) text)))
-              (insert (phonetic sphonetic) " ")
-              (setq tbeg (point))
-              (insert (propertize brief 'face 'gts-google-buffer-brief-result-face))
-              (setq tend (point))
-              (insert (phonetic tphonetic) "\n\n"))
-          (insert brief))
-        ;; details
-        (when details
-          (insert (headline "Details"))
-          (cl-loop for (label . items) in details
-                   unless (= 0 (length label))
-                   do (insert (format "\n%s:\n" label))
-                   do (cl-loop with index = 0
-                               for trans in items
-                               do (insert
-                                   (format "%2d. " (cl-incf index))
-                                   (car trans)
-                                   " (" (mapconcat #'identity (cdr trans) ", ")  ")"
-                                   "\n")))
-          (insert "\n"))
-        ;; definitions
-        (when definitions
-          (insert (headline "Definitions"))
-          (cl-loop for (label . items) in definitions
-                   unless (= 0 (length label))
-                   do (insert (format "\n%s:\n" label))
-                   do (cl-loop with index = 0
-                               for (exp . eg) in items
-                               do (insert (format "%2d. " (cl-incf index)) exp)
-                               when (> (length eg) 0)
-                               do (insert
-                                   "\n    > "
-                                   (propertize (or eg "") 'face 'gts-google-buffer-detail-demo-face))
-                               do (insert "\n"))))
-        ;; at last, fill and return
-        (gts-update-parsed task (buffer-string) (list :tbeg tbeg :tend tend))))))
-
-;; summary-mode
-
-(cl-defmethod gts-parse ((parser gts-google-summary-parser) task)
-  (let* ((json (gts-resp-to-json parser (oref task raw)))
-         (result (string-trim (gts-result--brief parser json))))
-    (gts-update-parsed task result (list :tbeg 1 :tend (+ 1 (length result))))))
-
-;; Extract results from response
-
-(cl-defmethod gts-resp-to-json ((_ gts-google-parser) resp)
-  "Convert the buffer RESP into JSON."
-  (condition-case err
-      (json-read-from-string (decode-coding-string resp 'utf-8))
-    (error (user-error "Result conversion error: %s" err))))
-
-(cl-defmethod gts-result--brief ((_ gts-google-parser) json)
-  "Get the translation text from JSON."
-  (mapconcat (lambda (item) (aref item 0)) (aref json 0) ""))
-
-(cl-defmethod gts-result--sphonetic ((_ gts-google-parser) json)
-  "Get the text phonetic from JSON."
-  (mapconcat (lambda (item) (if (> (length item) 3) (aref item 3) "")) (aref json 0) ""))
-
-(cl-defmethod gts-result--tphonetic ((_ gts-google-parser) json)
-  "Get the translation phonetic from JSON."
-  (mapconcat (lambda (item) (if (> (length item) 2) (aref item 2) "")) (aref json 0) ""))
-
-(cl-defmethod gts-result--details ((_ gts-google-parser) json)
-  "Get the details from JSON.
-Result style: ((noun (a (x y z))) (verb (b (m n o))))."
-  (cl-loop for i across (aref json 1)
-           collect
-           (cons
-            (aref i 0)
-            (cl-loop for j across (aref i 2)
-                     collect
-                     (cons
-                      (aref j 0)
-                      (cl-loop for k across (aref j 1) collect k))))))
-
-(cl-defmethod gts-result--definitions ((_ gts-google-parser) json)
-  "Get the definitions from JSON.
-Result style: ((noun (a b)) (verb (c d)))."
-  (cl-loop with defs = (ignore-errors (aref json 12))
-           for i across defs
-           collect
-           (cons
-            (aref i 0)
-            (cl-loop for j across (aref i 1)
-                     collect
-                     (cons
-                      (aref j 0)
-                      (ignore-errors (aref j 2)))))))
-
-(cl-defmethod gts-result--suggestion ((_ gts-google-parser) json)
-  "Get the suggestion from JSON."
-  (let ((info (aref json 7))) (unless (seq-empty-p info) (aref info 1))))
-
-\f
-;;; Token Key algorithm (deprecated)
-
-;; This algorithm is from `google-translate' project.
-;; https://github.com/atykhonov/google-translate/blob/master/google-translate-tk.el
-
-(defvar gts-google-token--bit-v-len 32)
-
-(defun gts-google-token--bit-v-2comp (v)
-  "Return the two's complement of V."
-  (let* ((vc (vconcat v))
-         (len (length vc)))
-    ;; Complement of v
-    (cl-loop for i from 0 below len do
-             (aset vc i (logxor (aref vc i) 1)))
-    ;; vc = complement of v + 1
-    (cl-loop for i downfrom (1- len) to 0
-             do (aset vc i (logxor (aref vc i) 1))
-             when (> (aref vc i) 0) return nil)
-    vc))
-
-(defun gts-google-token--number-to-bit-v (n)
-  "Return a bit vector from N."
-  (if (< n 0) (gts-google-token--bit-v-2comp
-               (gts-google-token--number-to-bit-v (abs n)))
-    (let ((v (make-vector gts-google-token--bit-v-len 0)))
-      (cl-loop for i downfrom (1- gts-google-token--bit-v-len) to 0
-               with q
-               when (< n 1) return nil do
-               (setq q (ffloor (* n 0.5)))
-               (aset v i (floor (- n (* 2.0 q))))
-               (setq n q))
-      v)))
-
-(defun gts-google-token--bit-v-to-number (v)
-  "Return a floating-point number from V."
-  (if (and (> (aref v 0) 0)
-           ;; Exclude [1 0 ... 0]
-           (cl-loop for i from 1 below gts-google-token--bit-v-len
-                    thereis (> (aref v i) 0)))
-      (- (gts-google-token--bit-v-to-number (gts-google-token--bit-v-2comp v)))
-    (funcall (if (> (aref v 0) 0)  #'- #'+)
-             (cl-reduce (lambda (acc e) (+ (* acc 2.0) e))
-                        v :initial-value 0.0))))
-
-(defun gts-google-token--logfn (fn n1 n2)
-  "Helper function for logical FN with N1 and N2."
-  (let ((v1 (gts-google-token--number-to-bit-v n1))
-        (v2 (gts-google-token--number-to-bit-v n2))
-        (v (make-vector gts-google-token--bit-v-len 0)))
-    (cl-loop for i from 0 below gts-google-token--bit-v-len do
-             (aset v i (funcall fn (aref v1 i) (aref v2 i))))
-    (gts-google-token--bit-v-to-number v)))
-
-(defun gts-google-token--logand (n1 n2)
-  "Return a floating-point number from N1 and N2."
-  (gts-google-token--logfn #'logand n1 n2))
-
-(defun gts-google-token--logxor (n1 n2)
-  "Return a floating-point number from N1 and N2."
-  (gts-google-token--logfn #'logxor n1 n2))
-
-(defun gts-google-token--lsh (n d)
-  "Return a floating-point number.
-Shift the bits in N to the left or rihgt D places.
-D is an integer."
-  (let ((v (gts-google-token--number-to-bit-v n))
-        (v-result (make-vector gts-google-token--bit-v-len 0)))
-    (if (< d 0)
-        ;; Shift Right Logical
-        (cl-loop for i from (abs d) below gts-google-token--bit-v-len
-                 for j from 0 do
-                 (aset v-result i (aref v j)))
-      ;; Shift Left Logical
-      (cl-loop for i from d below gts-google-token--bit-v-len
-               for j from 0 do
-               (aset v-result j (aref v i))))
-    (gts-google-token--bit-v-to-number v-result)))
-
-(defun gts-google-token--gen-rl (a b)
-  "Gen rl from A and B."
-  (cl-loop for c from 0 below (- (length b) 2) by 3
-           for d = (aref b (+ c 2)) do
-           (setq d (if (>= d ?a) (- d 87) (- d ?0)))
-           (setq d (if (= (aref b (1+ c)) ?+)
-                       (gts-google-token--lsh a (- d))
-                     (gts-google-token--lsh a d)))
-           (setq a (if (= (aref b c) ?+)
-                       (gts-google-token--logand (+ a d) 4294967295.0)
-                     (gts-google-token--logxor a d))))
-  a)
-
-(defun gts-google-tkk (token text)
-  "Calculate the Token for search TEXT.
-
-It will use the tkk from Google translate page."
-  (let* ((b (car token))
-         (d1 (cdr token))
-         (ub "+-3^+b+-f")
-         (vb "+-a^+6")
-         (a (cl-loop for c across (encode-coding-string text 'utf-8)
-                     for rr = (gts-google-token--gen-rl (+ (or rr b) c) vb)
-                     finally (return rr))))
-    (setq a (gts-google-token--gen-rl a ub))
-    (setq a (gts-google-token--logxor a d1))
-    (if (< a 0) (setq a (+ (gts-google-token--logand a 2147483647.0) 2147483648.0)))
-    (setq a (ffloor (mod a 1e6)))
-    (format "%s.%s"
-            (car (split-string (number-to-string a) "\\."))
-            (car (split-string (number-to-string
-                                (gts-google-token--logxor a b))
-                               "\\.")))))
-
-
-(provide 'gts-engine-google)
-
-;;; gts-engine-google.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-stardict.el b/elpa/go-translate-20230503.303/gts-engine-stardict.el
deleted file mode 100644 (file)
index 3ce059a..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-;;; gts-engine-stardict.el --- StarDict -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; Engine for sdcv (StarDict Console Version).
-;;
-;; sdcv is an offline translate software. It can be used when no network.
-;;
-;; Make sure sdcv is installed on your system:
-;;
-;;   sudo pacman -S sdcv
-;;
-;; Then download dictionary data to ~/.stardict/dic or /usr/share/stardict/dic:
-;;
-;;   # For example from http://download.huzheng.org:
-;;
-;;   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
-;;
-;; TODO: Provide more options and a better parser.
-
-;;; Code:
-
-(require 'gts-implements)
-
-(defclass gts-stardict-engine (gts-engine)
-  ((tag      :initform "StarDict")
-   (parser   :initform (gts-parser))))
-
-(defcustom gts-stardict-program "sdcv"
-  "Executable command of sdcv (StarDict Console Version)."
-  :type 'string
-  :group 'go-translate)
-
-(defcustom gts-stardict-args '("-n" "-c" "-0" "-1")
-  "Arguments passed to command `sdcv'."
-  :type 'list
-  :group 'go-translate)
-
-(cl-defmethod gts-translate ((engine gts-stardict-engine) task rendercb)
-  (let* ((text (oref task text))
-         (parser (oref engine parser))
-         (result (with-temp-buffer
-                   (require 'ansi-color)
-                   (apply #'call-process gts-stardict-program nil t nil
-                          (append gts-stardict-args (list text))) ;--json-output
-                   (if (string-equal (buffer-substring 1 16) "Nothing similar")
-                       "No translation result found, sorry :("
-                     (ansi-color-apply (buffer-string))))))
-    (gts-update-raw task result)
-    (gts-parse parser task)
-    (funcall rendercb)))
-
-(provide 'gts-engine-stardict)
-
-;;; gts-engine-stardict.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-engine-youdao.el b/elpa/go-translate-20230503.303/gts-engine-youdao.el
deleted file mode 100644 (file)
index 594d345..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-;;; gts-engine-youdao.el --- YouDao Dict -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; 有道词典 (dict.youdao.com), A chinese translation website.
-;;
-;; This engine is only for chinese user.
-;; > 此引擎仅供中文翻译使用
-;;
-;; Simply implement using EWW to do the parsing job for now. I am too lazy to parse and extract the html results.
-;; > 懒得去解析、组装 html,所以暂时直接借用 eww 的解析结果
-;; > 权作应急使用,*也许* 以后会增加更完善的 parser 方案
-
-;;; Code:
-
-(require 'gts-implements)
-
-(defclass gts-youdao-dict-eww-parser (gts-parser)
-  ((tag     :initform "EWW")))
-
-(defclass gts-youdao-dict-engine (gts-engine)
-  ((tag     :initform "有道词典")
-   (url     :initform "https://dict.youdao.com/result?word=%s&lang=%s")
-   (parser  :initform (gts-youdao-dict-eww-parser))))
-
-(cl-defmethod gts-translate ((engine gts-youdao-dict-engine) task rendercb)
-  (with-slots (text from to meta) task
-    (with-slots (url parser) engine
-      (let* ((lang (cond ((string-equal from "zh") to)
-                         ((string-equal to "zh") from)
-                         (t (user-error "只支持中文跟其他语言之间的翻译"))))
-             (url (format url (url-hexify-string text) (url-hexify-string lang))))
-        (setf meta (list :url url))
-        (gts-do-request url
-                        :done (lambda ()
-                                (gts-update-raw task (buffer-string)) ; html format response
-                                (gts-parse parser task)
-                                (funcall rendercb))
-                        :fail (lambda (err)
-                                (gts-render-fail task err)))))))
-
-(cl-defmethod gts-parse ((_ gts-youdao-dict-eww-parser) task)
-  (with-slots (raw meta) task
-    (with-temp-buffer
-      (let ((buf (current-buffer)))
-        (with-temp-buffer
-          (require 'eww)
-          (insert "Content-type: text/html; charset=utf-8\n\n" raw)
-          (eww-render nil (plist-get meta :url) nil buf))
-        (gts-update-parsed task (buffer-string) meta)))))
-
-
-(provide 'gts-engine-youdao)
-
-;;; gts-engine-youdao.el ends here
diff --git a/elpa/go-translate-20230503.303/gts-faces.el b/elpa/go-translate-20230503.303/gts-faces.el
deleted file mode 100644 (file)
index 107b4ed..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-;;; gts-faces.el --- Face definitions -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021 lorniu <lorniuNOSPAMgmail.com>
-;; SPDX-License-Identifier: MIT
-
-;;; Commentary:
-
-;; The faces and colors.
-
-;;; Code:
-
-(require 'gts-core)
-
-(defgroup go-translate-faces nil
-  "The faces for this framework."
-  :group 'go-translate)
-
-\f
-;;; Common
-
-(defface gts-logger-buffer-tag-face '((t :inherit font-lock-comment-face))
-  "Used in the logger buffer tag keyword."
-  :group 'go-translate-faces)
-
-(defface gts-logger-buffer-timestamp-face '((t :foreground "grey"))
-  "Used in the logger buffer timestamp keyword."
-  :group 'go-translate-faces)
-
-(defface gts-render-buffer-header-line-lang-face '((t :inherit font-lock-keyword-face))
-  "Used in the buffer's header-line-format, from/to."
-  :group 'go-translate-faces)
-
-(defface gts-render-buffer-header-line-desc-face '((t :inherit font-lock-variable-name-face))
-  "Used in the buffer's header-line-format, description."
-  :group 'go-translate-faces)
-
-(defface gts-render-buffer-error-face '((t :inherit font-lock-warning-face))
-  "Used in the buffer's error text."
-  :group 'go-translate-faces)
-
-(defface gts-render-buffer-source-face '((t :inherit font-lock-string-face))
-  "Used in the buffer's source."
-  :group 'go-translate-faces)
-
-(defface gts-render-buffer-me-header-backgroud-face '((t :background "slategray" :foreground "white" :extend t))
-  "Used in the buffer's multiple engine separator."
-  :group 'go-translate-faces)
-
-\f
-;;; Google Buffer Render
-
-(defface gts-google-buffer-headline-face '((t :inherit font-lock-function-name-face :weight bold))
-  "Propertize the headline in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-phonetic-face '((t :inherit font-lock-string-face :slant normal))
-  "Propertize the phonetic in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-suggestion-desc-face '((t :inherit font-lock-warning-face))
-  "Propertize the suggestion description in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-suggestion-text-face '((t :slant italic :underline t))
-  "Propertize the phonetic text in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-brief-result-face '((t :weight bold))
-  "Propertize the brief result in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-source-face '((t :inherit font-lock-string-face))
-  "Propertize the source in buffer rendering."
-  :group 'go-translate-faces)
-
-(defface gts-google-buffer-detail-demo-face '((t :inherit font-lock-doc-face))
-  "Propertize the detail demo in buffer rendering."
-  :group 'go-translate-faces)
-
-\f
-;;; Posframe Render
-
-(defcustom gts-pop-posframe-forecolor "white"
-  "Default foreground color of pop posframe."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defcustom gts-pop-posframe-backcolor "black"
-  "Default background color of pop posframe."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defcustom gts-pin-posframe-forecolor nil
-  "Default foreground color of pin posframe."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defcustom gts-pin-posframe-backcolor nil
-  "Default background color of pin posframe."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defcustom gts-pin-posframe-bdcolor "#000000"
-  "Default border color of pin posframe."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defcustom gts-pin-posframe-fringe-color nil
-  "Used in the posframe pip buffer fringe color."
-  :type 'string
-  :group 'go-translate-faces)
-
-(defface gts-pop-posframe-me-header-face '((t :foreground "grey"))
-  "Used in the posframe pop buffer engine header."
-  :group 'go-translate-faces)
-
-(defface gts-pop-posframe-me-header-2-face '((t (:foreground "#666666" :underline t :extend t)))
-  "Used in the posframe pop buffer engine header."
-  :group 'go-translate-faces)
-
-
-(provide 'gts-faces)
-
-;;; gts-faces.el ends here
diff --git a/elpa/go-translate-20240802.131/go-translate-autoloads.el b/elpa/go-translate-20240802.131/go-translate-autoloads.el
new file mode 100644 (file)
index 0000000..308a5d3
--- /dev/null
@@ -0,0 +1,133 @@
+;;; go-translate-autoloads.el --- automatically extracted autoloads (do not edit)   -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from go-translate.el
+
+(autoload 'gt-do-translate "go-translate" "\
+Translate using `gt-default-translator'.
+
+Define your default translator like this:
+
+  (setq gt-default-translator
+    (gt-translator :engines (gt-bing-engine)))
+
+  (setq gt-default-translator
+    (gt-translator :taker (gt-taker :langs '(en fr) :text 'sentence :prompt t)
+                   :engines (list (gt-google-engine) (gt-deepl-engine))
+                   :render (gt-buffer-render)))
+
+Or define several different translators and put them in `gt-preset-translators',
+and switch with `gt-do-setup' at any time.
+
+This is just a simple wrapper of `gt-start' method. Create other translate
+commands in the same way using your creativity.
+
+If ARG is not nil, translate with translator select by `gt-preset-translators'.
+
+(fn &optional ARG)" t)
+(register-definition-prefixes "go-translate" '("gt-"))
+
+\f
+;;; Generated autoloads from gt-core.el
+
+(autoload 'gt-do-speak "gt-core" "\
+Speak content around point.
+
+If the text under point or with selection has `gt-task' or `gt-tts-url'
+property, try to speak it with current translation engine.
+
+Otherwise try to TTS with local program or using selected engine.
+
+When TTS with specific engine, you can specify the language with `lang.' prefix." t)
+(register-definition-prefixes "gt-core" '("gt-"))
+
+\f
+;;; Generated autoloads from gt-engine-bing.el
+
+(register-definition-prefixes "gt-engine-bing" '("gt-bing-"))
+
+\f
+;;; Generated autoloads from gt-engine-chatgpt.el
+
+(register-definition-prefixes "gt-engine-chatgpt" '("gt-chatgpt-"))
+
+\f
+;;; Generated autoloads from gt-engine-deepl.el
+
+(register-definition-prefixes "gt-engine-deepl" '("gt-deepl-"))
+
+\f
+;;; Generated autoloads from gt-engine-echo.el
+
+(register-definition-prefixes "gt-engine-echo" '("gt-"))
+
+\f
+;;; Generated autoloads from gt-engine-google.el
+
+(register-definition-prefixes "gt-engine-google" '("gt-google-"))
+
+\f
+;;; Generated autoloads from gt-engine-google-rpc.el
+
+(register-definition-prefixes "gt-engine-google-rpc" '("gt-google-rpc-"))
+
+\f
+;;; Generated autoloads from gt-engine-libre.el
+
+(register-definition-prefixes "gt-engine-libre" '("gt-libre-"))
+
+\f
+;;; Generated autoloads from gt-engine-stardict.el
+
+(register-definition-prefixes "gt-engine-stardict" '("gt-stardict-"))
+
+\f
+;;; Generated autoloads from gt-engine-youdao.el
+
+(register-definition-prefixes "gt-engine-youdao" '("gt-youdao-"))
+
+\f
+;;; Generated autoloads from gt-extension.el
+
+(register-definition-prefixes "gt-extension" '("gt-"))
+
+\f
+;;; Generated autoloads from gt-faces.el
+
+(register-definition-prefixes "gt-faces" '("gt-p"))
+
+\f
+;;; Generated autoloads from gt-httpx.el
+
+(register-definition-prefixes "gt-httpx" '("gt-"))
+
+\f
+;;; Generated autoloads from gt-text-utility.el
+
+(autoload 'gt-qrcode "gt-text-utility" "\
+Genrate QR Code for STR.
+
+(fn STR)" t)
+(register-definition-prefixes "gt-text-utility" '("gt-text-"))
+\f
+;;; End of scraped data
+
+(provide 'go-translate-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; go-translate-autoloads.el ends here
diff --git a/elpa/go-translate-20240802.131/go-translate-pkg.el b/elpa/go-translate-20240802.131/go-translate-pkg.el
new file mode 100644 (file)
index 0000000..e22c61b
--- /dev/null
@@ -0,0 +1,10 @@
+;; -*- no-byte-compile: t; lexical-binding: nil -*-
+(define-package "go-translate" "20240802.131"
+  "Translation framework, configurable and scalable."
+  '((emacs "28.1"))
+  :url "https://github.com/lorniu/go-translate"
+  :commit "424682b94df9efb3dd59e396677b45e4a67b7723"
+  :revdesc "424682b94df9"
+  :keywords '("convenience")
+  :authors '(("lorniu" . "lorniuNOSPAMgmail.com"))
+  :maintainers '(("lorniu" . "lorniuNOSPAMgmail.com")))
diff --git a/elpa/go-translate-20240802.131/go-translate.el b/elpa/go-translate-20240802.131/go-translate.el
new file mode 100644 (file)
index 0000000..867e38e
--- /dev/null
@@ -0,0 +1,348 @@
+;;; go-translate.el --- Translation framework, configurable and scalable -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+;; URL: https://github.com/lorniu/go-translate
+;; Package-Requires: ((emacs "28.1"))
+;; Keywords: convenience
+;; Package-Version: 20240802.131
+;; Package-Revision: 424682b94df9
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; Translation framework on Emacs, with high configurability and extensibility.
+;;
+;;  - Support multiple translation engines, such as Google, Bing, DeepL, ChatGPT...
+;;  - With variety of output styles, such as Buffer, Overlay, Childframe and so on.
+;;  - With a flexible taker for easy retrieval of translated content and targets.
+;;  - Support multiple paragraphs/parts and multi-language translation.
+;;  - Support different http backends, such as url.el, curl. Async and non-blocking.
+;;  - Support caches, proxy and more.
+;;
+;; Notice, it is not limited to just being a translation framework. It can fulfill
+;; any text transformation tasks, such as ChatGPT and more.
+;;
+;; Custom it as you need, extend it using your creativity.
+
+;; You can install it via MELPA or from github. Make sure it is on your `load-path'.
+;;
+;; For the most basic use, add the following configuration:
+;;
+;;   (require 'go-translate)
+;;
+;;   (setq gt-default-translator
+;;        (gt-translator
+;;         :taker (gt-taker :langs '(en zh))
+;;         :engines (list (gt-google-engine) (gt-bing-engine))
+;;         :render (gt-buffer-render)))
+;;
+;; Then start your translate with command `gt-do-translate'.
+;;
+;; See README.org for details.
+
+;;; Code:
+
+(require 'transient)
+(require 'gt-core)
+(require 'gt-extension)
+(require 'gt-engine-bing)
+(require 'gt-engine-google)
+(require 'gt-engine-google-rpc)
+(require 'gt-engine-deepl)
+(require 'gt-engine-stardict)
+(require 'gt-engine-youdao)
+(require 'gt-engine-chatgpt)
+(require 'gt-engine-libre)
+(require 'gt-engine-echo)
+(require 'gt-text-utility)
+
+;;; Mask these commands in M-x
+(dolist (cmd '(gt-prompt-next-target
+               gt-buffer-render--cycle-next
+               gt-buffer-render--refresh
+               gt-buffer-render--browser
+               gt-buffer-render--keyboard-quit
+               gt-buffer-render--toggle-readonly
+               gt-buffer-render--toggle-polyglot
+               gt-buffer-render--delete-cache
+               gt-buffer-render--show-tips
+               gt-buffer-render--unfold-source-text
+               gt-posframe-render-auto-close-handler
+               gt-stardict-switch-dict
+               gt-overlay-render-save-to-kill-ring))
+  (put cmd 'completion-predicate #'ignore))
+
+\f
+;;; Presets
+
+(defun gt-define-custom-taker ()
+  (let ((text (completing-read "Initial text: "
+                               (gt-make-completion-table `(,@gt-taker-text-things nil))))
+        (pick (completing-read "Pick style: "
+                               (gt-make-completion-table `(nil ,@gt-taker-pick-things))))
+        (prompt (completing-read "Prompt style: "
+                                 (gt-make-completion-table (list 'disable 'buffer 'minibuffer)) nil t)))
+    (gt-taker :text (intern text) :pick (intern pick)
+              :prompt (pcase prompt ("buffer" 'buffer) ("minibuffer" t) (_ nil)))))
+
+(defcustom gt-preset-takers
+  (lambda ()
+    `((,(gt-face-lazy "new..." 'bold)  . ,#'gt-define-custom-taker)
+      (default                         . ,(gt-taker))
+      (interactively                   . ,(gt-taker :text t :pick t :prompt t))
+      (paragraph-at-point              . ,(gt-taker :text 'paragraph :pick nil))
+      (whole-buffer                    . ,(gt-taker :text 'buffer :pick 'nil))
+      (whole-buffer-by-paragraph       . ,(gt-taker :text 'buffer :pick 'paragraph))))
+  "Preset takers.
+
+It is an alist or a function return the alist, which the value is a valid
+instance of `gt-taker' and the key is a string or symbol, representing the
+display label of the taker.
+
+Custom your own takers and put them into this list, then change the taker
+of `gt-default-translator' at any time in `gt-do-setup'."
+  :type '(choice function
+                 (alist :key-type (choice string symbol)
+                        :value-type (sexp :tag "Instance of gt-taker")))
+  :group 'gt-do-translate)
+
+(defcustom gt-preset-engines
+  (lambda ()
+    `((Bing                 . ,(gt-bing-engine))
+      (DeepL                . ,(gt-deepl-engine))
+      (Google               . ,(gt-google-engine))
+      (ChatGPT              . ,(gt-chatgpt-engine))
+      (ChatGPT-Stream       . ,(gt-chatgpt-engine :stream t))
+      (Youdao-Dict          . ,(gt-youdao-dict-engine))
+      (Youdao-Suggest       . ,(gt-youdao-suggest-engine))
+      (StarDict             . ,(gt-stardict-engine))
+      (LibreTranslate       . ,(gt-libre-engine))
+      (GoogleRPC            . ,(gt-google-rpc-engine))
+      (Google-Summary       . ,(gt-google-engine :parse (gt-google-summary-parser)))
+      (Bionic_Reading       . ,(gt-echo-engine :do '(clean br) :tag "Bionic Reading"))))
+  "Preset engines.
+
+It is an alist or a function return the alist, which the value is a valid
+instance of `gt-engine' and the key is a string or symbol, representing the
+display label of the engine.
+
+Custom your own engines and put them into this list, then change the engines
+of `gt-default-translator' at any time in `gt-do-setup'."
+  :type '(choice function
+                 (alist :key-type (choice string symbol)
+                        :value-type (sexp :tag "Instance of gt-engine")))
+  :group 'gt-do-translate)
+
+(defcustom gt-preset-renders
+  (lambda ()
+    `((,gt-buffer-render-buffer-name  . ,(gt-buffer-render))
+      (insert/after                   . ,(gt-insert-render  :type 'after))
+      (insert/replace                 . ,(gt-insert-render  :type 'replace))
+      (overlay/after                  . ,(gt-overlay-render :type 'after))
+      (overlay/help-echo              . ,(gt-overlay-render :type 'help-echo))
+      (message->echo-area             . ,(gt-render))
+      (save->kill-ring                . ,(gt-kill-ring-render))
+      (Pop-Posframe                   . ,(gt-posframe-pop-render))
+      (Pin-Posframe                   . ,(gt-posframe-pin-render))
+      (overlay-or-insert              . ,(lambda ()
+                                           (if buffer-read-only
+                                               (gt-overlay-render :type 'after :then (gt-kill-ring-render))
+                                             (gt-insert-render :type 'after))))
+      (system-notification            . ,(gt-alert-render))))
+  "Preset renders.
+
+It is an alist or a function return the alist, which the value is a valid
+instance of `gt-render' and the key is a string or symbol, representing the
+display label of the render.
+
+Custom your own render and put them into this list, then change the render
+of `gt-default-translator' at any time in `gt-do-setup'."
+  :type '(choice function
+                 (alist :key-type (choice string symbol)
+                        :value-type (sexp :tag "Instance of gt-render")))
+  :group 'gt-do-translate)
+
+(defcustom gt-preset-translators
+  (lambda ()
+    `((default . ,(gt-translator :taker   (cdar (gt-ensure-plain gt-preset-takers))
+                                 :engines (cdar (gt-ensure-plain gt-preset-engines))
+                                 :render  (cdar (gt-ensure-plain gt-preset-renders))))
+      (Text-Utility . ,(gt-text-utility
+                        :taker (gt-taker :pick nil)
+                        :render (gt-buffer-render)))))
+  "Preset translators.
+
+It is an alist or a function return the alist, which the value is a valid
+instance of `gt-translator' and the key is a string or symbol, representing the
+display label of the translator.
+
+Custom your own translator and put them into this list, then change the the
+default translator to one of them at any time in `gt-do-setup'."
+  :type '(choice function
+                 (alist :key-type (choice string symbol)
+                        :value-type (sexp :tag "Instance of gt-translator")))
+  :group 'gt-do-translate)
+
+(defcustom gt-default-translator nil
+  "The translator used by `gt-do-translate'.
+
+If you leave this nil, then the first translator in `gt-preset-translators'
+will be used as the default translator."
+  :type '(restricted-sexp :match-alternatives (gt-translator-p 'nil))
+  :group 'gt-do-translate)
+
+(defun gt-ensure-default-translator ()
+  "Initial `gt-default-translator' if possible and make sure it's valid."
+  (unless gt-default-translator
+    (setq gt-default-translator (cdar (gt-ensure-plain gt-preset-translators))))
+  (if (cl-typep gt-default-translator 'gt-translator)
+      (let (gt-debug-p) (gt-reset gt-default-translator))
+    (user-error "The `gt-default-translator' is unavailable"))
+  gt-default-translator)
+
+(defun gt-translator-info (translator)
+  "Return TRANSLATOR's basic info for displaying."
+  (with-slots (taker engines render _taker _engines _render) translator
+    (cl-macrolet ((desc1 (name &rest body)
+                    `(if (not (slot-boundp translator ',(intern (format "_%s" name)))) "unbound"
+                       (when-let (,name (or ,name ,(intern (format "_%s" name))))
+                         (if (gt-functionp ,name)
+                             (replace-regexp-in-string "[ \n\t]+" " " (format "%s" ,name))
+                           ,@body)))))
+      (list (desc1 taker (if (consp taker) (format "%s" taker)
+                           (cl-flet ((desc2 (slot) (when (slot-boundp taker slot)
+                                                     (format "%s: %s" slot (slot-value taker slot)))))
+                             (format "<%s> %s" (gt-desc taker)
+                                     (string-join (remove nil (mapcar #'desc2 '(langs text pick prompt))) ", ")))))
+            (desc1 engines (mapconcat (lambda (en) (concat (format "%s" (oref en tag)) (if (gt-stream-p en) " (stream)")))
+                                      (ensure-list (gt-ensure-plain engines)) ", "))
+            (desc1 render (if (consp render) (format "%s" render)
+                            (gt-desc (gt-ensure-plain render))))))))
+
+(defun gt-set-taker (&optional translator taker)
+  "Set TRANSLATOR's TAKER to one from `gt-preset-takers'."
+  (interactive)
+  (unless translator (setq translator gt-default-translator))
+  (unless taker
+    (let ((cands (gt-ensure-plain gt-preset-takers)))
+      (setq taker (gt-ensure-plain
+                   (alist-get
+                    (completing-read "Taker to use: " (gt-make-completion-table cands) nil t)
+                    cands nil nil #'string-equal)))))
+  (oset translator taker nil)
+  (oset translator _taker taker)
+  (message "Changed taker done."))
+
+(defun gt-set-engines (&optional translator engines)
+  "Set TRANSLATOR's ENGINES to ones from `gt-preset-engines'."
+  (interactive)
+  (unless translator (setq translator gt-default-translator))
+  (unless engines
+    (let ((cands (gt-ensure-plain gt-preset-engines)))
+      (setq engines
+            (mapcar (lambda (item)
+                      (let ((engine (gt-ensure-plain (alist-get item cands nil nil #'string-equal))))
+                        (if (cl-typep engine 'gt-engine) engine
+                          (user-error "Invalid engine detected. Abort"))))
+                    (completing-read-multiple "Engines to use (can choose multiple): "
+                                              (gt-make-completion-table cands))))))
+  (oset translator engines nil)
+  (oset translator _engines engines)
+  (message "Changed engines done."))
+
+(defun gt-set-render (&optional translator render)
+  "Set TRANSLATOR's RENDER to one from `gt-preset-renders'."
+  (interactive)
+  (unless translator (setq translator gt-default-translator))
+  (unless render
+    (let ((cands (gt-ensure-plain gt-preset-renders)))
+      (setq render (gt-ensure-plain
+                    (alist-get
+                     (completing-read "Render to use: " (gt-make-completion-table cands) nil t)
+                     cands nil nil #'string-equal)))))
+  (oset translator render nil)
+  (oset translator _render render)
+  (message "Changed render done."))
+
+(defun gt-translator-copy-of-presets ()
+  (let* ((tss (gt-ensure-plain gt-preset-translators))
+         (tsn (completing-read "Preset translator: " tss nil t))
+         (translator (alist-get tsn tss nil nil #'string-equal)))
+    (list (clone translator) tsn)))
+
+(defun gt-switch-translator ()
+  "Switch `gt-default-translator' to another defined in `gt-preset-translators'."
+  (interactive)
+  (cl-destructuring-bind (translator name)
+      (gt-translator-copy-of-presets)
+    (setq gt-default-translator translator)
+    (gt-ensure-default-translator)
+    (message "Switch default translator to: %s" name)))
+
+(transient-define-prefix gt-do-setup ()
+  "Setup `gt-default-translator' in user interface provided by transient."
+  :transient-non-suffix #'transient--do-exit
+  [:description
+   (lambda ()
+     (format "Current Default Translator:\n\n    %s\n"
+             (condition-case err
+                 (apply #'format "Taker: %s\n    Engines: %s\n    Render: %s"  (gt-translator-info (gt-ensure-default-translator)))
+               (error (format "%s" err)))))
+   [("t"  "Set taker..."   gt-set-taker   :transient t)]
+   [("e"  "Set engines..." gt-set-engines :transient t)]
+   [("r"  "Set render..."  gt-set-render  :transient t)]
+   [("c"  "Switch preset translator..." gt-switch-translator)]]
+  (interactive)
+  (gt-ensure-default-translator)
+  (transient-setup 'gt-do-setup))
+
+\f
+;;; Entrance
+
+;;;###autoload
+(defun gt-do-translate (&optional arg)
+  "Translate using `gt-default-translator'.
+
+Define your default translator like this:
+
+  (setq gt-default-translator
+    (gt-translator :engines (gt-bing-engine)))
+
+  (setq gt-default-translator
+    (gt-translator :taker (gt-taker :langs '(en fr) :text 'sentence :prompt t)
+                   :engines (list (gt-google-engine) (gt-deepl-engine))
+                   :render (gt-buffer-render)))
+
+Or define several different translators and put them in `gt-preset-translators',
+and switch with `gt-do-setup' at any time.
+
+This is just a simple wrapper of `gt-start' method. Create other translate
+commands in the same way using your creativity.
+
+If ARG is not nil, translate with translator select by `gt-preset-translators'."
+  (interactive "P")
+  (let ((gt-default-translator (if arg (car (gt-translator-copy-of-presets)) gt-default-translator)))
+    (gt-ensure-default-translator)
+    (gt-start gt-default-translator)))
+
+(provide 'go-translate)
+
+;;; go-translate.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-core.el b/elpa/go-translate-20240802.131/gt-core.el
new file mode 100644 (file)
index 0000000..54537a9
--- /dev/null
@@ -0,0 +1,1965 @@
+;;; gt-core.el --- Core implement of the Translator -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+;; Package-Requires: ((emacs "28.1"))
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; The core implement of the translator
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'subr-x)
+(require 'dom)
+(require 'eieio)
+(require 'text-property-search)
+
+(defgroup go-translate nil
+  "Translation framework on Emacs, with high configurability and extensibility."
+  :group 'external
+  :prefix 'gt-)
+
+(defcustom gt-debug-p nil
+  "Whether enable the debug message."
+  :type 'boolean
+  :group 'go-translate)
+
+\f
+;;; Components and Variables
+
+(defclass gt-single ()
+  ((insts :allocation :class :initform nil))
+  :abstract t
+  :documentation "Only create one instance for same slots.")
+
+(cl-defmethod make-instance ((class (subclass gt-single)) &rest slots)
+  (if-let* ((key (sha1 (format "%s" slots)))
+            (insts (oref-default class insts))
+            (old (cdr-safe (assoc key insts))))
+      old
+    (let ((inst (cl-call-next-method)))
+      (prog1 inst
+        (oset-default class insts `((,key . ,inst) ,@insts))))))
+
+(defclass gt-validator ()
+  ((if :initarg :if
+     :type (or function symbol list)
+     :documentation "See `gt-valid' for details."))
+  :abstract t
+  :documentation "Use to detect whether component is available")
+
+(defclass gt-task ()
+  ((text
+    :initarg :text
+    :type (or string list)
+    :documentation "Source text list to be translated.")
+   (src
+    :initarg :src
+    :initform nil
+    :type symbol
+    :documentation "Generally, the source language.")
+   (tgt
+    :initarg :tgt
+    :type symbol
+    :documentation "Generally, the target language.")
+   (meta
+    :initform nil
+    :initarg :meta
+    :type t
+    :documentation "Extra data passed in translation.")
+   (res
+    :initform nil
+    :type (or string list function)
+    :documentation "Translation result.")
+   (err
+    :initform nil
+    :documentation "Translation error.")
+   (cache
+    :initform nil
+    :documentation "Used to temporarily save the data from cache.")
+   (markers
+    :initform nil
+    :documentation "Markers tracking output bounds.")
+   (process
+    :initform nil
+    :documentation "Which process current task running.")
+
+   (id
+    :initform (gensym "task-")
+    :documentation "Used to distinguish between different tasks.")
+   (version
+    :initform nil
+    :initarg :version
+    :documentation "If this is different with the one in translator, task is expired.")
+
+   (engine
+    :initarg :engine
+    :documentation "Engine of current task.")
+   (render
+    :initarg :render
+    :documentation "Render of current task.")
+   (translator
+    :initarg :translator
+    :documentation "Translator of current task."))
+  :documentation
+  "An object contains basic translation infomations.
+Its life cycle is in one translation. It is created by translator, then derived
+to engines for result, at last extracted by render for output.")
+
+(defclass gt-taker (gt-single gt-validator)
+  ((text
+    :initarg :text
+    :type (or function symbol string)
+    :documentation "See `gt-taker-text' for details.")
+   (pick
+    :initarg :pick
+    :type (or function symbol)
+    :documentation "See `gt-taker-pick' for details.")
+   (pick-pred
+    :initarg :pick-pred
+    :initform nil
+    :type (or function null)
+    :documentation "Filter the picked element with this function.")
+   (langs
+    :initarg :langs
+    :type list
+    :documentation "See `gt-langs' for details.")
+   (prompt
+    :initarg :prompt
+    :type (or function symbol)
+    :documentation "See `gt-taker-prompt' for details.")
+   (then
+    :initarg :then
+    :documentation "A function with current translator as argument.
+Used to do extra works after taking done. For example, one can filter the
+source text list to remove unnecessary elements, or modifiy the translate
+target as a final step."))
+  :documentation
+  "A component used to take the translate source text and targets for translator.
+
+The steps are:
+1. take the initial text by `text'
+2. get the available targets from `langs'
+3. ensure the text and langs via `prompt' if necessary
+4. split or filter text via `pick' if possible
+
+So you can define a taker fully like this:
+
+  (gt-taker :text `buffer :langs `(en it) :prompt `buffer :pick `sentence)
+
+Or use the default value or corresponding variables as:
+
+  (setq gt-langs `(en it))
+  (gt-taker)
+
+Then use the taker in the translator and start your translation.")
+
+(defclass gt-parser (gt-single gt-validator)
+  ((tag
+    :initarg :tag
+    :initform nil
+    :type (or symbol string)
+    :documentation "Display label for this parser."))
+  :documentation
+  "It is a component of `gt-engine', used to parse the response of the engine
+to a user-friendly string. One engine can have different parsers, but can only
+use one of them at a time.")
+
+(defclass gt-engine (gt-single gt-validator)
+  ((tag
+    :initarg :tag
+    :type (or string symbol)
+    :documentation "Display label for this engine.")
+   (parse
+    :initarg :parse
+    :initform (gt-parser)
+    :type (or function gt-parser)
+    :documentation "Used to generate user-friendly result from response.")
+   (delimiter
+    :initarg :delimiter
+    :type (or null string)
+    :documentation "The delimiter for multiple paragraphs or parts translation.")
+   (stream
+    :initarg :stream
+    :type boolean
+    :initform nil
+    :documentation "Enable stream support for this engine.
+This effect both engine and render. If this is t, then retrieve results
+part by part. Render supporting stream output should refresh the display
+every time new data arrived.")
+   (cache
+    :initarg :cache
+    :type (or symbol function gt-cacher)
+    :documentation "Cache style used by the engine.
+If this is nil, current engine will use no cache at all.
+If this is literally symbol, see `gt-valid-literally' for details.
+If this is a cacher, then cache using the cacher instead.")
+   (salt
+    :initarg :salt
+    :initform nil
+    :documentation "Distinguish from... now for cache key only.")
+   (then
+    :initarg :then
+    :type function
+    :documentation "Function run after result are responsed and parsed.
+Take current task as argument. you can do extra works for current task
+before rendering."))
+  :documentation "Translate engine, retrieve the result and parse it.
+
+If `delimiter' is non nil, the translation for multiple paragraphs or parts will
+take a strategy as `join-translate-split'. That is, join the source text list to
+a single string separated by `delimiter', translate the whole string, and then
+split the result by `delimiter' into multiple parts. This assumes that the
+`delimiter' in string is kept by engine in the translation progress.
+
+Set `delimiter' to nil to avoid this strategy. In that case, a string list will
+be passed to the engine, and a translated string list is expected.
+
+Also in some cases, you should turn the cache off by setting `no-cache' to t."
+  :abstract t)
+
+(defclass gt-render (gt-single gt-validator)
+  ((output
+    :initarg :output
+    :initform nil
+    :type (or function null)
+    :documentation "If this is not nil, then will override the default output logic.")
+   (then
+    :initarg :then
+    :initform nil
+    :type (or function gt-render null)
+    :documentation "Chain results to next render if possible.
+If this is a function, pass current translator as argument and execute it only.")
+   (prefix
+    :initarg :prefix
+    :type (or function t)
+    :documentation "Custom prefix displayed in the render.
+If this is nil, then don't display prefix. If this is function use its return value
+as prefix. If this is a string, use the string as prefix."))
+  :documentation
+  "A component used to output the all the results made by translator.
+Method `gt-init' only works once before output, method `gt-output' will run after
+every task finished.")
+
+(defclass gt-translator ()
+  ((text
+    :type list
+    :initform nil
+    :initarg :text
+    :documentation "The text taken by taker, and prepare to translate.
+This should be a list, every element is a string as one pargraph or one part.
+This list will be sent to translate engines later.")
+
+   (bounds
+    :type list
+    :initform nil
+    :documentation "The bounds in buffer corresponds to the `text' slot.
+The first element is the working buffer, while rest are buffer bounds.")
+
+   (target
+    :type list
+    :initform nil
+    :documentation "The target taken by taker.
+
+This should be a list. Generally, the first element is the source language and
+the rest are the target languages. Target languages can be one or multiple,
+that is, in one translation, can translate same text into multiple languages.
+
+Of course, the target may be more than just the languages. If translator is not
+used to translate, then target may be something else representing the transform
+targets.")
+
+   (tasks
+    :initform nil
+    :type list
+    :documentation "All tasks created according the source text and targets.")
+
+   (total
+    :initform 0
+    :type (integer 0 *)
+    :documentation "Total tasks in current translation.")
+
+   (state
+    :initform 0
+    :type (member 0 1 2 3 4 9)
+    :documentation "The inner state of the translator for current translation:
+0: new translator
+1: all tasks added
+2: render prepared
+3: all result parsed
+4: translate abort")
+
+   (keep
+    :initform nil
+    :type boolean
+    :documentation "If t then don't clean text and target in a new translation.")
+
+   (version
+    :documentation "Used to distinguish with expired translations.")
+
+   (taker
+    :initarg :taker
+    :initform nil
+    :type (or function gt-taker list)
+    :documentation "Used to take input for the translator.
+If this is a function, use its return value as the taker. It will take the
+source text and targets, and save them into `text', `bounds' and `target' slots
+for the following translate task. If this is a list, use the first available one.")
+
+   (engines
+    :initarg :engines
+    :initform nil
+    :type (or function gt-engine list)
+    :documentation "The engines used to translate.
+This should be one or more `gt-engine' instances or a function that return them.
+The tasks created after `gt-take' will be passed to every engines. Every engine
+should translate the task and parse the result. After every task finished, the
+rendering process will be performed.")
+
+   (render
+    :initarg :render
+    :initform nil
+    :type (or function gt-render list)
+    :documentation "Used to output the translation results.
+This should be a `gt-render' instance or a function return one.
+If this is a list, use the first available one.")
+
+   (_taker
+    :type (or function gt-taker list)
+    :documentation "Backup of initial value of `taker', which may be a
+function or `gt-taker' object. The value will be normalized at the start of
+every translation and stored into the `taker' slot. Avoid changing this during
+translation progress unless you are confident in what you are doing.")
+
+   (_engines
+    :type (or function gt-engine list)
+    :documentation "Backup of initial `engines' like above.")
+
+   (_render
+    :type (or function gt-render list)
+    :documentation "Backup of initial `render' like above.")
+
+   (bag :initform nil))
+  :documentation "Core component of the translation.
+
+The basic components of a translator is taker, engines and render:
+1. Use `gt-taker' to capture the source text and target
+2. Then use one or more `gt-engine' to get the translate result
+3. At last use a `gt-render' to output the results to user.
+
+The start a translation, call `gt-start' on a translator instance like this:
+
+  (gt-start (make-instance `gt-translator
+                   :taker (gt-taker)
+                   :engines (gt-google-engine)
+                   :render (gt-buffer-render)))
+
+That is: 1) define the translator 2) executable `gt-start'.")
+
+(defconst gt-lang-codes
+  '((en . "English")
+    (zh . "Chinese")
+    (ru . "Russian")
+    (fr . "French")
+    (it . "Italian")
+    (es . "Spanish")
+    (de . "German")
+    (ko . "Korean")
+    (ja . "Japanese")
+    (yue . "Cantonese")
+    (af . "Afrikaans")
+    (ak . "Akan")
+    (am . "Amharic")
+    (ar . "Arabic")
+    (as . "Assamese")
+    (asa . "Asu")
+    (az . "Azerbaijani")
+    (be . "Belarusian")
+    (bem . "Bemba")
+    (bez . "Bena")
+    (bg . "Bulgarian")
+    (bm . "Bambara")
+    (bn . "Bengali")
+    (bo . "Tibetan")
+    (bs . "Bosnian")
+    (ca . "Catalan")
+    (cgg . "Chiga")
+    (chr . "Cherokee")
+    (cs . "Czech")
+    (da . "Danish")
+    (dav . "Taita")
+    (ebu . "Embu")
+    (ee . "Ewe")
+    (el . "Greek")
+    (eo . "Esperanto")
+    (et . "Estonian")
+    (eu . "Basque")
+    (fa . "Persian")
+    (ff . "Fulah")
+    (fi . "Finnish")
+    (fil . "Filipino")
+    (fo . "Faroese")
+    (ga . "Irish")
+    (gl . "Galician")
+    (gsw . "Swiss German")
+    (gu . "Gujarati")
+    (guz . "Gusii")
+    (gv . "Manx")
+    (ha . "Hausa")
+    (haw . "Hawaiian")
+    (he . "Hebrew")
+    (hi . "Hindi")
+    (hr . "Croatian")
+    (hu . "Hungarian")
+    (hy . "Armenian")
+    (id . "Indonesian")
+    (ig . "Igbo")
+    (ii . "Sichuan Yi")
+    (is . "Icelandic")
+    (jmc . "Machame")
+    (ka . "Georgian")
+    (kab . "Kabyle")
+    (kam . "Kamba")
+    (kde . "Makonde")
+    (kea . "Kabuverdianu")
+    (khq . "Koyra Chiini")
+    (ki . "Kikuyu")
+    (kk . "Kazakh")
+    (kl . "Kalaallisut")
+    (kln . "Kalenjin")
+    (km . "Khmer")
+    (kn . "Kannada")
+    (kok . "Konkani")
+    (kw . "Cornish")
+    (lag . "Langi")
+    (lg . "Ganda")
+    (lt . "Lithuanian")
+    (luo . "Luo")
+    (luy . "Luyia")
+    (lv . "Latvian")
+    (mas . "Masai")
+    (mer . "Meru")
+    (mfe . "Morisyen")
+    (mg . "Malagasy")
+    (mk . "Macedonian")
+    (ml . "Malayalam")
+    (mr . "Marathi")
+    (ms . "Malay")
+    (mt . "Maltese")
+    (my . "Burmese")
+    (naq . "Nama")
+    (nd . "North Ndebele")
+    (ne . "Nepali")
+    (nl . "Dutch")
+    (nn . "Norwegian Nynorsk")
+    (nyn . "Nyankole")
+    (om . "Oromo")
+    (or . "Oriya")
+    (pa . "Punjabi")
+    (pl . "Polish")
+    (ps . "Pashto")
+    (pt . "Portuguese")
+    (rm . "Romansh")
+    (ro . "Romanian")
+    (rof . "Rombo")
+    (rw . "Kinyarwanda")
+    (rwk . "Rwa")
+    (saq . "Samburu")
+    (seh . "Sena")
+    (ses . "Koyraboro Senni")
+    (sg . "Sango")
+    (shi . "Tachelhit")
+    (si . "Sinhala")
+    (sk . "Slovak")
+    (sl . "Slovenian")
+    (sn . "Shona")
+    (so . "Somali")
+    (sq . "Albanian")
+    (sr . "Serbian")
+    (sv . "Swedish")
+    (sw . "Swahili")
+    (ta . "Tamil")
+    (te . "Telugu")
+    (teo . "Teso")
+    (th . "Thai")
+    (ti . "Tigrinya")
+    (to . "Tonga")
+    (tr . "Turkish")
+    (tzm . "Central Morocco Tamazight")
+    (uk . "Ukrainian")
+    (ur . "Urdu")
+    (uz . "Uzbek")
+    (vi . "Vietnamese")
+    (vun . "Vunjo")
+    (xog . "Soga")
+    (cy . "Welsh")
+    (yo . "Yoruba")
+    (zu . "Zulu")))
+
+(defconst gt-word-classes
+  '(pron adj adv adt art aux conj prep det abbr int vt vi v n a))
+
+(defvar gt-current-command nil)
+
+(defvar gt-current-translator nil)
+
+(defvar-local gt-tracking-marker nil)
+
+(cl-defgeneric gt-init (object &rest _args)
+  "Initialization for OBJECT.")
+
+(cl-defgeneric gt-desc (object)
+  "Return the string representation of the OBJECT."
+  (format "%s" (eieio-object-class object)))
+
+\f
+;;; Utility
+
+(defun gt-aref (vector &rest ns)
+  "Recursively find the element in VECTOR. NS is indexes, as the path."
+  (while ns
+    (setq vector (aref vector (pop ns))))
+  vector)
+
+(defmacro gt-orefs (instance &rest slots)
+  "Get all the SLOTS in INSTANCE as a list."
+  `(list ,@(cl-loop for slot in slots
+                    collect `(slot-value ,instance ',slot))))
+
+(defun gt-ensure-plain (obj &rest args)
+  "Ensure OBJ is non-function and non-symbol.
+If OBJ is a function, call it with ARGS and return the result.
+If OBJ is symbol, return its value."
+  (if (functionp obj) (apply obj args) obj))
+
+(defun gt-functionp (object)
+  "Check if OBJECT is a function and not a class."
+  (and (functionp object) (not (class-p object))))
+
+(defun gt-clean-properties (text &optional props)
+  "Remove specified PROPS from TEXT."
+  (unless props (setq props '(gt-result gt-task gt-done read-only)))
+  (remove-list-of-text-properties 0 (length text) props text)
+  text)
+
+(defun gt-face-lazy (str face &optional display)
+  "Propertize STR to add FACE only when no face exists on it.
+Set DISPLAY property to STR if it is not nil.
+
+For example:
+
+  (gt-face-lazy s `font-lock-doc-face `(space (:width 30) raise 0.5))
+
+When `space' in DISPLAY, prepend a space to the STR."
+  (if (or (null face)
+          (with-temp-buffer
+            (insert str)
+            (or (text-property-search-backward 'face)
+                (text-property-search-backward 'font-lock-face)
+                (text-property-search-backward 'display))))
+      str
+    (let ((r (if face (propertize str 'face face) str))
+          (ds (cl-loop for (k v) on display by #'cddr
+                       if (eq k 'space) append v into spaces
+                       else append (list k v) into normals
+                       finally (return (cons normals spaces)))))
+      (when-let (normals (car ds))
+        (setq r (propertize r 'display normals)))
+      (when-let (spaces (cdr ds))
+        (setq r (concat (propertize " " 'display (cons 'space spaces)) r)))
+      r)))
+
+(defun gt-line-height-separator (pixel)
+  "This can set line-height of the following line to PIXEL."
+  (concat (propertize "\s" 'display `(space :height (,pixel)))
+          (propertize "\n" 'line-height t)))
+
+(defmacro gt-simple-keymap (&rest definitions)
+  "A small wrapper to define new keymap for DEFINITIONS."
+  `(let ((map (make-sparse-keymap)))
+     (progn
+       ,@(cl-loop for (key value) on definitions by #'cddr
+                  for v = (if (memq (car value) '(function quote lambda))
+                              value
+                            `(lambda () (interactive) ,value))
+                  collect `(define-key map ,key ,v)))
+     map))
+
+(defun gt-collect-text (text-or-bounds)
+  "Collect the text corresponding to parts in TEXT-OR-BOUNDS."
+  (let* ((items (ensure-list text-or-bounds)) (car (car items)))
+    (unless (stringp car)
+      (with-current-buffer (if (bufferp car) car (current-buffer))
+        (setq items
+              (mapcar (lambda (bd) (buffer-substring (car bd) (cdr bd)))
+                      (if (bufferp car) (cdr items) items)))))
+    (mapcar #'gt-clean-properties items)))
+
+(defun gt-lookup-password (&rest params)
+  "Query password stored in '.authinfo'.
+PARAMS contains search keys like :user, :host same as `auth-source-search'."
+  (when-let (secret (plist-get (car (apply 'auth-source-search params)) :secret))
+    (if (functionp secret)
+        (funcall secret)
+      secret)))
+
+(defun gt-insert-text-at-marker (str marker &optional end-marker keep-cursor)
+  "Insert STR to position of MARKER.
+If END-MARKER exists, delete the content between the markers first.
+If KEEP-CURSOR is not nil, keep the cursor at front."
+  (with-current-buffer (marker-buffer marker)
+    (let ((inhibit-read-only t))
+      (when end-marker
+        (set-marker-insertion-type end-marker t)
+        (delete-region marker end-marker))
+      (goto-char marker)
+      (deactivate-mark)
+      (if keep-cursor
+          (save-excursion (insert str))
+        (insert str)))))
+
+(defun gt-parse-html-dom (str)
+  "Parse html STR and return the DOM body."
+  (with-temp-buffer
+    (insert str)
+    (xml-remove-comments (point-min) (point-max))
+    (dom-by-tag (libxml-parse-html-region) 'body)))
+
+(declare-function let-alist--deep-dot-search "ext:let-alist" t t)
+
+(defmacro gt-plist-let (varlist &rest body)
+  "Let-bind dotted symbols to plist and execute BODY like `let-alist'.
+
+The last form of VARLIST is the plist to be bound with dot, bind other variables
+like those in `let*'.
+
+For example:
+
+  (gt-plist-let plist (list .a .b))
+
+  (gt-plist-let \\=`(:a 3 :b 4) (+ .a .b))
+
+  (gt-plist-let ((x 1) \\='(:y 2 :z 3)) (+ x .y .z))
+
+See gt-tests.el for details."
+  (declare (indent 1) (debug t))
+  (require 'let-alist)
+  (let ((var (make-symbol "plist")))
+    `(let* (,@(if (or (atom varlist)
+                      (eq (car varlist) 'quote)
+                      (eq (car varlist) '\`))
+                  `((,var ,varlist))
+                `(,@(butlast varlist) ,(cons var (last varlist))))
+            ,@(cl-loop for (.k . k) in (delete-dups (let-alist--deep-dot-search body))
+                       collect `(,.k (plist-get ,var ,(intern (format ":%s" k))))))
+       ,@body)))
+
+(defun gt-make-completion-table (items &optional order)
+  "Make completion table that ensure ITEMS sort by original ORDER."
+  (lambda (input pred action)
+    (if (eq action 'metadata)
+        `(metadata (category . "go-translate")
+                   (display-sort-function . ,(or order #'identity)))
+      (complete-with-action action items input pred))))
+
+(defmacro gt-read-from-buffer (&rest forms)
+  "Read text in a new created buffer interactively.
+
+Available options can be specified as keyword arguments:
+  :buffer           The buffer used for reading user input
+  :initial-contents The initial contents of the buffer
+  :keymap           The keymap to use in the buffer
+  :window-config    The window configuration for the window display the buffer
+  :catch            Catch tag used in for inner `throw'
+  :apply-key        The keybinding for applying changes (default: \"C-c C-c\")
+  :cancel-key       The keybinding for canceling changes (default: \"C-c C-k\")
+  :apply-hook       Hook run before apply operation
+  :cancel-hook      Hook run before cancel operation
+
+Other FORMS is used to do extra initialization, you can config head line, mode
+line format or bind keys here.
+
+User can modify the buffer, and submit with `apply-key', then the contents of
+the buffer will returned as the result."
+  (gt-plist-let
+      ((cl-loop for item in forms by #'cddr
+                while (keywordp item) append (list (pop forms) (pop forms))))
+    (let ((buffer (or .buffer (format "*gt-%s*" (gensym "tmp-"))))
+          (tag (or .catch ''gt-read-from-buffer))
+          (apply-key  (or .apply-key  "C-c C-c"))
+          (cancel-key (or .cancel-key "C-c C-k")))
+      `(if (and (cl-plusp (recursion-depth)) (buffer-live-p (get-buffer ,buffer)))
+           (throw 'exit t) ; avoid recursion
+         (save-window-excursion
+           (catch ,tag
+             (unwind-protect
+                 (with-current-buffer (get-buffer-create ,buffer)
+                   ,(if .keymap `(use-local-map ,.keymap))
+                   (local-set-key (kbd ,apply-key)
+                                  (lambda ()
+                                    (interactive)
+                                    ,(if .apply-hook `(funcall ,.apply-hook))
+                                    (throw ,tag (buffer-string))))
+                   (local-set-key (kbd ,cancel-key)
+                                  (lambda ()
+                                    (interactive)
+                                    ,(if .cancel-hook `(funcall ,.cancel-hook))
+                                    (throw ,tag nil)))
+                   (add-hook 'kill-buffer-query-functions
+                             (lambda () ; only allow quit with C-c C-k
+                               (not (and (memq this-command '(kill-buffer kill-this-buffer))
+                                         (message "Quit the buffer using `%s' please" ,cancel-key))))
+                             nil t)
+                   (erase-buffer)
+                   ,(if .initial-contents `(insert ,.initial-contents))
+                   ,@forms
+                   (pop-to-buffer ,buffer ,.window-config)
+                   (recursive-edit))
+               (ignore-errors (kill-buffer ,buffer)))))))))
+
+(cl-defgeneric gt-word-p (lang text)
+  "Whether TEXT is a single word in LANG.
+This is a generic method, improve it for specific LANG as you wish."
+  (setq text (string-trim text))
+  (if (eq lang 'zh)
+      (and (< (length text) 5)
+           (with-temp-buffer
+             (insert text)
+             (goto-char (point-min))
+             (while (re-search-forward "\\s." nil t) (replace-match ""))
+             (equal (thing-at-point 'word) text)))
+    (with-temp-buffer
+      (insert text)
+      (equal (thing-at-point 'word) text))))
+
+\f
+;;; Validator Trait
+
+;; add validate feature to `taker/engine/render/cache' through `:if' slot
+
+(cl-defgeneric gt-valid-literally (v text src tgt)
+  "Check literally symbol or form V to determine next step.
+
+It can be used by cacher and engine.
+
+V should be a symbol like `word', `parts', `src:en', `tgt:en', `selection',
+`xxx-mode', `read-only', or symbol with `not-' or `no-' prefix such as
+`not-word', `not-src:en', `no-parts' etc.
+
+V also can be a list form grouping above symbols with `and/or', for example:
+
+  (or word not-parts (and tgt:cn #`xxxx)).
+
+TEXT, SRC and TGT are translation text and targets.
+
+This is a generic method, you can extend the V as you wish."
+  (:method :around (v text src tgt)
+           (cond ((or (null v) (eq v t)) v)
+                 ((symbolp v)
+                  (setq text (ensure-list text))
+                  (let ((vn (symbol-name v)) (notp nil))
+                    (when (string-match "^not?-\\(.*\\)" vn) ; not-xxx style
+                      (setq vn (match-string 1 vn))
+                      (setq v (intern vn))
+                      (setq notp t))
+                    (funcall
+                     (if notp #'not #'identity)
+                     (cl-call-next-method v text src text))))
+                 ((consp v) ; compose as: (or word (and not-parts src:zh))
+                  (unless (member (car v) '(or and)) (push 'or v))
+                  (funcall (if (eq (pop v) 'or) #'cl-some #'cl-every) #'identity
+                           (mapcar (lambda (x) (gt-valid-literally x text src tgt)) v)))))
+  (let ((vn (symbol-name v)))
+    (cond
+     ((eq v 'word) (and (car text) ; text is word
+                        (not (cdr text))
+                        (gt-word-p (intern-soft src) (car text))))
+     ((eq v 'parts) (cdr text)) ; text is multiple parts
+     ((eq v 'selection) (use-region-p)) ; use region is active
+     ((eq v 'read-only) buffer-read-only) ; buffer is read-only
+     ((string-suffix-p "-mode" vn) ; major-mode or minor-mode
+      (if (boundp v) (symbol-value v) (eq major-mode v)))
+     ((string-match "^\\(src\\|tgt\\):\\(.+\\)" vn)
+      (member (match-string 2 vn) ; src/tgt is specific one, as src:en
+              (mapcar (lambda (item) (format "%s" item))
+                      (ensure-list (if (equal (match-string 1 vn) "src") src tgt))))))))
+
+(cl-defgeneric gt-valid (component &rest _args)
+  "Validate COMPONENT, return t if it's available."
+  (:method ((component gt-validator) carrier)
+           "Determine if current COMPONENT is available through `if' slot.
+Slot value can be a function, a symbol or a list. CARRIER maybe a task, a
+translator or a list to provide more validation data."
+           (let (text src tgt)
+             (cond
+              ((cl-typep carrier 'gt-task)
+               (setq text (oref (oref carrier translator) text)
+                     src (oref carrier src)
+                     tgt (oref carrier tgt)))
+              ((cl-typep carrier 'gt-translator)
+               (setq text (oref carrier text)
+                     src (car (oref carrier target))
+                     tgt (cdr (oref carrier target))))
+              ((consp carrier)
+               (setq text (car carrier)
+                     src (cadr carrier)
+                     tgt (caddr carrier)))
+              (t (user-error "Carrier type is not supported")))
+             (with-slots (if) component
+               (cond ((not (slot-boundp component 'if)) t)
+                     ((and (functionp if) (= 2 (car (func-arity if))))
+                      (funcall if component carrier))
+                     ((and (functionp if) (= 1 (car (func-arity if))))
+                      (funcall if carrier))
+                     (t (gt-valid-literally if text src tgt)))))))
+
+\f
+;;; Logging
+
+(defvar gt-log-buffer-name "*gt-log*"
+  "Log buffer for translator.")
+
+(defun gt-log (tag &rest messages)
+  "Log MESSAGES to `gt-log-buffer-name'.
+TAG is a label for message being logged."
+  (declare (indent 1))
+  (when gt-debug-p
+    (with-current-buffer (get-buffer-create gt-log-buffer-name)
+      (goto-char (point-max))
+      (if (and (null messages) (stringp tag))
+          (insert tag)
+        (let* ((tag (when tag
+                      (concat
+                       (gt-face-lazy (cl-subseq (format "%-.1f" (time-to-seconds)) 6) 'gt-logger-buffer-timestamp-face)
+                       (gt-face-lazy (if (ignore-errors (string-blank-p tag)) tag (format " [%s]" tag)) 'gt-logger-buffer-tag-face)
+                       " ")))
+               (count (if (numberp (car messages)) (pop messages) (length tag)))
+               (messages (mapconcat (lambda (s) (format "%s" s)) (delq nil messages) "\n"))
+               (msg (if (string-blank-p messages)
+                        messages
+                      (with-temp-buffer
+                        (insert messages)
+                        (goto-char (point-min))
+                        (unless (= (length tag) count)
+                          (insert "\n")
+                          (goto-char (point-min)))
+                        (while (re-search-forward "\n" nil t)
+                          (save-excursion
+                            (beginning-of-line)
+                            (insert (make-string count ? ))))
+                        (buffer-string)))))
+          (insert (or tag "") (or msg "") "\n"))))))
+
+(defmacro gt-log-funcall (fmt &rest objects)
+  `(gt-log ""
+     (gt-face-lazy
+      (format ,(if (string-match-p "^[ \n]" fmt) fmt (concat "gt-" fmt))
+              ,@(cl-loop
+                 for o in objects collect
+                 `(let ((obj ,o)) (if (eieio-object-p obj) (gt-desc obj) obj))))
+      'font-lock-doc-face)))
+
+\f
+;;; Caching
+
+(defclass gt-cacher (gt-validator)
+  ((storage :initform nil :documentation "Where to save the caches."))
+  "Used to cache the translate results."
+  :abstract t)
+
+(defcustom gt-cache-alive-time (* 30 60)
+  "Default cache alive time."
+  :type 'integer
+  :group 'go-translate)
+
+(defvar gt-default-cacher nil)
+
+;; generic
+
+(defun gt-cacher-barking ()
+  (unless gt-default-cacher
+    (user-error "Make sure `gt-default-cacher' is properly configured. eg:\n
+ (setq gt-default-cacher (gt-memory-cacher))")))
+
+(cl-defgeneric gt-cache-get (_cacher _key &rest _)
+  "Query result of KEY from CACHER."
+  (:argument-precedence-order _key _cacher)
+  (gt-cacher-barking))
+
+(cl-defgeneric gt-cache-set (_cacher _key &rest _)
+  "Save result of KEY to CACHER."
+  (:argument-precedence-order _key _cacher)
+  (gt-cacher-barking))
+
+(cl-defgeneric gt-cache-purge (cacher &optional only-expired)
+  "Purge the storage of CACHER.
+If ONLY-EXPIRED not nil, purge caches expired only.")
+
+;; implement of caching in memory
+
+(defclass gt-memory-cacher (gt-cacher gt-single) ())
+
+(cl-defmethod gt-cache-get ((cacher gt-memory-cacher) key)
+  (with-slots (storage expired) cacher
+    (when-let (cache (ignore-errors (gethash key storage)))
+      (if (> (time-to-seconds) (cdr cache))
+          (remhash key storage)
+        (gt-log 'memory-cacher (format "get [%s] from caches (%s)" key (hash-table-count storage)))
+        (car cache)))))
+
+(cl-defmethod gt-cache-set ((cacher gt-memory-cacher) key value)
+  (with-slots (storage) cacher
+    (unless storage
+      (setf storage (make-hash-table :test #'equal)))
+    (if (null value)
+        (remhash key storage)
+      (let ((existp (gethash key storage))
+            (etime (+ (time-to-seconds) gt-cache-alive-time)))
+        (puthash key (cons value etime) storage)
+        (gt-cache-purge cacher t)
+        (gt-log 'memory-cacher
+          (format "%s [%s] to caches (%s)" (if existp "update" "add") key (hash-table-count storage)))))))
+
+(cl-defmethod gt-cache-purge ((cacher gt-memory-cacher) &optional only-expired)
+  (with-slots (storage) cacher
+    (when storage
+      (if only-expired
+          (maphash (lambda (key value)
+                     (when (> (time-to-seconds) (cdr value))
+                       (remhash key storage)))
+                   storage)
+        (clrhash storage)))))
+
+;; caching task
+
+(cl-defmethod gt-valid ((cacher gt-cacher) (task gt-task) pred text)
+  (with-slots (src tgt) task
+    (or (and (null pred) (not (slot-boundp cacher 'if)))
+        (let ((pred (or pred (oref cacher if))))
+          (or (and (functionp pred) (funcall pred task))
+              (gt-valid-literally pred text src tgt))))))
+
+(cl-defmethod gt-cache-key ((task gt-task) &optional n)
+  "Generate caching key for the Nth text in TASK."
+  (with-slots (text src tgt engine parse) task
+    (sha1 (format "%s:%s:%s:%s:%s:%s"
+                  (nth (or n 0) (ensure-list text)) src tgt
+                  (gt-desc engine)
+                  (gt-desc (oref engine parse))
+                  (or (oref engine salt) "1")))))
+
+(cl-defmethod gt-cache-get (cacher (task gt-task))
+  (let ((results (cl-loop
+                  for i from 1 to (length (ensure-list (oref task text)))
+                  collect (gt-cache-get cacher (gt-cache-key task (1- i))))))
+    (when (cl-some #'identity results)
+      (gt-log 'render (format "%s: result from cache!" (oref task id))))
+    results))
+
+(cl-defmethod gt-cache-set (cacher (task gt-task) &optional pred)
+  (with-slots (text src tgt res) task
+    (cl-loop for c in text
+             for r in res
+             for i from 0
+             if (gt-valid cacher task pred c)
+             do (gt-cache-set cacher (gt-cache-key task i) r))))
+
+(defun gt-purge-cache (cacher)
+  "Purge storage of CACHER."
+  (interactive (list gt-default-cacher))
+  (when (y-or-n-p (format "Purge all caches from %s now?" (gt-desc cacher)))
+    (gt-cacher-barking)
+    (gt-cache-purge (or cacher gt-default-cacher))
+    (message "Cache purged.")))
+
+\f
+;;; Http Client
+
+(require 'gt-httpx)
+
+(defvar plz-curl-program)
+
+(defvar gt-default-http-client
+  (if (and (require 'plz nil t) (executable-find plz-curl-program))
+      (gt-plz-http-client)
+    (gt-url-http-client)))
+
+(cl-defmethod gt-request (&rest args &key url method headers data filter done fail sync retry cache)
+  "Simple wrapper for http client, and add cache support."
+  (ignore method headers filter sync retry)
+  (let ((client (gt-ensure-plain gt-default-http-client (url-host (url-generic-parse-url url)))))
+    (if (and client (eieio-object-p client) (object-of-class-p client 'gt-http-client))
+        (let* ((ckey (sha1 (format "%s%s" url data)))
+               (donefn (when done
+                         (lambda (raw)
+                           ;; try to cache the result
+                           (when cache
+                             (let ((gt-cache-alive-time (if (numberp cache) cache gt-cache-alive-time)))
+                               (gt-cache-set gt-default-cacher ckey raw)))
+                           (funcall done raw)))))
+          ;; try to get from cache first
+          (if-let (r (and cache (gt-cache-get gt-default-cacher ckey)))
+              (progn (gt-log 'cacher (format "Find %s in cache..." ckey))
+                     (if donefn (funcall donefn r) r))
+            (cl-remf args :cache)
+            (apply #'gt-request client `(:done ,donefn ,@args))))
+      (let ((errmsg "Make sure `gt-default-http-client' is available. eg:\n\n(setq gt-default-http-client (gt-url-http-client))\n\n\n"))
+        (if fail (funcall fail errmsg) (user-error errmsg))))))
+
+\f
+;;; Taker
+
+(defcustom gt-langs nil
+  "Translation languages or targets.
+
+This is a list which element is a language string or symbol described in
+ISO 939-1. The taker will use this to determine the translate source and target.
+
+For example, if you want to translate between a and b, just set like:
+
+  (setq gt-langs `(a b)) or (setq gt-langs `(\"a\" \"b\"))
+
+If your working languages is more than two, just set like:
+
+  (setq gt-langs `(a b c d ..))
+
+The taker will choose the most possible translate source and target from
+the list, also you can switch between them in the following translation.
+
+Notice, the value can be overrided by `langs' slot in taker of translator. So
+config it for specific translator using slot is effectively."
+  :type '(repeat (choice string symbol))
+  :group 'go-translate)
+
+(defcustom gt-polyglot-p nil
+  "Toggle polyglot for translation.
+
+If this is t, the text will be translated into multiple targets.
+
+For example, assuming `gt-langs' or slot of `langs' is (a b c), the text
+may be translated from a to a and c if this is t, but translated from
+a to b or a to c if this is nil."
+  :type 'boolean
+  :group 'go-translate)
+
+(defvar gt-taker-text-things '(word paragraph sentence buffer line page list sexp defun symbol))
+
+(defvar gt-taker-pick-things '(word paragraph sentence line list sexp defun page symbol))
+
+(defcustom gt-taker-text 'word
+  "The initial text taken by taker of translator.
+
+If the region is actived, take the selection text. Otherwise, if this is a
+symbol, then try to use `thing-at-point' to take the text at point. If this
+is a function then invoke it and use its result as the initial text. Specially
+if this is t, take the text interactively.
+
+The default value is to take current active region or current word at point.
+
+Notice, the value can be overrided by `text' slot in taker of translator. So
+config it for specific translator using slot is effectively."
+  :type `(choice function
+                 (choice ,@(mapcar (lambda (item) `(const ,item)) gt-taker-text-things)))
+  :group 'go-translate)
+
+(defcustom gt-taker-pick 'paragraph
+  "Filter the initial text and pick some for translation.
+
+If this is nil, do not pick and use the initial text as source text to
+translate directly.
+
+If this is a symbol, then pick elements by thing from initial text for
+translation, thing is just like those in `thing-at-point'.
+
+If this is a function then pass the initial text into it and use its result
+as source text to translate.
+
+Specially if this is t, pick the text interactively.
+
+The default value is paragraph, that is, try to split initial text into
+paragraphs and do a multiple parts translation.
+
+Notice, the value can be overrided by `pick' slot in taker of translator. So
+config it for specific translator using slot is effectively."
+  :type `(choice function
+                 (choice ,@(mapcar (lambda (item) `(const ,item)) gt-taker-pick-things)))
+  :group 'go-translate)
+
+(defcustom gt-taker-prompt nil
+  "Whether or how to prompt for initial text and target.
+
+If you want to take the translation text and target interactively, set this to
+a non nil value.
+
+If this is t, then read and confirm using minibuffer. If this is `buffer' then
+open a new buffer instead to do the stuff.
+
+Notice, the value can be overrided by `prompt' slot in taker of translator. So
+config it for specific translator using slot is effectively."
+  :type '(choice (const :tag "Off" nil)
+                 (const :tag "With-Minibuffer" t)
+                 (const :tag "With-Buffer" buffer)
+                 symbol)
+  :group 'go-translate)
+
+(defcustom gt-lang-rules
+  (list (cons 'ja "[\u3040-\u30FF]")
+        (cons 'ko "[\uAC00-\uD7A3]")
+        (cons 'ru "[\u0410-\u044F]") ; if matched then text is ru, otherwise it's not
+        (cons 'zh (lambda () (re-search-forward "[\u4e00-\u9fa5]" nil t))))
+  "Language match rules used to check the language of a given text.
+
+This is alist, key is the language and value is a regexp string or function.
+
+This is used by `gt-available-langs' to determine who are source language from
+target list by matching this. Text will match against the rules one by one,
+if matched, the language in rule serves as the source language, otherwise the
+language is excluded from the source candidates.
+
+This is not perfect. It's not an easy thing to check the language precisely.
+Maybe use the third party, for example python's langid, to check language is a
+good idea.
+
+Please add rule for your own languages if possible, or fixup the mistakes in the
+default rules to fit your need."
+  :type '(alist :key-type (symbol :tag "Lang")
+                :value-type (choice (function :tag "Function Rule")
+                                    (regexp :tag "Regexp rule")))
+  :group 'go-translate)
+
+(defvar gt-skip-lang-rules-p nil)
+
+(defvar gt-ignore-target-history-p nil)
+
+(defvar gt-last-target nil)
+
+(defvar gt-target-history nil)
+
+(cl-defgeneric gt-langs-maybe (text langs)
+  "Which languages this TEXT maybe belongs to, pick from LANGS."
+  (let* ((srcs langs)
+         (hit (catch 'gt-langs
+                (with-temp-buffer
+                  ;; insert text
+                  (insert text)
+                  ;; clear punctuations
+                  (goto-char (point-min))
+                  (while (re-search-forward "\\s.\\|\n" nil t) (replace-match ""))
+                  ;; apply rules
+                  (cl-loop with rules = (cl-loop for r in gt-lang-rules if (member (car r) langs) collect r)
+                           for (l . m) in rules
+                           do (goto-char (point-min))
+                           do (if (if (functionp m) (funcall m) (re-search-forward m nil t))
+                                  (throw 'gt-langs l) ; hit the one, return directly
+                                (setq srcs (remove l srcs))) ; not the one, exclude it
+                           finally (return nil))))))
+    (if hit (ensure-list hit) srcs)))
+
+(cl-defgeneric gt-thing-at-point (thing _mode)
+  "Retrieve text at point or from the selected region.
+
+If there is an active region, return its bounds. Otherwise return
+the bounds of the thing at point as a list of cons cells.
+
+THING should be an element of `gt-taker-text-things', it is used as the type
+of thing to take. If it is nil, it uses the value of `gt-taker-text'.
+If it is t, prompt the user to choose one from the list.
+
+This is a generic method, so you can extend the THING or override it for
+specific MODE."
+  (:method :around (thing mode)
+           (if (use-region-p)
+               (region-bounds)
+             (when thing
+               (cl-call-next-method thing mode))))
+  (unless (member thing gt-taker-text-things)
+    (setq thing (intern (completing-read "Take _ at point as source text: " gt-taker-text-things nil t nil 'gt-text-hist))))
+  (gt-log 'taker (format "take text by: %s" thing))
+  (if (eq thing 'buffer)
+      (let ((beg (save-excursion
+                   (goto-char (point-min))
+                   (skip-chars-forward " \t\n")
+                   (beginning-of-line)
+                   (point)))
+            (end (save-excursion
+                   (goto-char (point-max))
+                   (skip-chars-backward " \t\n")
+                   (point))))
+        (when (> end beg)
+          (list (cons beg end))))
+    (when-let (rt (bounds-of-thing-at-point thing))
+      (list rt))))
+
+(cl-defgeneric gt-forward-thing (thing _mode)
+  "Get bound of THING after point and goto the end of THING.
+
+THING is is the ones in `gt-taker-pick-things'.
+
+This is a generic method, so you can override it for specific MODE."
+  (let ((ori (point)) pt end)
+    (pcase thing
+      ((or 'word 'symbol) (skip-syntax-forward "^w"))
+      (_ (skip-chars-forward " \r\n\t\f")
+         (when (string-match-p "[\n\r]" (buffer-substring ori (point)))
+           (skip-chars-backward " \t\f"))))
+    (setq pt (point))
+    (forward-thing thing)
+    (setq end (if (memq thing '(paragraph))
+                  (point)
+                (save-excursion (skip-chars-backward " \r\n\t\f") (point))))
+    (when (> end pt) (cons pt end))))
+
+(defun gt-available-langs (langs text)
+  "Find available translation languages for given TEXT.
+
+LANGS is a list of languages to choose from, TEXT is used to determine
+the available ones according rules in `gt-taker-lang-match-alist'.
+
+Return a list of language pairs (src . rest) where src is the source
+language and rest is the target languages. Rest should be a list contains
+one or more languages."
+  (if (cdr langs)
+      (setq langs (mapcar #'intern-soft langs))
+    (user-error "At least two languages should be configed, current: %s" langs))
+  (let* ((str (string-join (or (ensure-list text) "")))
+         (srcs (or (if gt-skip-lang-rules-p langs (gt-langs-maybe str langs))
+                   (user-error "Maybe no language match current translation")))
+         (pairs (if gt-polyglot-p
+                    (cl-loop for lang in srcs
+                             collect (cons lang (remove lang langs)))
+                  (cl-loop for lang in srcs
+                           append (cl-loop for tgt in (remove lang langs)
+                                           collect (list lang tgt))))))
+    (if gt-ignore-target-history-p
+        pairs
+      (let* ((head (cl-delete-duplicates (cl-remove-if-not (lambda (l) (member l pairs)) gt-target-history)))
+             (tail (cl-remove-if (lambda (l) (member l head)) pairs)))
+        (append head tail)))))
+
+(defun gt-pick-items-by-thing (text-or-bounds &optional thing pred)
+  "Pick elements by THING from TEXT-OR-BOUNDS.
+
+THING is the one in `gt-taker-pick-things', PRED is a function with one
+argument that used to filter the pick element.
+
+TEXT-OR-BOUNDS is a string or bound cell in the buffer. If it is a string,
+should return a list of strings representing the pieces picked from the text.
+If it is a bound, return a list of sub-bounds."
+  (if (null thing)
+      text-or-bounds
+    (unless (memq thing gt-taker-pick-things)
+      (setq thing (intern (completing-read "Pick _ for source text: " gt-taker-pick-things nil t nil 'gt-pick-hist))))
+    (cl-flet ((ps (beg end mode)
+                (save-excursion
+                  (save-restriction
+                    (narrow-to-region beg end)
+                    (goto-char (point-min))
+                    (let (bds)
+                      (while (not (save-excursion (skip-chars-forward " \r\n\t\f") (eobp)))
+                        (when-let (bd (gt-forward-thing thing mode))
+                          (cl-destructuring-bind (a . b) bd
+                            (when (or (null pred) (funcall pred (buffer-substring a b)))
+                              (push (cons a b) bds)))))
+                      (nreverse bds))))))
+      (gt-log 'taker (format "pick from text by: %s" thing))
+      (let ((mode major-mode))
+        (cond ((stringp text-or-bounds)
+               (with-temp-buffer
+                 (insert text-or-bounds)
+                 (mapcar (lambda (p) (buffer-substring (car p) (cdr p)))
+                         (ps (point-min) (point-max) mode))))
+              ((consp text-or-bounds)
+               (let ((bd text-or-bounds)) ; (a . b) or (buffer (a . b))
+                 (with-current-buffer (if (bufferp (car bd)) (pop bd) (current-buffer))
+                   (if (consp (car bd)) (setq bd (car bd)))
+                   (ps (car bd) (cdr bd) mode))))
+              (t (user-error "Pick items error, maybe invalid input")))))))
+
+(cl-defgeneric gt-take (taker translator)
+  "Take source text and targets for TRANSLATOR.
+
+This is core method of TAKER. It combines `gt-text', `gt-target', `gt-prompt'
+and `gt-pick' together, saves text/bounds/target to be used in later translation
+into the translator instance at last.
+
+See type `gt-taker' for more description."
+  (:method :before ((taker gt-taker) translator)
+           (gt-log-funcall "take (%s %s)" taker translator))
+  (:method ((taker gt-taker) translator)
+           (with-slots (text bounds target keep) translator
+             (let ((prompt (and (null text) (null target)
+                                (if (slot-boundp taker 'prompt)
+                                    (oref taker prompt)
+                                  gt-taker-prompt)))
+                   (nopick (or (and text keep)
+                               (gt-functionp (if (slot-boundp taker 'text)
+                                                 (oref taker text)
+                                               gt-taker-text)))))
+               ;; 1) text
+               (unless text
+                 (let ((rs (ensure-list (gt-text taker translator))))
+                   ;; bounds: (buffer (a . b) (c . d))
+                   (if (stringp (car rs))
+                       (setf bounds (list (current-buffer)))
+                     (unless (bufferp (car rs))
+                       (push (current-buffer) rs))
+                     (setf bounds rs))
+                   ;; text: ("aaa" "bbb")
+                   (setf text (gt-collect-text rs))))
+               ;; 2) target
+               (unless target
+                 (setf target (gt-target taker translator)))
+               ;; 3) prompt
+               (when prompt (gt-prompt taker translator prompt))
+               (unless text (user-error "No source text found at all"))
+               ;; 4) pick
+               (unless nopick (gt-pick taker translator)))))
+  (:method :after ((taker gt-taker) translator)
+           (with-slots (text target) translator
+             (unless text
+               (user-error "Source Text should not be null"))
+             (with-slots (then) taker ; if `then' slot exists
+               (when (and (slot-boundp taker 'then) then (gt-functionp then))
+                 (funcall then translator))))))
+
+(cl-defgeneric gt-text (_taker translator)
+  "Used to take initial text for TRANSLATOR.
+Return text, text list or bounds in buffer. This is non-destructive."
+  (:method ((taker gt-taker) _translator)
+           "Return the text selected by the TAKER."
+           (let ((text (if (slot-boundp taker 'text)
+                           (oref taker text)
+                         gt-taker-text)))
+             (cond ((symbolp text) (gt-thing-at-point text major-mode))
+                   ((gt-functionp text) (funcall text))
+                   (t text)))))
+
+(cl-defgeneric gt-target (_taker translator &optional dir)
+  "Used to pick target for TRANSLATOR.
+Return the target will be used. If DIR is `next or `prev, return the next or
+previous one. This is non-destructive."
+  (:method ((taker gt-taker) translator &optional dir)
+           (when-let* ((langs (ensure-list
+                               (if (slot-boundp taker 'langs)
+                                   (oref taker langs)
+                                 gt-langs)))
+                       (tgts (if (car langs)
+                                 (gt-available-langs langs (oref translator text))
+                               (cdr langs)))
+                       (index (let ((n (cl-position gt-last-target tgts :test #'equal)))
+                                (if n (+ n (pcase dir ('next 1) ('prev -1) (_ 0))) 0)))
+                       (target (nth (if (>= index (length tgts))
+                                        0
+                                      (if (< index 0) (- (length tgts) 1) index))
+                                    tgts)))
+             (setq gt-last-target target))))
+
+(defvar gt-prompt-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map minibuffer-local-map)
+    (define-key map "\C-g" #'top-level)
+    (define-key map "\C-n" #'gt-prompt-next-target)
+    (define-key map "\C-p" (lambda () (interactive) (gt-prompt-next-target t)))
+    (define-key map "\C-l" #'delete-minibuffer-contents)
+    map)
+  "The keymap used when taker prompt with minibuffer.")
+
+(defvar gt-prompt-target nil)
+
+(defvar gt-prompt-overlay nil)
+
+(defun gt-prompt-prefix (target)
+  (when target
+    (format "[%s%s] "
+            (if-let (src (car target)) (format "%s > " src) "")
+            (mapconcat (lambda (s) (format "%s" s)) (ensure-list (cdr target)) ", "))))
+
+(defun gt-prompt-next-target (&optional backwardp)
+  "Switch to next target in prompt minibuffer.
+If BACKWARDP is not nil then switch to previous one."
+  (interactive)
+  (let ((gt-skip-lang-rules-p (or current-prefix-arg (string-blank-p (minibuffer-contents)))))
+    (setq gt-prompt-target
+          (gt-target (oref gt-current-translator taker)
+                     (make-instance (eieio-object-class gt-current-translator)
+                                    :text (list (minibuffer-contents)))
+                     (if backwardp 'prev 'next)))
+    (overlay-put gt-prompt-overlay 'before-string (gt-prompt-prefix gt-prompt-target))))
+
+(cl-defgeneric gt-prompt (_taker translator _type)
+  "Prompt user with inital text and target, then update TRANSLATOR with new values."
+  (:method :before ((_taker gt-taker) translator _type)
+           (when (cdr (oref translator text)) ; for solo part only
+             (user-error "Multiple part text cannot be prompted")))
+  (:method ((_ gt-taker) translator _type)
+           (with-slots (text bounds target) translator
+             (let* ((minibuffer-allow-text-properties t)
+                    (newtext (minibuffer-with-setup-hook
+                                 (lambda ()
+                                   (setq gt-prompt-target target)
+                                   (setq gt-prompt-overlay (make-overlay 1 2))
+                                   (overlay-put gt-prompt-overlay 'before-string (gt-prompt-prefix gt-prompt-target)))
+                               (read-from-minibuffer "Text: " (car text) gt-prompt-map))))
+               (when (string-blank-p newtext)
+                 (user-error "Text should not be null"))
+               (unless (equal newtext (car text))
+                 (setf bounds (list (car bounds))))
+               (setf text (ensure-list newtext))
+               (setf target gt-prompt-target)))))
+
+(cl-defgeneric gt-pick (_taker translator)
+  "Update TRANSLATOR with pieces picked from initial text or bounds."
+  (:method :around ((taker gt-taker) translator)
+           (with-slots (text bounds) translator
+             (unless (bufferp (car bounds)) ; ensure buffer is recorded
+               (push (current-buffer) bounds))
+             (when (and (not (cdr text)) ; for solo part only
+                        (if (slot-boundp taker 'pick) (oref taker pick) gt-taker-pick)) ; pick is non-nil
+               (cl-call-next-method taker translator))))
+  (:method ((taker gt-taker) translator)
+           (with-slots (text bounds) translator
+             (if-let* ((buf (car bounds))
+                       (car (if (cdr bounds) (cl-subseq bounds 0 2) ; buffer bound
+                              (or (car text) ; text
+                                  (user-error "Where should I pick from?"))))
+                       (pick (if (slot-boundp taker 'pick) (oref taker pick) gt-taker-pick))
+                       (res (ensure-list
+                             (cond
+                              ((gt-functionp pick) (funcall pick car))
+                              ((memq pick gt-taker-pick-things)
+                               (gt-pick-items-by-thing car pick (oref taker pick-pred)))
+                              ((symbolp pick) (gt-pick pick translator))))))
+                 ;; string pieces vs bound pieces
+                 (if (stringp (car res))
+                     (setf text res)
+                   (unless (bufferp (car res)) (push buf res))
+                   (setf bounds res text (gt-collect-text res)))
+               (user-error "Nothing picked, empty request")))))
+
+\f
+;;; Engine
+
+(defcustom gt-cache-p t
+  "Whether enable the cache."
+  :type 'boolean
+  :group 'go-translate)
+
+(defconst gt-text-delimiter "314141592926666")
+
+(defun gt-current-cacher (engine task)
+  "Return the cacher used by ENGINE for current TASK."
+  (let (cacher condition)
+    (when gt-cache-p
+      (let ((cc (gt-ensure-plain
+                 (if (slot-boundp engine 'cache) (if-let (c (gt-ensure-plain (oref engine cache) task)) c) t)
+                 task)))
+        (cond ((null cc))
+              ((eq cc t) (setq cacher gt-default-cacher condition nil))
+              ((symbolp cc) (setq cacher gt-default-cacher condition cc))
+              (t (setq cacher cc condition nil)))))
+    (cl-values cacher condition)))
+
+(cl-defmethod initialize-instance :after ((engine gt-engine) &rest _)
+  (unless (slot-boundp engine 'tag)
+    (oset engine tag (gt-desc engine))))
+
+(cl-defmethod gt-init ((engine gt-engine) task)
+  "Preprocessing text in TASK for ENGINE. Try cache first if possible."
+  (with-slots (id text cache) task
+    (gt-log-funcall "init (%s %s)" engine id)
+    ;; if stream, not cache
+    (if (gt-stream-p engine) (oset engine cache nil))
+    ;; query cache first
+    (when-let (cacher (car-safe (gt-current-cacher engine task)))
+      (setf cache (gt-cache-get cacher task)))
+    ;; only translate the parts not in cache
+    (if (and cache (cl-every #'identity cache))
+        (gt-next engine task)
+      (let ((text-left (if cache (cl-loop for c in text for r in cache unless r collect c) text)))
+        ;; join with delimiter if possible
+        (if-let (delimiter (if (slot-boundp engine 'delimiter) (oref engine delimiter) gt-text-delimiter))
+            (setf text (string-join text-left (concat "\n\n" delimiter "\n\n")))
+          (setf text text-left))))))
+
+(cl-defgeneric gt-translate (engine task &optional next)
+  "Translate or transform text in TASK and parse the results using ENGINE.
+
+The raw response will save into `res' slot of translator. Then NEXT runs, that
+will do the parse and render job. See type `gt-engine' for more description."
+  (:method :around ((engine gt-engine) task &optional _next)
+           (gt-log-funcall "translate (%s %s)" engine (oref task id))
+           (gt-init engine task)
+           (with-slots (id text err res render translator process) task
+             (unless (or res err)
+               (gt-log 'next (format "%s: %s prepare to translate" id (gt-desc engine)))
+               (let ((ret (cl-call-next-method
+                           engine task
+                           (lambda (task)
+                             (if (equal (oref translator version) (oref task version))
+                                 (condition-case err1
+                                     (if (gt-stream-p engine)
+                                         (if gt-http-client-stream-abort-flag
+                                             (gt-log 'next (format "%s: %s translate streaming abort!" id (gt-desc engine)))
+                                           (gt-next-for-stream render task))
+                                       (gt-log 'next (format "%s: %s translate success!" id (gt-desc engine)))
+                                       (gt-next engine task))
+                                   (gt-fail task err1))
+                               (gt-log 'next (format "%s: ----- expired -----" id)))))))
+                 (if (processp ret) (setf process ret))))))
+  (:method ((engine gt-engine) _task _next)
+           (user-error "Method `gt-translate' of %s is not implement" (gt-desc engine))))
+
+(cl-defgeneric gt-parse (_parser task)
+  "Parse results of TASK to user-friendly string."
+  (:method :before ((parser gt-parser) task)
+           (gt-log-funcall "parse (%s %s)" parser (oref task id)))
+  (:method ((_ gt-parser) _task) nil))
+
+(cl-defgeneric gt-stream-p (_engine)
+  "Whether streaming query is on for ENGINE."
+  nil)
+
+(cl-defgeneric gt-next (_engine task)
+  "Chain to the parse and render phase for TASK."
+  (:method ((engine gt-engine) task)
+           (gt-log-funcall "next (%s %s)" engine (oref task id))
+           (with-slots (text res meta cache translator) task
+             (with-slots (parse delimiter then) engine
+               ;; parse
+               (when res
+                 (when-let (parser (and (slot-boundp engine 'parse) parse))
+                   (if (gt-functionp parser)
+                       (funcall parser task)
+                     (gt-parse parser task)))
+                 ;; split with delimiter if possible
+                 (when-let (delimiter (and (stringp res) (if (slot-boundp engine 'delimiter) delimiter gt-text-delimiter)))
+                   (setf res (mapcar (lambda (item) (string-trim item "\n+")) (split-string res delimiter))))
+                 (setf res (ensure-list res))
+                 ;; run res-hook if possible
+                 (when-let (hook (plist-get meta :res-hook))
+                   (setf res (funcall hook res))))
+               ;; resume text and merge cache to res
+               (setf text (oref translator text))
+               (if cache (setf res (cl-loop for c in cache if (null c) collect (pop res) else collect c)))
+               ;; verify
+               (unless res
+                 (user-error "No translate result found"))
+               (unless (= (length (remove nil res)) (length text))
+                 (user-error "Source text and result text have no same length"))
+               ;; set cache if possible
+               (cl-destructuring-bind (cacher condition) (gt-current-cacher engine task)
+                 (when cacher (gt-cache-set cacher task condition)))
+               ;; invoke then when it is exists
+               (when (and (slot-boundp engine 'then) then (gt-functionp then))
+                 (funcall then task))
+               ;; all right, render
+               (gt-update-state translator)
+               (gt-output (oref translator render) translator)))))
+
+(cl-defgeneric gt-next-for-stream (_render task)
+  "Output result in TASK to specific marker position directly.
+Notice, the result should propertized with `gt-result' to avoid issue.
+This is used for streaming output for _RENDER."
+  (:method :around ((render gt-render) task)
+           (with-slots (markers) task
+             (if (null markers)
+                 (progn
+                   (setq gt-http-client-stream-abort-flag t)
+                   (message "Stream output is unavailable for current task."))
+               (setf markers (ensure-list markers))
+               (cl-call-next-method render task))))
+  (:method ((_render gt-render) task)
+           (with-slots (markers res) task
+             (save-excursion
+               (gt-insert-text-at-marker
+                (propertize (string-join (ensure-list res) "\n") 'gt-result 'stream)
+                (car markers) (cdr markers) t)))))
+
+\f
+;;; Text to Speech
+
+(defcustom gt-tts-speaker (cl-find-if #'executable-find (list "mpv" "mplayer" "cvlc"))
+  "The program used to speak the translation result.
+It also can be a command with options like `mpv --af=xxx'.
+The value should not contain any space in the command path."
+  :type 'string
+  :group 'go-translate)
+
+(defvar gt-tts-langs nil)
+
+(defvar gt-tts-last-engine nil)
+
+(defvar gt-tts-speak-process nil)
+
+(defun gt-interrupt-speak-process ()
+  (when (and gt-tts-speak-process (process-live-p gt-tts-speak-process))
+    (ignore-errors (kill-process gt-tts-speak-process))
+    (setq gt-tts-speak-process nil)))
+
+(cl-defmethod gt-play-audio (data &optional wait)
+  "Play DATA with `gt-tts-playing-process'.
+DATA is raw audio data or audio url, or a buffer contains the raw data.
+If WAIT is not nil, play after current process finished."
+  (unless (and gt-tts-speaker (executable-find (car (split-string gt-tts-speaker))))
+    (user-error "You should install `mpv' first or config `gt-tts-speaker' correctly"))
+  (when wait
+    (condition-case _
+        (while (and gt-tts-speak-process (process-live-p gt-tts-speak-process))
+          (sleep-for 0.5))
+      (quit (gt-interrupt-speak-process) (user-error ""))))
+  (gt-interrupt-speak-process)
+  (cl-flet ((speak ()
+              (let ((proc (make-process
+                           :name (format "gt-tts-process-%s" (+ 1000 (random 1000)))
+                           :command (append (split-string-shell-command gt-tts-speaker) (list "-"))
+                           :buffer nil
+                           :noquery t
+                           :sentinel (lambda (_ s) (if (string-match-p "finished" s) (message "")))
+                           :connection-type 'pipe)))
+                (message "Speaking...")
+                (setq gt-tts-speak-process proc)
+                (process-send-region proc (point-min) (point-max))
+                (if (process-live-p proc) (process-send-eof proc)))))
+    (if (bufferp data)
+        (with-current-buffer data (speak))
+      (with-temp-buffer
+        (let ((buf (current-buffer)) state)
+          (if (string-prefix-p "http" data)
+              (progn (gt-request :url data
+                                 :cache 10
+                                 :done (lambda (raw)
+                                         (setq state 'done)
+                                         (with-current-buffer buf (insert raw)))
+                                 :fail (lambda (err) (setq state err)))
+                     (while (null state)
+                       (accept-process-output nil 0.5)))
+            (insert data))
+          (if (and state (not (eq state 'done))) (signal 'user-error state))
+          (speak))))))
+
+(cl-defgeneric gt-speak (engine text &optional lang)
+  "Speak TEXT with LANG by ENGINE."
+  (:method ((_ (eql 'local)) text &optional _lang)
+           "TEXT to speech with local program."
+           (cond ((and (eq system-type 'darwin) (executable-find "say"))
+                  (with-temp-buffer
+                    (insert text)
+                    (shell-command-on-region (point-min) (point-max) "say" t)))
+                 ((and (memq system-type '(cygwin windows-nt)) (executable-find "powershell"))
+                  (let ((cmd (format "$w = New-Object -ComObject SAPI.SpVoice; $w.speak(\\\"%s\\\")" text)))
+                    (shell-command (format "powershell -Command \"& {%s}\""
+                                           (encode-coding-string
+                                            (replace-regexp-in-string "\n" " " cmd)
+                                            (keyboard-coding-system))))))
+                 (t (user-error "No suitable local TTS service found"))))
+  (:method ((_ (eql 'interact)) text &optional noprompt)
+           "Text to speech with local program or using selected engine."
+           (let* ((prompt "Text to Speech: ")
+                  (regexp (format "^ *\\(%s\\)\\." (mapconcat #'symbol-name (mapcar #'car gt-lang-codes) "\\|")))
+                  (engines `(locally ,@(cl-loop
+                                        for m in (cl--generic-method-table (cl--generic #'gt-speak))
+                                        for n = (car (cl--generic-method-specializers m))
+                                        unless (or (consp n) (memq n '(t gt-engine))) collect n)))
+                  (overlay nil)
+                  (text (if noprompt text
+                          (minibuffer-with-setup-hook
+                              (lambda ()
+                                (use-local-map (make-composed-keymap nil (current-local-map)))
+                                (setq overlay (make-overlay (1- (length prompt)) (length prompt)))
+                                (unless gt-tts-last-engine (setq gt-tts-last-engine 'locally))
+                                (overlay-put overlay 'display (format " (%s):" gt-tts-last-engine))
+                                (local-set-key (kbd "C-n")
+                                               (lambda ()
+                                                 (interactive)
+                                                 (setq gt-tts-last-engine
+                                                       (let ((i (cl-position gt-tts-last-engine engines)))
+                                                         (nth (if (>= (+ i 1) (length engines)) 0 (1+ i)) engines)))
+                                                 (overlay-put overlay 'display (format " (%s):" gt-tts-last-engine)))))
+                            (read-string prompt (if (consp text) (buffer-substring (caar text) (cdar text)) text)))))
+                  (lang (if (string-match regexp text)
+                            (prog1 (intern-soft (match-string 1 text))
+                              (setq text (substring text (match-end 0))))
+                          (car (gt-langs-maybe text (or gt-tts-langs (mapcar #'car gt-lang-codes)))))))
+             (when (= (length (string-trim text)) 0)
+               (user-error "Input should not be null"))
+             (unless gt-tts-last-engine (setq gt-tts-last-engine 'locally))
+             (gt-log 'tts (format "Speak with %s to %s" gt-tts-last-engine lang))
+             (if (equal gt-tts-last-engine 'locally)
+                 (gt-speak 'local text lang)
+               (gt-speak (make-instance gt-tts-last-engine) text lang))))
+  (:method :around ((engine gt-engine) text &optional lang)
+           (cl-call-next-method engine text (intern-soft lang)))
+  (:method ((engine gt-engine) &rest _)
+           (user-error "No TTS service found on engine `%s'" (oref engine tag))))
+
+;;;###autoload
+(defun gt-do-speak ()
+  "Speak content around point.
+
+If the text under point or with selection has `gt-task' or `gt-tts-url'
+property, try to speak it with current translation engine.
+
+Otherwise try to TTS with local program or using selected engine.
+
+When TTS with specific engine, you can specify the language with `lang.' prefix."
+  (interactive)
+  (if-let (url (get-char-property (point) 'gt-tts-url))
+      ;; 1. play the url
+      (gt-play-audio url)
+    (let (items engine)
+      (if-let (task (get-char-property (point) 'gt-task))
+          (with-slots (src tgt res err translator) task
+            (with-slots (text target) translator
+              ;; 2. play current task
+              (setq engine (oref task engine))
+              (unless (cl-find-method #'gt-speak '() `(,(eieio-object-class engine) t t))
+                (user-error "No TTS service found on current engine `%s'" (oref engine tag)))
+              (let ((part (or (get-char-property (point) 'gt-part) 0))
+                    (col (lambda (l c) (push (format "%s. %s" l (substring-no-properties c)) items))))
+                (if (use-region-p)
+                    (cl-loop with text = (buffer-substring (region-beginning) (region-end))
+                             for l in (gt-langs-maybe text (list src tgt)) do (funcall col l text))
+                  (when-let (r (nth part text))
+                    (funcall col (car target) r))
+                  (when-let (r (and (not err) (or (get-pos-property (point) 'gt-brief) (nth part (ensure-list res)))))
+                    (funcall col tgt r)))
+                (when items
+                  (let* ((cand (completing-read
+                                (format "Text to Speech (with %s): " (oref engine tag))
+                                (gt-make-completion-table items)))
+                         (lang (if (string-match (format "^ *\\(%s\\)\\." (mapconcat #'symbol-name (mapcar #'car gt-lang-codes) "\\|")) cand)
+                                   (prog1 (intern-soft (match-string 1 cand))
+                                     (setq cand (substring cand (match-end 0))))
+                                 (car (gt-langs-maybe cand (list src tgt))))))
+                    (unless lang (user-error "Guess language of text failed"))
+                    (gt-log 'tts (format "Speak with %s to %s" engine lang))
+                    (gt-speak engine cand lang))))))
+        ;; 3. play with local program or using selected engine
+        (gt-speak 'interact (gt-text (gt-taker :text 'word) nil))))))
+
+\f
+;;; Render
+
+(cl-defmethod gt-init :around ((render gt-render) translator)
+  (gt-log-funcall "init (%s %s)" render translator)
+  (message "Processing...")
+  (condition-case err
+      (progn (cl-call-next-method render translator)
+             (gt-update-state translator))
+    (error (gt-log 'render (format "%s initialize failed, abort" (gt-desc render)))
+           (user-error (format "[output init error] %s" err)))))
+
+(cl-defmethod gt-init ((_render gt-render) _translator)
+  "Initialize the RENDER. Only invoke once before output."
+  nil)
+
+(cl-defgeneric gt-extract (render translator)
+  "Extract TRANSLATOR's responses that to be consumed by RENDER."
+  (:method ((render gt-render) translator)
+           (with-slots (text tasks) translator
+             (cl-loop with tgts = (cl-delete-duplicates (mapcar (lambda (item) (oref item tgt)) tasks))
+                      with engines = (cl-delete-duplicates (mapcar (lambda (task) (oref task engine)) tasks))
+                      for task in tasks
+                      for (tgt res err engine) = (gt-orefs task tgt res err engine)
+                      for state = (if err 1 (if res 2 0)) ; 0 loading 1 error 2 result
+                      for prefix = (with-slots (prefix) render
+                                     (if (and (slot-boundp render 'prefix) (not (eq prefix t)))
+                                         (when prefix
+                                           ;; custom prefix with :prefix slot
+                                           (if (gt-functionp prefix)
+                                               (funcall prefix task)
+                                             (format "%s" prefix)))
+                                       (when (or (cdr tgts) (cdr engines))
+                                         ;; style as: en.Google.Detail
+                                         (let ((lst (list (if (cdr tgts) tgt)
+                                                          (if (cdr engines) (oref engine tag))
+                                                          (if (cdr engines) (ignore-errors (oref (oref engine parse) tag))))))
+                                           (concat
+                                            (mapconcat (lambda (item) (format "%s" item)) (remove nil lst) ".")
+                                            (if (and (cdr engines) (gt-stream-p engine)) " (stream)"))))))
+                      for result = (pcase state (0 "Loading...") (1 err) (2 res))
+                      collect (list :result result :prefix prefix :state state :task task :tgt tgt :stream (gt-stream-p engine)) into lst
+                      finally (return ;; sort by target, then by engine
+                               (if (cdr tgts)
+                                   (cl-loop for tgt in tgts append
+                                            (cl-remove-if-not (lambda (tr) (equal tgt (plist-get tr :tgt))) lst))
+                                 lst))))))
+
+(cl-defgeneric gt-output (render translator)
+  "Output result of TRANSLATOR with RENDER, invoked after every task parsed.
+Output to minibuffer by default."
+  (:method :around ((render gt-render) translator)
+           (with-slots (state) translator
+             (gt-log-funcall "output (%s %s) %s" render translator state)
+             (when (<= state 4)
+               (with-slots (output then) render
+                 ;; error handler & functionp case
+                 (condition-case err
+                     (if (gt-functionp output)
+                         (funcall output render translator)
+                       (cl-call-next-method render translator)
+                       ;; chain next render if possible
+                       (when (and (= state 3) then)
+                         (cond ((gt-functionp then)
+                                (funcall then translator))
+                               ((and (eieio-object-p then) (object-of-class-p then 'gt-render))
+                                (gt-init then translator)
+                                (gt-output then translator))
+                               (t (error "Bad then value specified in %s" (gt-desc render))))))
+                   (error (setf state 4)
+                          (message "[output error] %s" (cadr err))))))))
+  (:method ((render gt-render) translator)
+           (when (= 3 (oref translator state))
+             (cl-loop with ret = (gt-extract render translator)
+                      with mpp = (cdr (oref translator text))
+                      for tr in ret
+                      for (prefix result) = (gt-plist-let tr (list (concat "[" .prefix "]" (if mpp "\n" " ")) .result))
+                      collect (concat (if (cdr ret) (propertize prefix 'face 'gt-render-prefix-face))
+                                      (if (consp result) (string-join result "\n") result))
+                      into lst
+                      finally (message "%s" (string-join lst "\n"))))))
+
+\f
+;;; Translator
+
+(defun gt-add-task (task)
+  "Add new TASK for translation."
+  (with-slots (id engine render version translator) task
+    (with-slots (tasks state) translator
+      (when (= state 0)
+        (setf tasks (append tasks (list task)))
+        (gt-log (format "%d" (length tasks))
+          (format "add task %s: (%s/%s)" id (gt-desc engine) (gt-desc render)))))))
+
+(defun gt-update-state (translator)
+  "Update state for TRANSLATOR."
+  (with-slots (state total tasks render) translator
+    (pcase state
+      (0
+       (when (= (length tasks) total)
+         (gt-log 'translator "<1> all tasks added")
+         (setf state 1)))
+      (1
+       (gt-log 'translator
+         (format "<2> %s prepared" (gt-desc render)))
+       (setf state 2))
+      (2
+       (when (= total (length (cl-remove-if-not (lambda (task) (or (oref task err) (oref task res))) tasks)))
+         (gt-log 'translator "<3> all result parsed")
+         (setf state 3))))))
+
+(defun gt-fail (task error)
+  "Render ERROR message and ternimate the TASK."
+  (declare (indent 1))
+  (gt-log-funcall "fail (%s)" (oref task id))
+  (with-slots (translator render err version id) task
+    (when (equal version (oref translator version))
+      (setf err (if (consp error)
+                    (mapconcat (lambda (r)
+                                 (format (if (string-suffix-p "error" (format "%s" r)) "[%s]" "%s") r))
+                               (cl-remove-if (lambda (r) (memq r '(error user-error))) error)
+                               " ")
+                  (or error "")))
+      (gt-update-state translator)
+      (gt-output render translator))
+    (gt-log 'next (format "%s: [----- error -----] %s" id error))
+    (gt-log nil 1 (with-temp-buffer
+                    (let ((standard-output (current-buffer)))
+                      (backtrace)
+                      (buffer-string))))))
+
+(cl-defgeneric gt-reset (translator)
+  "Reset status and other variables before translation for TRANSLATOR."
+  (:method ((translator gt-translator))
+           (gt-log-funcall "reset (%s)" translator)
+           (unless gt-default-cacher
+             (setq gt-default-cacher (gt-memory-cacher)))
+           (unless gt-default-http-client
+             (setq gt-default-http-client (gt-url-http-client)))
+           (with-slots (version state tasks bag total taker engines render _taker _engines _render) translator
+             (unless (slot-boundp translator '_taker)
+               (unless taker (setf taker (gt-taker)))
+               (unless render (setf render (gt-render)))
+               (setf _taker taker _engines engines _render render))
+             (setf version (time-to-seconds) state 0 tasks nil total 0 taker nil engines nil render nil bag nil))))
+
+(cl-defmethod gt-init ((translator gt-translator) &rest _)
+  "Initialize the components, text and target for TRANSLATOR."
+  (gt-log-funcall "init (%s)" translator)
+  (with-slots (keep text bounds target version taker engines render _taker _engines _render) translator
+    ;; reset
+    (gt-reset translator)
+    ;; take
+    (unless keep (setf text nil bounds nil target nil))
+    (setf taker (or (cl-find-if (lambda (tk) (gt-valid tk translator))
+                                (ensure-list (gt-ensure-plain _taker)))
+                    (user-error "No taker found in this translator")))
+    (gt-take taker translator)
+    (setf keep nil)
+    (let ((history-delete-duplicates t))
+      (add-to-history 'gt-target-history target 8))
+    ;; prepare engines and render
+    (setf engines (ensure-list (gt-ensure-plain _engines)))
+    (setf render (cl-find-if (lambda (rd) (gt-valid rd translator))
+                             (ensure-list (gt-ensure-plain _render))))
+    ;; log
+    (gt-log 'translator (format "version: %s\ntarget: %s\nbounds: %s\ntext: %s\ntaker: %s, engines: %s, render: %s"
+                                version target bounds text
+                                (gt-desc taker)
+                                (mapcar (lambda (en) (oref en tag)) engines)
+                                (gt-desc render)))))
+
+(cl-defgeneric gt-start (translator)
+  "Start a new translation with TRANSLATOR."
+  (gt-log "\n\n")
+  (gt-log-funcall "start (%s)" translator)
+  (setq gt-current-command this-command)
+  (setq gt-current-translator translator)
+  ;; init
+  (gt-init translator nil)
+  (with-slots (text target version total engines render) translator
+    ;; tasks
+    (cl-loop for engine in engines
+             do (cl-loop for tgt in (cdr target)
+                         for task = (gt-task
+                                     :text text
+                                     :src (car target)
+                                     :tgt tgt
+                                     :engine engine
+                                     :render render
+                                     :translator translator
+                                     :version version)
+                         if (gt-valid engine task)
+                         do (cl-incf total) and do (gt-add-task task))
+             finally (gt-update-state translator))
+    (when (zerop total)
+      (user-error "No task created, please check engines and langs setup"))
+    ;; translate
+    (if render (gt-init render translator))
+    (cl-loop for task in (oref translator tasks)
+             do (condition-case err
+                    (gt-translate (oref task engine) task)
+                  (error (gt-fail task err))))))
+
+(provide 'gt-core)
+
+;;; gt-core.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-engine-bing.el b/elpa/go-translate-20240802.131/gt-engine-bing.el
new file mode 100644 (file)
index 0000000..6d6f52d
--- /dev/null
@@ -0,0 +1,203 @@
+;;; gt-engine-bing.el --- Microsoft Translate -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; https://bing.com/translator
+
+;;; Code:
+
+(require 'gt-extension)
+
+(defgroup go-translate-bing nil
+  "Configs for Bing engine."
+  :group 'go-translate)
+
+\f
+
+(defclass gt-bing-parser (gt-parser) ())
+
+(defclass gt-bing-engine (gt-engine)
+  ((tag       :initform 'Bing)
+   (host      :initform "https://www.bing.com")
+   (host-tld  :initform nil)
+   (ig        :initform nil)
+   (key       :initform nil)
+   (token     :initform nil)
+   (last-time :initform nil)
+   (expired-time :initform (* 30 60))
+   (parse     :initform (gt-bing-parser))))
+
+\f
+;;; Translate
+
+(defvar gt-bing-extra-langs-mapping '((zh . "zh-Hans")))
+
+(defvar gt-bing-token-maybe-invalid nil)
+
+(defun gt-bing-get-lang (lang)
+  (or (cdr-safe (assoc lang gt-bing-extra-langs-mapping)) lang))
+
+(defun gt-bing-with-token (engine done)
+  (declare (indent 1))
+  (with-slots (ig key token host host-tld last-time expired-time) engine
+    (if (and token key ig last-time (not gt-bing-token-maybe-invalid)
+             (< (- (time-to-seconds) last-time) expired-time))
+        (funcall done)
+      (gt-request :url (concat host "/translator")
+                  :done (lambda (raw)
+                          (with-temp-buffer
+                            (insert raw)
+                            (goto-char (point-min))
+                            (re-search-forward "IG:\"\\([A-Za-z0-9]+\\)\"")
+                            (setf ig (match-string 1))
+                            (re-search-forward "curUrl=\"\\(http[a-z]*\\).*?\\([a-zA-Z]+\\.bing.com\\)")
+                            (setf host-tld (concat (match-string 1) "://" (match-string 2)))
+                            (re-search-forward "var params_AbusePreventionHelper = \\[\\([0-9]+\\), *\"\\([^\"]+\\)")
+                            (setf key (match-string 1) token (match-string 2))
+                            (setf last-time (time-to-seconds))
+                            (setq gt-bing-token-maybe-invalid nil)
+                            (gt-log 'bing (format "url: %s\nkey: %s\ntoken: %s\nig: %s" host-tld key token ig))
+                            (funcall done)))
+                  :fail (lambda (err) (user-error "[BING] Get Token failed. %s" err))))))
+
+(cl-defmethod gt-translate ((engine gt-bing-engine) task next)
+  (gt-bing-with-token engine
+    (lambda ()
+      (with-slots (text src tgt res) task
+        (with-slots (host-tld ig key token) engine
+          (gt-request :url (format "%s/ttranslatev3?isVertical=1&IID=translator.5022.1&IG=%s" host-tld ig)
+                      :headers `(("Content-Type" . "application/x-www-form-urlencoded;charset=UTF-8"))
+                      :data    `(("fromLang" . ,(gt-bing-get-lang src))
+                                 ("to"       . ,(gt-bing-get-lang tgt))
+                                 ("text"     . ,text)
+                                 ("key"      . ,key)
+                                 ("token"    . ,token))
+                      :done (lambda (raw)
+                              (setf res raw)
+                              (funcall next task))
+                      :fail (lambda (err)
+                              (gt-fail task
+                                (pcase (car-safe (cdr-safe err))
+                                  (429 "[429] Too many requests! Please try later")
+                                  (_ err))))))))))
+
+(cl-defmethod gt-parse ((_ gt-bing-parser) task)
+  (with-slots (res err) task
+    (if-let* ((json (json-read-from-string (decode-coding-string res 'utf-8)))
+              (result (ignore-errors (cdr (assoc 'text (aref (cdr (assoc 'translations (aref json 0))) 0))))))
+        (setf res result)
+      (setq gt-bing-token-maybe-invalid t) ; refresh token when error occurred
+      (setf err res)
+      (error "error %s" res))))
+
+\f
+;;; Text to Speech
+
+(defcustom gt-bing-tts-speed 1.0
+  "Playing speed of TTS audio, 1.0 is normal speed."
+  :type 'number
+  :group 'go-translate-bing)
+
+(defvar gt-bing-tts-mapping '((zh . ("zh-CN" "Female" "zh-CN-XiaoxiaoNeural"))
+                              (en . ("en-US" "Female" "en-US-AriaNeural"))
+                              (af . ("af-ZA" "Female" "af-ZA-AdriNeural"))
+                              (am . ("am-ET" "Female" "am-ET-MekdesNeural"))
+                              (ar . ("ar-SA" "Male" "ar-SA-HamedNeural"))
+                              (bn . ("bn-IN" "Female" "bn-IN-TanishaaNeural"))
+                              (bg . ("bg-BG" "Male" "bg-BG-BorislavNeural"))
+                              (ca . ("ca-ES" "Female" "ca-ES-JoanaNeural"))
+                              (cs . ("cs-CZ" "Male" "cs-CZ-AntoninNeural"))
+                              (cy . ("cy-GB" "Female" "cy-GB-NiaNeural"))
+                              (da . ("da-DK" "Female" "da-DK-ChristelNeural"))
+                              (de . ("de-DE" "Female" "de-DE-KatjaNeural"))
+                              (el . ("el-GR" "Male" "el-GR-NestorasNeural"))
+                              (es . ("es-ES" "Female" "es-ES-ElviraNeural"))
+                              (et . ("et-EE" "Female" "et-EE-AnuNeural"))
+                              (fa . ("fa-IR" "Female" "fa-IR-DilaraNeural"))
+                              (fi . ("fi-FI" "Female" "fi-FI-NooraNeural"))
+                              (fr . ("fr-FR" "Female" "fr-FR-DeniseNeural"))
+                              (ga . ("ga-IE" "Female" "ga-IE-OrlaNeural"))
+                              (gu . ("gu-IN" "Female" "gu-IN-DhwaniNeural"))
+                              (he . ("he-IL" "Male" "he-IL-AvriNeural"))
+                              (hi . ("hi-IN" "Female" "hi-IN-SwaraNeural"))
+                              (hr . ("hr-HR" "Male" "hr-HR-SreckoNeural"))
+                              (hu . ("hu-HU" "Male" "hu-HU-TamasNeural"))
+                              (id . ("id-ID" "Male" "id-ID-ArdiNeural"))
+                              (is . ("is-IS" "Female" "is-IS-GudrunNeural"))
+                              (it . ("it-IT" "Male" "it-IT-DiegoNeural"))
+                              (ja . ("ja-JP" "Female" "ja-JP-NanamiNeural"))
+                              (kk . ("kk-KZ" "Female" "kk-KZ-AigulNeural"))
+                              (km . ("km-KH" "Female" "km-KH-SreymomNeural"))
+                              (kn . ("kn-IN" "Female" "kn-IN-SapnaNeural"))
+                              (ko . ("ko-KR" "Female" "ko-KR-SunHiNeural"))
+                              (lo . ("lo-LA" "Female" "lo-LA-KeomanyNeural"))
+                              (lv . ("lv-LV" "Female" "lv-LV-EveritaNeural"))
+                              (lt . ("lt-LT" "Female" "lt-LT-OnaNeural"))
+                              (mk . ("mk-MK" "Female" "mk-MK-MarijaNeural"))
+                              (ml . ("ml-IN" "Female" "ml-IN-SobhanaNeural"))
+                              (mr . ("mr-IN" "Female" "mr-IN-AarohiNeural"))
+                              (ms . ("ms-MY" "Male" "ms-MY-OsmanNeural"))
+                              (mt . ("mt-MT" "Female" "mt-MT-GraceNeural"))
+                              (my . ("my-MM" "Female" "my-MM-NilarNeural"))
+                              (nl . ("nl-NL" "Female" "nl-NL-ColetteNeural"))
+                              (nb . ("nb-NO" "Female" "nb-NO-PernilleNeural"))
+                              (pl . ("pl-PL" "Female" "pl-PL-ZofiaNeural"))
+                              (ps . ("ps-AF" "Female" "ps-AF-LatifaNeural"))
+                              (pt . ("pt-BR" "Female" "pt-BR-FranciscaNeural"))
+                              (ro . ("ro-RO" "Male" "ro-RO-EmilNeural"))
+                              (ru . ("ru-RU" "Female" "ru-RU-DariyaNeural"))
+                              (sk . ("sk-SK" "Male" "sk-SK-LukasNeural"))
+                              (sl . ("sl-SI" "Male" "sl-SI-RokNeural"))
+                              (sv . ("sv-SE" "Female" "sv-SE-SofieNeural"))
+                              (ta . ("ta-IN" "Female" "ta-IN-PallaviNeural"))
+                              (te . ("te-IN" "Male" "te-IN-ShrutiNeural"))
+                              (th . ("th-TH" "Male" "th-TH-NiwatNeural"))
+                              (tr . ("tr-TR" "Female" "tr-TR-EmelNeural"))
+                              (uk . ("uk-UA" "Female" "uk-UA-PolinaNeural"))
+                              (ur . ("ur-IN" "Female" "ur-IN-GulNeural"))
+                              (uz . ("uz-UZ" "Female" "uz-UZ-MadinaNeural"))
+                              (vi . ("vi-VN" "Male" "vi-VN-NamMinhNeural"))
+                              (yue . ("zh-HK" "Female" "zh-HK-HiuGaaiNeural"))))
+
+(cl-defmethod gt-bing-tts-payload (lang text)
+  (let ((lm (assoc lang gt-bing-tts-mapping)))
+    (unless lm (user-error "Add the mapping of your language into `gt-bing-tts-langs-mapping' :)"))
+    (format "<speak version='1.0' xml:lang='%s'><voice xml:lang='%s' xml:gender='%s' name='%s'><prosody rate='%s'>%s</prosody></voice></speak>"
+            (cadr lm) (caddr lm) (cadr lm) (cadddr lm)
+            gt-bing-tts-speed (encode-coding-string text 'utf-8))))
+
+(cl-defmethod gt-speak ((engine gt-bing-engine) text lang)
+  (with-slots (host host-tld ig key token) engine
+    (message "Requesting from %s for %s..." (or host-tld host) lang)
+    (gt-bing-with-token engine
+      (lambda ()
+        (gt-request :url (format "%s/tfettts?isVertical=1&IID=translator.5022.2&IG=%s" host-tld ig)
+                    :headers '(("content-type" . "application/x-www-form-urlencoded"))
+                    :data `(("token" . ,token) ("key" . ,key) ("ssml" . ,(gt-bing-tts-payload lang text)))
+                    :cache (length text)
+                    :done #'gt-play-audio
+                    :fail (lambda (err) (message "[BING-TTS] error in request, %s" err)))))))
+
+(provide 'gt-engine-bing)
+
+;;; gt-engine-bing.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-engine-chatgpt.el b/elpa/go-translate-20240802.131/gt-engine-chatgpt.el
new file mode 100644 (file)
index 0000000..01d0f44
--- /dev/null
@@ -0,0 +1,190 @@
+;;; gt-engine-chatgpt.el --- Engine for ChatGPT -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; https://platform.openai.com/docs/api-reference
+
+;;; Code:
+
+(require 'gt-extension)
+
+(defgroup go-translate-chatgpt nil
+  "Configs for ChatGPT engine."
+  :group 'go-translate)
+
+\f
+;;; Components
+
+(defclass gt-chatgpt-engine (gt-engine)
+  ((tag         :initform 'ChatGPT)
+   (host        :initarg :host :initform nil)
+   (model       :initarg :model :initform nil)
+   (temperature :initarg :temperature :initform nil)
+   (key         :initarg :key :initform 'apikey
+                :documentation "The apikey of ChatGPT.
+Can also put into .authinfo file as:
+  machine api.openai.com login apikey password ***")))
+
+\f
+;;; Translate
+
+(defcustom gt-chatgpt-host "https://api.openai.com"
+  "API host of ChatGPT."
+  :type 'string
+  :group 'go-translate-chatgpt)
+
+(defcustom gt-chatgpt-key nil
+  "Auth Key of ChatGPT. Recommend to save in .authinfo file instead."
+  :type 'string
+  :group 'go-translate-chatgpt)
+
+(defcustom gt-chatgpt-model "gpt-3.5-turbo"
+  "Model to be used in chat."
+  :type 'string
+  :group 'go-translate-chatgpt)
+
+(defcustom gt-chatgpt-temperature 0.8
+  "Temperature of ChatGPT."
+  :type 'number
+  :group 'go-translate-chatgpt)
+
+(defcustom gt-chatgpt-system-prompt "You are a translation assistant"
+  "System prompt send to server when translation."
+  :type 'string
+  :group 'go-translate-chatgpt)
+
+(defcustom gt-chatgpt-user-prompt-template "Translate the text to %s, text is: \n%s"
+  "Template for user prompt when translation.
+When it is string, %s is placeholders of lang and text.
+When it is function, arguments passed to it should be text and lang."
+  :type '(choice string function)
+  :group 'go-translate-chatgpt)
+
+(declare-function pulse-momentary-highlight-region "pulse")
+
+(defvar gt-chatgpt-streaming-finished-hook #'pulse-momentary-highlight-region
+  "The logic runs after all streams finished.
+With two arguments BEG and END, which are the marker bounds of the result.")
+
+(cl-defmethod gt-ensure-key ((engine gt-chatgpt-engine))
+  (with-slots (host key) engine
+    (unless (stringp key)
+      (if-let (apikey (or (gt-lookup-password
+                           :user (if key (format "%s" key) "apikey")
+                           :host (url-host (url-generic-parse-url (or host gt-chatgpt-host))))
+                          gt-chatgpt-key))
+          (setf key apikey)
+        (user-error "You should provide a apikey for gt-chatgpt-engine")))))
+
+(cl-defmethod gt-translate ((engine gt-chatgpt-engine) task next)
+  (gt-ensure-key engine)
+  (with-slots (text src tgt res translator markers) task
+    (with-slots (host key model temperature stream) engine
+      (when (and stream (cdr (oref translator text)))
+        (user-error "Multiple parts not support streaming"))
+      (gt-request :url (concat (or host gt-chatgpt-host) "/v1/chat/completions")
+                  :headers `(("Content-Type" . "application/json")
+                             ("Authorization" . ,(concat "Bearer " (encode-coding-string key 'utf-8))))
+                  :data (encode-coding-string
+                         (json-encode
+                          `((model . ,(or model gt-chatgpt-model))
+                            (temperature . ,(or temperature gt-chatgpt-temperature))
+                            (stream . ,stream)
+                            (messages . [((role . system)
+                                          (content . ,gt-chatgpt-system-prompt))
+                                         ((role . user)
+                                          (content . ,(if (functionp gt-chatgpt-user-prompt-template)
+                                                          (funcall gt-chatgpt-user-prompt-template text tgt)
+                                                        (format gt-chatgpt-user-prompt-template (alist-get tgt gt-lang-codes) text))))])))
+                         'utf-8)
+                  :filter (when stream
+                            (lambda ()
+                              (unless gt-tracking-marker
+                                (setq gt-tracking-marker (make-marker))
+                                (set-marker gt-tracking-marker (point-min)))
+                              (goto-char gt-tracking-marker)
+                              (condition-case err
+                                  (while (re-search-forward "^data: +\\({.+}\\)" nil t)
+                                    (let* ((json (json-read-from-string (decode-coding-string (match-string 1) 'utf-8)))
+                                           (choice (aref (alist-get 'choices json) 0))
+                                           (content (alist-get 'content (alist-get 'delta choice)))
+                                           (finish (alist-get 'finish_reason choice)))
+                                      (if finish
+                                          (progn (message "")
+                                                 (when gt-chatgpt-streaming-finished-hook
+                                                   (with-current-buffer (marker-buffer (car markers))
+                                                     (funcall gt-chatgpt-streaming-finished-hook (car markers) (cdr markers)))))
+                                        (setf res (concat res content))
+                                        (set-marker gt-tracking-marker (point))
+                                        (unless (string-blank-p (concat res))
+                                          (funcall next task)))))
+                                (error (unless (string-prefix-p "json" (format "%s" (car err)))
+                                         (signal (car err) (cdr err)))))))
+                  :done (unless stream
+                          (lambda (raw)
+                            (with-slots (res) task
+                              (let* ((json (json-read-from-string raw))
+                                     (str (alist-get 'content (alist-get 'message (let-alist json (aref .choices 0))))))
+                                (setf res str))
+                              (funcall next task))))
+                  :fail (lambda (err) (gt-fail task err))))))
+
+(cl-defmethod gt-stream-p ((engine gt-chatgpt-engine))
+  (oref engine stream))
+
+\f
+;;; Text to Speech
+
+(defcustom gt-chatgpt-tts-model "tts-1"
+  "Model used by TTS of ChatGPT."
+  :type 'string
+  :group 'go-translate-chatgpt)
+
+(defcustom gt-chatgpt-tts-speed 1.0
+  "Speech speed of return audio from ChatGPT."
+  :type 'number
+  :group 'go-translate-chatgpt)
+
+(defcustom gt-chatgpt-tts-voice "alloy"
+  "Which voice to use by ChatGPT Speech.
+alloy, echo, fable, onyx, nova, or shimmer."
+  :type 'string
+  :group 'go-translate-chatgpt)
+
+(cl-defmethod gt-speak ((engine gt-chatgpt-engine) text _lang)
+  (gt-ensure-key engine)
+  (with-slots (host key) engine
+    (gt-request :url (concat (or host gt-chatgpt-host) "/v1/audio/speech")
+                :headers `(("Content-Type" . "application/json")
+                           ("Authorization" . ,(concat "Bearer " key)))
+                :data (json-encode
+                       `((input . ,text)
+                         (model . ,gt-chatgpt-tts-model)
+                         (speed . ,gt-chatgpt-tts-speed)
+                         (voice . ,gt-chatgpt-tts-voice)))
+                :done #'gt-play-audio
+                :cache (length text))))
+
+(provide 'gt-engine-chatgpt)
+
+;;; gt-engine-chatgpt.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-engine-deepl.el b/elpa/go-translate-20240802.131/gt-engine-deepl.el
new file mode 100644 (file)
index 0000000..4efdecb
--- /dev/null
@@ -0,0 +1,164 @@
+;;; gt-engine-deepl.el --- Translate Engine for DeepL -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; https://www.deepl.com
+
+;;; Code:
+
+(require 'gt-extension)
+
+(defgroup go-translate-deepl nil
+  "Configs for DeepL engine."
+  :group 'go-translate)
+
+\f
+;;; Components
+
+(defclass gt-deepl-parser (gt-parser) ())
+
+(defclass gt-deepl-engine (gt-engine)
+  ((tag       :initform 'DeepL)
+   (host      :initform "https://api.deepl.com")
+   (host-free :initform "https://api-free.deepl.com")
+   (path      :initform "/v2/translate")
+   (parse     :initform (gt-deepl-parser))
+
+   (pro       :initform nil
+              :initarg :pro
+              :documentation "Set t when use PRO version.")
+
+   (key       :initform 'auth-key
+              :initarg :key
+              :documentation "The auth-key of DeepL.
+You can also put it into .authinfo file as:
+  machine api.deepl.com login auth-key password ***")))
+
+\f
+;;; Utils
+
+(defcustom gt-deepl-fill-enable t
+  "Controller whether try to improve the input and output.
+Default behavior is removing excess linebreaks in input for better
+translation effect, and filling the output for better reading experience.
+You can override the behaviors by :around the method `gt-deepl-fill-input'
+or `gt-deepl-fill-output'."
+  :type 'boolean
+  :group 'go-translate-deepl)
+
+(cl-defmethod gt-deepl-fill-input (text)
+  "Improve the input TEXT for better translation effect.
+Mainly remove excess linebreaks. I want to skip unfill on comments and codes,
+but don't know how to implement easily. To make it better later, maybe."
+  (if gt-deepl-fill-enable
+      (with-temp-buffer
+        (insert text)
+        (let ((fill-column (* 2 (point-max))))
+          (fill-region (point-min) (point-max)))
+        (buffer-string))
+    text))
+
+(cl-defmethod gt-deepl-fill-output (text)
+  "Improve the output TEXT for better reading experience.
+Mainly fill the text to suitable length."
+  (if gt-deepl-fill-enable
+      (with-temp-buffer
+        (insert text)
+        (fill-region (point-min) (point-max))
+        (buffer-string))
+    text))
+
+\f
+;;; Engine
+
+(defcustom gt-deepl-extra-params '(("split_sentences"     . "1")
+                                   ("preserve_formatting" . "1"))
+  "Extra translation params send to DeepL server."
+  :type '(alist :key-type (string :tag "Key")
+                :value-type (string :tag "Value"))
+  :group 'go-translate-deepl)
+
+(defvar gt-deepl-langs-mapping '((en . "EN")
+                                 (zh . "ZH")
+                                 (de . "DE") ; German
+                                 (fr . "FR") ; French
+                                 (it . "IT") ; Italian
+                                 (ja . "JA") ; Japanese
+                                 (es . "ES") ; Spanish
+                                 (nl . "NL") ; Dutch
+                                 (pl . "PL") ; Polish
+                                 (pt . "PT") ; Portuguese (all Portuguese varieties mixed)
+                                 (ru . "RU") ; Russian
+                                 ))
+
+(defun gt-deepl-get-lang (lang)
+  (if-let (mapping (assoc lang gt-deepl-langs-mapping))
+      (cdr mapping)
+    (user-error "Language %s is not supported by DeepL.
+Supported list: %s" lang (mapconcat #'car gt-deepl-langs-mapping ", "))))
+
+(cl-defmethod gt-ensure-key ((engine gt-deepl-engine))
+  (with-slots (key) engine
+    (unless (stringp key)
+      (if-let (auth-key (gt-lookup-password
+                         :user (if key (format "%s" key) "auth-key")
+                         :host "api.deepl.com"))
+          (setf key auth-key)
+        (user-error "You should provide a auth-key for gt-deepl-engine")))))
+
+(cl-defmethod gt-translate ((engine gt-deepl-engine) task next)
+  (gt-ensure-key engine)
+  (with-slots (text src tgt res) task
+    (with-slots (host host-free path pro key) engine
+      (gt-request :url (concat (if pro host host-free) path)
+                  :headers `(("Content-Type"    . "application/x-www-form-urlencoded;charset=UTF-8")
+                             ("Authorization"   . ,(concat "DeepL-Auth-Key " key)))
+                  :data    `(("text"            . ,(gt-deepl-fill-input text))
+                             ("target_lang"     . ,(gt-deepl-get-lang tgt))
+                             ,(if-let (src (gt-deepl-get-lang src)) `("source_lang" . ,src))
+                             ,@gt-deepl-extra-params)
+                  :done (lambda (raw)
+                          (setf res raw)
+                          (funcall next task))
+                  :fail (lambda (err)
+                          (gt-fail task (pcase (car-safe (cdr-safe err))
+                                          (403 "[403] Authorization failed. Please supply a valid auth_key parameter")
+                                          (413 "[413] The request size exceeds the limit")
+                                          (414 "[414] Request-URI Too Long")
+                                          (429 "[429] Too many requests. Please wait and resend your request")
+                                          (456 "[456] Quota exceeded. The character limit has been reached")
+                                          (_ err))))))))
+
+\f
+;;; Parser
+
+(cl-defmethod gt-parse ((_ gt-deepl-parser) task)
+  (with-slots (res meta) task
+    (let* ((json (json-read-from-string res))
+           (str (decode-coding-string (mapconcat #'cdadr (cdar json) "\n") 'utf-8))
+           (hook (lambda (rs) (mapcar #'gt-deepl-fill-output rs))))
+      (setf res str meta (plist-put meta :res-hook hook)))))
+
+(provide 'gt-engine-deepl)
+
+;;; gt-engine-deepl.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-engine-echo.el b/elpa/go-translate-20240802.131/gt-engine-echo.el
new file mode 100644 (file)
index 0000000..165de0c
--- /dev/null
@@ -0,0 +1,70 @@
+;;; gt-engine-echo.el --- Echo Translate -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; For simple transform, and for test.
+
+;;; Code:
+
+(require 'gt-extension)
+
+(defclass gt-echo-engine (gt-engine)
+  ((tag        :initform 'Echo)
+   (do         :initform nil :initarg :do)))
+
+(cl-defmethod initialize-instance :after ((engine gt-echo-engine) &rest _)
+  (with-slots (cache delimiter) engine
+    (setf cache nil delimiter nil)))
+
+(cl-defmethod gt-translate ((engine gt-echo-engine) task next)
+  (with-slots (text res) task
+    (with-slots (do) engine
+      (setf res text)
+      (cl-labels ((process (sym)
+                    (cond ((eq sym 'clean)
+                           (setf res (mapcar #'substring-no-properties res)))
+                          ((eq sym 'br)
+                           (setf res (mapcar #'gt-prop-for-bionic-reading res)))
+                          ((functionp sym)
+                           (setf res (funcall sym res)))
+                          ((consp sym)
+                           (cl-loop for o in sym do (process o))))))
+        (process do)
+        (funcall next task)))))
+
+\f
+
+(defun gt-prop-for-bionic-reading (text)
+  (with-temp-buffer
+    (insert text)
+    (goto-char (point-min))
+    (skip-chars-forward " \t\n\r\f")
+    (while (forward-word)
+      (cl-destructuring-bind (beg . end) (bounds-of-thing-at-point 'word)
+        (setq end (+ beg (/ (- end beg) 2)))
+        (put-text-property beg end 'font-lock-face 'gt-bionic-reading-face)))
+    (buffer-string)))
+
+(provide 'gt-engine-echo)
+
+;;; gt-engine-echo.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-engine-google-rpc.el b/elpa/go-translate-20240802.131/gt-engine-google-rpc.el
new file mode 100644 (file)
index 0000000..c150b3a
--- /dev/null
@@ -0,0 +1,223 @@
+;;; gt-engine-google-rpc.el --- Google translation with RPC API -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; http://translate.google.com, new RPC API
+
+;;; Code:
+
+(require 'gt-engine-google)
+
+(defgroup go-translate-google-rpc nil
+  "Configs for GoogleRPC engine."
+  :group 'go-translate)
+
+(defcustom gt-google-rpc-host "https://translate.google.com"
+  "The base url of Google translate used by google-rpc engine.
+you can customize it according to your country region."
+  :type 'string
+  :group 'go-translate-google-rpc)
+
+\f
+;;; Components
+
+(defclass gt-google-rpc-parser (gt-google-parser)
+  ((tag :initform "Detail")))
+
+(defclass gt-google-rpc-summary-parser (gt-google-summary-parser gt-google-rpc-parser)
+  ((tag :initform "Summary")))
+
+(defclass gt-google-rpc-engine (gt-engine)
+  ((tag   :initform 'GoogleRPC)
+   (host  :initform nil)
+   (path  :initform "/_/TranslateWebserverUi/data/batchexecute")
+   (parse :initform (gt-google-rpc-parser))
+
+   (rpc-translate :initform "MkEWBc")
+   (rpc-tts       :initform "jQ1olc")
+   (rpc-sid       :initform "FdrFJe")
+   (rpc-bl        :initform "cfb2h")))
+
+\f
+;;; Engine
+
+(defcustom gt-google-rpc-request-headers
+  '(("Connection" . "Keep-Alive")
+    ("Content-Type" . "application/x-www-form-urlencoded;charset=UTF-8"))
+  "Extra request headers send to google-rpc server."
+  :type '(alist :key-type (string :tag "Key") :value-type (string :tag "Value"))
+  :group 'go-translate-google-rpc)
+
+(defun gt-google-rpc-with-token (engine done fail)
+  (declare (indent 1))
+  (with-slots (host path rpc-sid rpc-bl) engine
+    (gt-request :url (or host gt-google-rpc-host)
+                :done (lambda (raw)
+                        (with-temp-buffer
+                          (insert raw)
+                          (goto-char (point-min))
+                          (let* ((f-sid (progn
+                                          (re-search-forward (format "\"%s\":\"\\([^\"]*\\)\"" rpc-sid))
+                                          (match-string 1)))
+                                 (bl (progn (re-search-forward (format "\"%s\":\"\\([^\"]*\\)\"" rpc-bl))
+                                            (match-string 1)))
+                                 (url-tpl (lambda (rpcid lang)
+                                            (format "%s%s?%s" (or host gt-google-rpc-host) path
+                                                    (gt-format-params
+                                                     `(("rpcids"       . ,rpcid)
+                                                       ("f.sid"        . ,f-sid)
+                                                       ("bl"           . ,bl)
+                                                       ("hl"           . ,lang)
+                                                       ("soc-app"      . 1)
+                                                       ("soc-platform" . 1)
+                                                       ("soc-device"   . 1)
+                                                       ("_reqid"       . ,(+ 1000 (random 9000)))
+                                                       ("rt"           . "c")))))))
+                            (funcall done url-tpl))))
+                :fail fail)))
+
+(cl-defmethod gt-translate ((engine gt-google-rpc-engine) task next)
+  (gt-google-rpc-with-token engine
+    (lambda (url-tpl)
+      (with-slots (text src tgt res) task
+        (with-slots (rpc-translate) engine
+          (gt-request :url (funcall url-tpl rpc-translate tgt)
+                      :headers gt-google-rpc-request-headers
+                      :data (format
+                             "f.req=%s&"
+                             (url-hexify-string
+                              (json-encode `[[[,rpc-translate ,(json-encode `[[,text ,src ,tgt 1][]]) nil "generic"]]])))
+                      :done (lambda (raw) (setf res raw) (funcall next task))
+                      :fail (lambda (err) (gt-fail task err))))))
+    (lambda (err)
+      (gt-fail task (format "Take token failed, %s" err)))))
+
+(cl-defmethod gt-speak ((engine gt-google-rpc-engine) text lang)
+  (with-slots (host rpc-tts) engine
+    (message "Requesting %s for %s..." (or host gt-google-rpc-host) lang)
+    (gt-google-rpc-with-token engine
+      (lambda (url-tpl)
+        (gt-request :url (funcall url-tpl rpc-tts "en-US")
+                    :headers gt-google-rpc-request-headers
+                    :data (format
+                           "f.req=%s&"
+                           (url-hexify-string
+                            (json-encode `[[[,rpc-tts ,(json-encode `[,text ,lang nil "undefined" [0]]) nil "generic"]]])))
+                    :done (lambda (raw)
+                            (with-temp-buffer
+                              (insert raw)
+                              (goto-char (point-min))
+                              (let (beg end json code)
+                                (re-search-forward "^[0-9]+$")
+                                (setq beg (point))
+                                (re-search-forward "^\\([0-9]+\\)$")
+                                (setq end (- (point) (length (match-string 1))))
+                                (setq json (json-read-from-string (string-trim (buffer-substring-no-properties beg end))))
+                                (if-let (data (and (string= (gt-aref json 0 0) "wrb.fr") (gt-aref json 0 2)))
+                                    (progn (setq code (aref (json-read-from-string data) 0))
+                                           (erase-buffer)
+                                           (insert code)
+                                           (base64-decode-region (point-min) (point-max))
+                                           (gt-play-audio (current-buffer)))
+                                  (message "[GoogleRPC-TTS] No tts data responsed")))))
+                    :fail (lambda (err) (message "[GoogleRPC-TTS] Error, %s" err))))
+      (lambda (err) (message "[GoogleRPC-TTS] Take token failed, %s" err)))))
+
+\f
+;;; Parser
+
+(cl-defmethod gt-resp-to-json ((_ gt-google-rpc-parser) resp)
+  "Convert the buffer RESP into JSON format."
+  (condition-case err
+      (with-temp-buffer
+        (let (beg end str json)
+          (insert resp)
+          (goto-char (point-min))
+          (re-search-forward "^[0-9]+$")
+          (setq beg (point))
+          (re-search-forward "^\\([0-9]+\\)$")
+          (setq end (- (point) (length (match-string 1))))
+          (setq str
+                (decode-coding-string
+                 (string-trim (buffer-substring-no-properties beg end))
+                 'utf-8))
+          (setq json (json-read-from-string str))
+          (if (string= (aref (aref json 0) 0) "wrb.fr")
+              (let ((json-str (aref (aref json 0) 2)))
+                (json-read-from-string json-str))
+            (error "No results found"))))
+    (error (user-error "Result conversion error (%s)" err))))
+
+(cl-defmethod gt-result--brief ((_ gt-google-rpc-parser) json)
+  "Get the translation text from JSON."
+  (mapconcat
+   (lambda (item)
+     (if (ignore-errors (aref item 2))
+         (format " %s" (aref item 0))
+       (aref item 0)))
+   (gt-aref json 1 0 0 5)
+   ""))
+
+(cl-defmethod gt-result--sphonetic ((_ gt-google-rpc-parser) json)
+  "Get the text phonetic from JSON."
+  (gt-aref json 0 0))
+
+(cl-defmethod gt-result--tphonetic ((_ gt-google-rpc-parser) json)
+  "Get the translation phonetic from JSON."
+  (gt-aref json 1 0 0 1))
+
+(cl-defmethod gt-result--details ((_ gt-google-rpc-parser) json)
+  "Get the details from JSON.
+Result style: ((noun (a (x y z))) (verb (b (m n o))))."
+  (cl-loop with dts = (ignore-errors (gt-aref json 3 5 0))
+           for i across dts
+           collect
+           (cons
+            (aref i 0)
+            (cl-loop for j across (aref i 1)
+                     collect
+                     (cons
+                      (aref j 0)
+                      (cl-loop for k across (aref j 2) collect k))))))
+
+(cl-defmethod gt-result--definitions ((_ gt-google-rpc-parser) json)
+  "Get the definitions from JSON.
+Result style: ((noun (a b)) (verb (c d)))."
+  (cl-loop with defs = (ignore-errors (gt-aref json 3 1 0))
+           for i across defs
+           collect
+           (cons
+            (aref i 0)
+            (cl-loop for j across (aref i 1)
+                     collect
+                     (cons (aref j 0)
+                           (ignore-errors (aref j 1)))))))
+
+(cl-defmethod gt-result--suggestion ((_ gt-google-rpc-parser) json)
+  "Get the suggestion from JSON."
+  (ignore-errors
+    (replace-regexp-in-string "<b>\\|</b>\\|<i>\\|</i>" "" (gt-aref json 0 1 0 0 1))))
+
+(provide 'gt-engine-google-rpc)
+
+;;; gt-engine-google-rpc.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-engine-google.el b/elpa/go-translate-20240802.131/gt-engine-google.el
new file mode 100644 (file)
index 0000000..70c43b1
--- /dev/null
@@ -0,0 +1,447 @@
+;;; gt-engine-google.el --- Google translate -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; http://translate.google.com
+
+;;; Code:
+
+(require 'gt-extension)
+
+(defgroup go-translate-google nil
+  "Configs for Google engine."
+  :group 'go-translate)
+
+(defcustom gt-google-host "http://translate.googleapis.com"
+  "The base url of Google translate used by google engine.
+you can customize it according to your country region."
+  :type 'string
+  :group 'go-translate-google)
+
+\f
+;;; Components
+
+(defclass gt-google-parser (gt-parser)
+  ((tag :initform "Detail")))
+
+(defclass gt-google-summary-parser (gt-google-parser)
+  ((tag :initform "Summary")))
+
+(defclass gt-google-engine (gt-engine)
+  ((tag                :initform 'Google)
+   (host               :initform nil)
+   (path               :initform "/translate_a/single")
+   (token              :initform (cons 430675 2721866130) :initarg token) ; hard code
+   (token-time         :initform t)
+   (token-expired-time :initform (* 30 60))
+   (parse              :initform (gt-google-parser))))
+
+\f
+;;; Engine
+
+(defcustom gt-google-request-headers '(("Connection" . "Keep-Alive"))
+  "Extra request headers send to google server."
+  :type '(alist :key-type (string :tag "Key") :value-type (string :tag "Value"))
+  :group 'go-translate-google)
+
+(defun gt-google-gen-url (engine text src tgt)
+  "Generate url for google ENGINE with TEXT, SRC and TGT."
+  (with-slots (host path token) engine
+    (format "%s%s?%s"
+            (or host gt-google-host) path
+            (mapconcat (lambda (p)
+                         (format "%s=%s"
+                                 (url-hexify-string (car p))
+                                 (url-hexify-string (format "%s" (cdr p)))))
+                       `(("client" . "gtx")
+                         ("ie"     . "UTF-8")
+                         ("oe"     . "UTF-8")
+                         ("dt"     . "bd")
+                         ("dt"     . "ex")
+                         ("dt"     . "ld")
+                         ("dt"     . "md")
+                         ("dt"     . "qc")
+                         ("dt"     . "rw")
+                         ("dt"     . "rm")
+                         ("dt"     . "ss")
+                         ("dt"     . "t")
+                         ("dt"     . "at")
+                         ("pc"     . "1")
+                         ("otf"    . "1")
+                         ("srcrom" . "1")
+                         ("ssel"   . "0")
+                         ("tsel"   . "0")
+                         ("q"      . ,text)
+                         ("sl"     . ,src)
+                         ("tl"     . ,tgt)
+                         ("hl"     . ,tgt)
+                         ("tk"     . ,(gt-google-tkk token text)))
+                       "&"))))
+
+(defun gt-google-token-available-p (engine)
+  (with-slots (token token-time token-expired-time) engine
+    (and token
+         (or (eq token-time t)
+             (and token-time
+                  (<= (float-time (time-subtract (current-time) token-time))
+                      token-expired-time))))))
+
+(defun gt-google-with-token (engine done fail)
+  (declare (indent 1))
+  (with-slots (host token token-time) engine
+    (if (gt-google-token-available-p engine)
+        (funcall done)
+      (gt-request :url (or host gt-google-host)
+                  :headers gt-google-request-headers
+                  :done (lambda (raw)
+                          (with-temp-buffer
+                            (insert raw)
+                            (goto-char (point-min))
+                            (let ((tk (progn
+                                        (re-search-forward ",tkk:'\\([0-9]+\\)\\.\\([0-9]+\\)")
+                                        (cons (string-to-number (match-string 1))
+                                              (string-to-number (match-string 2))))))
+                              (setf token tk)
+                              (setf token-time (current-time))
+                              (funcall done))))
+                  :fail fail))))
+
+(cl-defmethod gt-translate ((engine gt-google-engine) task next)
+  (gt-google-with-token engine
+    (lambda ()
+      (with-slots (text src tgt res) task
+        (gt-request :url (gt-google-gen-url engine text src tgt)
+                    :headers gt-google-request-headers
+                    :done (lambda (raw) (setf res raw) (funcall next task))
+                    :fail (lambda (err) (gt-fail task err)))))
+    (lambda (err)
+      (gt-fail task (format "Take token failed, %s" err)))))
+
+;; tts
+
+(defun gt-google-tts-split-text (text)
+  "Split TEXT by maxlen at applicable point for translating.
+Code from `google-translate', maybe improve it someday."
+  (let (result (maxlen 200))
+    (if (or (null maxlen) (<= maxlen 0))
+           (push text result)
+      ;; split long text?
+      (with-temp-buffer
+           (save-excursion (insert text))
+           ;; strategy to split at applicable point
+           ;; 1) fill-region remaining text by maxlen
+           ;; 2) find end of sentence, end of punctuation, word boundary
+           ;; 3) consume from remaining text between start and (2)
+           ;; 4) repeat
+           (let ((fill-column (* maxlen 3))
+                 (sentence-end-double-space nil)
+                 (pos (point-min)))
+             (while (< pos (point-max))
+               (save-restriction
+                 (narrow-to-region pos (point-max))
+                 (fill-region pos (point-max))
+                 (let ((limit (+ pos maxlen)))
+                       (if (>= limit (point-max))
+                           (setq limit (point-max))
+                         (goto-char limit)
+                         ;; try to split at end of sentence
+                         (if (> (backward-sentence) pos)
+                             (setq limit (point))
+                           ;; try to split at end of punctuation
+                           (goto-char limit)
+                           (if (re-search-backward "[,、]" pos t)
+                                   (setq limit (1+ (point))) ; include punctuation
+                             (goto-char limit)
+                             ;; try to split at word boundary
+                             (forward-word-strictly -1)
+                             (when (> (point) pos)
+                                   (setq limit (point))))))
+                       (push (buffer-substring-no-properties pos limit) result)
+                       (goto-char limit)
+                       (setq pos limit)))))))
+    (reverse result)))
+
+(cl-defmethod gt-speak ((engine gt-google-engine) text lang)
+  (message "Requesting from %s for %s..." (or (oref engine host) gt-google-host) lang)
+  (cl-loop with texts = (gt-google-tts-split-text text)
+           with total = (length texts)
+           for c in texts
+           for i from 0
+           for ps = `(("ie"      . "UTF-8")
+                      ("client"  . "gtx")
+                      ("prev"    . "input")
+                      ("tl"      . ,lang)
+                      ("q"       . ,(string-trim c))
+                      ("total"   . ,total)
+                      ("idx"     . ,i)
+                      ("textlen" . ,(length c))
+                      ("tk"      . ,(gt-google-tkk (oref engine token) c)))
+           for url = (format "%s/translate_tts?%s"
+                             (or (oref engine host) gt-google-host)
+                             (mapconcat (lambda (p)
+                                          (format "%s=%s" (url-hexify-string (car p)) (url-hexify-string (format "%s" (cdr p)))))
+                                        ps "&"))
+           do (gt-play-audio url 'wait)))
+
+\f
+;;; Parser
+
+;; detail-mode, use as default
+
+(cl-defmethod gt-parse ((parser gt-google-parser) task)
+  (with-slots (res translator) task
+    (let* ((json (gt-resp-to-json parser res))
+           (brief-result (gt-result--brief parser json)))
+      (if (cdr (oref translator text)) ; multi-parts
+          (setf res (string-trim (gt-result--brief parser json) "\n+"))
+        (let* ((sphonetic    (gt-result--sphonetic parser json))
+               (tphonetic    (gt-result--tphonetic parser json))
+               (details      (gt-result--details parser json))
+               (definitions  (gt-result--definitions parser json))
+               (suggestion   (gt-result--suggestion parser json))
+               (suggestionp  (> (length suggestion) 0)) pt)
+          (cl-flet ((phonetic (ph)
+                      (if (and (or definitions definitions) (> (length ph) 0))
+                          (propertize (format " [%s]" ph) 'face 'gt-google-buffer-phonetic-face)
+                        ""))
+                    (headline (line)
+                      (propertize (format "[%s]\n" line) 'face 'gt-google-buffer-headline-face)))
+            (with-temp-buffer
+              ;; suggestion
+              (when suggestionp
+                (insert (propertize "Do you mean:" 'face 'gt-google-buffer-suggestion-desc-face) " "
+                        (propertize suggestion 'face 'gt-google-buffer-suggestion-text-face) "?\n\n"))
+              ;; phonetic & translate
+              (if (or details definitions)
+                  (progn
+                    (insert (if suggestionp suggestion
+                              (setq pt (point))
+                              (substring-no-properties (oref task text))))
+                    (insert (phonetic sphonetic) " ")
+                    (insert (propertize brief-result 'face 'gt-google-buffer-brief-result-face))
+                    (insert (phonetic tphonetic) "\n\n"))
+                (insert brief-result))
+              ;; details
+              (when details
+                (insert (headline "Details"))
+                (cl-loop for (label . items) in details
+                         unless (= 0 (length label))
+                         do (insert (format "\n%s:\n" label))
+                         do (cl-loop with index = 0
+                                     for trans in items
+                                     do (insert
+                                         (format "%2d. " (cl-incf index))
+                                         (car trans)
+                                         " (" (mapconcat #'identity (cdr trans) ", ")  ")"
+                                         "\n")))
+                (insert "\n"))
+              ;; definitions
+              (when definitions
+                (insert (headline "Definitions"))
+                (cl-loop for (label . items) in definitions
+                         unless (= 0 (length label))
+                         do (insert (format "\n%s:\n" label))
+                         do (cl-loop with index = 0
+                                     for (exp . eg) in items
+                                     do (insert (format "%2d. " (cl-incf index)) exp)
+                                     when (> (length eg) 0)
+                                     do (insert
+                                         "\n    > "
+                                         (propertize (or eg "") 'face 'gt-google-buffer-detail-demo-face))
+                                     do (insert "\n"))))
+              ;; at last, return
+              (add-text-properties (point-min) (point-max) (list 'gt-mark pt 'gt-brief brief-result))
+              (setf res (buffer-string)))))))))
+
+;; summary-mode
+
+(cl-defmethod gt-parse ((parser gt-google-summary-parser) task)
+  (let* ((json (gt-resp-to-json parser (oref task res)))
+         (result (string-trim (gt-result--brief parser json) "\n+")))
+    (oset task res result)))
+
+;; Extract results from response
+
+(cl-defmethod gt-resp-to-json ((_ gt-google-parser) resp)
+  "Convert the buffer RESP into JSON."
+  (condition-case err
+      (json-read-from-string (decode-coding-string resp 'utf-8))
+    (error (user-error "Result conversion error: %s" err))))
+
+(cl-defmethod gt-result--brief ((_ gt-google-parser) json)
+  "Get the translation text from JSON."
+  (mapconcat (lambda (item) (aref item 0)) (aref json 0) ""))
+
+(cl-defmethod gt-result--sphonetic ((_ gt-google-parser) json)
+  "Get the text phonetic from JSON."
+  (mapconcat (lambda (item) (if (> (length item) 3) (aref item 3) "")) (aref json 0) ""))
+
+(cl-defmethod gt-result--tphonetic ((_ gt-google-parser) json)
+  "Get the translation phonetic from JSON."
+  (mapconcat (lambda (item) (if (> (length item) 2) (aref item 2) "")) (aref json 0) ""))
+
+(cl-defmethod gt-result--details ((_ gt-google-parser) json)
+  "Get the details from JSON.
+Result style: ((noun (a (x y z))) (verb (b (m n o))))."
+  (cl-loop for i across (aref json 1)
+           collect
+           (cons
+            (aref i 0)
+            (cl-loop for j across (aref i 2)
+                     collect
+                     (cons
+                      (aref j 0)
+                      (cl-loop for k across (aref j 1) collect k))))))
+
+(cl-defmethod gt-result--definitions ((_ gt-google-parser) json)
+  "Get the definitions from JSON.
+Result style: ((noun (a b)) (verb (c d)))."
+  (cl-loop with defs = (ignore-errors (aref json 12))
+           for i across defs
+           collect
+           (cons
+            (aref i 0)
+            (cl-loop for j across (aref i 1)
+                     collect
+                     (cons
+                      (aref j 0)
+                      (ignore-errors (aref j 2)))))))
+
+(cl-defmethod gt-result--suggestion ((_ gt-google-parser) json)
+  "Get the suggestion from JSON."
+  (let ((info (aref json 7))) (unless (seq-empty-p info) (aref info 1))))
+
+\f
+;;; Token Key algorithm (deprecated)
+
+;; This algorithm is from `google-translate' project.
+;; https://github.com/atykhonov/google-translate/blob/master/google-translate-tk.el
+
+(defvar gt-google-token--bit-v-len 32)
+
+(defun gt-google-token--bit-v-2comp (v)
+  "Return the two's complement of V."
+  (let* ((vc (vconcat v))
+         (len (length vc)))
+    ;; Complement of v
+    (cl-loop for i from 0 below len do
+             (aset vc i (logxor (aref vc i) 1)))
+    ;; vc = complement of v + 1
+    (cl-loop for i downfrom (1- len) to 0
+             do (aset vc i (logxor (aref vc i) 1))
+             when (> (aref vc i) 0) return nil)
+    vc))
+
+(defun gt-google-token--number-to-bit-v (n)
+  "Return a bit vector from N."
+  (if (< n 0) (gt-google-token--bit-v-2comp
+               (gt-google-token--number-to-bit-v (abs n)))
+    (let ((v (make-vector gt-google-token--bit-v-len 0)))
+      (cl-loop for i downfrom (1- gt-google-token--bit-v-len) to 0
+               with q
+               when (< n 1) return nil do
+               (setq q (ffloor (* n 0.5)))
+               (aset v i (floor (- n (* 2.0 q))))
+               (setq n q))
+      v)))
+
+(defun gt-google-token--bit-v-to-number (v)
+  "Return a floating-point number from V."
+  (if (and (> (aref v 0) 0)
+           ;; Exclude [1 0 ... 0]
+           (cl-loop for i from 1 below gt-google-token--bit-v-len
+                    thereis (> (aref v i) 0)))
+      (- (gt-google-token--bit-v-to-number (gt-google-token--bit-v-2comp v)))
+    (funcall (if (> (aref v 0) 0)  #'- #'+)
+             (cl-reduce (lambda (acc e) (+ (* acc 2.0) e))
+                        v :initial-value 0.0))))
+
+(defun gt-google-token--logfn (fn n1 n2)
+  "Helper function for logical FN with N1 and N2."
+  (let ((v1 (gt-google-token--number-to-bit-v n1))
+        (v2 (gt-google-token--number-to-bit-v n2))
+        (v (make-vector gt-google-token--bit-v-len 0)))
+    (cl-loop for i from 0 below gt-google-token--bit-v-len do
+             (aset v i (funcall fn (aref v1 i) (aref v2 i))))
+    (gt-google-token--bit-v-to-number v)))
+
+(defun gt-google-token--logand (n1 n2)
+  "Return a floating-point number from N1 and N2."
+  (gt-google-token--logfn #'logand n1 n2))
+
+(defun gt-google-token--logxor (n1 n2)
+  "Return a floating-point number from N1 and N2."
+  (gt-google-token--logfn #'logxor n1 n2))
+
+(defun gt-google-token--lsh (n d)
+  "Return a floating-point number.
+Shift the bits in N to the left or rihgt D places.
+D is an integer."
+  (let ((v (gt-google-token--number-to-bit-v n))
+        (v-result (make-vector gt-google-token--bit-v-len 0)))
+    (if (< d 0)
+        ;; Shift Right Logical
+        (cl-loop for i from (abs d) below gt-google-token--bit-v-len
+                 for j from 0 do
+                 (aset v-result i (aref v j)))
+      ;; Shift Left Logical
+      (cl-loop for i from d below gt-google-token--bit-v-len
+               for j from 0 do
+               (aset v-result j (aref v i))))
+    (gt-google-token--bit-v-to-number v-result)))
+
+(defun gt-google-token--gen-rl (a b)
+  "Gen rl from A and B."
+  (cl-loop for c from 0 below (- (length b) 2) by 3
+           for d = (aref b (+ c 2)) do
+           (setq d (if (>= d ?a) (- d 87) (- d ?0)))
+           (setq d (if (= (aref b (1+ c)) ?+)
+                       (gt-google-token--lsh a (- d))
+                     (gt-google-token--lsh a d)))
+           (setq a (if (= (aref b c) ?+)
+                       (gt-google-token--logand (+ a d) 4294967295.0)
+                     (gt-google-token--logxor a d))))
+  a)
+
+(defun gt-google-tkk (token text)
+  "Calculate the TOKEN for search TEXT."
+  (let* ((b (car token))
+         (d1 (cdr token))
+         (ub "+-3^+b+-f")
+         (vb "+-a^+6")
+         (a (cl-loop for c across (encode-coding-string text 'utf-8)
+                     for rr = (gt-google-token--gen-rl (+ (or rr b) c) vb)
+                     finally (return rr))))
+    (setq a (gt-google-token--gen-rl a ub))
+    (setq a (gt-google-token--logxor a d1))
+    (if (< a 0) (setq a (+ (gt-google-token--logand a 2147483647.0) 2147483648.0)))
+    (setq a (ffloor (mod a 1e6)))
+    (format "%s.%s"
+            (car (split-string (number-to-string a) "\\."))
+            (car (split-string (number-to-string
+                                (gt-google-token--logxor a b))
+                               "\\.")))))
+
+(provide 'gt-engine-google)
+
+;;; gt-engine-google.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-engine-libre.el b/elpa/go-translate-20240802.131/gt-engine-libre.el
new file mode 100644 (file)
index 0000000..b09b7b2
--- /dev/null
@@ -0,0 +1,101 @@
+;;; gt-engine-libre.el --- Engine for LibreTranslate -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; https://github.com/LibreTranslate/LibreTranslate
+;; https://libretranslate.com/
+
+;;; Code:
+
+(require 'gt-extension)
+
+(defgroup go-translate-libre nil
+  "Configs for LibreTranslate engine."
+  :group 'go-translate)
+
+(defcustom gt-libre-host "https://translate.disroot.org/"
+  "API host of LibreTranslate.
+
+The official host is: https://libretranslate.com/, you can config this
+to use third-party or your local service."
+  :type 'string
+  :group 'go-translate-libre)
+
+\f
+;;; Components
+
+(defclass gt-libre-parser (gt-parser) ())
+
+(defclass gt-libre-engine (gt-engine)
+  ((tag       :initform 'LibreTranslate)
+   (host      :initform nil :initarg :host)
+   (path      :initform "/translate")
+   (parse     :initform (gt-libre-parser))
+   (delimiter :initform "21597"
+              ;; Any way to force specific segment not be changed after translate?
+              ;; I don't know! Just set this a random number, to make it work...sometimes.
+              ;; So this maybe failed in translating multiple parts text.
+              )
+   (cache     :initform nil)
+   (key       :initform 'api-key
+              :initarg :key
+              :documentation "The api-key of LibreTranslate.
+You can also put it into .authinfo file as:
+  machine [HOST like libretranslate.com] login api-key password [***]")))
+
+\f
+;;; Translate
+
+(cl-defmethod gt-ensure-key ((engine gt-libre-engine))
+  (with-slots (host key) engine
+    (unless (stringp key)
+      (setf key
+            (gt-lookup-password
+             :user (if key (format "%s" key) "api-key")
+             :host (url-host (url-generic-parse-url (or host gt-libre-host))))))))
+
+(cl-defmethod gt-translate ((engine gt-libre-engine) task next)
+  (gt-ensure-key engine)
+  (with-slots (text src tgt res) task
+    (with-slots (host path key) engine
+      (gt-request :url (concat (or host gt-libre-host) path)
+                  :data `(("q"      . ,text)
+                          ("source" . ,src)
+                          ("target" . ,tgt)
+                          ("format" . "text")
+                          ("alternatives" . 1)
+                          ,(if key `("api_key" . ,key)))
+                  :done (lambda (raw)
+                          (setf res raw)
+                          (funcall next task))
+                  :fail (lambda (err)
+                          (gt-fail task err))))))
+
+(cl-defmethod gt-parse ((_ gt-libre-parser) task)
+  (with-slots (res) task
+    (let ((json (json-read-from-string (decode-coding-string res 'utf-8))))
+      (setf res (cdr (assoc 'translatedText json))))))
+
+(provide 'gt-engine-libre)
+
+;;; gt-engine-libre.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-engine-stardict.el b/elpa/go-translate-20240802.131/gt-engine-stardict.el
new file mode 100644 (file)
index 0000000..3ec8c0a
--- /dev/null
@@ -0,0 +1,183 @@
+;;; gt-engine-stardict.el --- StarDict -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; Engine for sdcv (StarDict Console Version).
+;;
+;; sdcv is an offline translate software. It can be used when no network.
+;;
+;; Make sure sdcv is installed on your system:
+;;
+;;   sudo pacman -S sdcv
+;;
+;; Then download dictionary data to ~/.stardict/dic or /usr/share/stardict/dic:
+;;
+;;   # For example from http://download.huzheng.org:
+;;
+;;   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
+;;
+;; Also, you can specify the dictionary and data-dir in `gt-stardict-engine' definition:
+;;
+;;   (gt-stardict-engine :dict "a-dictionary" :dir "~/.stardict/dic" :dir-only t)
+;;
+;;; Code:
+
+(require 'gt-extension)
+
+(defclass gt-stardict-parser (gt-parser) ())
+
+(defclass gt-stardict-engine (gt-engine)
+  ((tag :initform 'StarDict)
+   (dict
+    :initarg :dict
+    :initform nil
+    :type (or string null)
+    :documentation "Search only this dictionary if not nil.")
+   (dir
+    :initarg :dir
+    :initform nil
+    :type (or string null)
+    :documentation "Use this dir as data directory if not nil.")
+   (dir-only
+    :initarg :dir-only
+    :initform nil
+    :type boolean
+    :documentation "Only use dictionaries in `dir'.")
+   (exact
+    :initarg :exact
+    :initform nil
+    :type boolean
+    :documentation "Exact search, that is disable fuzzy-search.")
+   (cache :initform nil)
+   (parse :initform (gt-stardict-parser))))
+
+(defvar gt-stardict-program "sdcv"
+  "Executable command of sdcv (StarDict Console Version).")
+
+(defconst gt-stardict-args '("--non-interactive" "--json-output" "-0" "-1"))
+
+(defvar gt-stardict-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map [mouse-1] #'gt-stardict-switch-dict)
+    (define-key map (kbd "C-c C-c") #'gt-stardict-switch-dict)
+    map)
+  "The keymap used by StarDict.")
+
+(defun gt-stardict-build-args (engine)
+  (with-slots (dict dir dir-only exact) engine
+    (let ((args gt-stardict-args))
+      (if dict (setq args `("--use-dict" ,dict ,@args)))
+      (if dir (setq args `("--data-dir" ,dir ,@args)))
+      (if dir-only (setq args `("--only-data-dir" ,@args)))
+      (if exact (setq args `("--exact-search" ,@args)))
+      (gt-log 'gt-stardict (format "[sdcv arguments]: %s" args))
+      args)))
+
+(defun gt-stardict-list-dicts (engine)
+  (with-temp-buffer
+    (apply #'call-process gt-stardict-program nil t nil
+           (cons "--list-dicts" (gt-stardict-build-args engine)))
+    (goto-char (point-min))
+    (cl-loop for d across (json-read)
+             collect (cons (alist-get 'name d) (alist-get 'wordcount d)))))
+
+(defun gt-stardict-switch-dict ()
+  (interactive)
+  (let* ((task (get-char-property (point) 'gt-task))
+         (engine (and task (oref task engine)))
+         (dicts (and engine (gt-stardict-list-dicts engine))))
+    (when (= (length dicts) 0) (user-error "No dict found, nothing can do"))
+    (let ((dict (completing-read "Dictionary to use: "
+                                 (gt-make-completion-table (cons 'ALL dicts))
+                                 nil t nil nil (oref engine dict))))
+      (oset engine dict (unless (string= dict "ALL") dict))
+      (oset gt-buffer-render-translator keep t)
+      (gt-start gt-buffer-render-translator))))
+
+(cl-defgeneric gt-stardict-pretty-definition (dictionary definition)
+  "Try to pretty the DEFINITION part of DICTIONARY for output."
+  (:method ((_ (eql '朗道)) definition)
+           (with-temp-buffer
+             (insert definition)
+             ;; phonetic
+             (goto-char (point-min))
+             (while (re-search-forward "^\\*\\(\\[.+\\]\\)" nil t)
+               (replace-match (propertize (match-string 1) 'face 'gt-stardict-phonetic-face)))
+             ;; related
+             (goto-char (point-min))
+             (while (re-search-forward "^相关词组:" nil t)
+               (replace-match (concat "\n" (propertize (match-string 0) 'face 'bold))))
+             ;; word class
+             (goto-char (point-min))
+             (while (re-search-forward
+                     (format "^\\(%s\\)\\. " (mapconcat (lambda (v) (format "%s" v)) gt-word-classes "\\|")) nil t)
+               (put-text-property (match-beginning 0) (match-end 0) 'face 'gt-stardict-word-class-face))
+             (buffer-string)))
+  (cond ((string-match-p "^朗道.+字典" (format "%s" dictionary))
+         (gt-stardict-pretty-definition '朗道 definition))
+        (t definition)))
+
+(cl-defmethod gt-translate ((engine gt-stardict-engine) task next)
+  (unless (executable-find gt-stardict-program)
+    (user-error "You should install `sdcv' first before use `gt-stardict-engine'"))
+  (with-slots (text res) task
+    (with-temp-buffer
+      (apply #'call-process gt-stardict-program nil t nil
+             (append (gt-stardict-build-args engine) (list text)))
+      (setf res (buffer-string))
+      (funcall next task))))
+
+(cl-defmethod gt-parse ((_ gt-stardict-parser) task)
+  (with-slots (res) task
+    (let ((json (condition-case _ (json-read-from-string res)
+                  (error (user-error res))))
+          (props `(pointer hand help-echo "Click to switch dictionary" keymap ,gt-stardict-map)))
+      (when (= (length json) 0)
+        (user-error (apply #'propertize "No translation result found, sorry :(" props)))
+      (with-temp-buffer
+        (cl-loop for d in (cl-remove-duplicates (mapcar #'cdar json) :test #'string=)
+                 do (insert (apply #'propertize d 'display '(height 0.6) 'face 'gt-stardict-dict-face props) "\n\n")
+                 collect (cl-loop for r across (cl-remove-if-not
+                                                (lambda (item) (string= (cdar item) d)) json)
+                                  for i from 1
+                                  for word = (propertize (alist-get 'word r)
+                                                         'face 'gt-stardict-word-face
+                                                         'display '(raise 0.6))
+                                  for definition = (propertize (gt-stardict-pretty-definition (intern d) (alist-get 'definition r))
+                                                               'display '(raise 0.3)
+                                                               'line-prefix "  "
+                                                               'wrap-prefix "  ")
+                                  collect (concat (if (= i 0) (gt-line-height-separator 10)) word definition) into r1
+                                  finally (return (string-join r1 "\n")))
+                 into rs finally (insert (string-join rs "\n\n")))
+        (setf res (buffer-string))))))
+
+(provide 'gt-engine-stardict)
+
+;;; gt-engine-stardict.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-engine-youdao.el b/elpa/go-translate-20240802.131/gt-engine-youdao.el
new file mode 100644 (file)
index 0000000..28f22d1
--- /dev/null
@@ -0,0 +1,188 @@
+;;; gt-engine-youdao.el --- YouDao Dict -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; 有道词典 (https://dict.youdao.com), A Chinese translation website.
+;;
+;; This engine is only for Chinese user.
+;;
+;; > 此引擎仅供中文翻译使用
+;;
+;; There are two engine implements here, one is `gt-youdao-dict-engine' and another is `gt-youdao-suggest-engine'.
+;;
+;; > 这里为有道实现了两个引擎:
+;; > 1. `gt-youdao-dict-engine',基于网页翻译接口,具备基本的翻译能力
+;; > 2. `gt-youdao-suggest-engine', 用于展示输入单词的同义词及相关词汇
+;; >
+;; > 另外,有道还提供 API 接口,罢了,要收费的。
+
+;;; Code:
+
+(require 'gt-extension)
+
+\f
+;;; 有道词典
+
+(defclass gt-youdao-dict-parser (gt-parser) ())
+
+(defclass gt-youdao-dict-engine (gt-engine)
+  ((tag     :initform '有道词典)
+   (url     :initform "https://dict.youdao.com/result?lang=%s&word=%s")
+   (parse   :initform (gt-youdao-dict-parser))))
+
+(cl-defmethod gt-translate ((engine gt-youdao-dict-engine) task next)
+  (with-slots (text src tgt res meta translator) task
+    (let* ((lang (cond ((equal src 'zh) tgt)
+                       ((equal tgt 'zh) src)
+                       (t (user-error "只支持中文跟其他语言之间的翻译"))))
+           (url (if (cdr (oref translator text))
+                    (user-error "不支持分段翻译")
+                  (format (oref engine url)
+                          (url-hexify-string (format "%s" lang))
+                          (url-hexify-string text)))))
+      (gt-request :url url
+                  :done (lambda (raw)
+                          (setf res (gt-parse-html-dom raw) meta url)
+                          (funcall next task))
+                  :fail (lambda (err) (gt-fail task err))))))
+
+(defun gt-youdao-dict--extract (dom)
+  "从 DOM 中解析结果。"
+  (let* ((phonetic (cl-loop for p in (dom-by-class dom "per-phone")
+                            collect (cons (nth 2 (nth 2 p)) (nth 2 (nth 3 p)))))
+         (node (dom-by-id dom "catalogue_author"))
+         (exp-ce (cl-loop for n in (dom-by-class node "word-exp-ce")
+                          for s = (car (dom-by-class n "point"))
+                          for r = (car (dom-by-class n "word-exp_tran"))
+                          collect (cons (nth 2 s) (nth 2 r))))
+         (exp-nw (unless exp-ce
+                   (cl-loop for n in (dom-by-class node "trans-content")
+                            collect (nth 2 n))))
+         (exp-ec (unless (or exp-nw exp-ce)
+                   (cl-loop for n in (dom-by-class node "word-exp")
+                            collect (cons (nth 2 (nth 2 n)) (nth 2 (nth 3 n))))))
+         (exam-type (mapcar #'caddr (dom-by-class node "exam_type-value")))
+         (word-wfs (cl-loop for n in (dom-by-class node "word-wfs-cell-less")
+                            collect (cons (nth 2 (nth 2 (nth 2 n))) (nth 2 (nth 3 n))))))
+    (list :exp-nw exp-nw :exp-ce exp-ce :exp-ec exp-ec :phonetic phonetic
+          :exam-type exam-type :word-wfs word-wfs)))
+
+(defun gt-youdao-dict--filter (result)
+  "对 RESULT 进行某些美化。"
+  (string-replace "】" "]" (string-replace "【" "[" result)))
+
+(defun gt-youdao-dict--tts-url (word &optional lang)
+  (format "http://dict.youdao.com/dictvoice?audio=%s&type=%s"
+          (url-hexify-string word)
+          (if (member lang '("英" 1 "1")) 1 0)))
+
+(cl-defmethod gt-parse ((_ gt-youdao-dict-parser) task)
+  (with-slots (res meta) task
+    (gt-plist-let ((gt-youdao-dict--extract res))
+      (with-temp-buffer
+        ;; 音标
+        (when .phonetic
+          (insert (substring-no-properties (oref task text)) "  "
+                  (propertize (if .exp-ce
+                                  (propertize (caar .phonetic) 'face 'gt-youdao-dict-phonetic-face)
+                                (mapconcat (lambda (p)
+                                             (concat (propertize (car p) 'face 'gt-youdao-dict-label-face) " "
+                                                     (propertize (cdr p)
+                                                                 'face 'gt-youdao-dict-phonetic-face
+                                                                 'mouse-face 'bold
+                                                                 'gt-tts-url (gt-youdao-dict--tts-url (oref task text) (car p))
+                                                                 'keymap (gt-simple-keymap [mouse-1] #'gt-do-speak))))
+                                           .phonetic "  "))
+                              'display '(height 0.7))
+                  "\n\n"))
+        ;; 释义
+        (insert (cond
+                 (.exp-nw (mapconcat #'identity .exp-nw "\n"))
+                 (.exp-ce (mapconcat (lambda (exp)
+                                       (concat (propertize (car exp) 'face 'gt-youdao-dict-entry-face) "\n\n  " (cdr exp)))
+                                     .exp-ce "\n\n"))
+                 (.exp-ec (mapconcat (lambda (exp)
+                                       (if (cdr exp)
+                                           (concat (propertize (car exp) 'face 'gt-youdao-dict-entry-face) " " (cdr exp))
+                                         (gt-youdao-dict--filter (car exp))))
+                                     .exp-ec "\n\n"))
+                 (t (user-error "也许没有翻译结果?")))
+                "\n\n")
+        ;; 相关
+        (cl-loop for wf in .word-wfs
+                 for i from 1
+                 do (insert (propertize (car wf) 'face 'gt-youdao-dict-label-face) "  " (cdr wf))
+                 do (insert (if (= (length .word-wfs) i) "\n\n" (if (> (current-column) fill-column) "\n" "\t"))))
+        ;; 考试
+        (when-let (et (and .exam-type (mapconcat #'identity .exam-type " / ")))
+          (insert (propertize et 'face 'gt-youdao-dict-phonetic-face 'display '(height 0.7))))
+        ;; 返回
+        (setf res (if (or .exp-ce .exp-ec)
+                      (propertize (buffer-string) 'gt-url meta)
+                    (buffer-string)))))))
+
+\f
+;;; 有道同义词
+
+(defclass gt-youdao-suggest-parser (gt-parser) ())
+
+(defclass gt-youdao-suggest-engine (gt-engine)
+  ((tag       :initform '有道同义词)
+   (url       :initform "https://dict.youdao.com/suggest?q=%s&num=%s&doctype=json")
+   (limit     :initform 9 :initarg :limit)
+   (delimiter :initform nil)
+   (parse     :initform (gt-youdao-suggest-parser))))
+
+(cl-defmethod gt-translate ((engine gt-youdao-suggest-engine) task next)
+  (with-slots (text res meta) task
+    (when (cdr text)
+      (user-error "不支持一次翻译多个单词或句子"))
+    (let ((url (format (oref engine url) (url-hexify-string (car text)) (oref engine limit))))
+      (gt-request :url url
+                  :done (lambda (raw) (setf res raw meta url) (funcall next task))
+                  :fail (lambda (err) (gt-fail task err))))))
+
+(cl-defmethod gt-parse ((_ gt-youdao-suggest-parser) task)
+  (with-slots (res meta) task
+    (let ((json (json-read-from-string res)))
+      (unless (= (alist-get 'code (alist-get 'result json)) 200)
+        (user-error (alist-get 'msg (alist-get 'result json))))
+      (let ((lst (cl-loop
+                  for item across (alist-get 'entries (alist-get 'data json))
+                  for i from 1
+                  for ent = (propertize (alist-get 'entry item) 'face 'gt-youdao-suggest-entry-face)
+                  for exp = (if-let (ex (alist-get 'explain item)) (propertize (concat "   " ex) 'wrap-prefix "   "))
+                  collect (concat (if (> i 1) (gt-line-height-separator 18)) ent
+                                  (if exp (concat "\n" (gt-line-height-separator 8) exp))))))
+        (with-temp-buffer
+          (insert (string-join lst "\n"))
+          (goto-char (point-min))
+          (while (re-search-forward (format "\\(%s\\)\\(\\. \\|.\\)"
+                                            (mapconcat (lambda (v) (format "%s" v)) gt-word-classes "\\|"))
+                                    nil t)
+            (put-text-property (match-beginning 0) (match-end 0) 'face 'gt-youdao-suggest-cixing-face))
+          (setf res (propertize (buffer-string) 'gt-url meta)))))))
+
+(provide 'gt-engine-youdao)
+
+;;; gt-engine-youdao.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-extension.el b/elpa/go-translate-20240802.131/gt-extension.el
new file mode 100644 (file)
index 0000000..6941181
--- /dev/null
@@ -0,0 +1,1175 @@
+;;; gt-extension.el --- Extension components -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+;; Package-Requires: ((emacs "28.1"))
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; Some extension components
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'json)
+(require 'gt-core)
+(require 'gt-faces)
+
+\f
+;;; [Render] buffer render
+
+(defclass gt-buffer-render (gt-render)
+  ((buffer-name     :initarg :buffer-name     :initform nil)
+   (window-config   :initarg :window-config   :initform nil)
+   (split-threshold :initarg :split-threshold :initform nil))
+  "Popup a new buffer to display the translation result.")
+
+(defcustom gt-buffer-render-follow-p nil
+  "Pop to the result buffer instead of just displaying."
+  :type 'boolean
+  :group 'go-translate)
+
+(defcustom gt-buffer-render-split-width-threshold 80
+  "Threshold width of window horizontal split for Buffer-Render."
+  :group 'go-translate
+  :type '(choice
+          (const :tag "Disable" nil)
+          (integer :tag "Threshold")))
+
+(defcustom gt-buffer-render-window-config
+  '((display-buffer-reuse-window display-buffer-in-direction)
+    (direction . right))
+  "Window configuration of buffer window of Buffer-Render.
+
+Notice, this can be overrided by `window-config' slot of render instance."
+  :type 'sexp
+  :group 'go-translate)
+
+(defcustom gt-buffer-render-init-hook nil
+  "Hook run after buffer initialization in Buffer-Render."
+  :type 'hook
+  :group 'go-translate)
+
+(defcustom gt-buffer-render-output-hook nil
+  "Hook run after output finished in Buffer-Render."
+  :type 'hook
+  :group 'go-translate)
+
+(defvar gt-buffer-render-buffer-name "*gt-result*")
+
+(defvar gt-buffer-render-evil-leading-key "," "Leading key for keybinds in evil mode.")
+
+(defvar-local gt-buffer-render-translator nil)
+(defvar-local gt-buffer-render-keybinding-messages nil)
+(defvar-local gt-buffer-render-local-map nil)
+
+(declare-function evil-define-key* "ext:evil-core.el" t t)
+
+(cl-defmacro gt-buffer-render-key ((key &optional tag test) &rest form)
+  (declare (indent 1))
+  `(when ,(or test `,test t)
+     (let ((fn ,(if (member (car-safe (car form)) '(function lambda))
+                    `,(car form)
+                  `(lambda () (interactive) ,@form))))
+       (if (bound-and-true-p evil-mode)
+           (evil-define-key* 'normal gt-buffer-render-local-map
+                             (kbd (concat gt-buffer-render-evil-leading-key
+                                          (if gt-buffer-render-evil-leading-key " ") ,key))
+                             fn)
+         (define-key gt-buffer-render-local-map (kbd ,key) fn))
+       (setq gt-buffer-render-keybinding-messages
+             (cl-remove ,key gt-buffer-render-keybinding-messages :key #'car :test #'string=))
+       (when ,tag
+         (push (cons ,key ,tag) gt-buffer-render-keybinding-messages)))))
+
+(defun gt-buffer-render--refresh ()
+  (interactive)
+  (oset gt-buffer-render-translator keep t)
+  (gt-start gt-buffer-render-translator))
+
+(defun gt-buffer-render--cycle-next (&optional ignore-rules)
+  (interactive "P")
+  (with-slots (target taker keep) gt-buffer-render-translator
+    (if (and (slot-boundp taker 'langs) (gt-functionp (oref taker langs)))
+        (user-error "Current taker not support cycle next")
+      (let* ((curr gt-last-target)
+             (gt-skip-lang-rules-p ignore-rules)
+             (gt-ignore-target-history-p t)
+             (next (gt-target taker gt-buffer-render-translator 'next)))
+        (unless (equal next curr)
+          (setf target next keep t)
+          (gt-start gt-buffer-render-translator))))))
+
+(defun gt-buffer-render--toggle-polyglot ()
+  (interactive)
+  (setq gt-polyglot-p (not gt-polyglot-p))
+  (with-slots (target keep) gt-buffer-render-translator
+    (setf target nil keep t)
+    (gt-start gt-buffer-render-translator)))
+
+(defun gt-buffer-render--browser ()
+  (interactive)
+  (if-let (url (get-char-property (point) 'gt-url))
+      (progn (browse-url url)
+             (message "Opening %s... Done!" url))
+    (message "No url found on current result.")))
+
+(defun gt-buffer-render--delete-cache ()
+  (interactive)
+  (when-let ((task (get-pos-property (point) 'gt-task))
+             (key (gt-cache-key task (or (get-pos-property (point) 'gt-part) 0))))
+    (gt-cache-set gt-default-cacher key nil)
+    (message "Delete %s from cacher" key)))
+
+(defun gt-buffer-render--keyboard-quit ()
+  (interactive)
+  (unwind-protect
+      (gt-interrupt-speak-process)
+    (keyboard-quit)))
+
+(defun gt-buffer-render--show-tips ()
+  (interactive)
+  (if gt-buffer-render-keybinding-messages
+      (message (mapconcat
+                (lambda (kd) (concat (propertize (car kd) 'face 'font-lock-keyword-face) ": " (cdr kd)))
+                (reverse gt-buffer-render-keybinding-messages) " "))
+    (message "No any help tips found")))
+
+(defun gt-buffer-render--toggle-readonly ()
+  (interactive)
+  (read-only-mode -1)
+  (use-local-map nil)
+  (local-set-key (kbd "C-x C-q")
+                 (lambda ()
+                   (interactive)
+                   (read-only-mode 1)
+                   (use-local-map gt-buffer-render-local-map))))
+
+(defun gt-buffer-render--unfold-source-text ()
+  (interactive)
+  (mapc (lambda (ov)
+          (delete-overlay ov))
+        (overlays-at (point))))
+
+(defvar gt-buffer-render-dislike-source-text nil)
+
+(defvar gt-buffer-render-source-text-limit 140
+  "Fold some of the source text if it's too long.
+This can be a number as visible length or a function with source text
+as argument that return a length.")
+
+(defvar gt-buffer-render-source-text-fold-bound-function
+  #'gt-buffer-render-source-text-fold-bound)
+
+(defun gt-buffer-render-source-text-fold-bound ()
+  (when (and (numberp gt-buffer-render-source-text-limit)
+             (> (point-max) gt-buffer-render-source-text-limit))
+    (goto-char (min (point-max) (+ (point-min) gt-buffer-render-source-text-limit)))
+    (move-to-column fill-column)
+    (skip-chars-backward " \t\n")
+    (when-let (bd (bounds-of-thing-at-point 'word))
+      (goto-char (cdr bd)))
+    (when (> (- (point-max) 20) (point))
+      (cons (point) (point-max)))))
+
+(defun gt-buffer-insert-source-text (text &optional fold)
+  "Propertize and insert the source TEXT into render buffer.
+If FOLD non nil, only make part of the text visible."
+  (let* ((text (string-trim text "\n+"))
+         (beg (point))
+         (end (progn (insert (substring-no-properties text)) (point))))
+    (insert "\n\n")
+    (save-excursion
+      (put-text-property beg end 'face 'gt-buffer-render-source-face)
+      (put-text-property beg (+ end 2) 'gt-source-text t)
+      (when fold
+        (when-let* ((bd (save-restriction
+                          (narrow-to-region beg end)
+                          (funcall gt-buffer-render-source-text-fold-bound-function)))
+                    (ov (make-overlay beg end)))
+          (overlay-put ov 'display
+                       (concat
+                        (propertize (buffer-substring beg (car bd)) 'face 'gt-buffer-render-source-face)
+                        " ..."))
+          (overlay-put ov 'keymap (gt-simple-keymap
+                                   [return] #'gt-buffer-render--unfold-source-text
+                                   [mouse-1] #'gt-buffer-render--unfold-source-text))
+          (overlay-put ov 'pointer 'hand)
+          (overlay-put ov 'help-echo "Click to unfold the text")))
+      (cons beg end))))
+
+(defun gt-buffer-render-init (buffer render translator)
+  "Init BUFFER for RENDER of TRANSLATOR."
+  (with-current-buffer buffer
+    (with-slots (text tasks engines) translator
+      ;; setup
+      (deactivate-mark)
+      (visual-line-mode -1)
+      (font-lock-mode 1)
+      (setq-local cursor-type 'hbar)
+      (setq-local cursor-in-non-selected-windows nil)
+      (setq-local gt-buffer-render-translator translator)
+      ;; headline
+      (let ((engines (cl-delete-duplicates (mapcar (lambda (task) (oref task engine)) tasks))))
+        (gt-header render translator (unless (cdr engines) (oref (car engines) tag))))
+      ;; content
+      (let ((inhibit-read-only t)
+            (ret (gt-extract render translator)))
+        (erase-buffer)
+        (newline)
+        (save-excursion
+          (unless (or gt-buffer-render-dislike-source-text (cdr text)) ;; single part, single task
+            (pcase-let ((`(,beg . ,end) (gt-buffer-insert-source-text (car text))))
+              (put-text-property beg end 'gt-task (car tasks))))
+          (cl-loop for c in text
+                   for i from 0
+                   if (cdr text) do (gt-buffer-insert-source-text c) ; multi parts
+                   do (cl-loop for tr in ret
+                               for stream = (plist-get tr :stream)
+                               for res = (propertize "Loading..."
+                                                     'face 'gt-buffer-render-loading-face
+                                                     'gt-result (if stream 'stream t))
+                               for (prefix task) = (list (plist-get tr :prefix) (plist-get tr :task))
+                               for output = (propertize (concat prefix res "\n\n") 'gt-task task 'gt-part i)
+                               do (let ((engine (oref task engine)))
+                                    (if (and (gt-stream-p engine) (= i 0)) ; record result bound for stream
+                                        (let ((beg (point)) end)
+                                          (insert output)
+                                          (setq beg (set-marker (make-marker) (+ beg (length prefix))))
+                                          (setq end (set-marker (make-marker) (save-excursion (skip-chars-backward " \t\n") (point))))
+                                          (oset task markers (cons beg end)))
+                                      (insert output)))))))
+      ;; keybinds
+      (setq gt-buffer-render-local-map (make-sparse-keymap))
+      (use-local-map gt-buffer-render-local-map)
+      (gt-keybinds render translator)
+      ;; state
+      (read-only-mode 1)
+      (if-let (w (get-buffer-window nil t)) (set-window-point w  (point)))
+      ;; execute the hook if exists
+      (run-hooks 'gt-buffer-render-init-hook))))
+
+(defun gt-buffer-render-output (buffer render translator)
+  "Output TRANSLATOR's result to BUFFER for RENDER."
+  (with-current-buffer buffer
+    (let ((inhibit-read-only t)
+          (ret (gt-extract render translator)) bds prop)
+      (with-slots (text tasks) translator
+        (save-excursion
+          ;; refresh source text
+          (goto-char (point-min))
+          (unless (cdr text)
+            (when-let (prop (text-property-search-forward 'gt-source-text))
+              (delete-region (prop-match-beginning prop) (prop-match-end prop))
+              (when (or (cdr ret) (not (get-pos-property 1 'gt-mark (car (ensure-list (plist-get (car ret) :result))))))
+                (let ((bd (gt-buffer-insert-source-text (car text) t)))
+                  (unless (cdr tasks)
+                    (put-text-property (car bd) (cdr bd) 'gt-task (car tasks)))))))
+          ;; collect positions
+          (goto-char (point-min))
+          (while (setq prop (text-property-search-forward 'gt-result))
+            (push (cons (set-marker (make-marker) (prop-match-beginning prop))
+                        (set-marker (make-marker) (prop-match-end prop)))
+                  bds))
+          (setq bds (nreverse bds))
+          ;; output results in bounds
+          (cl-loop for _ in text
+                   for i from 0
+                   do (cl-loop for tr in ret
+                               for (beg . end) = (pop bds)
+                               for (res state task) = (list (plist-get tr :result) (plist-get tr :state) (plist-get tr :task))
+                               do (goto-char beg)
+                               do (when (and (cl-plusp state) (null (get-char-property beg 'gt-done))
+                                             (or (not (eq (get-char-property (point) 'gt-result) 'stream)) (= state 1)))
+                                    (delete-region beg end)
+                                    (insert (propertize (if (consp res) (nth i res) res)
+                                                        'gt-result t 'gt-done t 'gt-task task 'gt-part i))))))))
+    ;; update states
+    (set-buffer-modified-p nil)
+    ;; execute the hook if exists
+    (run-hooks 'gt-buffer-render-output-hook)))
+
+(cl-defmethod gt-header ((_ gt-buffer-render) translator &optional tag)
+  "Set head line format for Buffer Render of TRANSLATOR.
+TAG is extra message show in the middle if not nil."
+  (with-slots (target) translator
+    (let ((line (append
+                 '(" ")
+                 (when-let (src (car target))
+                   (list
+                    "[" (propertize (format "%s" src) 'face 'gt-buffer-render-header-lang-face) "]"
+                    (if tag (concat " ― " (propertize (format "%s" tag) 'face 'gt-buffer-render-header-desc-face)) "")
+                    " → "))
+                 (list
+                  "["
+                  (mapconcat (lambda (s) (propertize (format "%s" s) 'face 'gt-buffer-render-header-lang-face)) (cdr target) ", ")
+                  "]"
+                  (when (and gt-buffer-render-keybinding-messages (not (bound-and-true-p evil-mode)))
+                    (concat "    (" (propertize "?" 'face 'font-lock-type-face) " for tips)"))))))
+      (setq header-line-format line))))
+
+(cl-defmethod gt-keybinds ((_ gt-buffer-render) _translator)
+  "Define keybinds for `gt-buffer-render-local-map'."
+  (gt-buffer-render-key ("t" "Cycle Next")        #'gt-buffer-render--cycle-next)
+  (gt-buffer-render-key ("T" "Toggle Polyglot")   #'gt-buffer-render--toggle-polyglot)
+  (gt-buffer-render-key ("y" "TTS")               (lambda () (interactive) (let (gt-tts-last-engine) (gt-do-speak))))
+  (gt-buffer-render-key ("O" "Browser")           #'gt-buffer-render--browser)
+  (gt-buffer-render-key ("c" "Del Cache")         #'gt-buffer-render--delete-cache)
+  (gt-buffer-render-key ("C")                     #'gt-purge-cache)
+  (gt-buffer-render-key ("g" "Refresh")           #'gt-buffer-render--refresh)
+  (gt-buffer-render-key ("n")                     #'next-line)
+  (gt-buffer-render-key ("p")                     #'previous-line)
+  (gt-buffer-render-key ("h")                     #'backward-char)
+  (gt-buffer-render-key ("j")                     #'next-line)
+  (gt-buffer-render-key ("k")                     #'previous-line)
+  (gt-buffer-render-key ("l")                     #'forward-char)
+  (gt-buffer-render-key ("q" "Quit")              #'kill-buffer-and-window)
+  (gt-buffer-render-key ("C-g")                   #'gt-buffer-render--keyboard-quit)
+  (gt-buffer-render-key ("C-x C-q")               #'gt-buffer-render--toggle-readonly)
+  (gt-buffer-render-key ("?")                     #'gt-buffer-render--show-tips))
+
+(cl-defmethod gt-extract :around ((render gt-buffer-render) translator)
+  "Decorate prefix and result for buffer render."
+  (cl-loop with mpp = (cdr (oref translator text))
+           with ret = (cl-call-next-method render translator)
+           for tr in ret
+           for (prefix result state) = (gt-plist-let tr (list .prefix (format "%s" .result) .state))
+           if (and prefix (or (not (slot-boundp render 'prefix)) (eq (oref render prefix) t)))
+           do (plist-put tr :prefix
+                         (concat (propertize (concat prefix (unless mpp "\n"))
+                                             'face (if mpp 'gt-buffer-render-inline-prefix-face 'gt-buffer-render-block-prefix-face))
+                                 "\n"))
+           if (= 1 state) do (plist-put tr :result (propertize result 'face 'gt-buffer-render-error-face))
+           collect tr))
+
+(cl-defmethod gt-init ((render gt-buffer-render) translator)
+  (with-slots (buffer-name split-threshold window-config) render
+    (let ((buf (get-buffer-create (or buffer-name gt-buffer-render-buffer-name)))
+          (split-width-threshold (or split-threshold gt-buffer-render-split-width-threshold split-width-threshold)))
+      (gt-buffer-render-init buf render translator)
+      (display-buffer buf (or window-config gt-buffer-render-window-config)))))
+
+(cl-defmethod gt-output ((render gt-buffer-render) translator)
+  (when-let (buf (get-buffer (or (oref render buffer-name) gt-buffer-render-buffer-name)))
+    (gt-buffer-render-output buf render translator)
+    (when (= (oref translator state) 3)
+      (if gt-buffer-render-follow-p
+          (pop-to-buffer buf)
+        (display-buffer buf)))))
+
+(cl-defmethod gt-output :after ((_ gt-buffer-render) translator)
+  (when (= (oref translator state) 3) (message "")))
+
+\f
+;;; [Render] Child-Frame Render (Popup Mode)
+;; implements via package Posframe, you should install it before use this
+
+(defclass gt-posframe-pop-render (gt-buffer-render)
+  ((width        :initarg :width        :initform nil)
+   (height       :initarg :height       :initform nil)
+   (forecolor    :initarg :forecolor    :initform nil)
+   (backcolor    :initarg :backcolor    :initform nil)
+   (padding      :initarg :padding      :initform 12)
+   (frame-params :initarg :frame-params :initform nil :type list
+                 :documentation "Other parameters passed to `posframe-show'. Have higher priority."))
+  "Pop up a childframe to show the result.
+The frame will disappear when do do anything but focus in it.
+Manually close the frame with `q'.")
+
+(defvar gt-posframe-pop-render-buffer " *GT-Pop-Posframe*")
+(defvar gt-posframe-pop-render-init-hook nil)
+(defvar gt-posframe-pop-render-output-hook nil)
+(defvar gt-posframe-pop-render-timeout 30)
+(defvar gt-posframe-pop-render-poshandler nil)
+
+(declare-function posframe-show "ext:posframe.el" t t)
+(declare-function posframe-delete "ext:posframe.el" t t)
+(declare-function posframe-hide "ext:posframe.el" t t)
+(declare-function posframe-refresh "ext:posframe.el" t t)
+(declare-function posframe-poshandler-frame-top-right-corner "ext:posframe.el" t t)
+
+(defun gt-posframe-render-auto-close-handler ()
+  "Close the pop-up posframe window."
+  (interactive)
+  (unless (or (and gt-current-command
+                   (member this-command (list gt-current-command #'exit-minibuffer)))
+              (and gt-posframe-pop-render-buffer
+                   (string= (buffer-name) gt-posframe-pop-render-buffer)))
+    (ignore-errors (posframe-delete gt-posframe-pop-render-buffer))
+    (remove-hook 'post-command-hook #'gt-posframe-render-auto-close-handler)))
+
+(cl-defmethod gt-init :before ((_ gt-posframe-pop-render) _)
+  (unless (require 'posframe nil t)
+    (user-error "To use `gt-posframe-render', you should install and load package `posframe' first")))
+
+(cl-defmethod gt-init ((render gt-posframe-pop-render) translator)
+  (with-slots (width height forecolor backcolor padding frame-params) render
+    (let ((inhibit-read-only t)
+          (gt-buffer-render-dislike-source-text t)
+          (gt-buffer-render-init-hook gt-posframe-pop-render-init-hook)
+          (buf (get-buffer-create gt-posframe-pop-render-buffer))
+          (rweight (cond ((functionp width) (funcall width translator))
+                         ((numberp width) width)
+                         (t (if fill-column (- fill-column 5) 50))))
+          (rheight (cond ((functionp height) (funcall height translator))
+                         ((numberp height) height)
+                         (t 0))))
+      ;; height
+      (when (< rheight 1)
+        (let* ((h 1) (factor 1) (ts (ensure-list (oref translator text))))
+          (with-temp-buffer
+            (insert (string-join ts "\n"))
+            (goto-char (point-min))
+            (while (re-search-forward "\n+" nil t) (cl-incf h)))
+          (setq factor (* factor (length ts)))
+          (setq factor (* factor (max 1 (length (ensure-list (oref translator engines))))))
+          (setq factor (* factor (max 1 (length (ensure-list (cdr (oref translator target)))))))
+          (setq rheight (* factor (+ 4 h)))))
+      ;; render
+      (gt-buffer-render-init buf render translator)
+      ;; create
+      (unless (get-buffer-window (get-buffer buf))
+        (apply #'posframe-show buf
+               (append frame-params
+                       (list
+                        :timeout gt-posframe-pop-render-timeout
+                        :width rweight
+                        :height rheight
+                        :max-height (/ (window-height) 2)
+                        :foreground-color (or forecolor gt-pop-posframe-forecolor)
+                        :background-color (or backcolor gt-pop-posframe-backcolor)
+                        :internal-border-width padding
+                        :internal-border-color (or backcolor gt-pop-posframe-backcolor)
+                        :accept-focus t
+                        :position (point)
+                        :poshandler gt-posframe-pop-render-poshandler))))
+      (posframe-refresh buf)
+      ;; setup
+      (with-current-buffer buf
+        (gt-buffer-render-key ("q" "Close") (posframe-delete buf)))
+      (add-hook 'post-command-hook #'gt-posframe-render-auto-close-handler))))
+
+(cl-defmethod gt-output ((render gt-posframe-pop-render) translator)
+  (when-let (buf (get-buffer gt-posframe-pop-render-buffer))
+    (let ((gt-buffer-render-output-hook gt-posframe-pop-render-output-hook))
+      (gt-buffer-render-output buf render translator)
+      (posframe-refresh buf))))
+
+\f
+;;; [Render] Child-Frame Render (Pin Mode)
+
+(defclass gt-posframe-pin-render (gt-posframe-pop-render)
+  ((width        :initarg :width        :initform 60)
+   (height       :initarg :height       :initform 20)
+   (padding      :initarg :padding      :initform 8)
+   (bd-width     :initarg :bd-width     :initform 1)
+   (bd-color     :initarg :bd-color     :initform nil)
+   (backcolor    :initarg :backcolor    :initform nil)
+   (fri-color    :initarg :fringe-color :initform nil)
+   (position     :initarg :position     :initform nil)
+   (frame-params :initarg :frame-params :initform nil :type list
+                 :documentation "Other parameters passed to `posframe-show'. Have higher priority."))
+  "Pin the childframe in a fixed position to display the translate result.
+The childframe will not close, until you kill it with `q'.
+Other operations in the childframe buffer, just like in 'gt-buffer-render'.")
+
+(defvar gt-posframe-pin-render-buffer " *GT-Pin-Posframe*")
+(defvar gt-posframe-pin-render-frame nil)
+(defvar gt-posframe-pin-render-init-hook nil)
+(defvar gt-posframe-pin-render-output-hook nil)
+(defvar gt-posframe-pin-render-poshandler #'posframe-poshandler-frame-top-right-corner)
+
+(cl-defmethod gt-init ((render gt-posframe-pin-render) translator)
+  (if (and (get-buffer gt-posframe-pin-render-buffer) gt-posframe-pin-render-frame)
+      (make-frame-visible gt-posframe-pin-render-frame)
+    (with-slots (width height min-width min-height bd-width forecolor backcolor bd-color padding position frame-params) render
+      (setq gt-posframe-pin-render-frame
+            (let ((inhibit-read-only t))
+              (apply #'posframe-show gt-posframe-pin-render-buffer
+                     (append frame-params
+                             (list
+                              :string "\nLoading..."
+                              :width width
+                              :height height
+                              :min-width width
+                              :min-height height
+                              :foreground-color (or forecolor gt-pin-posframe-forecolor)
+                              :background-color (or backcolor gt-pin-posframe-backcolor)
+                              :internal-border-width bd-width
+                              :border-color (or bd-color gt-pin-posframe-bdcolor)
+                              :left-fringe padding
+                              :right-fringe padding
+                              :refresh nil
+                              :accept-focus t
+                              :respect-header-line t
+                              :position position
+                              :poshandler (unless position gt-posframe-pin-render-poshandler)))))))
+    (set-frame-parameter gt-posframe-pin-render-frame 'drag-internal-border t)
+    (set-frame-parameter gt-posframe-pin-render-frame 'drag-with-header-line t)
+    (when-let (color (or (oref render fri-color) gt-pin-posframe-fringe-color))
+      (set-face-background 'fringe color  gt-posframe-pin-render-frame)))
+  ;; render
+  (let ((gt-buffer-render-init-hook gt-posframe-pin-render-init-hook))
+    (gt-buffer-render-init gt-posframe-pin-render-buffer render translator))
+  ;; setup
+  (with-current-buffer gt-posframe-pin-render-buffer
+    (gt-buffer-render-key ("q" "Close") (posframe-hide gt-posframe-pin-render-buffer))))
+
+(cl-defmethod gt-output ((render gt-posframe-pin-render) translator)
+  (let ((gt-buffer-render-output-hook gt-posframe-pin-render-output-hook))
+    (gt-buffer-render-output gt-posframe-pin-render-buffer render translator)))
+
+\f
+;;; [Render] kill-ring render
+
+(defclass gt-kill-ring-render (gt-render) ()
+  :documentation "Used to save the translate result into kill ring.")
+
+(cl-defmethod gt-output ((render gt-kill-ring-render) translator)
+  (deactivate-mark)
+  (when (= (oref translator state) 3)
+    (let ((ret (gt-extract render translator)))
+      (when-let (err (cl-find-if (lambda (r) (<= (plist-get r :state) 1)) ret))
+        (kill-new "")
+        (error "%s" (plist-get err :result)))
+      (kill-new (mapconcat (lambda (r) (string-join (plist-get r :result) "\n")) ret "\n\n"))
+      (message "Result already in the kill ring."))))
+
+\f
+;;; [Render] insert render
+
+(defclass gt-insert-render (gt-render)
+  ((type
+    :initarg :type
+    :initform 'after
+    :type (or (member after replace) boolean)
+    :documentation "How to insert the result.")
+   (rfmt
+    :initarg :rfmt
+    :initform nil
+    :documentation "Used to format the result string for insertion.
+See `gt-insert-render-format' for details.")
+   (sface
+    :initarg :sface
+    :initform nil
+    :documentation "The propertize face of the source text after translation.
+If this is nil then do nothing, if this is a face or a function return a face,
+just propertize the source text with the face.")
+   (rface
+    :initarg :rface
+    :initform nil
+    :documentation "Result face.")))
+
+(defcustom gt-insert-render-type 'after
+  "Where to insert the result in Insert-Render.
+
+If this is `replace', insert the result by taking place the source text,
+otherwise, insert after the source text.
+
+The value can be overrided by `type' slot of render."
+  :type '(choice (const :tag "Repace" replace)
+                 (other :tag "Insert after" after))
+  :group 'go-translate)
+
+(defcustom gt-insert-render-output-hook nil
+  "Hook run after output finished in Insert-Render.
+With current translator as the only argument."
+  :type 'hook
+  :group 'go-translate)
+
+(defun gt-insert-render-format (render src res)
+  "Format RES for insert RENDER.
+
+SRC is the source text, RES is list extracted from translate task.
+
+Join them to a string, format or pretty it, at last return it as the result that
+used to insert.
+
+If slot `rfmt' is a string contains `%s', format every part of results with
+function `format' and join them.
+
+   (gt-insert-render :rfmt \" <%s>\" :rface `font-lock-warning-face)
+
+If `rfmt' is a function with solo argument, apply the function on every part of
+results and join them. If with two arguments, pass source text as the first
+argument. If four arguments, then chain the formatting task to the function.
+
+   (gt-insert-render :rfmt (lambda (w) (format \" [%s]\"))
+                     :rface `font-lock-warning-face)
+
+   (gt-insert-render :rfmt (lambda (s w)
+                             (if (length< s 3)
+                               (format \"\\n- %s\" w)
+                              (propertize w `face `font-lock-warning-face))))
+
+Otherwise, join the results use the default logic."
+  (with-slots (type rfmt rface) render
+    (cond
+     ((stringp rfmt)
+      (mapconcat (lambda (r) (gt-face-lazy (format rfmt r) (gt-ensure-plain rface r))) res "\n"))
+     ((functionp rfmt)
+      (let ((n (cdr (func-arity rfmt))))
+        (if (<= n 2)
+            (mapconcat (lambda (r)
+                         (let ((ret (or (if (= n 2) (funcall rfmt src r) (funcall rfmt r)) r)))
+                           (gt-face-lazy ret (gt-ensure-plain rface r))))
+                       res "\n")
+          (funcall rfmt render src res))))
+     (t (let* ((before (when (eq type 'after)
+                         (if (string-blank-p src) ""
+                           (if (gt-word-p nil src) " "
+                             (if (or (looking-at "\n\n")
+                                     (save-excursion (skip-chars-forward " \n\t") (eobp)))
+                                 "\n\n"
+                               "\n")))))
+               (facefn (lambda (r) (gt-face-lazy r (gt-ensure-plain rface r))))
+               (separator (if (= (length before) 0) "\n\n" before)))
+          (concat before (mapconcat facefn res separator)))))))
+
+(cl-defmethod gt-init ((render gt-insert-render) translator)
+  (with-slots (text bounds tasks) translator
+    (unless (buffer-live-p (car bounds))
+      (error "Source buffer is unavailable, abort"))
+    (when (with-current-buffer (car bounds) buffer-read-only)
+      (error "Source buffer is readonly, can not insert"))
+    ;; only simple translate (single part, single target) allowing streaming output
+    ;; take bounds as output markers for it
+    (when (and (not (cdr text))
+               (not (cdr tasks))
+               (gt-stream-p (oref (car tasks) engine)))
+      (with-slots (markers engine) (car tasks)
+        (let ((type (oref render type))
+              (beg (if-let (p (caadr bounds))
+                       (save-excursion
+                         (goto-char p)
+                         (skip-chars-forward "\n" (cdadr bounds))
+                         (point))
+                     (point)))
+              (end (if-let (p (cdadr bounds))
+                       (save-excursion
+                         (goto-char p)
+                         (skip-chars-backward " \t\n" (caadr bounds))
+                         (point))
+                     (point))))
+          (setf markers (cons (set-marker (make-marker) (if (eq type 'after) end beg))
+                              (set-marker (make-marker) end))))))))
+
+(cl-defmethod gt-output ((render gt-insert-render) translator)
+  (with-slots (bounds state) translator
+    (when (= 3 state)
+      (let ((ret (gt-extract render translator)))
+        (when-let (err (cl-find-if (lambda (tr) (<= (plist-get tr :state) 1)) ret))
+          (user-error "Error in translation, %s" (plist-get err :result)))
+        (with-current-buffer (car bounds)
+          (with-slots (rfmt sface) render
+            (cl-loop with start = (point-marker)
+                     with bds = (mapcar (lambda (bd)
+                                          (cons (save-excursion
+                                                  (goto-char (car bd))
+                                                  (skip-chars-forward "\n")
+                                                  (point-marker))
+                                                (save-excursion
+                                                  (goto-char (cdr bd))
+                                                  (skip-chars-backward " \t\n")
+                                                  (point-marker))))
+                                        (cdr bounds))
+                     with type = (let ((type (or (oref render type) gt-insert-render-type))
+                                       (types '(after replace)))
+                                   (if (member type types) type
+                                     (intern (completing-read "Insert text as: " types nil t))))
+                     with hash = (when (and (eq type 'replace) (not (buffer-modified-p)))
+                                   (buffer-hash))
+                     with len = (length (plist-get (car ret) :result))
+                     for i from 0 below len
+                     for (beg . end) = (if bds (nth i bds)
+                                         (if (use-region-p)
+                                             (car (region-bounds))
+                                           (cons (point) (point))))
+                     for src = (if bds (buffer-substring beg end) "")
+                     for res = (mapcar (lambda (tr) (nth i (plist-get tr :result))) ret)
+                     do (goto-char end)
+                     for fres = (gt-insert-render-format render src res)
+                     do (let (p)
+                          (if (eq type 'replace)
+                              (delete-region beg end)
+                            (when-let (face (and (eq type 'after) (gt-ensure-plain sface src)))
+                              (delete-region beg end)
+                              (insert (propertize src 'face face))))
+                          (setq p (point))
+                          (insert (propertize fres 'type 'gt-insert-result))
+                          (save-excursion
+                            (goto-char p)
+                            (skip-chars-forward " \t\n")
+                            (push-mark)))
+                     finally (progn
+                               (if (> len 1) (goto-char start))
+                               (when (and hash (equal hash (buffer-hash)))
+                                 (set-buffer-modified-p nil))))
+            (deactivate-mark)
+            (run-hook-with-args 'gt-insert-render-output-hook translator)
+            (message "ok.")))))))
+
+(cl-defmethod gt-next-for-stream ((render gt-insert-render) task)
+  (with-slots (markers res) task
+    (let* ((beg (car markers)) (end (cdr markers))
+           (bounds (oref (oref task translator) bounds))
+           (insert-text (with-current-buffer (car bounds)
+                          (save-excursion
+                            (goto-char end)
+                            (gt-insert-render-format
+                             render
+                             (if (and (eq (oref render type) 'after) (caadr bounds))
+                                 (buffer-substring (caadr bounds) (cdadr bounds))
+                               "")
+                             (list (string-join (ensure-list res) "\n")))))))
+      (gt-insert-text-at-marker insert-text beg end))))
+
+(cl-defmethod gt-desc ((render gt-insert-render))
+  (format "gt-insert-render/%s" (oref render type)))
+
+\f
+;;; [Render] Render with Overlay
+
+(defclass gt-overlay-render (gt-render)
+  ((type
+    :initarg :type
+    :initform 'after
+    :type (or (member after replace help-echo before) boolean)
+    :documentation "How to display the result.")
+   (rfmt
+    :initarg :rfmt
+    :initform nil
+    :documentation
+    "Used to format the translation result to fit the overlay display.
+See `gt-overlay-render-format' for details.")
+   (sface
+    :initarg :sface
+    :initform 'gt-overlay-source-face
+    :documentation "The propertize face of the source text after translation.
+If this is nil then do nothing, if this is a face or a function return a face,
+just propertize the source text with the face.")
+   (rface
+    :initarg :rface
+    :initform 'gt-overlay-result-face
+    :documentation "Result face.")
+   (rdisp
+    :initarg :rdisp
+    :initform nil
+    :documentation "Same as rface but used in `display' property.")
+   (pface
+    :initarg :pface
+    :initform 'gt-overlay-prefix-face
+    :documentation "Prefix face.")
+   (pdisp
+    :initarg :pdisp
+    :initform nil
+    :documentation "Same as pface but used in `display' property.")))
+
+(defcustom gt-overlay-render-type 'after
+  "How to display result in Overlay-Render.
+
+If this is `help-echo', display with help echo, if this is `replace', display
+by covering the source text, otherwise, display before or after the source text.
+
+The value can be overrided by `type' slot of render."
+  :type '(choice (const :tag "Repace" replace)
+                 (const :tag "Help Echo" help-echo)
+                 (const :tag "After" after)
+                 (other :tag "Before" before))
+  :group 'go-translate)
+
+(defvar gt-overlay-render-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "<mouse-1>") #'gt-delete-render-overlays)
+    (define-key map (kbd "<mouse-3>") #'gt-overlay-render-save-to-kill-ring)
+    map)
+  "Keymap used in overlay render.")
+
+(defun gt-overlay-render-get-overlays (beg &optional end)
+  "Return overlays made by Overlay-Render in the region from BEG to END.
+If END is nil, return the overlays at BEG."
+  (cl-remove-if-not (lambda (ov) (overlay-get ov 'gt))
+                    (if end
+                        (overlays-in beg end)
+                      (overlays-at beg))))
+
+(defun gt-delete-render-overlays (beg &optional end)
+  "Delete overlays made by Overlay-Render in the region from BEG to END.
+If called interactively, delete overlays around point or in region. With
+`current-prefix-arg' non nil, delete all overlays in the buffer."
+  (interactive (cond (current-prefix-arg (list (point-min) (point-max)))
+                     ((use-region-p) (list (region-beginning) (region-end)))
+                     (t (list
+                         (if (and (> (point) 1) ; for overlay before point
+                                  (not (gt-overlay-render-get-overlays (point)))
+                                  (gt-overlay-render-get-overlays (1- (point))))
+                             (1- (point))
+                           (point))
+                         nil))))
+  (mapc #'delete-overlay (gt-overlay-render-get-overlays beg end)))
+
+(defun gt-overlay-render-save-to-kill-ring ()
+  "Copy translate content at point to kill ring for Overlay-Render."
+  (interactive)
+  (if-let* ((ov (car (gt-overlay-render-get-overlays (point))))
+            (rs (overlay-get ov 'gt)))
+      (progn (kill-new (string-trim rs))
+             (message "Result already in the kill ring."))
+    (user-error "No translate overlay found at point")))
+
+(defun gt-overlay-render-format (render src res prefix)
+  "Format RES for overlay RENDER.
+
+SRC is the source text, RES and PREFIX are list extracted from translate task.
+
+Join them to a string, format or pretty it, at last return it as the result that
+used to display.
+
+If slot `rfmt' is a string contains `%s', format every part of results with
+function `format' and join them.
+
+   (gt-overlay-render :rfmt \" <%s>\" :rface `font-lock-warning-face)
+
+If `rfmt' is a function with solo argument, apply the function on every part of
+results and join them. If with two arguments, pass source text as the first
+argument. If four arguments, then chain the formatting task to the function.
+
+   (gt-overlay-render :rfmt (lambda (w) (format \" [%s]\"))
+                      :rface `font-lock-warning-face)
+
+   (gt-overlay-render :rfmt (lambda (s w)
+                              (if (length< s 3)
+                                (format \"\\n- %s\")
+                               (propertize w `face `font-lock-warning-face))))
+
+Otherwise, join the results use the default logic."
+  (with-slots (type rfmt rface rdisp pface pdisp) render
+    (cond
+     ((stringp rfmt)
+      (mapconcat (lambda (r) (gt-face-lazy (format rfmt r)
+                                           (gt-ensure-plain rface r)
+                                           (gt-ensure-plain rdisp r)))
+                 res "\n"))
+     ((functionp rfmt)
+      (let ((n (cdr (func-arity rfmt))))
+        (if (<= n 2)
+            (mapconcat (lambda (r)
+                         (let ((ret (or (if (= n 2) (funcall rfmt src r) (funcall rfmt r)) r)))
+                           (gt-face-lazy ret (gt-ensure-plain rface r) (gt-ensure-plain rdisp r))))
+                       res "\n")
+          (funcall rfmt render src res prefix))))
+     (t (cl-loop with before = (when (eq type 'after)
+                                 (if (gt-word-p nil src) " "
+                                   (if (or (looking-at "\n\n")
+                                           (save-excursion (skip-chars-forward " \n\t") (eobp)))
+                                       "\n\n"
+                                     "\n")))
+                 with after = (when (eq type 'before) (if (gt-word-p nil src) " " "\n"))
+                 for r in res for p in prefix
+                 for pr = (gt-face-lazy r (gt-ensure-plain rface r) (gt-ensure-plain rdisp r))
+                 for pp = (if p (concat " " (gt-face-lazy p (gt-ensure-plain pface p) (gt-ensure-plain pdisp p))))
+                 collect (concat pr pp) into ret
+                 finally (return (concat before (string-join ret (or before "\n\n")) after)))))))
+
+(cl-defmethod gt-init ((render gt-overlay-render) translator)
+  (with-slots (bounds state) translator
+    (unless (cdr bounds)
+      (error "%s only works for buffer bounds, abort" (gt-desc render)))
+    (unless (buffer-live-p (car bounds))
+      (error "Source buffer is unavailable, abort"))))
+
+(cl-defmethod gt-output ((render gt-overlay-render) translator)
+  (with-slots (bounds state) translator
+    (when (= 3 state)
+      (let ((ret (gt-extract render translator)))
+        (when-let (err (cl-find-if (lambda (tr) (<= (plist-get tr :state) 1)) ret))
+          (user-error "Error in translation, %s" (plist-get err :result)))
+        (with-current-buffer (car bounds)
+          (cl-loop with start = (point-marker)
+                   with type = (let ((type (or (oref render type) gt-overlay-render-type))
+                                     (types '(help-echo replace after before)))
+                                 (if (memq type types) type
+                                   (intern (completing-read "Display with overlay as: " types nil t))))
+                   with hooks = `((lambda (o &rest _) (delete-overlay o)))
+                   for (beg . end) in (cdr bounds) for i from 0
+                   do (save-excursion
+                        (goto-char beg)
+                        (skip-chars-forward "\n")
+                        (setq beg (point))
+                        (goto-char end)
+                        (skip-chars-backward " \t\n")
+                        (setq end (point)))
+                   do (gt-delete-render-overlays beg end)
+                   for src = (buffer-substring beg end)
+                   do (save-excursion
+                        (goto-char end)
+                        (let* ((ov (make-overlay beg (point) nil t t))
+                               (fres (gt-overlay-render-format
+                                      render src
+                                      (mapcar (lambda (tr) (nth i (plist-get tr :result))) ret)
+                                      (mapcar (lambda (tr) (plist-get tr :prefix)) ret)))
+                               (sface (oref render sface))
+                               (sface (unless (eq type 'replace) (gt-ensure-plain sface src))))
+                          (pcase type
+                            ('help-echo (overlay-put ov 'help-echo fres))
+                            ('after (overlay-put ov 'after-string fres))
+                            ('before (overlay-put ov 'before-string fres)))
+                          (overlay-put ov 'gt fres)
+                          (overlay-put ov 'evaporate t)
+                          (overlay-put ov 'pointer 'arrow)
+                          (overlay-put ov 'insert-in-front-hooks hooks)
+                          (overlay-put ov 'insert-behind-hooks hooks)
+                          (overlay-put ov 'modification-hooks hooks)
+                          (overlay-put ov 'keymap gt-overlay-render-map)
+                          (if (eq type 'replace)
+                              (progn (overlay-put ov 'display fres)
+                                     (overlay-put ov 'help-echo src))
+                            (if sface (overlay-put ov 'face sface)))))
+                   finally (if (cddr bounds) (goto-char start)))
+          (deactivate-mark)
+          (message "ok."))))))
+
+(cl-defmethod gt-desc ((render gt-overlay-render))
+  (format "gt-overlay-render/%s" (oref render type)))
+
+\f
+;;; [Render] Alert Render
+
+(defclass gt-alert-render (gt-render) ()
+  :documentation "Output results as system notification.
+It depends on the `alert' package.")
+
+(defvar gt-alert-render-args '(:timeout 10))
+
+(declare-function alert "ext:alert.el" t t)
+
+(cl-defmethod gt-init ((_ gt-alert-render) _)
+  (unless (require 'alert nil t)
+    (user-error "To use `gt-alert-render', you should install and load package `alert' first")))
+
+(cl-defmethod gt-output ((render gt-alert-render) translator)
+  (when (= (oref translator state) 3)
+    (let ((ret (gt-extract render translator)) lst)
+      ;; format
+      (dolist (tr ret)
+        (let ((prefix (if (cdr ret) (plist-get tr :prefix)))
+              (result (string-join (ensure-list (plist-get tr :result)) "\n")))
+          (push (concat prefix result) lst)))
+      ;; output
+      (message "")
+      (apply #'alert (string-join (nreverse lst) "\n") :title "*Go-Translate*" gt-alert-render-args))))
+
+\f
+;;; [Taker] Prompt with new buffer
+
+(defcustom gt-buffer-prompt-window-config
+  '((display-buffer-reuse-window display-buffer-below-selected))
+  "Window configuration of taker's buffer prompt window."
+  :type 'sexp
+  :group 'go-translate)
+
+(defvar gt-buffer-prompt-name "*gt-taker*")
+
+(defvar gt-buffer-prompt-map (make-sparse-keymap))
+
+(defvar gt-buffer-prompt-after-init-function nil
+  "Function with no argument, executed after prompt buffer init.
+Can do extra setup works for the buffer through this function.")
+
+(declare-function gt-set-render "ext:go-translate")
+(declare-function gt-set-engines "ext:go-translate")
+(declare-function gt-translator-info "ext:go-translate")
+
+(cl-defmethod gt-prompt ((taker gt-taker) translator (_ (eql 'buffer)))
+  "Prompt the TAKER's result using a new buffer for TRANSLATOR.
+
+Only works when taker's prompt slot is config as `buffer:
+
+  :taker (gt-picker :prompt `buffer)
+
+Edit the text in buffer and confirm with `C-c C-c', you can also change
+target, engines and render in the buffer for the following translation."
+  (with-slots (text target render engines _render _engines) translator
+    (when (cdr text)
+      (user-error "Multiple text cannot be prompted"))
+    (cl-labels ((prop (s &optional fn not-key)
+                  (let (args)
+                    (unless not-key (setq args `(,@args face font-lock-keyword-face)))
+                    (if fn (setq args `(,@args local-map (keymap (mode-line keymap (mouse-1 . ,fn)))
+                                               mouse-face font-lock-warning-face)))
+                    (apply #'propertize (format "%s" s) args)))
+                (set-head-line ()
+                  (setq header-line-format
+                        (concat " " (cl-loop for (key . value) in `(("C-c C-c" . "to apply")
+                                                                    ("C-c C-k" . "to cancel"))
+                                             concat (format "%s %s " (prop key) value))
+                                " " (propertize "Translate taking..." 'face 'font-lock-warning-face))))
+                (set-mode-line ()
+                  (setq mode-line-format
+                        (let ((ms (concat "C-c C-n: Next Target\nC-c C-p: Prev Target\n\n"
+                                          "C-c C-e: Set Engines\nC-c C-r: Set Render")))
+                          (mapcar (lambda (item) (when item (propertize item 'help-echo ms)))
+                                  (cl-destructuring-bind (_ eg rd)
+                                      (ignore-errors (gt-translator-info translator))
+                                    (list (prop (concat
+                                                 (if-let (src (car target)) (concat "[" (prop src) "] → "))
+                                                 "[" (mapconcat (lambda (s) (prop s)) (cdr target) ", ") "]")
+                                                #'cycle-next-target t)
+                                          (if eg (concat "  Engines: " (prop eg #'set-engines)))
+                                          (if rd (concat "  Render: " (prop rd #'set-render)))))))))
+                (cycle-next-target (&optional backwardp)
+                  (interactive)
+                  (setf target
+                        (gt-target taker (make-instance
+                                          (eieio-object-class translator)
+                                          :text (list (buffer-string)))
+                                   (if backwardp 'prev 'next)))
+                  (set-mode-line))
+                (cycle-prev-target ()
+                  (interactive)
+                  (cycle-next-target t))
+                (set-engines ()
+                  (interactive)
+                  (gt-set-engines translator)
+                  (set-mode-line))
+                (set-render ()
+                  (interactive)
+                  (gt-set-render translator)
+                  (set-mode-line))
+                (set-local-keys ()
+                  (local-set-key (kbd "C-c C-n") #'cycle-next-target)
+                  (local-set-key (kbd "C-c C-p") #'cycle-prev-target)
+                  (local-set-key (kbd "C-c C-e") #'set-engines)
+                  (local-set-key (kbd "C-c C-r") #'set-render)))
+      (let* ((newtext (gt-read-from-buffer
+                       :buffer gt-buffer-prompt-name
+                       :initial-contents (or (car text) "")
+                       :catch 'gt-buffer-prompt
+                       :window-config gt-buffer-prompt-window-config
+                       :keymap gt-buffer-prompt-map
+                       (set-head-line)
+                       (set-mode-line)
+                       (set-local-keys)
+                       (when gt-buffer-prompt-after-init-function
+                         (funcall gt-buffer-prompt-after-init-function)))))
+        (when (null newtext)
+          (user-error ""))
+        (when (string-blank-p newtext)
+          (user-error "Text should not be null, abort"))
+        (setf text (ensure-list newtext))))))
+
+\f
+;;; [Taker] Pick only fresh words
+
+(defvar gt-fresh-word-class 'gt-fresh-word-with-file)
+
+(defvar gt-ripe-words-file (locate-user-emacs-file "gt-known-words.txt"))
+
+(defmacro gt-with-ripe-words-file (&rest form)
+  `(let* ((case-fold-search t)
+          (bufname " gt-known-words")
+          (buf (or (get-buffer bufname)
+                   (progn
+                     (unless (file-exists-p gt-ripe-words-file)
+                       (write-region (point-min) (point-min) gt-ripe-words-file))
+                     (find-file-noselect gt-ripe-words-file)))))
+     (with-current-buffer buf
+       (unless (equal (buffer-name) bufname)
+         (rename-buffer bufname))
+       (goto-char (point-min))
+       (prog1 (progn ,@form) (basic-save-buffer)))))
+
+(cl-defgeneric gt-word-fresh-p (word)
+  (:method ((_ (eql 'gt-fresh-word-with-file)) word)
+           (gt-with-ripe-words-file
+            (not (re-search-forward (concat "^" word "$") nil t))))
+  (gt-word-fresh-p gt-fresh-word-class word))
+
+(cl-defgeneric gt-fresh-word (&rest words)
+  (:method ((_ (eql 'gt-fresh-word-with-file)) &rest words)
+           (gt-with-ripe-words-file
+            (if (equal words (list t)) ; clear ripes
+                (erase-buffer)
+              (dolist (word words) ; remove from ripes
+                (goto-char (point-min))
+                (while (re-search-forward (concat "^" word "$") nil t)
+                  (delete-region (match-beginning 0) (match-end 0))
+                  (if (looking-at "\n") (delete-char 1)))))))
+  (apply #'gt-fresh-word gt-fresh-word-class words))
+
+(cl-defgeneric gt-ripen-word (&rest words)
+  (:method ((_ (eql 'gt-fresh-word-with-file)) &rest words)
+           (gt-with-ripe-words-file ; add to ripes
+            (dolist (word words)
+              (goto-char (point-min))
+              (unless (re-search-forward (concat "^" word "$") nil t)
+                (insert (downcase word) "\n")))))
+  (apply #'gt-ripen-word gt-fresh-word-class words))
+
+(defvar gt-fresh-words-last nil)
+
+(defun gt-record-words-as-known ()
+  "Record the words as known."
+  (interactive)
+  (let* ((si (string-join
+              (sort (delete-dups
+                     (mapcar #'downcase
+                             (or gt-fresh-words-last
+                                 (ensure-list (thing-at-point 'word))))))
+              " "))
+         (ss (read-string "Words to record as known: " si))
+         (ws (split-string ss)))
+    (apply #'gt-ripen-word ws)))
+
+(defun gt-record-words-as-unknown ()
+  "Record the words as unknown."
+  (interactive)
+  (let* ((ss (read-string "Words to record as unknown: " (thing-at-point 'word)))
+         (ws (split-string ss)))
+    (apply #'gt-fresh-word ws)))
+
+;; (gt-taker :pick 'fresh-word)
+
+(cl-defmethod gt-pick ((_ (eql 'fresh-word)) translator)
+  (with-slots (text bounds taker) translator
+    (setq gt-fresh-words-last nil)
+    (let* ((car (if (cdr bounds) (cl-subseq bounds 0 2) (car text)))
+           (pred (lambda (word)
+                   (and (if-let (p (oref taker pick-pred)) (funcall p word) t)
+                        (> (string-bytes word) 2)
+                        (not (string-match-p "^[0-9]+$" word))
+                        (gt-word-fresh-p word)
+                        (push word gt-fresh-words-last)))))
+      (gt-pick-items-by-thing car 'word pred))))
+
+\f
+;;; [Taker] pdf-view-mode
+
+(declare-function pdf-view-active-region-p "ext:pdf-view.el" t t)
+(declare-function pdf-view-active-region-text "ext:pdf-view.el" t t)
+
+(cl-defmethod gt-text-at-point (_thing (_ (eql 'pdf-view-mode)))
+  (if (pdf-view-active-region-p)
+      (pdf-view-active-region-text)
+    (user-error "You should make a selection before translate")))
+
+(provide 'gt-extension)
+
+;;; gt-extension.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-faces.el b/elpa/go-translate-20240802.131/gt-faces.el
new file mode 100644 (file)
index 0000000..14dea12
--- /dev/null
@@ -0,0 +1,221 @@
+;;; gt-faces.el --- Face definitions -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; The faces and colors.
+
+;;; Code:
+
+(require 'gt-core)
+
+(defgroup go-translate-faces nil
+  "The faces for this framework."
+  :group 'go-translate)
+
+\f
+;;; Common
+
+(defface gt-logger-buffer-tag-face '((t :inherit font-lock-comment-face))
+  "Used in the logger buffer tag keyword."
+  :group 'go-translate-faces)
+
+(defface gt-logger-buffer-timestamp-face '((t :foreground "grey"))
+  "Used in the logger buffer timestamp keyword."
+  :group 'go-translate-faces)
+
+(defface gt-render-prefix-face '((t :foreground "grey"))
+  "Used in the buffer's block prefix."
+  :group 'go-translate-faces)
+
+\f
+;;; Buffer Render
+
+(defface gt-buffer-render-header-lang-face '((t :inherit font-lock-keyword-face))
+  "Used in the buffer's `header-line-format' for langs."
+  :group 'go-translate-faces)
+
+(defface gt-buffer-render-header-desc-face '((t :inherit font-lock-variable-name-face))
+  "Used in the buffer's `header-line-format' for description."
+  :group 'go-translate-faces)
+
+(defface gt-buffer-render-source-face
+  '((((background light)) :background "ivory")
+    (((background dark)) :background "gray20" :foreground "gray" :weight light))
+  "Used in the buffer's source text."
+  :group 'go-translate-faces)
+
+(defface gt-buffer-render-inline-prefix-face
+  '((((background light)) :height 0.8 :foreground "grey")
+    (((background dark)) :height 0.8 :foreground "grey30"))
+  "Used in the buffer's inline prefix."
+  :group 'go-translate-faces)
+
+(defface gt-buffer-render-block-prefix-face '((t :background "slategray" :foreground "white" :extend t))
+  "Used in the buffer's block prefix."
+  :group 'go-translate-faces)
+
+(defface gt-buffer-render-error-face '((t :weight bold :inherit font-lock-comment-face))
+  "Used in the buffer's error text."
+  :group 'go-translate-faces)
+
+(defface gt-buffer-render-loading-face '((t :inherit font-lock-comment-face))
+  "Used in the buffer's loading messaget."
+  :group 'go-translate-faces)
+
+\f
+;;; Google Buffer Render
+
+(defface gt-google-buffer-headline-face '((t :inherit font-lock-function-name-face :weight bold))
+  "Propertize the headline in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gt-google-buffer-phonetic-face '((t :inherit font-lock-string-face :slant normal))
+  "Propertize the phonetic in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gt-google-buffer-suggestion-desc-face '((t :inherit font-lock-warning-face))
+  "Propertize the suggestion description in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gt-google-buffer-suggestion-text-face '((t :slant italic :underline t))
+  "Propertize the phonetic text in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gt-google-buffer-brief-result-face '((t :weight bold))
+  "Propertize the brief result in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gt-google-buffer-source-face '((t :inherit font-lock-string-face))
+  "Propertize the source in buffer rendering."
+  :group 'go-translate-faces)
+
+(defface gt-google-buffer-detail-demo-face '((t :inherit font-lock-doc-face))
+  "Propertize the detail demo in buffer rendering."
+  :group 'go-translate-faces)
+
+\f
+;;; Posframe Render
+
+(defcustom gt-pop-posframe-forecolor "white"
+  "Default foreground color of pop posframe."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defcustom gt-pop-posframe-backcolor "black"
+  "Default background color of pop posframe."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defcustom gt-pin-posframe-forecolor nil
+  "Default foreground color of pin posframe."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defcustom gt-pin-posframe-backcolor nil
+  "Default background color of pin posframe."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defcustom gt-pin-posframe-bdcolor "#000000"
+  "Default border color of pin posframe."
+  :type 'string
+  :group 'go-translate-faces)
+
+(defcustom gt-pin-posframe-fringe-color nil
+  "Used in the posframe pip buffer fringe color."
+  :type 'string
+  :group 'go-translate-faces)
+
+\f
+;;; Overlay Render
+
+(defface gt-overlay-source-face '((t :underline (:color "grey" :style wave)))
+  "Used in the overlay's source."
+  :group 'go-translate-faces)
+
+(defface gt-overlay-result-face '((t :inherit font-lock-string-face))
+  "Used in the overlay's result."
+  :group 'go-translate-faces)
+
+(defface gt-overlay-prefix-face '((t :inherit font-lock-comment-face))
+  "Used in the overlay's prefix."
+  :group 'go-translate-faces)
+
+\f
+;; Youdao
+
+(defface gt-youdao-dict-label-face '((((background light)) :foreground "lightblue")
+                                     (((background dark)) :foreground "darkcyan"))
+  "Face for label of youdao-dict."
+  :group 'go-translate-faces)
+
+(defface gt-youdao-dict-phonetic-face '((((background light)) :foreground "grey")
+                                        (((background dark)) :foreground "darkgray"))
+  "Face for phonetic of youdao-dict."
+  :group 'go-translate-faces)
+
+(defface gt-youdao-dict-entry-face '((t :bold t))
+  "Face for entry of youdao-dict."
+  :group 'go-translate-faces)
+
+(defface gt-youdao-suggest-entry-face '((t :bold t))
+  "Face for entry of youdao-suggest."
+  :group 'go-translate-faces)
+
+(defface gt-youdao-suggest-cixing-face '((((background light)) :foreground "grey")
+                                         (((background dark)) :foreground "grey"))
+  "Face for cixing of youdao-suggest."
+  :group 'go-translate-faces)
+
+\f
+;;; StarDict
+
+(defface gt-stardict-dict-face '((((background light)) :foreground "grey")
+                                 (((background dark)) :foreground "darkcyan"))
+  "Face for dict of StarDict."
+  :group 'go-translate-faces)
+
+(defface gt-stardict-word-face '((((background light)) :bold t :foreground "darkblue")
+                                 (((background dark)) :bold t))
+  "Face for word of StarDict."
+  :group 'go-translate-faces)
+
+(defface gt-stardict-phonetic-face '((((background light)) :foreground "lightblue")
+                                     (((background dark)) :foreground "darkcyan"))
+  "Face for phonetic of StarDict."
+  :group 'go-translate-faces)
+
+(defface gt-stardict-word-class-face '((t :bold t))
+  "Face for cixing of StarDict."
+  :group 'go-translate-faces)
+
+\f
+;;; Misc
+
+(defface gt-bionic-reading-face '((t :weight bold))
+  "Face for bionic reading word."
+  :group 'go-translate-faces)
+
+(provide 'gt-faces)
+
+;;; gt-faces.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-httpx.el b/elpa/go-translate-20240802.131/gt-httpx.el
new file mode 100644 (file)
index 0000000..def4a9b
--- /dev/null
@@ -0,0 +1,358 @@
+;;; gt-httpx.el --- Http Client -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+;; Package-Requires: ((emacs "28.1"))
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; Http client components used by the framework. With unified interface to
+;; compact different http backends.
+;;
+;;  - Support both sync/async request
+;;  - Support streaming request
+;;  - Support retry for timeout
+;;  - Support config proxies for backends
+;;  - Support file upload/download
+;;
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'eieio)
+(require 'url)
+
+(if (fboundp 'gt-log)
+    (declare-function gt-log "ext:gt-core")
+  (defalias 'gt-log 'ignore))
+
+(if (fboundp 'gt-single)
+    (declare-function gt-single--eieio-childp "ext:gt-core")
+  (defclass gt-single () () :abstract t))
+
+(defun gt-http-binary-p (content-type)
+  "Check if current CONTENT-TYPE is binary."
+  (if (null content-type) nil
+    (cl-destructuring-bind (mime sub) (string-split content-type "/" nil "[ \n\r\t]")
+      (not (or (equal mime "text")
+               (and (equal mime "application")
+                    (string-match-p "json\\|xml\\|php" sub)))))))
+
+(defun gt-format-params (alist)
+  "Format ALIST to k=v style query string."
+  (mapconcat (lambda (arg)
+               (format "%s=%s"
+                       (url-hexify-string (format "%s" (car arg)))
+                       (url-hexify-string (format "%s" (or (cdr arg) 1)))))
+             (delq nil alist) "&"))
+
+(defvar gt-multipart-boundary "gt-boundary-O0o0O69Oo")
+
+(defun gt-format-formdata (alist)
+  "Generate multipart/formdata string from ALIST."
+  (with-temp-buffer
+    (set-buffer-multibyte nil)
+    (cl-loop for (key . value) in alist for i from 1
+             for filep = nil for contentype = nil
+             do (setq key (format "%s" key))
+             do (if (consp value) ; ((afile "~/aaa.jpg" "image/jpeg"))
+                    (setq contentype (or (cadr value) "application/octet-stream")
+                          value (format "%s" (car value)) filep t)
+                  (setq value (format "%s" value)))
+             for newline = "\r\n"
+             do (insert "--" gt-multipart-boundary newline)
+             if filep do (let ((fn (url-encode-url (url-file-nondirectory value))))
+                           (insert "Content-Disposition: form-data; name=\"" key "\" filename=\"" fn "\"" newline)
+                           (insert "Content-Type: " contentype newline newline)
+                           (insert-file-contents-literally value)
+                           (goto-char (point-max)))
+             else do (insert "Content-Disposition: form-data; name=\"" key "\"" newline newline value)
+             if (< i (length alist)) do (insert newline)
+             else do (insert newline "--" gt-multipart-boundary "--"))
+    (buffer-substring-no-properties (point-min) (point-max))))
+
+\f
+;;; Http Client
+
+(defclass gt-http-client (gt-single)
+  ((user-agent :initarg :user-agent :initform nil :type (or string null)))
+  "Used to send http request."
+  :abstract t)
+
+(defvar gt-http-client-max-retry 3)
+
+(defvar gt-user-agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36")
+
+(defvar-local gt-http-client-stream-abort-flag nil
+  "Non-nil means to ignore following stream progress in callback of http filter.")
+
+(cl-defgeneric gt-request (http-client &rest _args &key url method headers data filter done fail sync retry &allow-other-keys)
+  "Send HTTP request using the given HTTP-CLIENT.
+
+Keyword arguments:
+  - URL: The URL to send the request to.
+  - METHOD: Request method, symbol like \\='post. If nil guess by data.
+  - HEADERS: Additional headers to include in the request. Alist.
+  - DATA: The data to include in the request. If this is a string, it will be
+          sent directly as request body. If this is a list and every element
+          is (key . value) then this will be joined to a string like a=1&b=2 and
+          then be sent. If this is a list and some element is (key filename)
+          format, then the list will be normalized as multipart formdata string
+          and be sent.
+  - FILTER: A function to be called every time when some data returned.
+  - DONE: A function to be called when the request succeeds.
+  - FAIL: A function to be called when the request fails.
+  - RETRY: How many times it can retry for timeout. Number.
+  - SYNC: Non-nil means request synchronized. Boolean.
+
+If request async, return the process behind the request."
+  (:method :around ((client gt-http-client) &rest args &key url method _headers data filter done fail sync retry)
+           ;; normalize and validate
+           (if (and (null filter) (null done)) (setq sync t args `(:sync t ,@args)))
+           (cl-assert (and url (or (and sync (not filter)) (and (not sync) (or filter done)))))
+           (if (null method) (setq args `(:method ,(if data 'post 'get) ,@args)))
+           ;; sync
+           (if sync (apply #'cl-call-next-method client args)
+             ;; async
+             (let* ((tag (eieio-object-class client))
+                    (failfn (lambda (status)
+                              ;; retry for timeout
+                              (unless retry (setq retry gt-http-client-max-retry))
+                              (if (and (string-match-p "Operation timeout" (format "%s" status)) (cl-plusp retry))
+                                  (progn (gt-log tag (format "request timeout, retrying (remains %d times)..." retry))
+                                         (apply #'gt-request client `(:retry ,(1- retry) ,@args)))
+                                ;; failed finally
+                                (gt-log tag (format "Request FAIL: (%s) %s" url status))
+                                (if fail (funcall fail status)
+                                  (signal (car status) (cdr status))))))
+                    (filterfn (when filter
+                                (lambda ()
+                                  ;; abort action and error case
+                                  (unless gt-http-client-stream-abort-flag
+                                    (condition-case err
+                                        (funcall filter)
+                                      (error
+                                       (setq gt-http-client-stream-abort-flag t)
+                                       (gt-log tag (format "Error in filter: (%s) %s" url err))
+                                       (funcall failfn err)))))))
+                    (donefn (lambda (raw)
+                              (gt-log tag (format "✓ %s" url))
+                              (when done (funcall done raw)))))
+               (apply #'cl-call-next-method client `(:fail ,failfn :filter ,filterfn :done ,donefn ,@args))))))
+
+\f
+;; http client implemented using `url.el'
+
+(defclass gt-url-http-client (gt-http-client)
+  ((proxy-services
+    :initarg :proxies
+    :initform nil
+    :type (or list null)
+    :documentation "Proxy services passed to `url.el', see `url-proxy-services' for details."))
+  :documentation "Http Client implemented using `url.el'.")
+
+(defvar url-http-content-type)
+(defvar url-http-end-of-headers)
+(defvar url-http-transfer-encoding)
+
+(defvar gt-url-extra-filter nil)
+
+(defun gt-url-http-extra-filter (beg end len)
+  (when (and gt-url-extra-filter (bound-and-true-p url-http-end-of-headers)
+             (if (equal url-http-transfer-encoding "chunked") (= beg end) ; when delete
+               (= len 0))) ; when insert
+    (save-excursion
+      (save-restriction
+        (narrow-to-region url-http-end-of-headers (point-max))
+        (funcall gt-url-extra-filter)))))
+
+(cl-defmethod gt-request ((client gt-url-http-client) &key url method headers data filter done fail sync retry)
+  (ignore retry)
+  (let* ((inhibit-message t)
+         (message-log-max nil)
+         (url-user-agent (or (oref client user-agent) gt-user-agent))
+         (url-proxy-services (or (oref client proxy-services) url-proxy-services))
+         (formdatap (and (consp data)
+                         (or (string-match-p "multipart/formdata"
+                                             (or (alist-get "Content-Type" headers nil nil #'string-equal-ignore-case) ""))
+                             (cl-some (lambda (x) (consp (cdr x))) data))))
+         (url-request-data (funcall (if (atom data) #'identity ; string
+                                      (if formdatap #'gt-format-formdata #'gt-format-params)) ; alist
+                                    data))
+         (url-request-extra-headers (progn
+                                      (when formdatap
+                                        (setf (alist-get "Content-Type" headers nil nil #'string-equal-ignore-case)
+                                              (concat "multipart/form-data; boundary=" gt-multipart-boundary)))
+                                      headers))
+         (url-request-method (string-to-unibyte (upcase (format "%s" method))))
+         (url-mime-encoding-string "identity")
+         (get-resp-content (lambda ()
+                             (set-buffer-multibyte (not (gt-http-binary-p url-http-content-type)))
+                             (buffer-substring-no-properties (min (1+ url-http-end-of-headers) (point-max)) (point-max)))))
+    ;; log
+    (gt-log (eieio-object-class client)
+      (format "> %s\n> %s" client url)
+      (if url-request-extra-headers (format "> HEADER: %S" url-request-extra-headers))
+      (if url-request-data (format "> DATA:   %s" url-request-data)))
+    ;; sync
+    (if sync
+        (condition-case err
+            (let ((buf (url-retrieve-synchronously url nil t)))
+              (unwind-protect
+                  (with-current-buffer buf
+                    (let ((s (funcall get-resp-content))) (if done (funcall done s) s)))
+                (ignore-errors (kill-buffer buf))))
+          (error (if fail (funcall fail err) (signal 'user-error (cdr err)))))
+      ;; async
+      (let ((buf (url-retrieve url
+                               (lambda (status)
+                                 (let ((cb (current-buffer)))
+                                   (remove-hook 'after-change-functions #'gt-url-http-extra-filter t)
+                                   (unwind-protect
+                                       (if-let (err (or (cdr-safe (plist-get status :error))
+                                                        (when (or (null url-http-end-of-headers) (= 1 (point-max)))
+                                                          (list 'empty-response "Nothing responsed from server"))))
+                                           (if fail (funcall fail err) (signal 'user-error err))
+                                         (if done (funcall done (funcall get-resp-content))))
+                                     (kill-buffer cb))))
+                               nil t)))
+        (when (and filter (buffer-live-p buf))
+          (with-current-buffer buf
+            (setq-local gt-url-extra-filter filter)
+            (add-hook 'after-change-functions #'gt-url-http-extra-filter nil t)))
+        (get-buffer-process buf)))))
+
+\f
+;;; request with `curl' by package `plz.el'
+
+;; you should install `plz.el' first before use this
+
+(defclass gt-plz-http-client (gt-http-client)
+  ((extra-args
+    :initarg :args
+    :type list
+    :documentation "Extra arguments passed to curl programe."))
+  :documentation "Http Client implemented using `plz.el'.")
+
+(defvar plz-curl-program)
+(defvar plz-curl-default-args)
+(defvar plz-http-end-of-headers-regexp)
+
+(declare-function plz "ext:plz.el" t t)
+(declare-function plz-error-message "ext:plz.el" t t)
+(declare-function plz-error-curl-error "ext:plz.el" t t)
+(declare-function plz-error-response "ext:plz.el" t t)
+(declare-function plz-response-status "ext:plz.el" t t)
+(declare-function plz-response-body "ext:plz.el" t t)
+(declare-function plz--narrow-to-body "ext:plz.el" t t)
+
+(defvar gt-plz-initialize-error-message
+  "\n\nTry to install curl and specify the program like this to solve the problem:\n
+  (setq plz-curl-program \"c:/msys64/usr/bin/curl.exe\")\n
+Or switch http client to `gt-url-http-client' instead:\n
+  (setq gt-default-http-client (gt-url-http-client))")
+
+(cl-defmethod gt-request :before ((_ gt-plz-http-client) &rest _)
+  (unless (and (require 'plz nil t) (executable-find plz-curl-program))
+    (error "You should have `plz.el' and `curl' installed before using `gt-plz-http-client'")))
+
+(cl-defmethod gt-request ((client gt-plz-http-client) &key url method headers data filter done fail sync retry)
+  (ignore retry)
+  (let* ((plz-curl-default-args (if (slot-boundp client 'extra-args)
+                                    (append (oref client extra-args) plz-curl-default-args)
+                                  plz-curl-default-args))
+         (formdatap (and (consp data)
+                         (or (string-match-p "multipart/formdata"
+                                             (or (alist-get "Content-Type" headers nil nil #'string-equal-ignore-case) ""))
+                             (cl-some (lambda (x) (consp (cdr x))) data))))
+         (data (funcall (if (atom data) #'identity ; string
+                          (if formdatap #'gt-format-formdata #'gt-format-params)) ; alist
+                        data))
+         (string-or-binary (lambda () ; decode according content-type. there is no builtin way to do this in plz
+                             (widen)
+                             (let* ((content-type (mail-fetch-field "content-type"))
+                                    (binaryp (gt-http-binary-p content-type)))
+                               (set-buffer-multibyte (not binaryp))
+                               (goto-char (point-min))
+                               (plz--narrow-to-body)
+                               (unless binaryp (decode-coding-region (point-min) (point-max) 'utf-8))
+                               (buffer-string)))))
+    ;; headers
+    (when formdatap
+      (setf (alist-get "Content-Type" headers nil nil #'string-equal-ignore-case)
+            (concat "multipart/form-data; boundary=" gt-multipart-boundary)))
+    (unless (alist-get "User-Agent" headers nil nil #'string-equal-ignore-case)
+      (push `("User-Agent" . ,(or (oref client user-agent) gt-user-agent)) headers))
+    ;; log
+    (gt-log (eieio-object-class client)
+      (format "> %s\n> %s" client url)
+      (if headers (format "> HEADER: %s" headers))
+      (if data (format "> DATA:   %s" data))
+      (if plz-curl-default-args (format "> EXTRA: %s" plz-curl-default-args)))
+    ;; sync
+    (if sync
+        (condition-case err
+            (let ((r (plz method url
+                       :headers headers
+                       :body data
+                       :body-type (if formdatap 'binary 'text)
+                       :decode nil
+                       :as string-or-binary
+                       :then 'sync)))
+              (if done (funcall done r) r))
+          (error (if fail (funcall fail err)
+                   (signal 'user-error (cdr err)))))
+      ;; async
+      (plz method url
+        :headers headers
+        :body data
+        :body-type (if formdatap 'binary 'text)
+        :decode nil
+        :as string-or-binary
+        :filter (when filter
+                  (lambda (proc string)
+                    (with-current-buffer (process-buffer proc)
+                      (save-excursion
+                        (goto-char (point-max))
+                        (insert string)
+                        (goto-char (point-min))
+                        (when (re-search-forward plz-http-end-of-headers-regexp nil t)
+                          (save-restriction
+                            (narrow-to-region (point) (point-max))
+                            (funcall filter)))))))
+        :then (lambda (raw)
+                (when done (funcall done raw)))
+        :else (lambda (err)
+                (let ((ret ;; try to compat with error object of url.el, see `url-retrieve' for details
+                       (or (plz-error-message err)
+                           (when-let (r (plz-error-curl-error err))
+                             (list 'curl-error
+                                   (concat (format "%s" (or (cdr r) (car r)))
+                                           (pcase (car r)
+                                             (2 (when (memq system-type '(cygwin windows-nt ms-dos))
+                                                  gt-plz-initialize-error-message))))))
+                           (when-let (r (plz-error-response err))
+                             (list 'http (plz-response-status r) (plz-response-body r))))))
+                  (if fail (funcall fail ret)
+                    (signal 'user-error ret))))))))
+
+(provide 'gt-httpx)
+
+;;; gt-httpx.el ends here
diff --git a/elpa/go-translate-20240802.131/gt-text-utility.el b/elpa/go-translate-20240802.131/gt-text-utility.el
new file mode 100644 (file)
index 0000000..c2cc152
--- /dev/null
@@ -0,0 +1,124 @@
+;;; gt-text-utility.el --- Text utilities base on the translator -*- lexical-binding: t -*-
+
+;; Copyright (C) 2024 lorniu <lorniuNOSPAMgmail.com>
+;; Author: lorniu <lorniuNOSPAMgmail.com>
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; Integrate md5, sha1, base64, qrcode and others into this translation framework.
+;;
+;; Shows that this framework is more than just translation purposes.
+;;
+;; Use like this:
+;;
+;;   \\=(gt-start (gt-text-utility :render (gt-buffer-render)))
+;;
+;; To avoid be prompted, specify the targets like this:
+;;
+;;   \\=(gt-start (gt-text-utility :taker (gt-taker :langs '(md5 sha1)) :render (gt-insert-render)))
+
+;;; Code:
+
+(require 'gt-extension)
+
+(defvar gt-text-utilities `(base64 rot13 qrcode speak ,@(secure-hash-algorithms))
+  "List of available targets for `gt-text-utility-engine'.")
+
+(defclass gt-text-utility (gt-translator) ())
+
+(defclass gt-text-utility-engine (gt-engine)
+  ((tag :initform 'Text-Utility)
+   (cache :initform nil)
+   (delimiter :initform nil)))
+
+(cl-defmethod gt-reset :after ((translator gt-text-utility) &rest _)
+  (with-slots (_engines) translator
+    (unless _engines (setf _engines (gt-text-utility-engine)))
+    (setf _engines (ensure-list _engines))
+    (unless (and (not (cdr _engines)) (cl-typep (car _engines) 'gt-text-utility-engine))
+      (user-error "%s should use only one gt-text-utility-engine as engine" (gt-desc translator)))))
+
+(cl-defmethod gt-target ((taker gt-taker) (_ gt-text-utility) &rest _)
+  (let ((tgts (if (slot-boundp taker 'langs)
+                  (ensure-list (oref taker langs))
+                (mapcar #'intern
+                        (completing-read-multiple
+                         "Utilities (can choose multiple): " gt-text-utilities
+                         nil t nil 'gt-text-utility-hist)))))
+    (unless tgts (user-error "No targets found"))
+    (cons nil (cl-delete-duplicates tgts))))
+
+(cl-defmethod gt-translate ((_ gt-text-utility-engine) task _)
+  "Translate and render the result directly, skip parse and other steps."
+  (with-slots (text tgt res translator) task
+    (setf res (mapcar (lambda (c) (gt-text-util tgt (encode-coding-string c 'utf-8))) text))
+    (gt-update-state translator)
+    (gt-output (oref translator render) translator)))
+
+(cl-defmethod gt-keybinds ((_buffer gt-buffer-render) (_ gt-text-utility))
+  (gt-buffer-render-key ("t" "Cycle Next")  #'gt-buffer-render--cycle-next)
+  (gt-buffer-render-key ("g" "Refresh")     #'gt-buffer-render--refresh)
+  (gt-buffer-render-key ("q" "Quit")        #'kill-buffer-and-window)
+  (gt-buffer-render-key ("C-x C-q")         #'gt-buffer-render--toggle-readonly)
+  (gt-buffer-render-key ("?")               #'gt-buffer-render--show-tips))
+
+\f
+
+(cl-defgeneric gt-text-util (target text)
+  "Dispatch to different TEXT utility methods for different TARGET."
+  (:method ((_ (eql 'base64)) text) (base64-encode-string text t))
+  (:method ((_ (eql 'rot13)) text) (rot13 text))
+  (:method ((_ (eql 'qrcode)) text) (gt-qrcode text))
+  (:method ((_ (eql 'speak)) text) (gt-text-speak-button text))
+  (if (member target (secure-hash-algorithms))
+      (secure-hash target text)
+    (user-error "Unimplemented")))
+
+(defun gt-text-speak-button (str)
+  (propertize str
+              'display " click to speak... "
+              'pointer 'hand
+              'keymap (gt-simple-keymap [mouse-1] (lambda () (interactive) (gt-speak 'interact str t)))
+              'face '(:box t)
+              'mouse-face '(:reverse-video t :inherit font-lock-warning-face)))
+
+(declare-function qrencode "ext:qrencode.el" t t)
+
+;;;###autoload
+(defun gt-qrcode (str)
+  "Genrate QR Code for STR."
+  (interactive (list (read-string "String to generate QRCode: ")))
+  (let ((code (if (executable-find "qrencode")
+                  (with-temp-buffer
+                    (let ((coding-system-for-read 'raw-text))
+                      (set-buffer-multibyte nil)
+                      (insert str)
+                      (shell-command-on-region (point-min) (point-max) "qrencode -t PNG -o -" (current-buffer))
+                      (if (string-match-p "PNG" (buffer-substring 1 (min (point-max) 8)))
+                          (propertize " " 'display `(image :type png :data ,(buffer-string) :width 200))
+                        (user-error (buffer-string)))))
+                (if (require 'qrencode nil t)
+                    (qrencode str)
+                  (user-error "Install program `qrencode' or package `qrencode.el' first to generate QR Code")))))
+    (if (called-interactively-p 'any) (message "%s" code) code)))
+
+(provide 'gt-text-utility)
+
+;;; gt-text-utility.el ends here
diff --git a/elpa/graphql-mode-20230411.1943/graphql-mode-autoloads.el b/elpa/graphql-mode-20230411.1943/graphql-mode-autoloads.el
deleted file mode 100644 (file)
index 46d7976..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-;;; graphql-mode-autoloads.el --- automatically extracted autoloads  -*- lexical-binding: t -*-
-;;
-;;; Code:
-
-(add-to-list 'load-path (directory-file-name
-                         (or (file-name-directory #$) (car load-path))))
-
-\f
-;;;### (autoloads nil "graphql-mode" "graphql-mode.el" (0 0 0 0))
-;;; Generated autoloads from graphql-mode.el
-
-(autoload 'graphql-mode "graphql-mode" "\
-A major mode to edit GraphQL schemas.
-
-\(fn)" t nil)
-
-(add-to-list 'auto-mode-alist '("\\.graphql\\'" . graphql-mode))
-
-(add-to-list 'auto-mode-alist '("\\.gql\\'" . graphql-mode))
-
-(register-definition-prefixes "graphql-mode" '("graphql-"))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; graphql-mode-autoloads.el ends here