-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathdeepl.el
192 lines (154 loc) · 6.28 KB
/
deepl.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
;;; deepl.el --- Elisp library for the DeepL API -*- lexical-binding: t; -*-
;; Copyright (C) 2023-2025 Shen, Jen-Chieh
;; Author: Shen, Jen-Chieh <[email protected]>
;; Maintainer: Shen, Jen-Chieh <[email protected]>
;; URL: https://github.com/emacs-openai/deepl
;; Version: 0.1.0
;; Package-Requires: ((emacs "26.1") (request "0.3.0"))
;; Keywords: comm deepl
;; This file is not part of GNU Emacs.
;; 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/>.
;;; Commentary:
;;
;; Elisp library for the DeepL API
;;
;;; Code:
(require 'auth-source)
(require 'cl-lib)
(require 'json)
(require 'request)
(defgroup deepl nil
"Elisp library for the DeepL API."
:prefix "deepl-"
:group 'comm
:link '(url-link :tag "Repository" "https://github.com/emacs-openai/deepl"))
;;
;;; Logger
(defvar deepl--show-log nil
"Get more information from the program.")
(defun deepl--log (fmt &rest args)
"Debug message like function `message' with same argument FMT and ARGS."
(when deepl--show-log
(apply 'message fmt args)))
;;
;;; Request
(defvar deepl-key ""
"Variable storing the deepl key or a function name to retrieve it.
The function should take no arguments and return a string containing the key.
A function, `deepl-key-auth-source', that retrieves the key from auth-source is
provided for convenience.")
(defcustom deepl-free t
"Non-nil then use free version."
:type 'boolean
:group 'deepl)
(defcustom deepl-base-url (if deepl-free
"https://api-free.deepl.com/v2"
"https://api.deepl.com/v2")
"The base URL for DeepL API requests."
:type 'string
:group 'deepl)
(defcustom deepl-parameters '()
"The parameters for the DeepL request."
:type 'list
:group 'deepl)
;;;###autoload
(defun deepl-key-auth-source (&optional base-url)
"Retrieve the DeepL API key from auth-source given a BASE-URL.
If BASE-URL is not specified, it defaults to `deepl-base-url'."
(if-let ((auth-info (auth-source-search
:max 1
:host (url-host (url-generic-parse-url (or base-url deepl-base-url)))
:require '(:user :secret))))
(funcall (plist-get (car auth-info) :secret))
(error "DeepL API key not found in auth-source")))
(defun deepl--resolve-key (key)
"If the given KEY is a function call it and return the result, otherwise
return KEY."
(cond ((functionp key) (funcall key))
((and (stringp key) (not (string-empty-p key))) key)
(t (user-error "[INFO] Invalid API key, please set it to the correct value: %s" key))))
(defun deepl--alist-omit-null (alist)
"Omit null value or empty string in ALIST."
(cl-remove-if (lambda (pair)
(let ((value (cdr pair)))
(or (null value) ; ignore null
(and (stringp value) ; ignore empty string
(string-empty-p value)))))
alist))
(defun deepl--headers (key)
"Construct request headers.
Argument KEY is common request headers."
(setq key (deepl--resolve-key key))
(deepl--alist-omit-null `(("Authorization" . ,(if (or (null key)
(string-empty-p key))
""
(concat "DeepL-Auth-Key " key))))))
(defun deepl--handle-error (response)
"Handle error status code from the RESPONSE."
(let ((status-code (request-response-status-code response)))
(deepl--log "[ERROR]: %s" response)
(pcase status-code
(400 (message "400 - Bad request. Please check error message and your parameters"))
(401 (message "401 - Invalid Authentication"))
(429 (message "429 - Rate limit reached for requests"))
(500 (message "500 - The server had an error while processing your request"))
(_ (message "Internal error: %s" status-code)))))
(defvar deepl-error nil
"Records for the last error.")
(defmacro deepl-request (url &rest body)
"Wrapper for `request' function.
The URL is the url for `request' function; then BODY is the arguments for rest."
(declare (indent 1))
`(progn
(setq deepl-error nil)
(request ,url
:error (cl-function
(lambda (&key response &allow-other-keys)
(setq deepl-error response)
(deepl--handle-error response)))
,@body)))
;;
;;; API
;;;###autoload
(cl-defun deepl-translate ( text target-lang callback
&key
(base-url deepl-base-url)
(parameters deepl-parameters)
(key deepl-key))
"Send translate request.
Arguments MESSAGES and CALLBACK are required for this type of request.
TEXT is the text to translate. TARGET-LANG is the target language to be
translated. CALLBACK is the execuation after request is made.
Arguments BASE-URL, PARAMETERS and KEY are global options; however, you can
overwrite the value by passing it in."
(deepl-request (concat base-url "/translate")
:type "POST"
:params parameters
:headers (deepl--headers key)
:data `(("text" . ,text)
("target_lang" . ,target-lang))
:parser 'json-read
:complete (cl-function
(lambda (&key data &allow-other-keys)
(funcall callback data)))))
;;
;;; Application
(defun deepl-translate-this ()
"Input text for translation."
(interactive)
(let ((text (read-string "Text to be translated: "))
(target-lang (read-string "Translate to what language? " "ZH")))
(deepl-translate text target-lang
(lambda (data)
(message "%s" data)))))
(provide 'deepl)
;;; deepl.el ends here