diff --git a/frontend/apps/filemanager/filemanagerbookinfo.lua b/frontend/apps/filemanager/filemanagerbookinfo.lua index 7ddf688f0..85b97d5ce 100644 --- a/frontend/apps/filemanager/filemanagerbookinfo.lua +++ b/frontend/apps/filemanager/filemanagerbookinfo.lua @@ -374,8 +374,8 @@ end function BookInfo:getCurrentPageLineWordCounts() local lines_nb, words_nb = 0, 0 if self.ui.rolling then - local res = self.ui.document._document:getTextFromPositions(0, 0, Screen:getWidth(), Screen:getHeight(), - false, false) -- do not highlight + local res = self.ui.document:getTextFromPositions({x = 0, y = 0}, + {x = Screen:getWidth(), y = Screen:getHeight()}, true) -- do not highlight if res then lines_nb = #self.ui.document:getScreenBoxesFromPositions(res.pos0, res.pos1, true) for word in util.gsplit(res.text, "[%s%p]+", false) do diff --git a/frontend/apps/reader/modules/readerdictionary.lua b/frontend/apps/reader/modules/readerdictionary.lua index 1b7f6a542..54179cafb 100644 --- a/frontend/apps/reader/modules/readerdictionary.lua +++ b/frontend/apps/reader/modules/readerdictionary.lua @@ -206,6 +206,9 @@ function ReaderDictionary:updateSdcvDictNamesOptions() end function ReaderDictionary:addToMainMenu(menu_items) + menu_items.search_settings = { -- submenu with Dict, Wiki, Translation settings + text = _("Settings"), + } menu_items.dictionary_lookup = { text = _("Dictionary lookup"), callback = function() diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index 19ad088b4..609c1bc10 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -361,6 +361,8 @@ function ReaderHighlight:addToMainMenu(menu_items) end, } end + + -- main menu Typeset menu_items.highlight_options = { text = _("Highlight style"), sub_item_table = {}, @@ -466,8 +468,8 @@ function ReaderHighlight:addToMainMenu(menu_items) sub_item_table = self:genPanelZoomMenu(), } end - menu_items.translation_settings = Translator:genSettingsMenu() + -- main menu Settings menu_items.long_press = { text = _("Long-press on text"), sub_item_table = { @@ -533,6 +535,15 @@ function ReaderHighlight:addToMainMenu(menu_items) menu_items.selection_text = util.tableDeepCopy(menu_items.long_press) menu_items.selection_text.text = _("Select on text") end + + -- main menu Search + menu_items.translation_settings = Translator:genSettingsMenu() + menu_items.translate_current_page = { + text = _("Translate current page"), + callback = function() + self:onTranslateCurrentPage() + end, + } end function ReaderHighlight:genPanelZoomMenu() @@ -1373,7 +1384,39 @@ dbg:guard(ReaderHighlight, "translate", end) function ReaderHighlight:onTranslateText(text, page, index) - Translator:showTranslation(text, false, false, true, page, index) + local doc_props = self.ui.doc_settings:readSetting("doc_props") + local doc_lang = doc_props and doc_props.language + Translator:showTranslation(text, true, doc_lang, nil, true, page, index) +end + +function ReaderHighlight:onTranslateCurrentPage() + local x0, y0, x1, y1, page, is_reflow + if self.ui.rolling then + x0 = 0 + y0 = 0 + x1 = Screen:getWidth() + y1 = Screen:getHeight() + else + page = self.ui:getCurrentPage() + is_reflow = self.ui.document.configurable.text_wrap + self.ui.document.configurable.text_wrap = 0 + local page_boxes = self.ui.document:getTextBoxes(page) + if page_boxes and page_boxes[1][1].word then + x0 = page_boxes[1][1].x0 + y0 = page_boxes[1][1].y0 + x1 = page_boxes[#page_boxes][#page_boxes[#page_boxes]].x1 + y1 = page_boxes[#page_boxes][#page_boxes[#page_boxes]].y1 + end + end + local res = x0 and self.ui.document:getTextFromPositions({x = x0, y = y0, page = page}, {x = x1, y = y1}, true) + if self.ui.paging then + self.ui.document.configurable.text_wrap = is_reflow + end + if res and res.text then + local doc_props = self.ui.doc_settings:readSetting("doc_props") + local doc_lang = doc_props and doc_props.language + Translator:showTranslation(res.text, false, doc_lang) + end end function ReaderHighlight:onHoldRelease() diff --git a/frontend/dispatcher.lua b/frontend/dispatcher.lua index ec8f76af8..cd8dbb79f 100644 --- a/frontend/dispatcher.lua +++ b/frontend/dispatcher.lua @@ -150,6 +150,8 @@ local settingsList = { book_description = {category="none", event="ShowBookDescription", title=_("Book description"), reader=true}, book_cover = {category="none", event="ShowBookCover", title=_("Book cover"), reader=true, separator=true}, + translate_page = {category="none", event="TranslateCurrentPage", title=_("Translate current page"), reader=true, separator=true}, + -- rolling reader settings set_font = {category="string", event="SetFont", title=_("Set font"), rolling=true, args_func=require("fontlist").getFontArgFunc,}, increase_font = {category="incrementalnumber", event="IncreaseFontSize", min=0.5, max=255, step=0.5, title=_("Increase font size"), rolling=true}, @@ -340,6 +342,8 @@ local dispatcher_menu_order = { "book_description", "book_cover", + "translate_page", + "set_font", "increase_font", "decrease_font", diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index 433bab2dd..164f2a7be 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -672,8 +672,13 @@ function CreDocument:getWordFromPosition(pos) ]]-- end -function CreDocument:getTextFromPositions(pos0, pos1) - local text_range = self._document:getTextFromPositions(pos0.x, pos0.y, pos1.x, pos1.y) +function CreDocument:getTextFromPositions(pos0, pos1, do_not_draw_selection) + local drawSelection, drawSegmentedSelection + if do_not_draw_selection then + drawSelection, drawSegmentedSelection = false, false + end + local text_range = self._document:getTextFromPositions(pos0.x, pos0.y, pos1.x, pos1.y, + drawSelection, drawSegmentedSelection) logger.dbg("CreDocument: get text range", text_range) if text_range then -- local line_boxes = self:getScreenBoxesFromPositions(text_range.pos0, text_range.pos1) diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index b6f2e89a1..d6a4c40e0 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -144,20 +144,24 @@ local order = { "developer_options", }, search = { + "search_settings", + "----------------------------", "dictionary_lookup", "dictionary_lookup_history", "vocabbuilder", - "dictionary_settings", "----------------------------", "wikipedia_lookup", "wikipedia_history", - "wikipedia_settings", "----------------------------", "find_book_in_calibre_catalog", "find_file", "----------------------------", "opds", }, + search_settings = { + "dictionary_settings", + "wikipedia_settings", + }, main = { "history", "open_last_document", diff --git a/frontend/ui/elements/reader_menu_order.lua b/frontend/ui/elements/reader_menu_order.lua index 99fd03ebe..c4364e43e 100644 --- a/frontend/ui/elements/reader_menu_order.lua +++ b/frontend/ui/elements/reader_menu_order.lua @@ -193,21 +193,26 @@ local order = { "patch_management", }, search = { + "search_settings", + "----------------------------", "dictionary_lookup", "dictionary_lookup_history", "vocabbuilder", - "dictionary_settings", "----------------------------", "wikipedia_lookup", "wikipedia_history", - "wikipedia_settings", "----------------------------", - "translation_settings", + "translate_current_page", "----------------------------", "find_book_in_calibre_catalog", "fulltext_search", "bookmark_search", }, + search_settings = { + "dictionary_settings", + "wikipedia_settings", + "translation_settings", + }, filemanager = {}, main = { "history", diff --git a/frontend/ui/translator.lua b/frontend/ui/translator.lua index b9ef74fa9..7fcec3fc5 100644 --- a/frontend/ui/translator.lua +++ b/frontend/ui/translator.lua @@ -22,7 +22,7 @@ local T = ffiutil.template local _ = require("gettext") -- From https://cloud.google.com/translate/docs/languages --- 20181217: 104 supported languages +-- 20230514: 132 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 @@ -31,10 +31,14 @@ local SUPPORTED_LANGUAGES = { am = _("Amharic"), ar = _("Arabic"), hy = _("Armenian"), + as = _("Assamese"), + ay = _("Aymara"), az = _("Azerbaijani"), + bm = _("Bambara"), eu = _("Basque"), be = _("Belarusian"), bn = _("Bengali"), + bho = _("Bhojpuri"), bs = _("Bosnian"), bg = _("Bulgarian"), ca = _("Catalan"), @@ -45,10 +49,14 @@ local SUPPORTED_LANGUAGES = { hr = _("Croatian"), cs = _("Czech"), da = _("Danish"), + dv = _("Dhivehi"), + doi = _("Dogri"), nl = _("Dutch"), en = _("English"), eo = _("Esperanto"), et = _("Estonian"), + ee = _("Ewe"), + fil = _("Filipino (Tagalog)"), fi = _("Finnish"), fr = _("French"), fy = _("Frisian"), @@ -56,6 +64,7 @@ local SUPPORTED_LANGUAGES = { ka = _("Georgian"), de = _("German"), el = _("Greek"), + gn = _("Guarani"), -- @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 gu = _("Gujarati"), ht = _("Haitian Creole"), @@ -67,6 +76,7 @@ local SUPPORTED_LANGUAGES = { hu = _("Hungarian"), is = _("Icelandic"), ig = _("Igbo"), + ilo = _("Ilocano"), id = _("Indonesian"), ga = _("Irish"), it = _("Italian"), @@ -76,35 +86,48 @@ local SUPPORTED_LANGUAGES = { kn = _("Kannada"), kk = _("Kazakh"), km = _("Khmer"), + rw = _("Kinyarwanda"), + gom = _("Konkani"), ko = _("Korean"), + kri = _("Krio"), ku = _("Kurdish"), + ckb = _("Kurdish (Sorani)"), ky = _("Kyrgyz"), lo = _("Lao"), la = _("Latin"), lv = _("Latvian"), + ln = _("Lingala"), lt = _("Lithuanian"), + lg = _("Luganda"), lb = _("Luxembourgish"), mk = _("Macedonian"), + mai = _("Maithili"), mg = _("Malagasy"), ms = _("Malay"), ml = _("Malayalam"), mt = _("Maltese"), mi = _("Maori"), mr = _("Marathi"), + lus = _("Mizo"), mn = _("Mongolian"), my = _("Myanmar (Burmese)"), ne = _("Nepali"), no = _("Norwegian"), ny = _("Nyanja (Chichewa)"), + ["or"] = _("Odia (Oriya)"), + om = _("Oromo"), ps = _("Pashto"), fa = _("Persian"), pl = _("Polish"), pt = _("Portuguese"), pa = _("Punjabi"), + qu = _("Quechua"), ro = _("Romanian"), ru = _("Russian"), sm = _("Samoan"), + sa = _("Sanskrit"), gd = _("Scots Gaelic"), + nso = _("Sepedi"), sr = _("Serbian"), st = _("Sesotho"), sn = _("Shona"), @@ -120,11 +143,17 @@ local SUPPORTED_LANGUAGES = { tl = _("Tagalog (Filipino)"), tg = _("Tajik"), ta = _("Tamil"), + tt = _("Tatar"), te = _("Telugu"), th = _("Thai"), + ti = _("Tigrinya"), + ts = _("Tsonga"), tr = _("Turkish"), + tk = _("Turkmen"), + ak = _("Twi (Akan)"), uk = _("Ukrainian"), ur = _("Urdu"), + ug = _("Uyghur"), uz = _("Uzbek"), vi = _("Vietnamese"), cy = _("Welsh"), @@ -278,8 +307,7 @@ This is useful: end function Translator:getDocumentLanguage() - local ReaderUI = require("apps/reader/readerui") - local ui = ReaderUI:_getRunningInstance() + local ui = require("apps/reader/readerui").instance if not ui or not ui.document then return end @@ -475,16 +503,19 @@ end Show translated text in TextViewer, with alternate translations @string text -@string target_lang[opt] (`"en"`, `"fr"`, `…`) +@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, target_lang, source_lang, from_highlight, page, index) +function Translator:showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, page, index) if Device:hasClipboard() then Device.input.setClipboardText(text) end local NetworkMgr = require("ui/network/manager") - if NetworkMgr:willRerunWhenOnline(function() self:showTranslation(text, target_lang, source_lang, from_highlight, page, index) end) then + if NetworkMgr:willRerunWhenOnline(function() + self:showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, page, index) + end) then return end @@ -492,11 +523,11 @@ function Translator:showTranslation(text, target_lang, source_lang, from_highlig -- translation service query. local Trapper = require("ui/trapper") Trapper:wrap(function() - self:_showTranslation(text, target_lang, source_lang, from_highlight, page, index) + self:_showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, page, index) end) end -function Translator:_showTranslation(text, target_lang, source_lang, from_highlight, page, index) +function Translator:_showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, page, index) if not target_lang then target_lang = self:getTargetLanguage() end @@ -527,50 +558,60 @@ function Translator:_showTranslation(text, target_lang, source_lang, from_highli 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 result[1] and type(result[1]) == "table" and #result[1] > 0 then + 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 = {} for i, r in ipairs(result[1]) do - local s = type(r[2]) == "string" and r[2] or "" + if detailed_view then + local s = type(r[2]) == "string" and r[2] or "" + table.insert(source, s) + end local t = type(r[1]) == "string" and r[1] or "" - table.insert(source, s) table.insert(translated, t) end - table.insert(output, "▣ " .. table.concat(source, " ")) - text_main = "● " .. table.concat(translated, " ") + text_main = table.concat(translated, " ") + if detailed_view then + text_main = "● " .. text_main + table.insert(output, "▣ " .. table.concat(source, " ")) + end table.insert(output, text_main) end - if result[6] and type(result[6]) == "table" and #result[6] > 0 then - -- Alternative translations: - table.insert(output, "________") - 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) + if detailed_view then + if is_result_valid(result[6]) then + -- Alternative translations: + table.insert(output, "________") + 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 - end - - if result[13] and type(result[13]) == "table" and #result[13] > 0 then - -- Definition(word) - table.insert(output, "________") - 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]) + if is_result_valid(result[13]) then + -- Definition(word) + table.insert(output, "________") + 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 @@ -578,77 +619,80 @@ function Translator:_showTranslation(text, target_lang, source_lang, from_highli -- table.insert(output, require("dump")(result)) -- for debugging local text_all = table.concat(output, "\n") - local textviewer - local 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 page then - ui.highlight:editHighlight(page, 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 page then - ui.highlight:editHighlight(page, index, false, text_all) - else - ui.highlight:addNote(text_all) - end - end, - }, - } - ) - end - if Device:hasClipboard() then - table.insert(buttons_table, - { + + 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 = _("Copy main translation"), - callback = function() - Device.input.setClipboardText(text_main) - end, - }, + { + text = _("Save main translation to note"), + callback = function() + UIManager:close(textviewer) + UIManager:close(ui.highlight.highlight_dialog) + ui.highlight.highlight_dialog = nil + if page then + ui.highlight:editHighlight(page, 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 page then + ui.highlight:editHighlight(page, 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 all"), - callback = function() - Device.input.setClipboardText(text_all) - end, - }, - } - ) + { + 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, - height = math.floor(Screen:getHeight() * 0.8), + height = height, justified = G_reader_settings:nilOrTrue("dict_justify"), add_default_buttons = true, buttons_table = buttons_table, - close_callback = function() - if from_highlight then - local ui = require("apps/reader/readerui").instance - if not ui.highlight.highlight_dialog then - ui.highlight:clear() - end - end - end, + close_callback = close_callback, } UIManager:show(textviewer) end diff --git a/frontend/ui/widget/dictquicklookup.lua b/frontend/ui/widget/dictquicklookup.lua index c170e9f11..77ec6d689 100644 --- a/frontend/ui/widget/dictquicklookup.lua +++ b/frontend/ui/widget/dictquicklookup.lua @@ -1246,27 +1246,29 @@ function DictQuickLookup:lookupInputWord(hint) self.input_dialog = InputDialog:new{ title = _("Enter a word or phrase to look up"), input = hint, - input_hint = hint or "", - input_type = "text", + input_hint = hint, buttons = { { { text = _("Translate"), - is_enter_default = false, callback = function() - if self.input_dialog:getInputText() == "" then return end - self:closeInputDialog() - Translator:showTranslation(self.input_dialog:getInputText()) + local text = self.input_dialog:getInputText() + if text ~= "" then + UIManager:close(self.input_dialog) + Translator:showTranslation(text, true) + end end, }, { text = _("Search Wikipedia"), is_enter_default = self.is_wiki, callback = function() - if self.input_dialog:getInputText() == "" then return end - self.is_wiki = true - self:closeInputDialog() - self:inputLookup() + local text = self.input_dialog:getInputText() + if text ~= "" then + UIManager:close(self.input_dialog) + self.is_wiki = true + self:lookupWikipedia(false, text, true) + end end, }, }, @@ -1275,17 +1277,19 @@ function DictQuickLookup:lookupInputWord(hint) text = _("Cancel"), id = "close", callback = function() - self:closeInputDialog() + UIManager:close(self.input_dialog) end, }, { text = _("Search dictionary"), is_enter_default = not self.is_wiki, callback = function() - if self.input_dialog:getInputText() == "" then return end - self.is_wiki = false - self:closeInputDialog() - self:inputLookup() + local text = self.input_dialog:getInputText() + if text ~= "" then + UIManager:close(self.input_dialog) + self.is_wiki = false + self.ui:handleEvent(Event:new("LookupWord", text, true)) + end end, }, }, @@ -1295,22 +1299,6 @@ function DictQuickLookup:lookupInputWord(hint) self.input_dialog:onShowKeyboard() end -function DictQuickLookup:inputLookup() - local word = self.input_dialog:getInputText() - if word and word ~= "" then - -- Trust that input text does not need any cleaning (allows querying for "-suffix") - if self.is_wiki then - self:lookupWikipedia(false, word, true) - else - self.ui:handleEvent(Event:new("LookupWord", word, true)) - end - end -end - -function DictQuickLookup:closeInputDialog() - UIManager:close(self.input_dialog) -end - function DictQuickLookup:lookupWikipedia(get_fullpage, word, is_sane, lang) if not lang then -- Use the lang of the current or nearest is_wiki DictQuickLookup.