Files
koreader-mirror/frontend/ui/translator.lua

1124 lines
37 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--[[--
This module translates text using Google Translate.
<https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=fr&dt=t&q=alea%20jacta%20est>
--]]
-- Useful other implementation and discussion:
-- https://github.com/ssut/py-googletrans/blob/master/googletrans/client.py
-- https://stackoverflow.com/questions/26714426/what-is-the-meaning-of-google-translate-query-params
local Device = require("device")
local InfoMessage = require("ui/widget/infomessage")
local TextViewer = require("ui/widget/textviewer")
local UIManager = require("ui/uimanager")
local JSON = require("json")
local Screen = require("device").screen
local ffiUtil = require("ffi/util")
local logger = require("logger")
local util = require("util")
local T = ffiUtil.template
local _ = require("gettext")
-- Extracted from https://translate.google.com/
-- 20251003: 249 supported languages
local AUTODETECT_LANGUAGE = "auto"
local SUPPORTED_LANGUAGES = {
-- @translators Many of the names for languages can be conveniently found pre-translated in the relevant language of this Wikipedia article: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
ab = _("Abkhaz"),
ace = _("Acehnese"),
ach = _("Acholi"),
aa = _("Afar"),
af = _("Afrikaans"),
sq = _("Albanian"),
alz = _("Alur"),
am = _("Amharic"),
ar = _("Arabic"),
hy = _("Armenian"),
as = _("Assamese"),
av = _("Avar"),
awa = _("Awadhi"),
ay = _("Aymara"),
az = _("Azerbaijani"),
ban = _("Balinese"),
bal = _("Baluchi"),
bm = _("Bambara"),
bci = _("Baoulé"),
ba = _("Bashkir"),
eu = _("Basque"),
btx = _("Batak Karo"),
bts = _("Batak Simalungun"),
bbc = _("Batak Toba"),
be = _("Belarusian"),
bem = _("Bemba"),
bn = _("Bengali"),
bew = _("Betawi"),
bho = _("Bhojpuri"),
bik = _("Bikol"),
bs = _("Bosnian"),
br = _("Breton"),
bg = _("Bulgarian"),
bua = _("Buryat"),
yue = _("Cantonese"),
ca = _("Catalan"),
ceb = _("Cebuano"),
ch = _("Chamorro"),
ce = _("Chechen"),
ny = _("Chichewa"),
-- @translators Many of the names for languages can be conveniently found pre-translated in the relevant language of this Wikipedia article: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
zh = _("Chinese (Simplified)"), -- "Simplified Chinese may be specified either by zh-CN or zh"
zh_TW = _("Chinese (Traditional)"), -- converted to "zh-TW" below
chk = _("Chuukese"),
cv = _("Chuvash"),
co = _("Corsican"),
crh = _("Crimean Tatar (Cyrillic)"),
["crh-Latn"] = _("Crimean Tatar (Latin)"),
hr = _("Croatian"),
cs = _("Czech"),
da = _("Danish"),
["fa-AF"] = _("Dari"),
dv = _("Dhivehi"),
din = _("Dinka"),
doi = _("Dogri"),
dov = _("Dombe"),
nl = _("Dutch"),
dyu = _("Dyula"),
dz = _("Dzongkha"),
en = _("English"),
eo = _("Esperanto"),
et = _("Estonian"),
ee = _("Ewe"),
fo = _("Faroese"),
fj = _("Fijian"),
tl = _("Filipino"),
fi = _("Finnish"),
fon = _("Fon"),
fr = _("French"),
["fr-CA"] = _("French (Canada)"),
fy = _("Frisian"),
fur = _("Friulian"),
ff = _("Fulani"),
gaa = _("Ga"),
gl = _("Galician"),
ka = _("Georgian"),
de = _("German"),
el = _("Greek"),
gn = _("Guarani"),
gu = _("Gujarati"),
ht = _("Haitian Creole"),
-- @translators Many of the names for languages can be conveniently found pre-translated in the relevant language of this Wikipedia article: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
cnh = _("Hakha Chin"),
ha = _("Hausa"),
haw = _("Hawaiian"),
he = _("Hebrew"), -- "Hebrew may be specified either by he or iw"
hil = _("Hiligaynon"),
hi = _("Hindi"),
hmn = _("Hmong"),
hu = _("Hungarian"),
hrx = _("Hunsrik"),
iba = _("Iban"),
is = _("Icelandic"),
ig = _("Igbo"),
ilo = _("Ilocano"),
id = _("Indonesian"),
["iu-Latn"] = _("Inuktut (Latin)"),
iu = _("Inuktut (Syllabics)"),
ga = _("Irish"),
it = _("Italian"),
jam = _("Jamaican Patois"),
ja = _("Japanese"),
jw = _("Javanese"),
kac = _("Jingpo"),
kl = _("Kalaallisut"),
kn = _("Kannada"),
kr = _("Kanuri"),
pam = _("Kapampangan"),
kk = _("Kazakh"),
kha = _("Khasi"),
km = _("Khmer"),
cgg = _("Kiga"),
kg = _("Kikongo"),
rw = _("Kinyarwanda"),
ktu = _("Kituba"),
trp = _("Kokborok"),
kv = _("Komi"),
gom = _("Konkani"),
ko = _("Korean"),
kri = _("Krio"),
ku = _("Kurdish (Kurmanji)"),
ckb = _("Kurdish (Sorani)"),
-- @translators Many of the names for languages can be conveniently found pre-translated in the relevant language of this Wikipedia article: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
ky = _("Kyrgyz"),
lo = _("Lao"),
ltg = _("Latgalian"),
la = _("Latin"),
lv = _("Latvian"),
lij = _("Ligurian"),
li = _("Limburgish"),
ln = _("Lingala"),
lt = _("Lithuanian"),
lmo = _("Lombard"),
lg = _("Luganda"),
luo = _("Luo"),
lb = _("Luxembourgish"),
mk = _("Macedonian"),
mad = _("Madurese"),
mai = _("Maithili"),
mak = _("Makassar"),
mg = _("Malagasy"),
ms = _("Malay"),
["ms-Arab"] = _("Malay (Jawi)"),
ml = _("Malayalam"),
mt = _("Maltese"),
mam = _("Mam"),
gv = _("Manx"),
mi = _("Maori"),
mr = _("Marathi"),
mh = _("Marshallese"),
mwr = _("Marwadi"),
mfe = _("Mauritian Creole"),
chm = _("Meadow Mari"),
["mni-Mtei"] = _("Meiteilon (Manipuri)"),
min = _("Minang"),
lus = _("Mizo"),
mn = _("Mongolian"),
my = _("Myanmar (Burmese)"),
["bm-Nkoo"] = _("NKo"),
nhe = _("Nahuatl (Eastern Huasteca)"),
["ndc-ZW"] = _("Ndau"),
nr = _("Ndebele (South)"),
new = _("Nepalbhasa (Newari)"),
-- @translators Many of the names for languages can be conveniently found pre-translated in the relevant language of this Wikipedia article: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
ne = _("Nepali"),
no = _("Norwegian"),
nus = _("Nuer"),
oc = _("Occitan"),
["or"] = _("Odia (Oriya)"),
om = _("Oromo"),
os = _("Ossetian"),
pag = _("Pangasinan"),
pap = _("Papiamento"),
ps = _("Pashto"),
fa = _("Persian"),
pl = _("Polish"),
pt = _("Portuguese (Brazil)"),
["pt-PT"] = _("Portuguese (Portugal)"),
pa = _("Punjabi (Gurmukhi)"),
["pa-Arab"] = _("Punjabi (Shahmukhi)"),
qu = _("Quechua"),
kek = _("Qʼeqchiʼ"),
rom = _("Romani"),
ro = _("Romanian"),
rn = _("Rundi"),
ru = _("Russian"),
se = _("Sami (North)"),
sm = _("Samoan"),
sg = _("Sango"),
sa = _("Sanskrit"),
["sat-Latn"] = _("Santali (Latin)"),
sat = _("Santali (Ol Chiki)"),
gd = _("Scots Gaelic"),
nso = _("Sepedi"),
sr = _("Serbian"),
st = _("Sesotho"),
crs = _("Seychellois Creole"),
shn = _("Shan"),
sn = _("Shona"),
scn = _("Sicilian"),
szl = _("Silesian"),
sd = _("Sindhi"),
si = _("Sinhala"),
sk = _("Slovak"),
-- @translators Many of the names for languages can be conveniently found pre-translated in the relevant language of this Wikipedia article: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
sl = _("Slovenian"),
so = _("Somali"),
es = _("Spanish"),
su = _("Sundanese"),
sus = _("Susu"),
sw = _("Swahili"),
ss = _("Swati"),
sv = _("Swedish"),
ty = _("Tahitian"),
tg = _("Tajik"),
["ber-Latn"] = _("Tamazight"),
ber = _("Tamazight (Tifinagh)"),
ta = _("Tamil"),
tt = _("Tatar"),
te = _("Telugu"),
tet = _("Tetum"),
th = _("Thai"),
bo = _("Tibetan"),
ti = _("Tigrinya"),
tiv = _("Tiv"),
tpi = _("Tok Pisin"),
to = _("Tongan"),
lua = _("Tshiluba"),
ts = _("Tsonga"),
tn = _("Tswana"),
tcy = _("Tulu"),
tum = _("Tumbuka"),
tr = _("Turkish"),
tk = _("Turkmen"),
tyv = _("Tuvan"),
ak = _("Twi"),
udm = _("Udmurt"),
uk = _("Ukrainian"),
ur = _("Urdu"),
ug = _("Uyghur"),
uz = _("Uzbek"),
ve = _("Venda"),
vec = _("Venetian"),
vi = _("Vietnamese"),
war = _("Waray"),
-- @translators Many of the names for languages can be conveniently found pre-translated in the relevant language of this Wikipedia article: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
cy = _("Welsh"),
wo = _("Wolof"),
xh = _("Xhosa"),
sah = _("Yakut"),
yi = _("Yiddish"),
yo = _("Yoruba"),
yua = _("Yucatec Maya"),
zap = _("Zapotec"),
zu = _("Zulu"),
}
-- Fix zh_TW => zh-TW:
SUPPORTED_LANGUAGES["zh-TW"] = SUPPORTED_LANGUAGES["zh_TW"]
SUPPORTED_LANGUAGES["zh_TW"] = nil
local ALT_LANGUAGE_CODES = {}
ALT_LANGUAGE_CODES["zh-CN"] = "zh"
ALT_LANGUAGE_CODES["iw"] = "he"
ALT_LANGUAGE_CODES["tl"] = "fil"
local Translator = {
trans_servers = {
"https://translate.googleapis.com/",
-- "http://translate.google.cn",
},
trans_path = "/translate_a/single",
trans_params = {
client = "gtx", -- (using "t" raises 403 Forbidden)
ie = "UTF-8", -- input encoding
oe = "UTF-8", -- output encoding
sl = "auto", -- source language (we need to specify "auto" to detect language)
tl = "en", -- target language
hl = "en", -- ?
otf = 1, -- ?
ssel = 0, -- ?
tsel = 0, -- ?
-- tk = "" -- auth token
dt = { -- what we want in result
"t", -- translation of source text
"at", -- alternate translations
-- Next options only give additional results when text is a single word
-- "bd", -- dictionary (articles, reverse translations, etc)
-- "ex", -- examples
-- "ld", -- ?
"md", -- definitions of source text
-- "qca", -- ?
-- "rw", -- "see also" list
-- "rm", -- transcription / transliteration of source and translated texts
-- "ss", -- synonyms of source text, if it's one word
}
-- q = text to translate
},
default_lang = "en",
}
function Translator:getTransServer()
return G_reader_settings:readSetting("trans_server") or self.trans_servers[1]
end
function Translator:getLanguageName(lang, default_string)
if SUPPORTED_LANGUAGES[lang] then
return SUPPORTED_LANGUAGES[lang], true
elseif ALT_LANGUAGE_CODES[lang] then
return SUPPORTED_LANGUAGES[ALT_LANGUAGE_CODES[lang]], true
elseif lang then
return lang:upper(), false
end
return default_string, false
end
-- Will be called by ReaderHighlight to make it available in Reader menu
function Translator:genSettingsMenu()
local function genLanguagesItems(setting_name, default_checked_item)
local items_table = {}
for lang_key, lang_name in ffiUtil.orderedPairs(SUPPORTED_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,
radio = true,
callback = function()
G_reader_settings:saveSetting(setting_name, lang_key)
end,
})
end
return items_table
end
return {
text = _("Translation settings"),
sub_item_table = {
{
text_func = function()
local __, name = self:getDocumentLanguage()
return T(_("Translate from book language: %1"), name or _("N/A"))
end,
help_text = _([[
With books that specify their main language in their metadata (most EPUBs and FB2s), enabling this option will make this language the source language. Otherwise, auto-detection or the selected language will be used.
This is useful:
- For books in a foreign language, where consistent translation is needed and words in other languages are rare.
- For books in familiar languages, to get definitions for words from the translation service.]]),
enabled_func = function()
return self:getDocumentLanguage() ~= nil
end,
checked_func = function()
return G_reader_settings:isTrue("translator_from_doc_lang")
end,
callback = function()
G_reader_settings:flipTrue("translator_from_doc_lang")
end,
},
{
text = _("Auto-detect source language"),
help_text = _("This setting is best suited for foreign text found in books written in your native language."),
enabled_func = function()
return not (G_reader_settings:isTrue("translator_from_doc_lang") and self:getDocumentLanguage() ~= nil)
end,
checked_func = function()
return G_reader_settings:nilOrTrue("translator_from_auto_detect")
and not (G_reader_settings:isTrue("translator_from_doc_lang") and self:getDocumentLanguage() ~= nil)
end,
callback = function()
G_reader_settings:flipNilOrTrue("translator_from_auto_detect")
end,
},
{
text = _("Show romanizations"),
help_text = _("Displays source language text in Latin characters. This is useful for reading languages with non-Latin scripts."),
checked_func = function()
return G_reader_settings:isTrue("translator_with_romanizations")
end,
callback = function()
G_reader_settings:flipTrue("translator_with_romanizations")
end,
},
{
text_func = function()
local lang = G_reader_settings:readSetting("translator_from_language")
return T(_("Translate from: %1"), self:getLanguageName(lang, ""))
end,
help_text = _("If a specific source language is manually selected, it will be used everywhere, in all your books."),
enabled_func = function()
return not G_reader_settings:nilOrTrue("translator_from_auto_detect")
and not (G_reader_settings:isTrue("translator_from_doc_lang") and self:getDocumentLanguage() ~= nil)
end,
sub_item_table = genLanguagesItems("translator_from_language"),
separator = true,
},
{
text_func = function()
local lang = self:getTargetLanguage()
return T(_("Translate to: %1"), self:getLanguageName(lang, ""))
end,
sub_item_table = genLanguagesItems("translator_to_language", self:getTargetLanguage()),
},
},
}
end
function Translator:getDocumentLanguage()
local ui = require("apps/reader/readerui").instance
local lang = ui and ui.doc_props and ui.doc_props.language
if not lang then
return
end
lang = lang:match("(.*)-") or lang
lang = lang:lower()
local name, supported = self:getLanguageName(lang, "")
if supported then
return lang, name
end
-- ReaderTypography has a map of lang aliases (that we may meet
-- in book metadata) to their normalized lang tag: use it
local ReaderTypography = require("apps/reader/modules/readertypography")
lang = ReaderTypography.LANG_ALIAS_TO_LANG_TAG[lang]
if not lang then
return
end
name, supported = self:getLanguageName(lang, "")
if supported then
return lang, name
end
end
function Translator:getSourceLanguage()
if G_reader_settings:isTrue("translator_from_doc_lang") then
local lang = self:getDocumentLanguage()
if lang then
return lang
end
-- No document or metadata lang tag not supported:
-- fallback to other settings
end
if G_reader_settings:isFalse("translator_from_auto_detect") and
G_reader_settings:has("translator_from_language") then
return G_reader_settings:readSetting("translator_from_language")
end
return AUTODETECT_LANGUAGE -- "auto"
end
function Translator:getTargetLanguage()
local lang = G_reader_settings:readSetting("translator_to_language")
if not lang then
-- Fallback to the UI language the user has selected
lang = G_reader_settings:readSetting("language")
if lang and lang ~= "" then
-- convert "zh-CN" and "zh-TW" to "zh"
lang = lang:match("(.*)-") or lang
if lang == "C" then
lang="en"
end
lang = lang:lower()
end
end
return lang or "en"
end
--[[--
Returns decoded JSON table from translate server.
@string text
@string target_lang
@string source_lang
@treturn string result, or nil
--]]
function Translator:loadPage(text, target_lang, source_lang)
local socket = require("socket")
local socketutil = require("socketutil")
local url = require("socket.url")
local http = require("socket.http")
local ltn12 = require("ltn12")
local query = ""
self.trans_params.tl = target_lang
self.trans_params.sl = source_lang
for k,v in pairs(self.trans_params) do
if type(v) == "table" then
for _, v2 in ipairs(v) do
query = query .. k .. '=' .. v2 .. '&'
end
else
query = query .. k .. '=' .. v .. '&'
end
end
if G_reader_settings:isTrue("translator_with_romanizations") then
query = query .. "dt=rm&"
end
local parsed = url.parse(self:getTransServer())
parsed.path = self.trans_path
parsed.query = query .. "q=" .. url.escape(text)
-- HTTP request
local sink = {}
socketutil:set_timeout()
local request = {
url = url.build(parsed),
method = "GET",
sink = ltn12.sink.table(sink),
}
logger.dbg("Calling", request.url)
-- Skip first argument (body, goes to the sink)
local code, headers, status = socket.skip(1, 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
local content = table.concat(sink)
-- logger.dbg("translator content:", content)
local first_char = content:sub(1, 1)
if content ~= "" and (first_char == "{" or first_char == "[") then
-- Get nil instead of functions for 'null' by using JSON.decode.simple
-- (so the result can be fully serialized when used
-- with Trapper:dismissableRunInSubprocess())
local ok, result = pcall(JSON.decode, content, JSON.decode.simple)
if ok and result then
logger.dbg("translator json:", result)
return result
else
logger.warn("translator error:", result)
end
else
logger.warn("not JSON in translator response:", content)
end
end
-- The JSON result is a list of 9 to 15 items:
-- 1: translation
-- 2: all-translations
-- 3: original-language
-- 6: possible-translations
-- 7: confidence
-- 8: possible-mistakes
-- 9: language
-- 12: synonyms
-- 13: definitions
-- 14: examples
-- 15: see-also
-- Depending on the 'dt' parameters used, some may be null or absent.
-- See bottom of this file for some sample results.
--[[--
Tries to automatically detect language of `text`.
@string text
@treturn string lang (`"en"`, `"fr"`, `…`)
--]]
function Translator:detect(text)
local result = self:loadPage(text, "en", AUTODETECT_LANGUAGE)
if result and result[3] then
local src_lang = result[3]
logger.dbg("detected language:", src_lang)
return src_lang
else
return self.default_lang
end
end
--[[--
Translate text, returns translation as a single string.
@string text
@string target_lang[opt] (`"en"`, `"fr"`, `…`)
@string source_lang[opt="auto"] (`"en"`, `"fr"`, `…`) or `"auto"` to auto-detect source language
@treturn string translated text, or nil
--]]
function Translator:translate(text, target_lang, source_lang)
if not target_lang then
target_lang = self:getTargetLanguage()
end
if not source_lang then
source_lang = self:getSourceLanguage()
end
local result = self:loadPage(text, target_lang, source_lang)
if result and result[1] and type(result[1]) == "table" then
local translated = {}
for i, r in ipairs(result[1]) do
table.insert(translated, r[1])
end
return table.concat(translated, "")
end
return nil
end
--[[--
Show translated text in TextViewer, with alternate translations
@string text
@bool detailed_view "true" to show alternate translation, definition, additional buttons
@string source_lang[opt="auto"] (`"en"`, `"fr"`, `…`) or `"auto"` to auto-detect source language
@string target_lang[opt] (`"en"`, `"fr"`, `…`)
--]]
function Translator:showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, index)
if Device:hasClipboard() then
Device.input.setClipboardText(text)
end
local NetworkMgr = require("ui/network/manager")
if NetworkMgr:willRerunWhenOnline(function()
self:showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, index)
end) then
return
end
-- Wrap next function with Trapper to be able to interrupt
-- translation service query.
local Trapper = require("ui/trapper")
Trapper:wrap(function()
self:_showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, index)
end)
end
function Translator:_showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, index)
if not target_lang then
target_lang = self:getTargetLanguage()
end
if not source_lang then
source_lang = self:getSourceLanguage()
end
local Trapper = require("ui/trapper")
local completed, result = Trapper:dismissableRunInSubprocess(function()
return self:loadPage(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[3] then
source_lang = result[3]
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[1]) 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[1]) do
if detailed_view then
local s = type(r[2]) == "string" and r[2] or ""
table.insert(source, s)
if type(r[4]) == "string" then
table.insert(romanized, r[4])
end
end
local t = type(r[1]) == "string" and r[1] 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
if detailed_view then
if is_result_valid(result[6]) then
-- Alternative translations:
table.insert(output, "")
table.insert(output, _("Alternate translations:"))
for i, r in ipairs(result[6]) do
if type(r[3]) == "table" then
local s = type(r[1]) == "string" and r[1]:gsub("\n", "") or ""
table.insert(output, "" .. s)
for j, rt in ipairs(r[3]) do
-- Use number in solid black circle symbol (U+2776...277F)
local symbol = util.unicodeCodepointToUtf8(10101 + (j < 10 and j or 10))
local t = type(rt[1]) == "string" and rt[1]:gsub("\n", "") or ""
table.insert(output, symbol .. " " .. t)
end
end
end
end
if is_result_valid(result[13]) then
-- Definition(word)
table.insert(output, "")
table.insert(output, _("Definition:"))
for i, r in ipairs(result[13]) do
if r[2] and type(r[2]) == "table" then
local symbol = util.unicodeCodepointToUtf8(10101 + (i < 10 and i or 10))
table.insert(output, symbol.. " ".. r[1])
for j, res in ipairs(r[2]) do
table.insert(output, "\t".. res[1])
end
end
end
end
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
return Translator
-- Sample JSON results:
--
-- Multiple words result:
-- {
-- [1] = {
-- [1] = {
-- [1] = "I know you did not destroy your King's house, because then you had none. ",
-- [2] = "Ich weiß, dass ihr nicht eures Königs Haus zerstört habt, denn damals hattet ihr ja keinen.",
-- [5] = 3,
-- ["n"] = 5
-- },
-- [2] = {
-- [1] = "But you can not deny that you destroyed a royal palace. ",
-- [2] = "Aber ihr könnt nicht leugnen, dass ihr einen Königspalast zerstört habt.",
-- [5] = 3,
-- ["n"] = 5
-- },
-- [3] = {
-- [1] = "If the king is dead, then the kingdom remains, just as a ship remains, whose helmsman has fallen",
-- [2] = "Ist der König tot, so bleibt doch das Reich bestehen, ebenso wie ein Schiff bleibt, dessen Steuermann gefallen ist",
-- [5] = 3,
-- ["n"] = 5
-- }
-- },
-- [3] = "de",
-- [6] = {
-- [1] = {
-- [1] = "Ich weiß, dass ihr nicht eures Königs Haus zerstört habt, denn damals hattet ihr ja keinen.",
-- [3] = {
-- [1] = {
-- [1] = "I know you did not destroy your King's house, because then you had none.",
-- [2] = 0,
-- [3] = true,
-- [4] = false
-- },
-- [2] = {
-- [1] = "I know that you have not destroyed your king house, because at that time you had not any.",
-- [2] = 0,
-- [3] = true,
-- [4] = false
-- }
-- },
-- [4] = {
-- [1] = {
-- [1] = 0,
-- [2] = 91
-- }
-- },
-- [5] = "Ich weiß, dass ihr nicht eures Königs Haus zerstört habt, denn damals hattet ihr ja keinen.",
-- [6] = 0,
-- [7] = 0
-- },
-- [2] = {
-- [1] = "Aber ihr könnt nicht leugnen, dass ihr einen Königspalast zerstört habt.",
-- [3] = {
-- [1] = {
-- [1] = "But you can not deny that you destroyed a royal palace.",
-- [2] = 0,
-- [3] = true,
-- [4] = false
-- },
-- [2] = {
-- [1] = "But you can not deny that you have destroyed a royal palace.",
-- [2] = 0,
-- [3] = true,
-- [4] = false
-- }
-- },
-- [4] = {
-- [1] = {
-- [1] = 0,
-- [2] = 72
-- }
-- },
-- [5] = "Aber ihr könnt nicht leugnen, dass ihr einen Königspalast zerstört habt.",
-- [6] = 0,
-- [7] = 0
-- },
-- [3] = {
-- [1] = "Ist der König tot, so bleibt doch das Reich bestehen, ebenso wie ein Schiff bleibt, dessen Steuermann gefallen ist",
-- [3] = {
-- [1] = {
-- [1] = "If the king is dead, then the kingdom remains, just as a ship remains, whose helmsman has fallen",
-- [2] = 0,
-- [3] = true,
-- [4] = false
-- },
-- [2] = {
-- [1] = "yet the king dead, remains the kingdom stand remains as a ship the helmsman has fallen",
-- [2] = 0,
-- [3] = true,
-- [4] = false
-- }
-- },
-- [4] = {
-- [1] = {
-- [1] = 0,
-- [2] = 114
-- }
-- },
-- [5] = "Ist der König tot, so bleibt doch das Reich bestehen, ebenso wie ein Schiff bleibt, dessen Steuermann gefallen ist",
-- [6] = 0,
-- [7] = 0
-- }
-- },
-- [7] = 1,
-- [9] = {
-- [1] = {
-- [1] = "de"
-- },
-- [3] = {
-- [1] = 1
-- },
-- [4] = {
-- [1] = "de"
-- }
-- },
-- ["n"] = 9
-- }
--
-- Single word result with all dt= enabled:
-- {
-- [1] = {
-- [1] = {
-- [1] = "fork",
-- [2] = "fourchette",
-- [5] = 0,
-- ["n"] = 5
-- }
-- },
-- [2] = {
-- [1] = {
-- [1] = "noun",
-- [2] = {
-- [1] = "fork"
-- },
-- [3] = {
-- [1] = {
-- [1] = "fork",
-- [2] = {
-- [1] = "fourche",
-- [2] = "fourchette",
-- [3] = "embranchement",
-- [4] = "chariot",
-- [5] = "chariot à fourche"
-- },
-- [4] = 0.21967085
-- }
-- },
-- [4] = "fourchette",
-- [5] = 1
-- }
-- },
-- [3] = "fr",
-- [6] = {
-- [1] = {
-- [1] = "fourchette",
-- [3] = {
-- [1] = {
-- [1] = "fork",
-- [2] = 1000,
-- [3] = true,
-- [4] = false
-- },
-- [2] = {
-- [1] = "band",
-- [2] = 0,
-- [3] = true,
-- [4] = false
-- },
-- [3] = {
-- [1] = "bracket",
-- [2] = 0,
-- [3] = true,
-- [4] = false
-- },
-- [4] = {
-- [1] = "range",
-- [2] = 0,
-- [3] = true,
-- [4] = false
-- }
-- },
-- [4] = {
-- [1] = {
-- [1] = 0,
-- [2] = 10
-- }
-- },
-- [5] = "fourchette",
-- [6] = 0,
-- [7] = 1
-- }
-- },
-- [7] = 1,
-- [9] = {
-- [1] = {
-- [1] = "fr"
-- },
-- [3] = {
-- [1] = 1
-- },
-- [4] = {
-- [1] = "fr"
-- }
-- },
-- [12] = {
-- [1] = {
-- [1] = "noun",
-- [2] = {
-- [1] = {
-- [1] = {
-- [1] = "ramification",
-- [2] = "enfourchure"
-- },
-- [2] = ""
-- },
-- [2] = {
-- [1] = {
-- [1] = "échéance",
-- [2] = "bande"
-- },
-- [2] = ""
-- },
-- [3] = {
-- [1] = {
-- [1] = "ramification",
-- [2] = "jambe"
-- },
-- [2] = ""
-- },
-- [4] = {
-- [1] = {
-- [1] = "bifurcation"
-- },
-- [2] = ""
-- },
-- [5] = {
-- [1] = {
-- [1] = "fourche",
-- [2] = "bifurcation",
-- [3] = "entrejambe"
-- },
-- [2] = ""
-- },
-- [6] = {
-- [1] = {
-- [1] = "fourche",
-- [2] = "bifurcation"
-- },
-- [2] = ""
-- }
-- },
-- [3] = "fourchette"
-- }
-- },
-- [13] = {
-- [1] = {
-- [1] = "noun",
-- [2] = {
-- [1] = {
-- [1] = "Ustensile de table.",
-- [2] = "12518.0",
-- [3] = "Des fourchettes, des couteaux et des cuillères ."
-- },
-- [2] = {
-- [1] = "Ecart entre deux valeurs.",
-- [2] = "12518.1",
-- [3] = "La fourchette des prix ."
-- }
-- },
-- [3] = "fourchette"
-- }
-- },
-- [14] = {
-- [1] = {
-- [1] = {
-- [1] = "La <b>fourchette</b> des prix .",
-- [5] = 3,
-- [6] = "12518.1",
-- ["n"] = 6
-- }
-- }
-- },
-- ["n"] = 14
-- }