2017-04-12 16:55:22 +00:00
--[[--
This module translates text using Google Translate .
2014-08-20 06:41:45 +00:00
2018-12-16 17:02:38 +00:00
< https : // translate.googleapis . com / translate_a / single ? client = gtx & sl = auto & tl = fr & dt = t & q = alea % 20 jacta % 20 est >
2014-08-20 06:41:45 +00:00
--]]
2018-12-16 17:02:38 +00:00
-- 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
2021-06-21 16:50:49 +00:00
local Device = require ( " device " )
2018-12-17 13:15:13 +00:00
local InfoMessage = require ( " ui/widget/infomessage " )
local TextViewer = require ( " ui/widget/textviewer " )
local UIManager = require ( " ui/uimanager " )
2017-04-12 16:55:22 +00:00
local JSON = require ( " json " )
2018-12-17 13:15:13 +00:00
local Screen = require ( " device " ) . screen
local ffiutil = require ( " ffi/util " )
2017-04-12 16:55:22 +00:00
local logger = require ( " logger " )
2018-12-17 13:15:13 +00:00
local util = require ( " util " )
2020-09-15 18:39:32 +00:00
local T = ffiutil.template
2018-12-17 13:15:13 +00:00
local _ = require ( " gettext " )
-- From https://cloud.google.com/translate/docs/languages
2023-05-17 04:34:37 +00:00
-- 20230514: 132 supported languages
2018-12-17 13:15:13 +00:00
local AUTODETECT_LANGUAGE = " auto "
local SUPPORTED_LANGUAGES = {
2019-08-24 07:25:38 +00:00
-- @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
2018-12-17 13:15:13 +00:00
af = _ ( " Afrikaans " ) ,
sq = _ ( " Albanian " ) ,
am = _ ( " Amharic " ) ,
ar = _ ( " Arabic " ) ,
hy = _ ( " Armenian " ) ,
2023-05-17 04:34:37 +00:00
as = _ ( " Assamese " ) ,
ay = _ ( " Aymara " ) ,
2018-12-17 13:15:13 +00:00
az = _ ( " Azerbaijani " ) ,
2023-05-17 04:34:37 +00:00
bm = _ ( " Bambara " ) ,
2018-12-17 13:15:13 +00:00
eu = _ ( " Basque " ) ,
be = _ ( " Belarusian " ) ,
bn = _ ( " Bengali " ) ,
2023-05-17 04:34:37 +00:00
bho = _ ( " Bhojpuri " ) ,
2018-12-17 13:15:13 +00:00
bs = _ ( " Bosnian " ) ,
bg = _ ( " Bulgarian " ) ,
ca = _ ( " Catalan " ) ,
ceb = _ ( " Cebuano " ) ,
zh = _ ( " Chinese (Simplified) " ) , -- "Simplified Chinese may be specified either by zh-CN or zh"
zh_TW = _ ( " Chinese (Traditional) " ) , -- converted to "zh-TW" below
co = _ ( " Corsican " ) ,
hr = _ ( " Croatian " ) ,
cs = _ ( " Czech " ) ,
da = _ ( " Danish " ) ,
2023-05-17 04:34:37 +00:00
dv = _ ( " Dhivehi " ) ,
doi = _ ( " Dogri " ) ,
2018-12-17 13:15:13 +00:00
nl = _ ( " Dutch " ) ,
en = _ ( " English " ) ,
eo = _ ( " Esperanto " ) ,
et = _ ( " Estonian " ) ,
2023-05-17 04:34:37 +00:00
ee = _ ( " Ewe " ) ,
fil = _ ( " Filipino (Tagalog) " ) ,
2018-12-17 13:15:13 +00:00
fi = _ ( " Finnish " ) ,
fr = _ ( " French " ) ,
fy = _ ( " Frisian " ) ,
gl = _ ( " Galician " ) ,
ka = _ ( " Georgian " ) ,
de = _ ( " German " ) ,
el = _ ( " Greek " ) ,
2023-05-17 04:34:37 +00:00
gn = _ ( " Guarani " ) ,
2019-08-24 07:25:38 +00:00
-- @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
2018-12-17 13:15:13 +00:00
gu = _ ( " Gujarati " ) ,
ht = _ ( " Haitian Creole " ) ,
ha = _ ( " Hausa " ) ,
haw = _ ( " Hawaiian " ) ,
he = _ ( " Hebrew " ) , -- "Hebrew may be specified either by he or iw"
hi = _ ( " Hindi " ) ,
hmn = _ ( " Hmong " ) ,
hu = _ ( " Hungarian " ) ,
is = _ ( " Icelandic " ) ,
ig = _ ( " Igbo " ) ,
2023-05-17 04:34:37 +00:00
ilo = _ ( " Ilocano " ) ,
2018-12-17 13:15:13 +00:00
id = _ ( " Indonesian " ) ,
ga = _ ( " Irish " ) ,
it = _ ( " Italian " ) ,
ja = _ ( " Japanese " ) ,
jw = _ ( " Javanese " ) ,
2019-08-24 07:25:38 +00:00
-- @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
2018-12-17 13:15:13 +00:00
kn = _ ( " Kannada " ) ,
kk = _ ( " Kazakh " ) ,
km = _ ( " Khmer " ) ,
2023-05-17 04:34:37 +00:00
rw = _ ( " Kinyarwanda " ) ,
gom = _ ( " Konkani " ) ,
2018-12-17 13:15:13 +00:00
ko = _ ( " Korean " ) ,
2023-05-17 04:34:37 +00:00
kri = _ ( " Krio " ) ,
2018-12-17 13:15:13 +00:00
ku = _ ( " Kurdish " ) ,
2023-05-17 04:34:37 +00:00
ckb = _ ( " Kurdish (Sorani) " ) ,
2018-12-17 13:15:13 +00:00
ky = _ ( " Kyrgyz " ) ,
lo = _ ( " Lao " ) ,
la = _ ( " Latin " ) ,
lv = _ ( " Latvian " ) ,
2023-05-17 04:34:37 +00:00
ln = _ ( " Lingala " ) ,
2018-12-17 13:15:13 +00:00
lt = _ ( " Lithuanian " ) ,
2023-05-17 04:34:37 +00:00
lg = _ ( " Luganda " ) ,
2018-12-17 13:15:13 +00:00
lb = _ ( " Luxembourgish " ) ,
mk = _ ( " Macedonian " ) ,
2023-05-17 04:34:37 +00:00
mai = _ ( " Maithili " ) ,
2018-12-17 13:15:13 +00:00
mg = _ ( " Malagasy " ) ,
ms = _ ( " Malay " ) ,
ml = _ ( " Malayalam " ) ,
mt = _ ( " Maltese " ) ,
mi = _ ( " Maori " ) ,
mr = _ ( " Marathi " ) ,
2023-05-17 04:34:37 +00:00
lus = _ ( " Mizo " ) ,
2018-12-17 13:15:13 +00:00
mn = _ ( " Mongolian " ) ,
my = _ ( " Myanmar (Burmese) " ) ,
ne = _ ( " Nepali " ) ,
no = _ ( " Norwegian " ) ,
ny = _ ( " Nyanja (Chichewa) " ) ,
2023-05-17 04:34:37 +00:00
[ " or " ] = _ ( " Odia (Oriya) " ) ,
om = _ ( " Oromo " ) ,
2018-12-17 13:15:13 +00:00
ps = _ ( " Pashto " ) ,
fa = _ ( " Persian " ) ,
pl = _ ( " Polish " ) ,
pt = _ ( " Portuguese " ) ,
pa = _ ( " Punjabi " ) ,
2023-05-17 04:34:37 +00:00
qu = _ ( " Quechua " ) ,
2018-12-17 13:15:13 +00:00
ro = _ ( " Romanian " ) ,
ru = _ ( " Russian " ) ,
sm = _ ( " Samoan " ) ,
2023-05-17 04:34:37 +00:00
sa = _ ( " Sanskrit " ) ,
2018-12-17 13:15:13 +00:00
gd = _ ( " Scots Gaelic " ) ,
2023-05-17 04:34:37 +00:00
nso = _ ( " Sepedi " ) ,
2018-12-17 13:15:13 +00:00
sr = _ ( " Serbian " ) ,
st = _ ( " Sesotho " ) ,
sn = _ ( " Shona " ) ,
sd = _ ( " Sindhi " ) ,
si = _ ( " Sinhala (Sinhalese) " ) ,
sk = _ ( " Slovak " ) ,
sl = _ ( " Slovenian " ) ,
so = _ ( " Somali " ) ,
es = _ ( " Spanish " ) ,
su = _ ( " Sundanese " ) ,
sw = _ ( " Swahili " ) ,
sv = _ ( " Swedish " ) ,
tl = _ ( " Tagalog (Filipino) " ) ,
tg = _ ( " Tajik " ) ,
ta = _ ( " Tamil " ) ,
2023-05-17 04:34:37 +00:00
tt = _ ( " Tatar " ) ,
2018-12-17 13:15:13 +00:00
te = _ ( " Telugu " ) ,
th = _ ( " Thai " ) ,
2023-05-17 04:34:37 +00:00
ti = _ ( " Tigrinya " ) ,
ts = _ ( " Tsonga " ) ,
2018-12-17 13:15:13 +00:00
tr = _ ( " Turkish " ) ,
2023-05-17 04:34:37 +00:00
tk = _ ( " Turkmen " ) ,
ak = _ ( " Twi (Akan) " ) ,
2018-12-17 13:15:13 +00:00
uk = _ ( " Ukrainian " ) ,
ur = _ ( " Urdu " ) ,
2023-05-17 04:34:37 +00:00
ug = _ ( " Uyghur " ) ,
2018-12-17 13:15:13 +00:00
uz = _ ( " Uzbek " ) ,
vi = _ ( " Vietnamese " ) ,
cy = _ ( " Welsh " ) ,
2019-08-24 07:25:38 +00:00
-- @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
2018-12-17 13:15:13 +00:00
xh = _ ( " Xhosa " ) ,
yi = _ ( " Yiddish " ) ,
yo = _ ( " Yoruba " ) ,
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 "
2017-04-12 16:55:22 +00:00
2014-08-20 06:41:45 +00:00
local Translator = {
2018-12-16 17:02:38 +00:00
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", -- ?
2020-06-22 13:17:14 +00:00
" md " , -- definitions of source text
2018-12-16 17:02:38 +00:00
-- "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 " ,
2014-08-20 06:41:45 +00:00
}
function Translator : getTransServer ( )
return G_reader_settings : readSetting ( " trans_server " ) or self.trans_servers [ 1 ]
end
2018-12-17 13:15:13 +00:00
function Translator : getLanguageName ( lang , default_string )
if SUPPORTED_LANGUAGES [ lang ] then
2021-08-17 22:42:14 +00:00
return SUPPORTED_LANGUAGES [ lang ] , true
2018-12-17 13:15:13 +00:00
elseif ALT_LANGUAGE_CODES [ lang ] then
2021-08-17 22:42:14 +00:00
return SUPPORTED_LANGUAGES [ ALT_LANGUAGE_CODES [ lang ] ] , true
2018-12-17 13:15:13 +00:00
elseif lang then
2021-08-17 22:42:14 +00:00
return lang : upper ( ) , false
2018-12-17 13:15:13 +00:00
end
2021-08-17 22:42:14 +00:00
return default_string , false
2018-12-17 13:15:13 +00:00
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 ( )
2024-06-21 17:15:03 +00:00
return lang_key == ( G_reader_settings : readSetting ( setting_name ) or default_checked_item )
2018-12-17 13:15:13 +00:00
end ,
callback = function ( )
G_reader_settings : saveSetting ( setting_name , lang_key )
end ,
} )
end
return items_table
end
return {
text = _ ( " Translation settings " ) ,
sub_item_table = {
2024-06-21 17:15:03 +00:00
{
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 ,
} ,
2018-12-17 13:15:13 +00:00
{
text = _ ( " Auto-detect source language " ) ,
2021-08-17 22:42:14 +00:00
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 ,
2018-12-17 13:15:13 +00:00
checked_func = function ( )
return G_reader_settings : nilOrTrue ( " translator_from_auto_detect " )
2024-06-21 17:15:03 +00:00
and not ( G_reader_settings : isTrue ( " translator_from_doc_lang " ) and self : getDocumentLanguage ( ) ~= nil )
2018-12-17 13:15:13 +00:00
end ,
callback = function ( )
G_reader_settings : flipNilOrTrue ( " translator_from_auto_detect " )
end ,
} ,
{
text_func = function ( )
local lang = G_reader_settings : readSetting ( " translator_from_language " )
return T ( _ ( " Translate from: %1 " ) , self : getLanguageName ( lang , " " ) )
end ,
2021-08-17 22:42:14 +00:00
help_text = _ ( " If a specific source language is manually selected, it will be used everywhere, in all your books. " ) ,
2018-12-17 13:15:13 +00:00
enabled_func = function ( )
return not G_reader_settings : nilOrTrue ( " translator_from_auto_detect " )
2021-08-17 22:42:14 +00:00
and not ( G_reader_settings : isTrue ( " translator_from_doc_lang " ) and self : getDocumentLanguage ( ) ~= nil )
2018-12-17 13:15:13 +00:00
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
2021-08-17 22:42:14 +00:00
function Translator : getDocumentLanguage ( )
2023-05-17 04:34:37 +00:00
local ui = require ( " apps/reader/readerui " ) . instance
2023-08-30 04:53:59 +00:00
local lang = ui and ui.doc_props and ui.doc_props . language
if not lang then
2021-08-17 22:42:14 +00:00
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
2018-12-17 13:15:13 +00:00
function Translator : getSourceLanguage ( )
2021-08-17 22:42:14 +00:00
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
2018-12-17 13:15:13 +00:00
if G_reader_settings : isFalse ( " translator_from_auto_detect " ) and
2021-03-06 21:44:18 +00:00
G_reader_settings : has ( " translator_from_language " ) then
2018-12-17 13:15:13 +00:00
return G_reader_settings : readSetting ( " translator_from_language " )
end
return AUTODETECT_LANGUAGE -- "auto"
end
2018-12-16 17:02:38 +00:00
function Translator : getTargetLanguage ( )
2018-12-17 13:15:13 +00:00
local lang = G_reader_settings : readSetting ( " translator_to_language " )
2018-12-16 17:02:38 +00:00
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
2017-04-12 16:55:22 +00:00
--[[--
Returns decoded JSON table from translate server .
2018-12-16 17:02:38 +00:00
@ string text
2017-04-12 16:55:22 +00:00
@ string target_lang
@ string source_lang
@ treturn string result , or nil
2014-08-20 06:41:45 +00:00
--]]
2018-12-16 17:02:38 +00:00
function Translator : loadPage ( text , target_lang , source_lang )
2021-03-15 00:25:10 +00:00
local socket = require ( " socket " )
local socketutil = require ( " socketutil " )
local url = require ( " socket.url " )
local http = require ( " socket.http " )
local ltn12 = require ( " ltn12 " )
2014-09-25 14:19:36 +00:00
2014-08-20 06:41:45 +00:00
local query = " "
self.trans_params . tl = target_lang
self.trans_params . sl = source_lang
for k , v in pairs ( self.trans_params ) do
2018-12-16 17:02:38 +00:00
if type ( v ) == " table " then
for _ , v2 in ipairs ( v ) do
query = query .. k .. ' = ' .. v2 .. ' & '
end
else
query = query .. k .. ' = ' .. v .. ' & '
end
2014-08-20 06:41:45 +00:00
end
local parsed = url.parse ( self : getTransServer ( ) )
parsed.path = self.trans_path
2018-12-16 17:02:38 +00:00
parsed.query = query .. " q= " .. url.escape ( text )
2014-08-20 06:41:45 +00:00
-- HTTP request
2021-03-15 00:25:10 +00:00
local sink = { }
socketutil : set_timeout ( )
local request = {
url = url.build ( parsed ) ,
method = " GET " ,
sink = ltn12.sink . table ( sink ) ,
}
2018-12-16 17:02:38 +00:00
logger.dbg ( " Calling " , request.url )
2021-03-15 00:25:10 +00:00
-- Skip first argument (body, goes to the sink)
local code , headers , status = socket.skip ( 1 , http.request ( request ) )
socketutil : reset_timeout ( )
2014-08-20 06:41:45 +00:00
-- raise error message when network is unavailable
if headers == nil then
2022-09-16 22:08:00 +00:00
error ( status or code or " network unreachable " )
2014-08-20 06:41:45 +00:00
end
2021-03-15 00:25:10 +00:00
if code ~= 200 then
2022-09-16 22:08:00 +00:00
logger.warn ( " translator HTTP status not okay: " , status or code or " network unreachable " )
logger.dbg ( " Response headers: " , headers )
2015-08-23 18:39:08 +00:00
return
end
2014-08-20 06:41:45 +00:00
local content = table.concat ( sink )
2018-12-16 17:02:38 +00:00
-- 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 )
2014-08-20 06:41:45 +00:00
if ok and result then
2018-12-16 17:02:38 +00:00
logger.dbg ( " translator json: " , result )
2014-08-20 06:41:45 +00:00
return result
else
2016-12-29 08:10:38 +00:00
logger.warn ( " translator error: " , result )
2014-08-20 06:41:45 +00:00
end
2015-08-23 18:39:08 +00:00
else
2016-12-29 08:10:38 +00:00
logger.warn ( " not JSON in translator response: " , content )
2014-08-20 06:41:45 +00:00
end
end
2018-12-16 17:02:38 +00:00
-- 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.
2014-08-20 06:41:45 +00:00
2017-04-12 16:55:22 +00:00
--[[--
Tries to automatically detect language of ` text ` .
@ string text
@ treturn string lang ( ` " en " ` , ` " fr " ` , ` … ` )
--]]
2014-08-20 06:41:45 +00:00
function Translator : detect ( text )
2018-12-17 13:15:13 +00:00
local result = self : loadPage ( text , " en " , AUTODETECT_LANGUAGE )
2018-12-16 17:02:38 +00:00
if result and result [ 3 ] then
local src_lang = result [ 3 ]
2016-12-29 08:10:38 +00:00
logger.dbg ( " detected language: " , src_lang )
2014-08-20 06:41:45 +00:00
return src_lang
2014-08-20 07:46:43 +00:00
else
return self.default_lang
2014-08-20 06:41:45 +00:00
end
end
2018-12-16 17:02:38 +00:00
--[[--
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
2018-12-17 13:15:13 +00:00
source_lang = self : getSourceLanguage ( )
2018-12-16 17:02:38 +00:00
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
2023-05-17 04:34:37 +00:00
@ bool detailed_view " true " to show alternate translation , definition , additional buttons
2018-12-16 17:02:38 +00:00
@ string source_lang [ opt = " auto " ] ( ` " en " ` , ` " fr " ` , ` … ` ) or ` " auto " ` to auto - detect source language
2023-05-17 04:34:37 +00:00
@ string target_lang [ opt ] ( ` " en " ` , ` " fr " ` , ` … ` )
2018-12-16 17:02:38 +00:00
--]]
2024-05-03 06:08:57 +00:00
function Translator : showTranslation ( text , detailed_view , source_lang , target_lang , from_highlight , index )
2021-06-21 16:50:49 +00:00
if Device : hasClipboard ( ) then
Device.input . setClipboardText ( text )
end
2018-12-16 17:02:38 +00:00
local NetworkMgr = require ( " ui/network/manager " )
2023-05-17 04:34:37 +00:00
if NetworkMgr : willRerunWhenOnline ( function ( )
2024-05-03 06:08:57 +00:00
self : showTranslation ( text , detailed_view , source_lang , target_lang , from_highlight , index )
2023-05-17 04:34:37 +00:00
end ) then
2018-12-16 17:02:38 +00:00
return
end
Various Wi-Fi QoL improvements (#6424)
* Revamped most actions that require an internet connection to a new/fixed backend that allows forwarding the initial action and running it automatically once connected. (i.e., it'll allow you to set "Action when Wi-Fi is off" to "turn_on", and whatch stuff connect and do what you wanted automatically without having to re-click anywhere instead of showing you a Wi-Fi prompt and then not doing anything without any other feedback).
* Speaking of, fixed the "turn_on" beforeWifi action to, well, actually work. It's no longer marked as experimental.
* Consistently use "Wi-Fi" everywhere.
* On Kobo/Cervantes/Sony, implemented a "Kill Wi-Fi connection when inactive" system that will automatically disconnect from Wi-Fi after sustained *network* inactivity (i.e., you can keep reading, it'll eventually turn off on its own). This should be smart and flexible enough not to murder Wi-Fi while you need it, while still not keeping it uselessly on and murdering your battery.
(i.e., enable that + turn Wi-Fi on when off and enjoy never having to bother about Wi-Fi ever again).
* Made sending `NetworkConnected` / `NetworkDisconnected` events consistent (they were only being sent... sometimes, which made relying on 'em somewhat problematic).
* restoreWifiAsync is now only run when really needed (i.e., we no longer stomp on an existing working connection just for the hell of it).
* We no longer attempt to kill a bogus non-existent Wi-Fi connection when going to suspend, we only do it when it's actually needed.
* Every method of enabling Wi-Fi will now properly tear down Wi-Fi on failure, instead of leaving it in an undefined state.
* Fixed an issue in the fancy crash screen on Kobo/reMarkable that could sometime lead to the log excerpt being missing.
* Worked-around a number of sneaky issues related to low-level Wi-Fi/DHCP/DNS handling on Kobo (see the lengthy comments [below](https://github.com/koreader/koreader/pull/6424#issuecomment-663881059) for details). Fix #6421
Incidentally, this should also fix the inconsistencies experienced re: Wi-Fi behavior in Nickel when toggling between KOReader and Nickel (use NM/KFMon, and run a current FW for best results).
* For developers, this involves various cleanups around NetworkMgr and NetworkListener. Documentation is in-line, above the concerned functions.
2020-07-27 01:39:06 +00:00
2018-12-16 17:02:38 +00:00
-- Wrap next function with Trapper to be able to interrupt
-- translation service query.
local Trapper = require ( " ui/trapper " )
Trapper : wrap ( function ( )
2024-05-03 06:08:57 +00:00
self : _showTranslation ( text , detailed_view , source_lang , target_lang , from_highlight , index )
2018-12-16 17:02:38 +00:00
end )
end
2024-05-03 06:08:57 +00:00
function Translator : _showTranslation ( text , detailed_view , source_lang , target_lang , from_highlight , index )
2018-12-16 17:02:38 +00:00
if not target_lang then
target_lang = self : getTargetLanguage ( )
end
if not source_lang then
2018-12-17 13:15:13 +00:00
source_lang = self : getSourceLanguage ( )
2018-12-16 17:02:38 +00:00
end
2018-12-17 13:15:13 +00:00
local Trapper = require ( " ui/trapper " )
2018-12-16 17:02:38 +00:00
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 = { }
2022-01-16 19:54:08 +00:00
local text_main = " "
2018-12-16 17:02:38 +00:00
2023-05-17 04:34:37 +00:00
local function is_result_valid ( res )
return res and type ( res ) == " table " and # res > 0
end
2018-12-16 17:02:38 +00:00
-- For both main and alternate translations, we may get multiple slices
-- of the original text and its translations.
2023-05-17 04:34:37 +00:00
if is_result_valid ( result [ 1 ] ) then
2018-12-16 17:02:38 +00:00
-- Main translation: we can make a single string from the multiple parts
-- for easier quick reading
local source = { }
local translated = { }
for i , r in ipairs ( result [ 1 ] ) do
2023-05-17 04:34:37 +00:00
if detailed_view then
local s = type ( r [ 2 ] ) == " string " and r [ 2 ] or " "
table.insert ( source , s )
end
2018-12-16 17:02:38 +00:00
local t = type ( r [ 1 ] ) == " string " and r [ 1 ] or " "
table.insert ( translated , t )
end
2023-05-17 04:34:37 +00:00
text_main = table.concat ( translated , " " )
if detailed_view then
text_main = " ● " .. text_main
table.insert ( output , " ▣ " .. table.concat ( source , " " ) )
end
2022-01-16 19:54:08 +00:00
table.insert ( output , text_main )
2018-12-16 17:02:38 +00:00
end
2023-05-17 04:34:37 +00:00
if detailed_view then
if is_result_valid ( result [ 6 ] ) then
-- Alternative translations:
2023-08-10 14:48:39 +00:00
table.insert ( output , " " )
table.insert ( output , _ ( " Alternate translations: " ) )
2023-05-17 04:34:37 +00:00
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
2018-12-16 17:02:38 +00:00
end
end
2020-06-22 13:17:14 +00:00
end
2023-05-17 04:34:37 +00:00
if is_result_valid ( result [ 13 ] ) then
-- Definition(word)
2023-08-10 14:48:39 +00:00
table.insert ( output , " " )
table.insert ( output , _ ( " Definition: " ) )
2023-05-17 04:34:37 +00:00
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
2020-06-22 13:17:14 +00:00
end
end
2018-12-16 17:02:38 +00:00
end
end
-- table.insert(output, require("dump")(result)) -- for debugging
2022-01-16 19:54:08 +00:00
local text_all = table.concat ( output , " \n " )
2023-05-17 04:34:37 +00:00
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 ,
2022-09-13 21:09:49 +00:00
{
2023-05-17 04:34:37 +00:00
{
text = _ ( " Save main translation to note " ) ,
callback = function ( )
UIManager : close ( textviewer )
UIManager : close ( ui.highlight . highlight_dialog )
ui.highlight . highlight_dialog = nil
2024-05-03 06:08:57 +00:00
if index then
ui.highlight : editHighlight ( index , false , text_main )
2023-05-17 04:34:37 +00:00
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
2024-05-03 06:08:57 +00:00
if index then
ui.highlight : editHighlight ( index , false , text_all )
2023-05-17 04:34:37 +00:00
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 ,
2022-09-13 21:09:49 +00:00
{
2023-05-17 04:34:37 +00:00
{
text = _ ( " Copy main translation " ) ,
callback = function ( )
Device.input . setClipboardText ( text_main )
end ,
} ,
{
text = _ ( " Copy all " ) ,
callback = function ( )
Device.input . setClipboardText ( text_all )
end ,
} ,
}
)
end
2022-09-13 21:09:49 +00:00
end
2023-05-17 04:34:37 +00:00
2022-01-16 19:54:08 +00:00
textviewer = TextViewer : new {
2018-12-17 13:15:13 +00:00
title = T ( _ ( " Translation from %1 " ) , self : getLanguageName ( source_lang , " ? " ) ) ,
2022-01-16 19:54:08 +00:00
title_multilines = true ,
2018-12-17 13:15:13 +00:00
-- Showing the translation target language in this title may make
-- it quite long and wrapped, taking valuable vertical spacing
2022-01-16 19:54:08 +00:00
text = text_all ,
2023-12-14 05:50:54 +00:00
text_type = " lookup " ,
2023-05-17 04:34:37 +00:00
height = height ,
2022-09-13 21:09:49 +00:00
add_default_buttons = true ,
2022-01-16 19:54:08 +00:00
buttons_table = buttons_table ,
2023-05-17 04:34:37 +00:00
close_callback = close_callback ,
2022-01-16 19:54:08 +00:00
}
UIManager : show ( textviewer )
2018-12-16 17:02:38 +00:00
end
2014-08-20 06:41:45 +00:00
return Translator
2018-12-16 17:02:38 +00:00
-- 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
-- }