-
-
Save IntrovertedMage/6f17103b14fdc77849ad111315d4ec24 to your computer and use it in GitHub Desktop.
| --SETTINGS--======================================== | |
| local api_key = "ENTER API KEY HERE" | |
| local USE_FOR_TRANSLATION = true | |
| local USE_FOR_DETECTION = true | |
| local SHOW_DEEPL_LANGUAGES_AS_OPTIONS = true | |
| -------------========================= | |
| local Translator = require("ui/translator") | |
| local Device = require("device") | |
| local InfoMessage = require("ui/widget/infomessage") | |
| local TextViewer = require("ui/widget/textviewer") | |
| local UIManager = require("ui/uimanager") | |
| local Screen = require("device").screen | |
| local ffiUtil = require("ffi/util") | |
| local logger = require("logger") | |
| local T = ffiUtil.template | |
| local _ = require("gettext") | |
| local AUTODETECT_LANGUAGE = "auto" | |
| local DEEPL_SUPPORTED_SOURCE_LANGUAGES = { | |
| AR = "Arabic", | |
| BG = "Bulgarian", | |
| CS = "Czech", | |
| DA = "Danish", | |
| DE = "German", | |
| EL = "Greek", | |
| EN = "English (all English variants)", | |
| ES = "Spanish", | |
| ET = "Estonian", | |
| FI = "Finnish", | |
| FR = "French", | |
| HU = "Hungarian", | |
| ID = "Indonesian", | |
| IT = "Italian", | |
| JA = "Japanese", | |
| KO = "Korean", | |
| LT = "Lithuanian", | |
| LV = "Latvian", | |
| NB = "Norwegian Bokmål", | |
| NL = "Dutch", | |
| PL = "Polish", | |
| PT = "Portuguese (all Portuguese variants)", | |
| RO = "Romanian", | |
| RU = "Russian", | |
| SK = "Slovak", | |
| SL = "Slovenian", | |
| SV = "Swedish", | |
| TR = "Turkish", | |
| UK = "Ukrainian", | |
| ZH = "Chinese (all Chinese variants)" | |
| } | |
| local DEEPL_SUPPORTED_TARGET_LANGUAGES = { | |
| AR = "Arabic", | |
| BG = "Bulgarian", | |
| CS = "Czech", | |
| DA = "Danish", | |
| DE = "German", | |
| EL = "Greek", | |
| EN = "English (unspecified variant for backward compatibility; please select EN-GB or EN-US instead)", | |
| ["EN-GB"] = "English (British)", | |
| ["EN-US"] = "English (American)", | |
| ES = "Spanish", | |
| ET = "Estonian", | |
| FI = "Finnish", | |
| FR = "French", | |
| HU = "Hungarian", | |
| ID = "Indonesian", | |
| IT = "Italian", | |
| JA = "Japanese", | |
| KO = "Korean", | |
| LT = "Lithuanian", | |
| LV = "Latvian", | |
| NB = "Norwegian Bokmål", | |
| NL = "Dutch", | |
| PL = "Polish", | |
| PT = "Portuguese (unspecified variant for backward compatibility; please select PT-BR or PT-PT instead)", | |
| ["PT-BR"] = "Portuguese (Brazilian)", | |
| ["PT-PT"] = "Portuguese (all Portuguese variants excluding Brazilian Portuguese)", | |
| RO = "Romanian", | |
| RU = "Russian", | |
| SK = "Slovak", | |
| SL = "Slovenian", | |
| SV = "Swedish", | |
| TR = "Turkish", | |
| UK = "Ukrainian", | |
| ZH = "Chinese (unspecified variant for backward compatibility; please select ZH-HANS or ZH-HANT instead)", | |
| ["ZH-HANS"] = "Chinese (Simplified)", | |
| ["ZH-HANT"] = "Chinese (Traditional)" | |
| } | |
| local function loadDeepLPage(text, target_lang, source_lang) | |
| local http = require("socket.http") | |
| local ltn12 = require("ltn12") | |
| local json = require("json") | |
| local socketutil = require("socketutil") | |
| local socket = require("socket") | |
| local url = "https://api-free.deepl.com/v2/translate" | |
| -- request_body.target_lang = target_lang | |
| --request_body.source_lang = source_lang | |
| local request_body = { | |
| text = {text}, -- Wrap text in a table (array) for DeepL API | |
| target_lang = target_lang, | |
| } | |
| if source_lang and source_lang ~= "auto" then | |
| request_body.source_lang = source_lang | |
| end | |
| local request_json = json.encode(request_body) | |
| local response_body = {} | |
| socketutil:set_timeout() | |
| local request = { | |
| url = url, | |
| method = "POST", | |
| headers = { | |
| ["User-Agent"] = "KOReader", | |
| ["Host"] = "api-free.deepl.com", | |
| ["Accept"] = "application/json", | |
| ["Authorization"] = "DeepL-Auth-Key " .. api_key, | |
| ["Content-Type"] = "application/json", | |
| ["Content-Length"] = tostring(#request_json) -- Fix Content-Length | |
| }, | |
| source = ltn12.source.string(request_json), -- Send JSON | |
| sink = ltn12.sink.table(response_body) | |
| } | |
| logger.dbg("Calling", request.url) | |
| -- Skip first argument (body, goes to the sink) | |
| local res, code, headers, status = http.request(request) | |
| socketutil:reset_timeout() | |
| -- raise error message when network is unavailable | |
| if headers == nil then | |
| error(status or code or "network unreachable") | |
| end | |
| if code ~= 200 then | |
| logger.warn("translator HTTP status not okay:", status or code or "network unreachable") | |
| logger.dbg("Response headers:", headers) | |
| return | |
| end | |
| if table.concat(response_body) then | |
| local response_str = table.concat(response_body) | |
| local content = json.decode(response_str) | |
| logger.dbg("translator content:", content) | |
| if content.translations then | |
| return content.translations | |
| else | |
| logger.warn("translator error") | |
| end | |
| else | |
| logger.warn("not JSON in translator response:", response_body) | |
| end | |
| end | |
| local detect_original = Translator.detect | |
| Translator.detect = function(self,text) | |
| if not USE_FOR_DETECTION then | |
| return detect_original(self,text) | |
| end | |
| local result = loadDeepLPage(text, "en", AUTODETECT_LANGUAGE) | |
| if result and result[1] and result[1].detected_source_language then | |
| local src_lang = result.detected_source_language | |
| logger.dbg("detected language:", src_lang) | |
| return src_lang | |
| else | |
| return self.default_lang | |
| end | |
| end | |
| local translate_original = Translator.translate | |
| Translator.translate = function(self, text, target_lang, source_lang) | |
| if not USE_FOR_TRANSLATION then | |
| return translate_original(self, text, target_lang, source_lang) | |
| end | |
| if not target_lang then | |
| target_lang = self:getTargetLanguage() | |
| end | |
| if not source_lang then | |
| source_lang = self:getSourceLanguage() | |
| end | |
| if not DEEPL_SUPPORTED_TARGET_LANGUAGES[target_lang] then | |
| UIManager:show(InfoMessage:new{ | |
| text = _("Language: "..target_lang.." not supported as target language for translation by DeepL") | |
| }) | |
| return | |
| end | |
| if source_lang ~= "auto" and not DEEPL_SUPPORTED_SOURCE_LANGUAGES[source_lang] then | |
| UIManager:show(InfoMessage:new{ | |
| text = _("Language: "..target_lang.." not supported as source language for translation by DeepL") | |
| }) | |
| return | |
| end | |
| local result = loadDeepLPage(text, target_lang, source_lang) | |
| if result and result[1] and type(result[1]) == "table" then | |
| local translated = {} | |
| for i, r in ipairs(result) do | |
| table.insert(translated, r.text) | |
| end | |
| return table.concat(translated, "") | |
| end | |
| return nil | |
| end | |
| local _showTranslation_origianl = Translator._showTranslation | |
| Translator._showTranslation = function(self, text, detailed_view, source_lang, target_lang, from_highlight, index) | |
| if not USE_FOR_TRANSLATION then | |
| return _showTranslation_origianl(self, text, detailed_view, source_lang, target_lang, from_highlight, index) | |
| end | |
| if not target_lang then | |
| target_lang = self:getTargetLanguage() | |
| end | |
| if not source_lang then | |
| source_lang = self:getSourceLanguage() | |
| end | |
| if not DEEPL_SUPPORTED_TARGET_LANGUAGES[(string.upper(target_lang))] then | |
| UIManager:show(InfoMessage:new{ | |
| text = _("Language: "..target_lang.." not supported as target language for translation by DeepL") | |
| }) | |
| return | |
| end | |
| if source_lang ~= "auto" and not DEEPL_SUPPORTED_SOURCE_LANGUAGES[(string.upper(source_lang))] then | |
| UIManager:show(InfoMessage:new{ | |
| text = _("Language: "..target_lang.." not supported as source language for translation by DeepL") | |
| }) | |
| return | |
| end | |
| local Trapper = require("ui/trapper") | |
| local completed, result = Trapper:dismissableRunInSubprocess(function() | |
| return loadDeepLPage(text, target_lang, source_lang) | |
| end, _("Querying translation service…")) | |
| if not completed then | |
| UIManager:show(InfoMessage:new{ | |
| text = _("Translation interrupted.") | |
| }) | |
| return | |
| end | |
| if not result or type(result) ~= "table" then | |
| UIManager:show(InfoMessage:new{ | |
| text = _("Translation failed.") | |
| }) | |
| return | |
| end | |
| if result[1] and result[1].detected_source_language then | |
| source_lang = result[1].detected_source_language | |
| end | |
| local output = {} | |
| local text_main = "" | |
| local function is_result_valid(res) | |
| return res and type(res) == "table" and #res > 0 | |
| end | |
| -- For both main and alternate translations, we may get multiple slices | |
| -- of the original text and its translations. | |
| if is_result_valid(result) then | |
| -- Main translation: we can make a single string from the multiple parts | |
| -- for easier quick reading | |
| local source = {} | |
| local translated = {} | |
| local romanized = {} | |
| for i, r in ipairs(result) do | |
| if detailed_view then | |
| local s = type(text) == "string" and text or "" | |
| table.insert(source, s) | |
| if type(r.romanized) == "string" then | |
| table.insert(romanized, r.romanized) | |
| end | |
| end | |
| local t = type(r.text) == "string" and r.text or "" | |
| table.insert(translated, t) | |
| end | |
| text_main = table.concat(translated, " ") | |
| if detailed_view then | |
| text_main = "● " .. text_main | |
| table.insert(output, "▣ " .. table.concat(source, " ")) | |
| if #romanized > 0 then | |
| table.insert(output, table.concat(romanized, " ")) | |
| end | |
| end | |
| table.insert(output, text_main) | |
| end | |
| -- table.insert(output, require("dump")(result)) -- for debugging | |
| local text_all = table.concat(output, "\n") | |
| local textviewer, height, buttons_table, close_callback | |
| if detailed_view then | |
| height = math.floor(Screen:getHeight() * 0.8) | |
| buttons_table = {} | |
| if from_highlight then | |
| local ui = require("apps/reader/readerui").instance | |
| table.insert(buttons_table, | |
| { | |
| { | |
| text = _("Save main translation to note"), | |
| callback = function() | |
| UIManager:close(textviewer) | |
| UIManager:close(ui.highlight.highlight_dialog) | |
| ui.highlight.highlight_dialog = nil | |
| if index then | |
| ui.highlight:editNote(index, false, text_main) | |
| else | |
| ui.highlight:addNote(text_main) | |
| end | |
| end, | |
| }, | |
| { | |
| text = _("Save all to note"), | |
| callback = function() | |
| UIManager:close(textviewer) | |
| UIManager:close(ui.highlight.highlight_dialog) | |
| ui.highlight.highlight_dialog = nil | |
| if index then | |
| ui.highlight:editNote(index, false, text_all) | |
| else | |
| ui.highlight:addNote(text_all) | |
| end | |
| end, | |
| }, | |
| } | |
| ) | |
| close_callback = function() | |
| if not ui.highlight.highlight_dialog then | |
| ui.highlight:clear() | |
| end | |
| end | |
| end | |
| if Device:hasClipboard() then | |
| table.insert(buttons_table, | |
| { | |
| { | |
| text = _("Copy main translation"), | |
| callback = function() | |
| Device.input.setClipboardText(text_main) | |
| end, | |
| }, | |
| { | |
| text = _("Copy all"), | |
| callback = function() | |
| Device.input.setClipboardText(text_all) | |
| end, | |
| }, | |
| } | |
| ) | |
| end | |
| end | |
| textviewer = TextViewer:new{ | |
| title = T(_("Translation from %1"), self:getLanguageName(source_lang, "?")), | |
| title_multilines = true, | |
| -- Showing the translation target language in this title may make | |
| -- it quite long and wrapped, taking valuable vertical spacing | |
| text = text_all, | |
| text_type = "lookup", | |
| height = height, | |
| add_default_buttons = true, | |
| buttons_table = buttons_table, | |
| close_callback = close_callback, | |
| } | |
| UIManager:show(textviewer) | |
| end | |
| local genSettingsMenu_original = Translator.genSettingsMenu | |
| Translator.genSettingsMenu = function (self) | |
| local menu = genSettingsMenu_original(self) | |
| if not SHOW_DEEPL_LANGUAGES_AS_OPTIONS then | |
| return menu | |
| end | |
| local function genLanguagesItems(setting_name, default_checked_item) | |
| local items_table = {} | |
| for lang_key, lang_name in ffiUtil.orderedPairs(DEEPL_SUPPORTED_TARGET_LANGUAGES) do | |
| table.insert(items_table, { | |
| text_func = function() | |
| return T("%1 (%2)", lang_name, lang_key) | |
| end, | |
| checked_func = function() | |
| return lang_key == (G_reader_settings:readSetting(setting_name) or default_checked_item) | |
| end, | |
| callback = function() | |
| G_reader_settings:saveSetting(setting_name, lang_key) | |
| end, | |
| }) | |
| end | |
| return items_table | |
| end | |
| menu.sub_item_table[#menu.sub_item_table].sub_item_table = genLanguagesItems("translator_to_language", self:getTargetLanguage()) | |
| table.insert(menu.sub_item_table, { | |
| text = "Currently using DeepL API for translations" | |
| }) | |
| return menu | |
| end | |
I'm having a "translation error" message when I enter my API key. How do I fix it?
I put the API key only on line 5; do I have to write it on another line, too? Should I copy and paste the "-fx" characters of the key? Thanks in advance!
Yes it should be the full api key, including whatever is at the end, in my api key there' as :fx at the end of my key and it works but your one may differ. Just copy and paste straight from your deepl api dashboard and make sure line 5 is like this:
local api_key = "ABC-DEF-GHI:fx"
Worked it! Thank you! I'm native Portuguese brazilian speaker so to translate from en to PT_BR was giving me the error. I changed to PT and worked. Anyway how I can make PT_BR translation work?
Worked it! Thank you! I'm native Portuguese brazilian speaker so to translate from en to PT_BR was giving me the error. I changed to PT and worked. Anyway how I can make PT_BR translation work?
I've updated the code, I think it was just the wrong language code used (it's actually PT-BR)
Just copy and paste the new version of the user patch, go into the menu and reselect Portuguese Brazilian again to store the correct language code and it should work.
Worked it like a charm, thank you for the update!
Thank you!
On my Android device (Boox Go Color 7), it translates fine, but KOReader instantly freezes as soon as the result comes up. I have to force quit it to proceed reading. Any ideas why?
I'm having a "translation error" message when I enter my API key. How do I fix it?
I put the API key only on line 5; do I have to write it on another line, too? Should I copy and paste the "-fx" characters of the key? Thanks in advance!