Translator: translate current page (#10438)

reviewable/pr10450/r1
hius07 12 months ago committed by GitHub
parent 946f7931ae
commit 3dce41269d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -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()

@ -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()

@ -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",

@ -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)

@ -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",

@ -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",

@ -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

@ -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.

Loading…
Cancel
Save