[RTL UI] Bidi-wrap filenames, paths, urls, metadata

bidi.lua:
- Revert "Alias everything to Bidi.nowrap() when in LTR UI,
  as using LTR isolates seems uneeded when already LTR" (part
  of a628714f) which was a wrong assumption: we need proper
  wrappers for all things paths. Enhance some of these wrappers.
- Fix GetText RTL wrapping which was losing empty lines and
  trailing \n.

- Wrap all paths, directories, filenames in the code with
  these wrappers.
- Wrap all book metadata (title, authors...) with BD.auto(),
  as it helps fixing some edge cases (like open/close quotation
  marks which are not considered as bracket types by FriBiDi).
  (Needed some minor logic changes in CoverBrowser.)

- Tweak hyphenation menu text
- Update forgotten SortWidget for UI mirroring
- KoptConfig: update "justification" index for RTL re-ordering,
  following the recent addition of the page_gap_height option.
reviewable/pr5742/r1
poire-z 4 years ago
parent a31abf79de
commit 0599c440cc

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ButtonDialog = require("ui/widget/buttondialog") local ButtonDialog = require("ui/widget/buttondialog")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
@ -343,7 +344,7 @@ end
function CloudStorage:onMenuHold(item) function CloudStorage:onMenuHold(item)
if item.type == "folder_long_press" then if item.type == "folder_long_press" then
local title = T(_("Select this directory?\n\n%1"), item.url) local title = T(_("Select this directory?\n\n%1"), BD.dirpath(item.url))
local onConfirm = self.onConfirm local onConfirm = self.onConfirm
local button_dialog local button_dialog
button_dialog = ButtonDialogTitle:new{ button_dialog = ButtonDialogTitle:new{
@ -524,7 +525,7 @@ function CloudStorage:synchronizeSettings(item)
local dropbox_sync_folder = item.sync_source_folder or "not set" local dropbox_sync_folder = item.sync_source_folder or "not set"
local local_sync_folder = item.sync_dest_folder or "not set" local local_sync_folder = item.sync_dest_folder or "not set"
syn_dialog = ButtonDialogTitle:new { syn_dialog = ButtonDialogTitle:new {
title = T(_("Dropbox folder:\n%1\nLocal folder:\n%2"), dropbox_sync_folder, local_sync_folder), title = T(_("Dropbox folder:\n%1\nLocal folder:\n%2"), BD.dirpath(dropbox_sync_folder), BD.dirpath(local_sync_folder)),
title_align = "center", title_align = "center",
buttons = { buttons = {
{ {

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DocumentRegistry = require("document/documentregistry") local DocumentRegistry = require("document/documentregistry")
local DropBoxApi = require("apps/cloudstorage/dropboxapi") local DropBoxApi = require("apps/cloudstorage/dropboxapi")
@ -26,12 +27,12 @@ function DropBox:downloadFile(item, password, path, close)
local __, filename = util.splitFilePathName(path) local __, filename = util.splitFilePathName(path)
if G_reader_settings:isTrue("show_unsupported") and not DocumentRegistry:hasProvider(filename) then if G_reader_settings:isTrue("show_unsupported") and not DocumentRegistry:hasProvider(filename) then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("File saved to:\n%1"), path), text = T(_("File saved to:\n%1"), BD.filename(path)),
}) })
else else
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("File saved to:\n%1\nWould you like to read the downloaded book now?"), text = T(_("File saved to:\n%1\nWould you like to read the downloaded book now?"),
path), BD.filepath(path)),
ok_callback = function() ok_callback = function()
close() close()
ReaderUI:showReader(path) ReaderUI:showReader(path)
@ -40,7 +41,7 @@ function DropBox:downloadFile(item, password, path, close)
end end
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Could not save file to:\n%1"), path), text = T(_("Could not save file to:\n%1"), BD.filepath(path)),
timeout = 3, timeout = 3,
}) })
end end

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DocumentRegistry = require("document/documentregistry") local DocumentRegistry = require("document/documentregistry")
local FtpApi = require("apps/cloudstorage/ftpapi") local FtpApi = require("apps/cloudstorage/ftpapi")
@ -30,12 +31,12 @@ function Ftp:downloadFile(item, address, user, pass, path, close)
local __, filename = util.splitFilePathName(path) local __, filename = util.splitFilePathName(path)
if G_reader_settings:isTrue("show_unsupported") and not DocumentRegistry:hasProvider(filename) then if G_reader_settings:isTrue("show_unsupported") and not DocumentRegistry:hasProvider(filename) then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("File saved to:\n%1"), path), text = T(_("File saved to:\n%1"), BD.filepath(path)),
}) })
else else
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("File saved to:\n%1\nWould you like to read the downloaded book now?"), text = T(_("File saved to:\n%1\nWould you like to read the downloaded book now?"),
path), BD.filepath(path)),
ok_callback = function() ok_callback = function()
close() close()
ReaderUI:showReader(path) ReaderUI:showReader(path)
@ -44,7 +45,7 @@ function Ftp:downloadFile(item, address, user, pass, path, close)
end end
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Could not save file to:\n%1"), path), text = T(_("Could not save file to:\n%1"), BD.filepath(path)),
timeout = 3, timeout = 3,
}) })
end end

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DocumentRegistry = require("document/documentregistry") local DocumentRegistry = require("document/documentregistry")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
@ -22,12 +23,12 @@ function WebDav:downloadFile(item, address, username, password, local_path, clos
local __, filename = util.splitFilePathName(local_path) local __, filename = util.splitFilePathName(local_path)
if G_reader_settings:isTrue("show_unsupported") and not DocumentRegistry:hasProvider(filename) then if G_reader_settings:isTrue("show_unsupported") and not DocumentRegistry:hasProvider(filename) then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("File saved to:\n%1"), local_path), text = T(_("File saved to:\n%1"), BD.filepath(local_path)),
}) })
else else
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("File saved to:\n%1\nWould you like to read the downloaded book now?"), text = T(_("File saved to:\n%1\nWould you like to read the downloaded book now?"),
local_path), BD.filepath(local_path)),
ok_callback = function() ok_callback = function()
close() close()
ReaderUI:showReader(local_path) ReaderUI:showReader(local_path)
@ -36,7 +37,7 @@ function WebDav:downloadFile(item, address, username, password, local_path, clos
end end
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Could not save file to:\n%1"), local_path), text = T(_("Could not save file to:\n%1"), BD.filepath(local_path)),
timeout = 3, timeout = 3,
}) })
end end

@ -109,8 +109,7 @@ function FileManager:init()
self.path_text = TextWidget:new{ self.path_text = TextWidget:new{
face = Font:getFace("xx_smallinfofont"), face = Font:getFace("xx_smallinfofont"),
text = filemanagerutil.abbreviate(self.root_path), text = BD.directory(filemanagerutil.abbreviate(self.root_path)),
para_direction_rtl = false, -- force LTR
max_width = Screen:getWidth() - 2*Size.padding.small, max_width = Screen:getWidth() - 2*Size.padding.small,
truncate_left = true, truncate_left = true,
} }
@ -179,7 +178,7 @@ function FileManager:init()
self.focused_file = nil -- use it only once self.focused_file = nil -- use it only once
function file_chooser:onPathChanged(path) -- luacheck: ignore function file_chooser:onPathChanged(path) -- luacheck: ignore
FileManager.instance.path_text:setText(filemanagerutil.abbreviate(path)) FileManager.instance.path_text:setText(BD.directory(filemanagerutil.abbreviate(path)))
UIManager:setDirty(FileManager.instance, function() UIManager:setDirty(FileManager.instance, function()
return "ui", FileManager.instance.path_text.dimen, FileManager.instance.dithered return "ui", FileManager.instance.path_text.dimen, FileManager.instance.dithered
end) end)
@ -223,7 +222,7 @@ function FileManager:init()
enabled = DocSettings:hasSidecarFile(util.realpath(file)), enabled = DocSettings:hasSidecarFile(util.realpath(file)),
callback = function() callback = function()
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = util.template(_("Purge .sdr to reset settings for this document?\n\n%1"), self.file_dialog.title), text = util.template(_("Purge .sdr to reset settings for this document?\n\n%1"), BD.filename(self.file_dialog.title)),
ok_text = _("Purge"), ok_text = _("Purge"),
ok_callback = function() ok_callback = function()
filemanagerutil.purgeSettings(file) filemanagerutil.purgeSettings(file)
@ -247,7 +246,7 @@ function FileManager:init()
text = _("Delete"), text = _("Delete"),
callback = function() callback = function()
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = _("Are you sure that you want to delete this file?\n") .. file .. ("\n") .. _("If you delete a file, it is permanently lost."), text = _("Are you sure that you want to delete this file?\n") .. BD.filepath(file) .. ("\n") .. _("If you delete a file, it is permanently lost."),
ok_text = _("Delete"), ok_text = _("Delete"),
ok_callback = function() ok_callback = function()
deleteFile(file) deleteFile(file)
@ -358,8 +357,15 @@ function FileManager:init()
}) })
end end
local title
if lfs.attributes(file, "mode") == "directory" then
title = BD.directory(file:match("([^/]+)$"))
else
title = BD.filename(file:match("([^/]+)$"))
end
self.file_dialog = ButtonDialogTitle:new{ self.file_dialog = ButtonDialogTitle:new{
title = file:match("([^/]+)$"), title = title,
title_align = "center", title_align = "center",
buttons = buttons, buttons = buttons,
} }
@ -567,7 +573,7 @@ function FileManager:tapPlus()
end end
self.file_dialog = ButtonDialogTitle:new{ self.file_dialog = ButtonDialogTitle:new{
title = filemanagerutil.abbreviate(self.file_chooser.path), title = BD.dirpath(filemanagerutil.abbreviate(self.file_chooser.path)),
title_align = "center", title_align = "center",
buttons = buttons, buttons = buttons,
} }
@ -666,7 +672,7 @@ end
function FileManager:setHome(path) function FileManager:setHome(path)
path = path or self.file_chooser.path path = path or self.file_chooser.path
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = util.template(_("Set '%1' as HOME directory?"), path), text = util.template(_("Set '%1' as HOME directory?"), BD.dirpath(path)),
ok_text = _("Set as HOME"), ok_text = _("Set as HOME"),
ok_callback = function() ok_callback = function()
G_reader_settings:saveSetting("home_dir", path) G_reader_settings:saveSetting("home_dir", path)
@ -679,7 +685,7 @@ function FileManager:openRandomFile(dir)
local random_file = DocumentRegistry:getRandomFile(dir, false) local random_file = DocumentRegistry:getRandomFile(dir, false)
if random_file then if random_file then
UIManager:show(MultiConfirmBox:new { UIManager:show(MultiConfirmBox:new {
text = T(_("Do you want to open %1?"), util.basename(random_file)), text = T(_("Do you want to open %1?"), BD.filename(util.basename(random_file))),
choice1_text = _("Open"), choice1_text = _("Open"),
choice1_callback = function() choice1_callback = function()
FileManager.instance:onClose() FileManager.instance:onClose()
@ -724,12 +730,12 @@ function FileManager:pasteHere(file)
end end
if util.execute(self.cp_bin, "-r", orig, dest) == 0 then if util.execute(self.cp_bin, "-r", orig, dest) == 0 then
UIManager:show(InfoMessage:new { UIManager:show(InfoMessage:new {
text = T(_("Copied to: %1"), dest), text = T(_("Copied to: %1"), BD.dirpath(dest)),
timeout = 2, timeout = 2,
}) })
else else
UIManager:show(InfoMessage:new { UIManager:show(InfoMessage:new {
text = T(_("An error occurred while trying to copy %1"), orig), text = T(_("An error occurred while trying to copy %1"), BD.filepath(orig)),
timeout = 2, timeout = 2,
}) })
end end
@ -750,12 +756,12 @@ function FileManager:pasteHere(file)
G_reader_settings:saveSetting("lastfile", dest_file) G_reader_settings:saveSetting("lastfile", dest_file)
end end
UIManager:show(InfoMessage:new { UIManager:show(InfoMessage:new {
text = T(_("Moved to: %1"), dest), text = T(_("Moved to: %1"), BD.dirpath(dest)),
timeout = 2, timeout = 2,
}) })
else else
UIManager:show(InfoMessage:new { UIManager:show(InfoMessage:new {
text = T(_("An error occurred while trying to move %1"), orig), text = T(_("An error occurred while trying to move %1"), BD.filepath(orig)),
timeout = 2, timeout = 2,
}) })
end end
@ -772,9 +778,9 @@ function FileManager:pasteHere(file)
if mode == "file" or mode == "directory" then if mode == "file" or mode == "directory" then
local text local text
if mode == "file" then if mode == "file" then
text = T(_("The file %1 already exists. Do you want to overwrite it?"), basename) text = T(_("The file %1 already exists. Do you want to overwrite it?"), BD.filename(basename))
else else
text = T(_("The directory %1 already exists. Do you want to overwrite it?"), basename) text = T(_("The directory %1 already exists. Do you want to overwrite it?"), BD.directory(basename))
end end
UIManager:show(ConfirmBox:new { UIManager:show(ConfirmBox:new {
@ -800,7 +806,7 @@ function FileManager:createFolder(curr_folder, new_folder)
local text local text
if code == 0 then if code == 0 then
self:onRefresh() self:onRefresh()
text = T(_("Folder created:\n%1"), new_folder) text = T(_("Folder created:\n%1"), BD.directory(new_folder))
else else
text = _("The folder has not been created.") text = _("The folder has not been created.")
end end
@ -815,7 +821,7 @@ function FileManager:deleteFile(file)
local file_abs_path = util.realpath(file) local file_abs_path = util.realpath(file)
if file_abs_path == nil then if file_abs_path == nil then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = util.template(_("File %1 not found"), file), text = util.template(_("File %1 not found"), BD.filepath(file)),
}) })
return return
end end
@ -839,12 +845,12 @@ function FileManager:deleteFile(file)
end end
ReadCollection:removeItemByPath(file, is_dir) ReadCollection:removeItemByPath(file, is_dir)
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = util.template(_("Deleted %1"), file), text = util.template(_("Deleted %1"), BD.filepath(file)),
timeout = 2, timeout = 2,
}) })
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = util.template(_("An error occurred while trying to delete %1"), file), text = util.template(_("An error occurred while trying to delete %1"), BD.filepath(file)),
}) })
end end
end end
@ -867,21 +873,21 @@ function FileManager:renameFile(file)
end end
if move_history then if move_history then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = util.template(_("Renamed from %1 to %2"), file, dest), text = util.template(_("Renamed from %1 to %2"), BD.filepath(file), BD.filepath(dest)),
timeout = 2, timeout = 2,
}) })
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = util.template( text = util.template(
_("Failed to move history data of %1 to %2.\nThe reading history may be lost."), _("Failed to move history data of %1 to %2.\nThe reading history may be lost."),
file, dest), BD.filepath(file), BD.filepath(dest)),
}) })
end end
end end
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = util.template( text = util.template(
_("Failed to rename from %1 to %2"), file, dest), _("Failed to rename from %1 to %2"), BD.filepath(file), BD.filepath(dest)),
}) })
end end
end end

@ -2,6 +2,7 @@
This module provides a way to display book information (filename and book metadata) This module provides a way to display book information (filename and book metadata)
]] ]]
local BD = require("ui/bidi")
local DocSettings = require("docsettings") local DocSettings = require("docsettings")
local DocumentRegistry = require("document/documentregistry") local DocumentRegistry = require("document/documentregistry")
local ImageViewer = require("ui/widget/imageviewer") local ImageViewer = require("ui/widget/imageviewer")
@ -46,10 +47,10 @@ function BookInfo:show(file, book_props)
local size_f = util.getFriendlySize(file_size) local size_f = util.getFriendlySize(file_size)
local size_b = util.getFormattedSize(file_size) local size_b = util.getFormattedSize(file_size)
local size = string.format("%s (%s bytes)", size_f, size_b) local size = string.format("%s (%s bytes)", size_f, size_b)
table.insert(kv_pairs, { _("Filename:"), filename }) table.insert(kv_pairs, { _("Filename:"), BD.filename(filename) })
table.insert(kv_pairs, { _("Format:"), filetype:upper() }) table.insert(kv_pairs, { _("Format:"), filetype:upper() })
table.insert(kv_pairs, { _("Size:"), size }) table.insert(kv_pairs, { _("Size:"), size })
table.insert(kv_pairs, { _("Directory:"), filemanagerutil.abbreviate(directory) }) table.insert(kv_pairs, { _("Directory:"), BD.dirpath(filemanagerutil.abbreviate(directory)) })
table.insert(kv_pairs, "----") table.insert(kv_pairs, "----")
-- book_props may be provided if caller already has them available -- book_props may be provided if caller already has them available
@ -123,10 +124,20 @@ function BookInfo:show(file, book_props)
local title = book_props.title local title = book_props.title
if title == "" or title == nil then title = _("N/A") end if title == "" or title == nil then title = _("N/A") end
table.insert(kv_pairs, { _("Title:"), title }) table.insert(kv_pairs, { _("Title:"), BD.auto(title) })
local authors = book_props.authors local authors = book_props.authors
if authors == "" or authors == nil then authors = _("N/A") end if authors == "" or authors == nil then
authors = _("N/A")
elseif authors:find("\n") then -- BD auto isolate each author
authors = util.splitToArray(authors, "\n")
for i=1, #authors do
authors[i] = BD.auto(authors[i])
end
authors = table.concat(authors, "\n")
else
authors = BD.auto(authors)
end
table.insert(kv_pairs, { _("Authors:"), authors }) table.insert(kv_pairs, { _("Authors:"), authors })
local series = book_props.series local series = book_props.series
@ -135,7 +146,7 @@ function BookInfo:show(file, book_props)
else -- Shorten calibre series decimal number (#4.0 => #4) else -- Shorten calibre series decimal number (#4.0 => #4)
series = series:gsub("(#%d+)%.0$", "%1") series = series:gsub("(#%d+)%.0$", "%1")
end end
table.insert(kv_pairs, { _("Series:"), series }) table.insert(kv_pairs, { _("Series:"), BD.auto(series) })
local pages = book_props.pages local pages = book_props.pages
if pages == "" or pages == nil then pages = _("N/A") end if pages == "" or pages == nil then pages = _("N/A") end
@ -146,7 +157,17 @@ function BookInfo:show(file, book_props)
table.insert(kv_pairs, { _("Language:"), language }) table.insert(kv_pairs, { _("Language:"), language })
local keywords = book_props.keywords local keywords = book_props.keywords
if keywords == "" or keywords == nil then keywords = _("N/A") end if keywords == "" or keywords == nil then
keywords = _("N/A")
elseif keywords:find("\n") then -- BD auto isolate each keywords
keywords = util.splitToArray(keywords, "\n")
for i=1, #keywords do
keywords[i] = BD.auto(keywords[i])
end
keywords = table.concat(keywords, "\n")
else
keywords = BD.auto(keywords)
end
table.insert(kv_pairs, { _("Keywords:"), keywords }) table.insert(kv_pairs, { _("Keywords:"), keywords })
local description = book_props.description local description = book_props.description
@ -157,6 +178,9 @@ function BookInfo:show(file, book_props)
-- in PDF) be HTML. -- in PDF) be HTML.
description = util.htmlToPlainTextIfHtml(book_props.description) description = util.htmlToPlainTextIfHtml(book_props.description)
end end
-- (We don't BD wrap description: it may be multi-lines, and the value we set
-- here may be viewed in a TextViewer that has auto_para_direction=true, which
-- will show the right thing, that'd we rather not mess with BD wrapping.)
table.insert(kv_pairs, { _("Description:"), description }) table.insert(kv_pairs, { _("Description:"), description })
-- Cover image -- Cover image

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local DocSettings = require("docsettings") local DocSettings = require("docsettings")
local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo")
@ -53,7 +54,7 @@ function FileManagerHistory:onMenuHold(item)
callback = function() callback = function()
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = util.template(_("Purge .sdr to reset settings for this document?\n\n%1"), item.text), text = util.template(_("Purge .sdr to reset settings for this document?\n\n%1"), BD.filename(item.text)),
ok_text = _("Purge"), ok_text = _("Purge"),
ok_callback = function() ok_callback = function()
filemanagerutil.purgeSettings(item.file) filemanagerutil.purgeSettings(item.file)
@ -80,7 +81,7 @@ function FileManagerHistory:onMenuHold(item)
callback = function() callback = function()
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = _("Are you sure that you want to delete this file?\n") .. item.file .. ("\n") .. _("If you delete a file, it is permanently lost."), text = _("Are you sure that you want to delete this file?\n") .. BD.filepath(item.file) .. ("\n") .. _("If you delete a file, it is permanently lost."),
ok_text = _("Delete"), ok_text = _("Delete"),
ok_callback = function() ok_callback = function()
local FileManager = require("apps/filemanager/filemanager") local FileManager = require("apps/filemanager/filemanager")
@ -116,7 +117,7 @@ function FileManagerHistory:onMenuHold(item)
}, },
} }
self.histfile_dialog = ButtonDialogTitle:new{ self.histfile_dialog = ButtonDialogTitle:new{
title = item.text:match("([^/]+)$"), title = BD.filename(item.text:match("([^/]+)$")),
title_align = "center", title_align = "center",
buttons = buttons, buttons = buttons,
} }

@ -469,7 +469,7 @@ function FileManagerMenu:setUpdateItemTable()
end end
local last_file = G_reader_settings:readSetting("lastfile") local last_file = G_reader_settings:readSetting("lastfile")
local path, file_name = util.splitFilePathName(last_file); -- luacheck: no unused local path, file_name = util.splitFilePathName(last_file); -- luacheck: no unused
return T(_("Last: %1"), file_name) return T(_("Last: %1"), BD.filename(file_name))
end, end,
enabled_func = function() enabled_func = function()
return G_reader_settings:readSetting("lastfile") ~= nil return G_reader_settings:readSetting("lastfile") ~= nil
@ -480,7 +480,7 @@ function FileManagerMenu:setUpdateItemTable()
hold_callback = function() hold_callback = function()
local last_file = G_reader_settings:readSetting("lastfile") local last_file = G_reader_settings:readSetting("lastfile")
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("Would you like to open the last document: %1?"), last_file), text = T(_("Would you like to open the last document: %1?"), BD.filepath(last_file)),
ok_text = _("OK"), ok_text = _("OK"),
ok_callback = function() ok_callback = function()
self:openLastDoc() self:openLastDoc()

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ButtonDialog = require("ui/widget/buttondialog") local ButtonDialog = require("ui/widget/buttondialog")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
@ -73,7 +74,7 @@ function FileManagerShortcuts:addNewFolder()
title = self.title, title = self.title,
input = friendly_name, input = friendly_name,
input_type = "text", input_type = "text",
description = T(_("Title for selected folder:\n%1"), path), description = T(_("Title for selected folder:\n%1"), BD.dirpath(path)),
buttons = { buttons = {
{ {
{ {
@ -162,7 +163,7 @@ function FileManagerShortcuts:editFolderShortcut(item)
title = _("Edit friendly name"), title = _("Edit friendly name"),
input = item.friendly_name, input = item.friendly_name,
input_type = "text", input_type = "text",
description = T(_("Rename title for selected folder:\n%1"), item.folder), description = T(_("Rename title for selected folder:\n%1"), BD.dirpath(item.folder)),
buttons = { buttons = {
{ {
{ {

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local Blitbuffer = require("ffi/blitbuffer") local Blitbuffer = require("ffi/blitbuffer")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local FrameContainer = require("ui/widget/container/framecontainer") local FrameContainer = require("ui/widget/container/framecontainer")
@ -26,7 +27,7 @@ function OPDSCatalog:init()
file_downloaded_callback = function(downloaded_file) file_downloaded_callback = function(downloaded_file)
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("File saved to:\n%1\nWould you like to read the downloaded book now?"), text = T(_("File saved to:\n%1\nWould you like to read the downloaded book now?"),
downloaded_file), BD.filepath(downloaded_file)),
ok_text = _("Read now"), ok_text = _("Read now"),
cancel_text = _("Read later"), cancel_text = _("Read later"),
ok_callback = function() ok_callback = function()

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local Device = require("device") local Device = require("device")
@ -233,7 +234,7 @@ function ReaderDictionary:addToMainMenu(menu_items)
text = T(_([[ text = T(_([[
If you'd like to change the order in which dictionaries are queried (and their results displayed), you can: If you'd like to change the order in which dictionaries are queried (and their results displayed), you can:
- move all dictionary directories out of %1. - move all dictionary directories out of %1.
- move them back there, one by one, in the order you want them to be used.]]), self.data_dir) - move them back there, one by one, in the order you want them to be used.]]), BD.dirpath(self.data_dir))
}) })
end, end,
}, },
@ -928,7 +929,7 @@ function ReaderDictionary:downloadDictionary(dict, download_location, continue)
logger.dbg("file downloaded to", download_location) logger.dbg("file downloaded to", download_location)
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = _("Could not save file to:\n") .. download_location, text = _("Could not save file to:\n") .. BD.filepath(download_location),
--timeout = 3, --timeout = 3,
}) })
return false return false

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local CenterContainer = require("ui/widget/container/centercontainer") local CenterContainer = require("ui/widget/container/centercontainer")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device") local Device = require("device")
@ -397,7 +398,7 @@ a { color: black; }
f:write("</body></html>\n") f:write("</body></html>\n")
f:close() f:close()
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("Document created as:\n%1\n\nWould you like to read it now?"), fonts_test_path), text = T(_("Document created as:\n%1\n\nWould you like to read it now?"), BD.filepath(fonts_test_path)),
ok_callback = function() ok_callback = function()
UIManager:scheduleIn(1.0, function() UIManager:scheduleIn(1.0, function()
self.ui:switchDocument(fonts_test_path) self.ui:switchDocument(fonts_test_path)

@ -1490,7 +1490,7 @@ function ReaderGesture:gestureAction(action, ges)
local current_network = NetworkMgr:getCurrentNetwork() local current_network = NetworkMgr:getCurrentNetwork()
-- this method is only available for some implementations -- this method is only available for some implementations
if current_network and current_network.ssid then if current_network and current_network.ssid then
info_text = T(_("Already connected to network %1."), current_network.ssid) info_text = T(_("Already connected to network %1."), BD.wrap(current_network.ssid))
else else
info_text = _("Already connected.") info_text = _("Already connected.")
end end

@ -791,7 +791,7 @@ function ReaderHighlight:viewSelectionHTML(debug_view)
if css_files then if css_files then
for i=1, #css_files do for i=1, #css_files do
local button = { local button = {
text = T(_("View %1"), css_files[i]), text = T(_("View %1"), BD.filepath(css_files[i])),
callback = function() callback = function()
local css_text = self.ui.document:getDocumentFileContent(css_files[i]) local css_text = self.ui.document:getDocumentFileContent(css_files[i])
local cssviewer local cssviewer

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local Device = require("device") local Device = require("device")
local Event = require("ui/event") local Event = require("ui/event")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
@ -25,13 +26,16 @@ function ReaderHyphenation:init()
table.insert(self.hyph_table, { table.insert(self.hyph_table, {
text_func = function() text_func = function()
local limits_text = _("language defaults") -- Note: with our callback, we either get hyph_left_hyphen_min and
if G_reader_settings:readSetting("hyph_left_hyphen_min") -- hyph_right_hyphen_min both nil, or both defined.
or G_reader_settings:readSetting("hyph_right_hyphen_min") then if G_reader_settings:readSetting("hyph_left_hyphen_min") or
limits_text = T("%1 - %2", G_reader_settings:readSetting("hyph_left_hyphen_min"), G_reader_settings:readSetting("hyph_right_hyphen_min") then
-- @translators to RTL language translators: %1/left is the min length of the start of a hyphenated word, %2/right is the min length of the end of a hyphenated word (note that there is yet no support for hyphenation with RTL languages, so this will mostly apply to LTR documents)
return T(_("Left/right minimal sizes: %1 - %2"),
G_reader_settings:readSetting("hyph_left_hyphen_min"),
G_reader_settings:readSetting("hyph_right_hyphen_min")) G_reader_settings:readSetting("hyph_right_hyphen_min"))
end end
return T(_("Left/right minimal sizes: %1"), limits_text) return _("Left/right minimal sizes: language defaults")
end, end,
callback = function() callback = function()
local DoubleSpinWidget = require("/ui/widget/doublespinwidget") local DoubleSpinWidget = require("/ui/widget/doublespinwidget")
@ -120,7 +124,7 @@ These settings will apply to all books with any hyphenation dictionary.
self.hyph_alg = v.filename self.hyph_alg = v.filename
self.ui.doc_settings:saveSetting("hyph_alg", self.hyph_alg) self.ui.doc_settings:saveSetting("hyph_alg", self.hyph_alg)
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Changed hyphenation to %1."), v.name), text = T(_("Changed hyphenation to %1."), BD.wrap(v.name)),
}) })
self.ui.document:setHyphDictionary(v.filename) self.ui.document:setHyphDictionary(v.filename)
-- Apply hyphenation sides limits -- Apply hyphenation sides limits
@ -137,7 +141,7 @@ These settings will apply to all books with any hyphenation dictionary.
-- one is set, no fallback will ever be used - if a fallback one -- one is set, no fallback will ever be used - if a fallback one
-- is set, no default is wanted; so when we set one below, we -- is set, no default is wanted; so when we set one below, we
-- remove the other). -- remove the other).
text = T( _("Would you like %1 to be used as the default (★) or fallback (<28>) hyphenation language?\n\nDefault will always take precedence while fallback will only be used if the language of the book can't be automatically determined."), v.name), text = T( _("Would you like %1 to be used as the default (★) or fallback (<28>) hyphenation language?\n\nDefault will always take precedence while fallback will only be used if the language of the book can't be automatically determined."), BD.wrap(v.name)),
choice1_text = _("Default"), choice1_text = _("Default"),
choice1_callback = function() choice1_callback = function()
G_reader_settings:saveSetting("hyph_alg_default", v.filename) G_reader_settings:saveSetting("hyph_alg_default", v.filename)

@ -534,7 +534,7 @@ function ReaderLink:onGotoLink(link, neglect_current_location, allow_footnote_po
display_filename = display_filename .. anchor display_filename = display_filename .. anchor
end end
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("Would you like to read this local document?\n\n%1\n"), display_filename), text = T(_("Would you like to read this local document?\n\n%1\n"), BD.filepath(display_filename)),
ok_callback = function() ok_callback = function()
UIManager:scheduleIn(0.1, function() UIManager:scheduleIn(0.1, function()
self.ui:switchDocument(linked_filename) self.ui:switchDocument(linked_filename)
@ -543,7 +543,7 @@ function ReaderLink:onGotoLink(link, neglect_current_location, allow_footnote_po
}) })
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Link to unsupported local file:\n%1"), link_url), text = T(_("Link to unsupported local file:\n%1"), BD.url(link_url)),
}) })
end end
return true return true
@ -551,7 +551,7 @@ function ReaderLink:onGotoLink(link, neglect_current_location, allow_footnote_po
-- Not supported -- Not supported
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Invalid or external link:\n%1"), link_url), text = T(_("Invalid or external link:\n%1"), BD.url(link_url)),
-- no timeout to allow user to type that link in his web browser -- no timeout to allow user to type that link in his web browser
}) })
-- don't propagate, user will notice and tap elsewhere if he wants to change page -- don't propagate, user will notice and tap elsewhere if he wants to change page
@ -658,7 +658,7 @@ function ReaderLink:onGoToExternalLink(link_url)
-- No external link handler -- No external link handler
return false return false
end end
text = T(_("External link:\n\n%1"), link_url) text = T(_("External link:\n\n%1"), BD.url(link_url))
end end
-- Add all alternative handlers buttons -- Add all alternative handlers buttons

@ -228,7 +228,7 @@ function ReaderMenu:setUpdateItemTable()
return _("Open previous document") return _("Open previous document")
end end
local path, file_name = util.splitFilePathName(previous_file) -- luacheck: no unused local path, file_name = util.splitFilePathName(previous_file) -- luacheck: no unused
return T(_("Previous: %1"), file_name) return T(_("Previous: %1"), BD.filename(file_name))
end, end,
enabled_func = function() enabled_func = function()
return self:getPreviousFile() ~= nil return self:getPreviousFile() ~= nil
@ -239,7 +239,7 @@ function ReaderMenu:setUpdateItemTable()
hold_callback = function() hold_callback = function()
local previous_file = self:getPreviousFile() local previous_file = self:getPreviousFile()
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("Would you like to open the previous document: %1?"), previous_file), text = T(_("Would you like to open the previous document: %1?"), BD.filepath(previous_file)),
ok_text = _("OK"), ok_text = _("OK"),
ok_callback = function() ok_callback = function()
self.ui:switchDocument(previous_file) self.ui:switchDocument(previous_file)

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local BookStatusWidget = require("ui/widget/bookstatuswidget") local BookStatusWidget = require("ui/widget/bookstatuswidget")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local Device = require("device") local Device = require("device")
@ -181,7 +182,7 @@ function ReaderStatus:deleteFile(file, text_end_book)
message_end_book = "You've reached the end of the document.\n" message_end_book = "You've reached the end of the document.\n"
end end
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("%1Are you sure that you want to delete this file?\n%2\nIf you delete a file, it is permanently lost."), message_end_book, file), text = T(_("%1Are you sure that you want to delete this file?\n%2\nIf you delete a file, it is permanently lost."), message_end_book, BD.filepath(file)),
ok_text = _("Delete"), ok_text = _("Delete"),
ok_callback = function() ok_callback = function()
local FileManager = require("apps/filemanager/filemanager") local FileManager = require("apps/filemanager/filemanager")

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local Blitbuffer = require("ffi/blitbuffer") local Blitbuffer = require("ffi/blitbuffer")
local ButtonTable = require("ui/widget/buttontable") local ButtonTable = require("ui/widget/buttontable")
local CenterContainer = require("ui/widget/container/centercontainer") local CenterContainer = require("ui/widget/container/centercontainer")
@ -498,7 +499,7 @@ You can enable individual tweaks on this book with a tap, or view more details a
table.insert(item_table, { table.insert(item_table, {
title = title, title = title,
id = file, -- keep ".css" in id, to distinguish between koreader/user tweaks id = file, -- keep ".css" in id, to distinguish between koreader/user tweaks
description = T(_("User style tweak at %1"), filepath), description = T(_("User style tweak at %1"), BD.filepath(filepath)),
priority = 10, -- give user tweaks a higher priority priority = 10, -- give user tweaks a higher priority
css_path = filepath, css_path = filepath,
}) })

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local Event = require("ui/event") local Event = require("ui/event")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
@ -252,7 +253,7 @@ function ReaderTypeset:genStyleSheetMenu()
text_func = function() text_func = function()
local text = _("Obsolete") local text = _("Obsolete")
if obsoleted_css[self.css] then if obsoleted_css[self.css] then
text = T(_("Obsolete (%1)"), obsoleted_css[self.css]) text = T(_("Obsolete (%1)"), BD.filename(obsoleted_css[self.css]))
end end
if obsoleted_css[G_reader_settings:readSetting("copt_css")] then if obsoleted_css[G_reader_settings:readSetting("copt_css")] then
text = text .. "" text = text .. ""
@ -450,7 +451,7 @@ end
function ReaderTypeset:makeDefaultStyleSheet(css, text, touchmenu_instance) function ReaderTypeset:makeDefaultStyleSheet(css, text, touchmenu_instance)
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T( _("Set default style to %1?"), text), text = T( _("Set default style to %1?"), BD.filename(text)),
ok_callback = function() ok_callback = function()
G_reader_settings:saveSetting("copt_css", css) G_reader_settings:saveSetting("copt_css", css)
if touchmenu_instance then touchmenu_instance:updateItems() end if touchmenu_instance then touchmenu_instance:updateItems() end

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
@ -175,7 +176,7 @@ function ReaderWikipedia:addToMainMenu(menu_items)
if not default_dir then default_dir = require("apps/filemanager/filemanagerutil").getDefaultDir() end if not default_dir then default_dir = require("apps/filemanager/filemanagerutil").getDefaultDir() end
local dialog local dialog
dialog = ButtonDialogTitle:new{ dialog = ButtonDialogTitle:new{
title = T(_("Current Wikipedia 'Save as EPUB' directory:\n\n%1\n"), default_dir), title = T(_("Current Wikipedia 'Save as EPUB' directory:\n\n%1\n"), BD.dirpath(default_dir)),
buttons = { buttons = {
{ {
{ {
@ -220,7 +221,7 @@ function ReaderWikipedia:addToMainMenu(menu_items)
onConfirm = function(path) onConfirm = function(path)
G_reader_settings:saveSetting("wikipedia_save_dir", path) G_reader_settings:saveSetting("wikipedia_save_dir", path)
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Wikipedia 'Save as EPUB' directory set to:\n%1"), path), text = T(_("Wikipedia 'Save as EPUB' directory set to:\n%1"), BD.dirpath(path)),
}) })
end end
} }
@ -257,7 +258,7 @@ Where do you want them saved?]])
end end
G_reader_settings:saveSetting("wikipedia_save_dir", wikipedia_dir) G_reader_settings:saveSetting("wikipedia_save_dir", wikipedia_dir)
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Wikipedia 'Save as EPUB' directory set to:\n%1"), wikipedia_dir), text = T(_("Wikipedia 'Save as EPUB' directory set to:\n%1"), BD.dirpath(wikipedia_dir)),
}) })
end, end,
cancel_text = _("Select directory"), cancel_text = _("Select directory"),

@ -4,6 +4,7 @@ ReaderUI is an abstraction for a reader interface.
It works using data gathered from a document interface. It works using data gathered from a document interface.
]]-- ]]--
local BD = require("ui/bidi")
local Cache = require("cache") local Cache = require("cache")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device") local Device = require("device")
@ -448,14 +449,14 @@ function ReaderUI:showReader(file, provider)
if lfs.attributes(file, "mode") ~= "file" then if lfs.attributes(file, "mode") ~= "file" then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("File '%1' does not exist."), file) text = T(_("File '%1' does not exist."), BD.filepath(file))
}) })
return return
end end
if not DocumentRegistry:hasProvider(file) and provider == nil then if not DocumentRegistry:hasProvider(file) and provider == nil then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("File '%1' is not supported."), file) text = T(_("File '%1' is not supported."), BD.filepath(file))
}) })
self:showFileManager(file) self:showFileManager(file)
return return
@ -471,7 +472,7 @@ function ReaderUI:showReader(file, provider)
(provider.provider == "mupdf" and type(bookmarks[1].page) == "string")) then (provider.provider == "mupdf" and type(bookmarks[1].page) == "string")) then
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("The document '%1' with bookmarks or highlights was previously opened with a different engine. To prevent issues, bookmarks need to be deleted before continuing."), text = T(_("The document '%1' with bookmarks or highlights was previously opened with a different engine. To prevent issues, bookmarks need to be deleted before continuing."),
file), BD.filepath(file)),
ok_text = _("Delete"), ok_text = _("Delete"),
ok_callback = function() ok_callback = function()
doc_settings:delSetting("bookmarks") doc_settings:delSetting("bookmarks")
@ -488,7 +489,7 @@ end
function ReaderUI:showReaderCoroutine(file, provider) function ReaderUI:showReaderCoroutine(file, provider)
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Opening file '%1'."), file), text = T(_("Opening file '%1'."), BD.filepath(file)),
timeout = 0.0, timeout = 0.0,
}) })
-- doShowReader might block for a long time, so force repaint here -- doShowReader might block for a long time, so force repaint here

@ -142,9 +142,10 @@ function Device:init()
-- we cannot blit to a window here since we have no focus yet. -- we cannot blit to a window here since we have no focus yet.
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
local BD = require("ui/bidi")
UIManager:scheduleIn(0.1, function() UIManager:scheduleIn(0.1, function()
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Opening file '%1'."), new_file), text = T(_("Opening file '%1'."), BD.filepath(new_file)),
timeout = 0.0, timeout = 0.0,
}) })
end) end)
@ -255,7 +256,8 @@ function Device:retrieveNetworkInfo()
if ip == "0" or gw == "0" then if ip == "0" or gw == "0" then
return _("Not connected") return _("Not connected")
else else
return T(_("Connected to %1\n IP address: %2\n gateway: %3"), ssid, ip, gw) local BD = require("ui/bidi")
return T(_("Connected to %1\n IP address: %2\n gateway: %3"), BD.wrap(ssid), BD.ltr(ip), BD.ltr(gw))
end end
end end

@ -84,31 +84,35 @@ function Bidi.setup(lang)
Bidi.default = Bidi.rtl Bidi.default = Bidi.rtl
Bidi.wrap = Bidi.rtl Bidi.wrap = Bidi.rtl
Bidi.filename = Bidi._filename_rtl Bidi.filename = Bidi._filename_rtl
Bidi.filepath = Bidi.ltr -- see if we need to split and _filename_rtl() the filename part Bidi.filepath = Bidi._filepath_rtl -- filename auto, but with extension on the right
Bidi.directory = Bidi.ltr Bidi.directory = Bidi._path -- will keep any trailing / on the right
Bidi.dirpath = Bidi.ltr Bidi.dirpath = Bidi._path
Bidi.path = Bidi.ltr Bidi.path = Bidi._path
Bidi.url = Bidi.ltr Bidi.url = Bidi._path
else else
Bidi.default = Bidi.ltr Bidi.default = Bidi.ltr
Bidi.wrap = Bidi.nowrap Bidi.wrap = Bidi.nowrap
Bidi.filename = Bidi.nowrap Bidi.filename = Bidi._filename_ltr
Bidi.filepath = Bidi.nowrap Bidi.filepath = Bidi._filepath_ltr
Bidi.directory = Bidi.nowrap Bidi.directory = Bidi._path -- will keep any trailing / on the right
Bidi.dirpath = Bidi.nowrap Bidi.dirpath = Bidi._path
Bidi.path = Bidi.nowrap Bidi.path = Bidi._path
Bidi.url = Bidi.nowrap Bidi.url = Bidi._path
end end
-- If RTL UI text, let's have untranslated strings (so english) still rendered LTR -- If RTL UI text, let's have untranslated strings (so english) still rendered LTR
if Bidi._rtl_ui_text then if Bidi._rtl_ui_text then
_.wrapUntranslated = function(text) _.wrapUntranslated = function(text)
-- We need to split by line and wrap each line as LTR (as the -- We need to split by line and wrap each line as LTR (as the
-- paragraph direction will still be RTL). -- paragraph direction will still be RTL).
local lines = {} local parts = {}
for s in text:gmatch("[^\r\n]+") do for part in util.gsplit(text, "\n", true, true) do
table.insert(lines, Bidi.ltr(s)) if part == "\n" then
table.insert(parts, "\n")
elseif part ~= "" then
table.insert(parts, Bidi.ltr(part))
end
end end
return table.concat(lines, "\n") return table.concat(parts)
end end
else else
_.wrapUntranslated = _.wrapUntranslated_nowrap _.wrapUntranslated = _.wrapUntranslated_nowrap
@ -171,6 +175,10 @@ local RLI = "\xE2\x81\xA7" -- U+2067 RLI / RIGHT-TO-LEFT ISOLATE
local FSI = "\xE2\x81\xA8" -- U+2068 FSI / FIRST STRONG ISOLATE local FSI = "\xE2\x81\xA8" -- U+2068 FSI / FIRST STRONG ISOLATE
local PDI = "\xE2\x81\xA9" -- U+2069 PDI / POP DIRECTIONAL ISOLATE local PDI = "\xE2\x81\xA9" -- U+2069 PDI / POP DIRECTIONAL ISOLATE
-- Not currently needed:
-- local LRM = "\xE2\x80\x8E" -- U+200E LRM / LEFT-TO-RIGHT MARK
-- local RLM = "\xE2\x80\x8F" -- U+200F RLM / RIGHT-TO-LEFT MARK
function Bidi.ltr(text) function Bidi.ltr(text)
return string.format("%s%s%s", LRI, text, PDI) return string.format("%s%s%s", LRI, text, PDI)
end end
@ -205,12 +213,29 @@ end
-- shown as real RTL). -- shown as real RTL).
-- Note: when the filename or path are standalone in a TextWidget, it's -- Note: when the filename or path are standalone in a TextWidget, it's
-- better to use "para_direction_rtl = false" without any wrapping. -- better to use "para_direction_rtl = false" without any wrapping.
Bidi.filename = Bidi.nowrap -- aliased to Bidi._filename_rtl if _rtl_ui_text -- These are replaced above and aliased to the right wrapper depending
Bidi.filepath = Bidi.nowrap -- aliased to Bidi.ltr if _rtl_ui_text -- on Bidi._rtl_ui_text
Bidi.directory = Bidi.nowrap -- aliased to Bidi.ltr if _rtl_ui_text Bidi.filename = Bidi.nowrap
Bidi.dirpath = Bidi.nowrap -- aliased to Bidi.ltr if _rtl_ui_text Bidi.filepath = Bidi.nowrap
Bidi.path = Bidi.nowrap -- aliased to Bidi.ltr if _rtl_ui_text Bidi.directory = Bidi.nowrap
Bidi.url = Bidi.nowrap -- aliased to Bidi.ltr if _rtl_ui_text Bidi.dirpath = Bidi.nowrap
Bidi.path = Bidi.nowrap
Bidi.url = Bidi.nowrap
function Bidi._filename_ltr(filename)
-- We always want to show the extension on the left,
-- but the text before should be auto.
local name, suffix = util.splitFileNameSuffix(filename)
-- Let the first strong character of the filename decides
-- about the direction
if suffix == "" then
return Bidi.auto(name)
end
return Bidi.auto(name) .. "." .. suffix
-- No need to additionally wrap it in ltr(), as the
-- default text direction must be LTR.
-- return Bidi.ltr(Bidi.auto(name) .. "." .. suffix)
end
function Bidi._filename_rtl(filename) function Bidi._filename_rtl(filename)
-- We always want to show the extension either on the left -- We always want to show the extension either on the left
@ -219,7 +244,43 @@ function Bidi._filename_rtl(filename)
local name, suffix = util.splitFileNameSuffix(filename) local name, suffix = util.splitFileNameSuffix(filename)
-- Let the first strong character of the filename decides -- Let the first strong character of the filename decides
-- about the direction -- about the direction
if suffix == "" then
return Bidi.auto(name)
end
return Bidi.auto(name .. "." .. Bidi.ltr(suffix)) return Bidi.auto(name .. "." .. Bidi.ltr(suffix))
end end
function Bidi._filename_auto_ext_right(filename)
-- Auto/First strong char for name part, but extension still
-- on the right
local name, suffix = util.splitFileNameSuffix(filename)
if suffix == "" then
return Bidi.auto(name)
end
return Bidi.ltr(Bidi.auto(name) .. "." .. suffix)
end
function Bidi._path(path)
-- by wrapping each component in path in FSI (first strong char)
local parts = {}
for part in util.gsplit(path, "/", true, true) do
if part == "/" then
table.insert(parts, "/")
elseif part ~= "" then
table.insert(parts, Bidi.auto(part))
end
end
return Bidi.ltr(table.concat(parts))
end
function Bidi._filepath_ltr(path)
local dirpath, filename = util.splitFilePathName(path)
return Bidi.ltr(Bidi._path(dirpath) .. filename)
end
function Bidi._filepath_rtl(path)
local dirpath, filename = util.splitFilePathName(path)
return Bidi.ltr(Bidi._path(dirpath) .. Bidi._filename_auto_ext_right(filename))
end
return Bidi return Bidi

@ -334,7 +334,7 @@ if BD.mirroredUILayout() then
-- be mirrored - but that's not enough: we need to swap LEFT and RIGHT, -- be mirrored - but that's not enough: we need to swap LEFT and RIGHT,
-- so they appear in a more expected and balanced order to RTL users: -- so they appear in a more expected and balanced order to RTL users:
-- {JUSTIFY, LEFT, CENTER, RIGHT, AUTO} -- {JUSTIFY, LEFT, CENTER, RIGHT, AUTO}
local j = KoptOptions[3].options[6] local j = KoptOptions[3].options[7]
assert(j.name == "justification") assert(j.name == "justification")
j.item_icons[2], j.item_icons[4] = j.item_icons[4], j.item_icons[2] j.item_icons[2], j.item_icons[4] = j.item_icons[4], j.item_icons[2]
j.values[2], j.values[4] = j.values[4], j.values[2] j.values[2], j.values[4] = j.values[4], j.values[2]

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device") local Device = require("device")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
@ -44,7 +45,7 @@ common_info.about = {
keep_menu_open = true, keep_menu_open = true,
callback = function() callback = function()
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("KOReader %1\n\nA document viewer for E Ink devices.\n\nLicensed under Affero GPL v3. All dependencies are free software.\n\nhttp://koreader.rocks/"), version), text = T(_("KOReader %1\n\nA document viewer for E Ink devices.\n\nLicensed under Affero GPL v3. All dependencies are free software.\n\nhttp://koreader.rocks/"), BD.ltr(version)),
icon_file = "resources/ko-icon.png", icon_file = "resources/ko-icon.png",
alpha = true, alpha = true,
}) })

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local Device = require("device") local Device = require("device")
@ -225,7 +226,7 @@ function NetworkMgr:getProxyMenuTable()
end end
return { return {
text_func = function() text_func = function()
return T(_("HTTP proxy %1"), (proxy_enabled() and proxy() or "")) return T(_("HTTP proxy %1"), (proxy_enabled() and BD.url(proxy()) or ""))
end, end,
checked_func = function() return proxy_enabled() end, checked_func = function() return proxy_enabled() end,
callback = function() callback = function()
@ -388,7 +389,7 @@ function NetworkMgr:reconnectOrShowNetworkMenu(complete_callback)
complete_callback() complete_callback()
end end
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Connected to network %1"), network.ssid), text = T(_("Connected to network %1"), BD.wrap(network.ssid)),
timeout = 3, timeout = 3,
}) })
return return

@ -2,6 +2,7 @@
Checks for updates on the specified nightly build server. Checks for updates on the specified nightly build server.
]] ]]
local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local Device = require("device") local Device = require("device")
@ -243,13 +244,13 @@ function OTAManager:fetchAndProcessUpdate()
}) })
elseif ota_version then elseif ota_version then
local update_message = T(_("Do you want to update?\nInstalled version: %1\nAvailable version: %2"), local update_message = T(_("Do you want to update?\nInstalled version: %1\nAvailable version: %2"),
local_version, BD.ltr(local_version),
ota_version) BD.ltr(ota_version))
local update_ok_text = _("Update") local update_ok_text = _("Update")
if ota_version < local_version then if ota_version < local_version then
update_message = T(_("The currently installed version is newer than the available version.\nWould you still like to continue and downgrade?\nInstalled version: %1\nAvailable version: %2"), update_message = T(_("The currently installed version is newer than the available version.\nWould you still like to continue and downgrade?\nInstalled version: %1\nAvailable version: %2"),
local_version, BD.ltr(local_version),
ota_version) BD.ltr(ota_version))
update_ok_text = _("Downgrade") update_ok_text = _("Downgrade")
end end
@ -262,9 +263,9 @@ function OTAManager:fetchAndProcessUpdate()
if isAndroid then if isAndroid then
-- download the package if not present. -- download the package if not present.
if android.download(link, ota_package) then if android.download(link, ota_package) then
android.notification(T(_("The file %1 already exists."), ota_package)) android.notification(T(_("The file %1 already exists."), BD.filename(ota_package)))
else else
android.notification(T(_("Downloading %1"), ota_package)) android.notification(T(_("Downloading %1"), BD.filename(ota_package)))
end end
elseif Device:isSDL() then elseif Device:isSDL() then
Device:openLink(link) Device:openLink(link)

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local Blitbuffer = require("ffi/blitbuffer") local Blitbuffer = require("ffi/blitbuffer")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local BookStatusWidget = require("ui/widget/bookstatuswidget") local BookStatusWidget = require("ui/widget/bookstatuswidget")
@ -66,7 +67,7 @@ function Screensaver:chooseFolder()
logger.dbg("set screensaver directory to", path) logger.dbg("set screensaver directory to", path)
G_reader_settings:saveSetting("screensaver_dir", path) G_reader_settings:saveSetting("screensaver_dir", path)
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Screensaver directory set to:\n%1"), path), text = T(_("Screensaver directory set to:\n%1"), BD.dirpath(path)),
timeout = 3, timeout = 3,
}) })
end, end,
@ -87,7 +88,7 @@ function Screensaver:chooseFolder()
screensaver_dir = DataStorage:getDataDir() .. "/screenshots/" screensaver_dir = DataStorage:getDataDir() .. "/screenshots/"
end end
self.choose_dialog = ButtonDialogTitle:new{ self.choose_dialog = ButtonDialogTitle:new{
title = T(_("Current screensaver image directory:\n%1"), screensaver_dir), title = T(_("Current screensaver image directory:\n%1"), BD.dirpath(screensaver_dir)),
buttons = buttons buttons = buttons
} }
UIManager:show(self.choose_dialog) UIManager:show(self.choose_dialog)
@ -120,13 +121,13 @@ function Screensaver:chooseFile(document_cover)
if document_cover then if document_cover then
G_reader_settings:saveSetting("screensaver_document_cover", file_path) G_reader_settings:saveSetting("screensaver_document_cover", file_path)
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Screensaver document cover set to:\n%1"), file_path), text = T(_("Screensaver document cover set to:\n%1"), BD.filepath(file_path)),
timeout = 3, timeout = 3,
}) })
else else
G_reader_settings:saveSetting("screensaver_image", file_path) G_reader_settings:saveSetting("screensaver_image", file_path)
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Screensaver image set to:\n%1"), file_path), text = T(_("Screensaver image set to:\n%1"), BD.filepath(file_path)),
timeout = 3, timeout = 3,
}) })
end end
@ -149,8 +150,8 @@ function Screensaver:chooseFile(document_cover)
if screensaver_image == nil then if screensaver_image == nil then
screensaver_image = DataStorage:getDataDir() .. "/resources/koreader.png" screensaver_image = DataStorage:getDataDir() .. "/resources/koreader.png"
end end
local title = document_cover and T(_("Current screensaver document cover:\n%1"), screensaver_document_cover) local title = document_cover and T(_("Current screensaver document cover:\n%1"), BD.filepath(screensaver_document_cover))
or T(_("Current screensaver image:\n%1"), screensaver_image) or T(_("Current screensaver image:\n%1"), BD.filepath(screensaver_image))
self.choose_dialog = ButtonDialogTitle:new{ self.choose_dialog = ButtonDialogTitle:new{
title = title, title = title,
buttons = buttons buttons = buttons

@ -365,14 +365,14 @@ function DictQuickLookup:update()
end end
local epub_path = dir .. "/" .. filename local epub_path = dir .. "/" .. filename
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("Save as %1?"), filename), text = T(_("Save as %1?"), BD.filename(filename)),
ok_callback = function() ok_callback = function()
UIManager:scheduleIn(0.1, function() UIManager:scheduleIn(0.1, function()
local Wikipedia = require("ui/wikipedia") local Wikipedia = require("ui/wikipedia")
Wikipedia:createEpubWithUI(epub_path, self.lookupword, lang, function(success) Wikipedia:createEpubWithUI(epub_path, self.lookupword, lang, function(success)
if success then if success then
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("Article saved to:\n%1\n\nWould you like to read the downloaded article now?"), epub_path), text = T(_("Article saved to:\n%1\n\nWould you like to read the downloaded article now?"), BD.filepath(epub_path)),
ok_callback = function() ok_callback = function()
-- close all dict/wiki windows, without scheduleIn(highlight.clear()) -- close all dict/wiki windows, without scheduleIn(highlight.clear())
self:onHoldClose(true) self:onHoldClose(true)

@ -627,8 +627,7 @@ function Menu:init()
if self.show_path then if self.show_path then
self.path_text = TextWidget:new{ self.path_text = TextWidget:new{
face = Font:getFace("xx_smallinfofont"), face = Font:getFace("xx_smallinfofont"),
text = self.path, text = BD.directory(self.path),
para_direction_rtl = false, -- force LTR
max_width = self.dimen.w - 2*Size.padding.small, max_width = self.dimen.w - 2*Size.padding.small,
truncate_left = true, truncate_left = true,
} }
@ -1035,7 +1034,7 @@ function Menu:updateItems(select_number)
self:updatePageInfo(select_number) self:updatePageInfo(select_number)
if self.show_path then if self.show_path then
self.path_text:setText(self.path) self.path_text:setText(BD.directory(self.path))
end end
UIManager:setDirty(self.show_parent, function() UIManager:setDirty(self.show_parent, function()

@ -30,6 +30,7 @@ Example:
]] ]]
local BD = require("ui/bidi")
local bit = require("bit") local bit = require("bit")
local Blitbuffer = require("ffi/blitbuffer") local Blitbuffer = require("ffi/blitbuffer")
local CenterContainer = require("ui/widget/container/centercontainer") local CenterContainer = require("ui/widget/container/centercontainer")
@ -469,7 +470,7 @@ function NetworkSetting:init()
UIManager:close(self, 'ui', self.dimen) UIManager:close(self, 'ui', self.dimen)
end end
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Connected to network %1"), connected_item.info.ssid), text = T(_("Connected to network %1"), BD.wrap(connected_item.info.ssid)),
timeout = 3, timeout = 3,
}) })
if self.connect_callback then if self.connect_callback then

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ButtonDialog = require("ui/widget/buttondialog") local ButtonDialog = require("ui/widget/buttondialog")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local Cache = require("cache") local Cache = require("cache")
@ -303,7 +304,7 @@ function OPDSBrowser:fetchFeed(item_url, username, password, method)
return return
elseif code == 301 then elseif code == 301 then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("The catalog has been permanently moved. Please update catalog URL to '%1'."), headers['Location']), text = T(_("The catalog has been permanently moved. Please update catalog URL to '%1'."), BD.url(headers['Location'])),
}) })
elseif code == 401 then elseif code == 401 then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
@ -355,7 +356,7 @@ function OPDSBrowser:getCatalog(item_url, username, password)
elseif not ok and catalog then elseif not ok and catalog then
logger.info("cannot get catalog info from", item_url, catalog) logger.info("cannot get catalog info from", item_url, catalog)
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Cannot get catalog info from %1"), (item_url or "")), text = T(_("Cannot get catalog info from %1"), (BD.url(item_url) or "")),
}) })
return return
end end
@ -545,7 +546,7 @@ function OPDSBrowser:downloadFile(item, format, remote_url)
end end
else else
UIManager:show(InfoMessage:new { UIManager:show(InfoMessage:new {
text = _("Could not save file to:\n") .. local_path, text = _("Could not save file to:\n") .. BD.filepath(local_path),
timeout = 3, timeout = 3,
}) })
end end
@ -559,7 +560,7 @@ function OPDSBrowser:downloadFile(item, format, remote_url)
if lfs.attributes(local_path, "mode") == "file" then if lfs.attributes(local_path, "mode") == "file" then
UIManager:show(ConfirmBox:new { UIManager:show(ConfirmBox:new {
text = T(_("The file %1 already exists. Do you want to overwrite it?"), local_path), text = T(_("The file %1 already exists. Do you want to overwrite it?"), BD.filepath(local_path)),
ok_text = _("Overwrite"), ok_text = _("Overwrite"),
ok_callback = function() ok_callback = function()
download() download()
@ -572,7 +573,7 @@ end
function OPDSBrowser:createNewDownloadDialog(path, buttons) function OPDSBrowser:createNewDownloadDialog(path, buttons)
self.download_dialog = ButtonDialogTitle:new{ self.download_dialog = ButtonDialogTitle:new{
title = T(_("Download directory:\n%1\n\nDownload file type:"), path), title = T(_("Download directory:\n%1\n\nDownload file type:"), BD.dirpath(path)),
buttons = buttons buttons = buttons
} }
end end

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local FileChooser = require("ui/widget/filechooser") local FileChooser = require("ui/widget/filechooser")
local Font = require("ui/font") local Font = require("ui/font")
@ -104,14 +105,14 @@ function PathChooser:onMenuHold(item)
local filesize = util.getFormattedSize(attr.size) local filesize = util.getFormattedSize(attr.size)
local lastmod = os.date("%Y-%m-%d %H:%M", attr.modification) local lastmod = os.date("%Y-%m-%d %H:%M", attr.modification)
title = T(_("Select this file?\n\n%1\n\nFile size: %2 bytes\nLast modified: %3"), title = T(_("Select this file?\n\n%1\n\nFile size: %2 bytes\nLast modified: %3"),
path, filesize, lastmod) BD.filepath(path), filesize, lastmod)
else else
title = T(_("Select this file?\n\n%1"), path) title = T(_("Select this file?\n\n%1"), BD.filepath(path))
end end
elseif attr.mode == "directory" then elseif attr.mode == "directory" then
title = T(_("Select this directory?\n\n%1"), path) title = T(_("Select this directory?\n\n%1"), BD.dirpath(path))
else -- just in case we get something else else -- just in case we get something else
title = T(_("Select this path?\n\n%1"), path) title = T(_("Select this path?\n\n%1"), BD.path(path))
end end
local onConfirm = self.onConfirm local onConfirm = self.onConfirm
self.button_dialog = ButtonDialogTitle:new{ self.button_dialog = ButtonDialogTitle:new{

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
@ -42,7 +43,7 @@ function Screenshoter:onScreenshot(filename)
local screenshot_name = filename or os.date(self.screenshot_fn_fmt) local screenshot_name = filename or os.date(self.screenshot_fn_fmt)
Screen:shot(screenshot_name) Screen:shot(screenshot_name)
local widget = ConfirmBox:new{ local widget = ConfirmBox:new{
text = T( _("Saved screenshot to %1.\nWould you like to set it as screensaver?"), screenshot_name), text = T( _("Saved screenshot to %1.\nWould you like to set it as screensaver?"), BD.filepath(screenshot_name)),
ok_text = _("Yes"), ok_text = _("Yes"),
ok_callback = function() ok_callback = function()
G_reader_settings:saveSetting("screensaver_type", "image_file") G_reader_settings:saveSetting("screensaver_type", "image_file")
@ -67,7 +68,7 @@ function Screenshoter:chooseFolder()
onConfirm = function(path) onConfirm = function(path)
G_reader_settings:saveSetting("screenshot_dir", path .. "/") G_reader_settings:saveSetting("screenshot_dir", path .. "/")
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Screenshot directory set to:\n%1"), path), text = T(_("Screenshot directory set to:\n%1"), BD.dirpath(path)),
timeout = 3, timeout = 3,
}) })
end, end,
@ -85,7 +86,7 @@ function Screenshoter:chooseFolder()
}) })
local screenshot_dir = G_reader_settings:readSetting("screenshot_dir") or DataStorage:getDataDir() .. "/screenshots/" local screenshot_dir = G_reader_settings:readSetting("screenshot_dir") or DataStorage:getDataDir() .. "/screenshots/"
self.choose_dialog = ButtonDialogTitle:new{ self.choose_dialog = ButtonDialogTitle:new{
title = T(_("Current screenshot directory:\n%1"), screenshot_dir), title = T(_("Current screenshot directory:\n%1"), BD.dirpath(screenshot_dir)),
buttons = buttons buttons = buttons
} }
UIManager:show(self.choose_dialog) UIManager:show(self.choose_dialog)

@ -182,8 +182,16 @@ function SortWidget:init()
self.item_height = Size.item.height_big self.item_height = Size.item.height_big
-- group for footer -- group for footer
local footer_left_text = ""
local footer_right_text = ""
local footer_first_up_text = "◀◀"
local footer_last_down_text = "▶▶"
if BD.mirroredUILayout() then
footer_left_text, footer_right_text = footer_right_text, footer_left_text
footer_first_up_text, footer_last_down_text = footer_last_down_text, footer_first_up_text
end
self.footer_left = Button:new{ self.footer_left = Button:new{
text = "", text = footer_left_text,
width = self.width_widget * 13 / 100, width = self.width_widget * 13 / 100,
callback = function() self:prevPage() end, callback = function() self:prevPage() end,
text_font_size = 28, text_font_size = 28,
@ -192,7 +200,7 @@ function SortWidget:init()
radius = 0, radius = 0,
} }
self.footer_right = Button:new{ self.footer_right = Button:new{
text = "", text = footer_right_text,
width = self.width_widget * 13 / 100, width = self.width_widget * 13 / 100,
callback = function() self:nextPage() end, callback = function() self:nextPage() end,
text_font_size = 28, text_font_size = 28,
@ -201,7 +209,7 @@ function SortWidget:init()
radius = 0, radius = 0,
} }
self.footer_first_up = Button:new{ self.footer_first_up = Button:new{
text = "◀◀", text = footer_first_up_text,
width = self.width_widget * 13 / 100, width = self.width_widget * 13 / 100,
callback = function() callback = function()
if self.marked > 0 then if self.marked > 0 then
@ -216,7 +224,7 @@ function SortWidget:init()
radius = 0, radius = 0,
} }
self.footer_last_down = Button:new{ self.footer_last_down = Button:new{
text = "▶▶", text = footer_last_down_text,
width = self.width_widget * 13 / 100, width = self.width_widget * 13 / 100,
callback = function() callback = function()
if self.marked > 0 then if self.marked > 0 then
@ -415,13 +423,18 @@ function SortWidget:_populateItems()
) )
end end
self.footer_page:setText(T(_("%1/%2"), self.show_page, self.pages), self.width_widget * 22 / 100) self.footer_page:setText(T(_("%1 / %2"), self.show_page, self.pages), self.width_widget * 22 / 100)
local footer_first_up_text = "◀◀"
local footer_last_down_text = "▶▶"
if BD.mirroredUILayout() then
footer_first_up_text, footer_last_down_text = footer_last_down_text, footer_first_up_text
end
if self.marked > 0 then if self.marked > 0 then
self.footer_first_up:setText("", self.width_widget * 13 / 100) self.footer_first_up:setText("", self.width_widget * 13 / 100)
self.footer_last_down:setText("", self.width_widget * 13 / 100) self.footer_last_down:setText("", self.width_widget * 13 / 100)
else else
self.footer_first_up:setText("◀◀", self.width_widget * 13 / 100) self.footer_first_up:setText(footer_first_up_text, self.width_widget * 13 / 100)
self.footer_last_down:setText("▶▶", self.width_widget * 13 / 100) self.footer_last_down:setText(footer_last_down_text, self.width_widget * 13 / 100)
end end
self.footer_left:enableDisable(self.show_page > 1) self.footer_left:enableDisable(self.show_page > 1)
self.footer_right:enableDisable(self.show_page < self.pages) self.footer_right:enableDisable(self.show_page < self.pages)

@ -954,14 +954,15 @@ end
function util.unpackArchive(archive, extract_to) function util.unpackArchive(archive, extract_to)
dbg.dassert(type(archive) == "string") dbg.dassert(type(archive) == "string")
local BD = require("ui/bidi")
local ok local ok
if archive:match("%.tar%.bz2$") or archive:match("%.tar%.gz$") or archive:match("%.tar%.lz$") or archive:match("%.tgz$") then if archive:match("%.tar%.bz2$") or archive:match("%.tar%.gz$") or archive:match("%.tar%.lz$") or archive:match("%.tgz$") then
ok = os.execute(("./tar xf %q -C %q"):format(archive, extract_to)) ok = os.execute(("./tar xf %q -C %q"):format(archive, extract_to))
else else
return false, T(_("Couldn't extract archive:\n\n%1\n\nUnrecognized filename extension."), archive) return false, T(_("Couldn't extract archive:\n\n%1\n\nUnrecognized filename extension."), BD.filepath(archive))
end end
if not ok then if not ok then
return false, T(_("Extracting archive failed:\n\n%1", archive)) return false, T(_("Extracting archive failed:\n\n%1", BD.filepath(archive)))
end end
return true return true
end end

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local Device = require("device") local Device = require("device")
local InfoMessage = require("ui/widget/infomessage") -- luacheck:ignore local InfoMessage = require("ui/widget/infomessage") -- luacheck:ignore
@ -178,7 +179,7 @@ function SSH:addToMainMenu(menu_items)
callback = function() callback = function()
local info = InfoMessage:new{ local info = InfoMessage:new{
timeout = 60, timeout = 60,
text = T(_("Put your public SSH keys in %1"), path.."/settings/SSH/authorized_keys"), text = T(_("Put your public SSH keys in %1"), BD.filepath(path.."/settings/SSH/authorized_keys")),
} }
UIManager:show(info) UIManager:show(info)
end, end,

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
@ -127,7 +128,7 @@ function CalibreCompanion:addToMainMenu(menu_items)
address = G_reader_settings:readSetting("calibre_wireless_url") address = G_reader_settings:readSetting("calibre_wireless_url")
address = string.format("%s:%s", address["address"], address["port"]) address = string.format("%s:%s", address["address"], address["port"])
end end
return T(_("Server address (%1)"), address) return T(_("Server address (%1)"), BD.ltr(address))
end, end,
sub_item_table = { sub_item_table = {
{ {
@ -214,7 +215,7 @@ function CalibreCompanion:initCalibreMQ(host, port)
self:onReceiveJSON(data) self:onReceiveJSON(data)
if not self.connect_message then if not self.connect_message then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Connected to calibre server at %1:%2"), host, port), text = T(_("Connected to calibre server at %1"), BD.ltr(T("%1:%2", host, port))),
}) })
self.connect_message = true self.connect_message = true
if self.failed_connect_callback then if self.failed_connect_callback then
@ -467,7 +468,7 @@ function CalibreCompanion:sendBook(arg)
outfile:close() outfile:close()
logger.info("complete writing file", filename) logger.info("complete writing file", filename)
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = _("Received file:") .. filename, text = _("Received file:") .. BD.filepath(filename),
timeout = 1, timeout = 1,
}) })
-- switch to JSON data receiving mode -- switch to JSON data receiving mode

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local Blitbuffer = require("ffi/blitbuffer") local Blitbuffer = require("ffi/blitbuffer")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local Device = require("device") local Device = require("device")
@ -844,7 +845,7 @@ Do you want to prune the cache of removed books?]]
local orig_moved_offset = info.movable:getMovedOffset() local orig_moved_offset = info.movable:getMovedOffset()
info:free() info:free()
info.text = T(_("Indexing %1 / %2…\n\n%3"), i, nb_files, filename) info.text = T(_("Indexing %1 / %2…\n\n%3"), i, nb_files, BD.filename(filename))
info:init() info:init()
local text_widget = table.remove(info.movable[1][1], 3) local text_widget = table.remove(info.movable[1][1], 3)
local text_widget_size = text_widget:getSize() local text_widget_size = text_widget:getSize()

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local DocumentRegistry = require("document/documentregistry") local DocumentRegistry = require("document/documentregistry")
local DocSettings = require("docsettings") local DocSettings = require("docsettings")
local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo")
@ -95,7 +96,7 @@ function CoverMenu:updateItems(select_number)
self:updatePageInfo(select_number) self:updatePageInfo(select_number)
if self.show_path then if self.show_path then
self.path_text:setText(self.path) self.path_text:setText(BD.directory(self.path))
end end
self.show_parent.dithered = self._has_cover_images self.show_parent.dithered = self._has_cover_images
UIManager:setDirty(self.show_parent, function() UIManager:setDirty(self.show_parent, function()

@ -230,7 +230,7 @@ function ListMenuItem:update()
local pad_width = Screen:scaleBySize(10) -- on the left, in between, and on the right local pad_width = Screen:scaleBySize(10) -- on the left, in between, and on the right
local wleft_width = dimen.w - wright:getWidth() - 3*pad_width local wleft_width = dimen.w - wright:getWidth() - 3*pad_width
local wleft = TextBoxWidget:new{ local wleft = TextBoxWidget:new{
text = self.text, text = BD.directory(self.text),
face = Font:getFace("cfont", _fontSize(20)), face = Font:getFace("cfont", _fontSize(20)),
width = wleft_width, width = wleft_width,
alignment = "left", alignment = "left",
@ -479,17 +479,26 @@ function ListMenuItem:update()
local series_mode = BookInfoManager:getSetting("series_mode") local series_mode = BookInfoManager:getSetting("series_mode")
-- whether to use or not title and authors -- whether to use or not title and authors
-- (We wrap each metadata text with BD.auto() to get for each of them
-- the text direction from the first strong character - which should
-- individually be the best thing, and additionnaly prevent shuffling
-- if concatenated.)
if self.do_filename_only or bookinfo.ignore_meta then if self.do_filename_only or bookinfo.ignore_meta then
title = filename_without_suffix -- made out above title = filename_without_suffix -- made out above
title = BD.auto(title)
authors = nil authors = nil
else else
title = bookinfo.title and bookinfo.title or filename_without_suffix title = bookinfo.title and bookinfo.title or filename_without_suffix
title = BD.auto(title)
authors = bookinfo.authors authors = bookinfo.authors
-- If multiple authors (crengine separates them with \n), we -- If multiple authors (crengine separates them with \n), we
-- can display them on multiple lines, but limit to 2, and -- can display them on multiple lines, but limit to 2, and
-- append "et al." to the 2nd if there are more -- append "et al." to the 2nd if there are more
if authors and authors:find("\n") then if authors and authors:find("\n") then
authors = util.splitToArray(authors, "\n") authors = util.splitToArray(authors, "\n")
for i=1, #authors do
authors[i] = BD.auto(authors[i])
end
if #authors > 1 and bookinfo.series and series_mode == "series_in_separate_line" then if #authors > 1 and bookinfo.series and series_mode == "series_in_separate_line" then
authors = { T(_("%1 et al."), authors[1]) } authors = { T(_("%1 et al."), authors[1]) }
elseif #authors > 2 then elseif #authors > 2 then
@ -499,12 +508,15 @@ function ListMenuItem:update()
-- as we'll fit 3 lines instead of 2, we can avoid some loops by starting from a lower font size -- as we'll fit 3 lines instead of 2, we can avoid some loops by starting from a lower font size
fontsize_title = _fontSize(17) fontsize_title = _fontSize(17)
fontsize_authors = _fontSize(15) fontsize_authors = _fontSize(15)
elseif authors then
authors = BD.auto(authors)
end end
end end
-- add Series metadata if requested -- add Series metadata if requested
if bookinfo.series then if bookinfo.series then
-- Shorten calibre series decimal number (#4.0 => #4) -- Shorten calibre series decimal number (#4.0 => #4)
bookinfo.series = bookinfo.series:gsub("(#%d+)%.0$", "%1") bookinfo.series = bookinfo.series:gsub("(#%d+)%.0$", "%1")
bookinfo.series = BD.auto(bookinfo.series)
if series_mode == "append_series_to_title" then if series_mode == "append_series_to_title" then
if title then if title then
title = title .. " - " .. bookinfo.series title = title .. " - " .. bookinfo.series
@ -645,6 +657,7 @@ function ListMenuItem:update()
if self.file_deleted then -- unless file was deleted (can happen with History) if self.file_deleted then -- unless file was deleted (can happen with History)
hint = " " .. _("(deleted)") hint = " " .. _("(deleted)")
end end
local text = BD.filename(self.text)
local text_widget local text_widget
local fontsize_no_bookinfo = _fontSize(18) local fontsize_no_bookinfo = _fontSize(18)
repeat repeat
@ -652,7 +665,7 @@ function ListMenuItem:update()
text_widget:free() text_widget:free()
end end
text_widget = TextBoxWidget:new{ text_widget = TextBoxWidget:new{
text = self.text .. hint, text = text .. hint,
face = Font:getFace("cfont", fontsize_no_bookinfo), face = Font:getFace("cfont", fontsize_no_bookinfo),
width = dimen.w - 2 * Screen:scaleBySize(10), width = dimen.w - 2 * Screen:scaleBySize(10),
alignment = "left", alignment = "left",

@ -99,10 +99,15 @@ local FakeCover = FrameContainer:new{
padding = 0, padding = 0,
bordersize = Size.line.thin, bordersize = Size.line.thin,
dim = nil, dim = nil,
-- Provided filename, title and authors should not be BD wrapped
filename = nil, filename = nil,
file_deleted = nil, file_deleted = nil,
title = nil, title = nil,
authors = nil, authors = nil,
-- The *_add should be provided BD wrapped if needed
filename_add = nil,
title_add = nil,
authors_add = nil,
-- these font sizes will be scaleBySize'd by Font:getFace() -- these font sizes will be scaleBySize'd by Font:getFace()
authors_font_max = 20, authors_font_max = 20,
authors_font_min = 6, authors_font_min = 6,
@ -123,14 +128,25 @@ function FakeCover:init()
local title = self.title local title = self.title
local filename = self.filename local filename = self.filename
-- (some engines may have already given filename (without extension) as title) -- (some engines may have already given filename (without extension) as title)
local bd_wrap_title_as_filename = false
if not title then -- use filename as title (big and centered) if not title then -- use filename as title (big and centered)
title = filename title = filename
filename = nil filename = nil
if not self.title_add and self.filename_add then
-- filename_add ("…" or "(deleted)") always comes without any title_add
self.title_add = self.filename_add
self.filename_add = nil
end
bd_wrap_title_as_filename = true
end
if filename then
filename = BD.filename(filename)
end end
-- If no authors, and title is filename without extension, it was -- If no authors, and title is filename without extension, it was
-- probably made by an engine, and we can consider it a filename, and -- probably made by an engine, and we can consider it a filename, and
-- act according to common usage in naming files. -- act according to common usage in naming files.
if not authors and title and self.filename:sub(1,title:len()) == title then if not authors and title and self.filename and self.filename:sub(1,title:len()) == title then
bd_wrap_title_as_filename = true
-- Replace a hyphen surrounded by spaces (which most probably was -- Replace a hyphen surrounded by spaces (which most probably was
-- used to separate Authors/Title/Serie/Year/Categorie in the -- used to separate Authors/Title/Serie/Year/Categorie in the
-- filename with a \n -- filename with a \n
@ -150,16 +166,35 @@ function FakeCover:init()
-- together on a last line: so, move the zero-width-space -- together on a last line: so, move the zero-width-space
-- before it. -- before it.
title = title:gsub("%.\xE2\x80\x8B(%w%w?%w?%w?%w?)$", "\xE2\x80\x8B.%1") title = title:gsub("%.\xE2\x80\x8B(%w%w?%w?%w?%w?)$", "\xE2\x80\x8B.%1")
-- These substitutions will hopefully have no impact with the following BD wrapping
end
if title then
title = bd_wrap_title_as_filename and BD.filename(title) or BD.auto(title)
end end
-- If multiple authors (crengine separates them with \n), we -- If multiple authors (crengine separates them with \n), we
-- can display them on multiple lines, but limit to 3, and -- can display them on multiple lines, but limit to 3, and
-- append "et al." on a 4th line if there are more -- append "et al." on a 4th line if there are more
if authors and authors:find("\n") then if authors and authors:find("\n") then
authors = util.splitToArray(authors, "\n") authors = util.splitToArray(authors, "\n")
for i=1, #authors do
authors[i] = BD.auto(authors[i])
end
if #authors > 3 then if #authors > 3 then
authors = { authors[1], authors[2], T(_("%1 et al."), authors[3]) } authors = { authors[1], authors[2], T(_("%1 et al."), authors[3]) }
end end
authors = table.concat(authors, "\n") authors = table.concat(authors, "\n")
elseif authors then
authors = BD.auto(authors)
end
-- Add any _add, which must be already BD wrapped if needed
if self.filename_add then
filename = (filename and filename or "") .. self.filename_add
end
if self.title_add then
title = (title and title or "") .. self.title_add
end
if self.authors_add then
authors = (authors and authors or "") .. self.authors_add
end end
-- We build the VerticalGroup widget with decreasing font sizes till -- We build the VerticalGroup widget with decreasing font sizes till
@ -207,7 +242,7 @@ function FakeCover:init()
end end
if filename then if filename then
filename_wg = TextBoxWidget:new{ filename_wg = TextBoxWidget:new{
text = BD.filename(filename), text = filename,
face = Font:getFace("cfont", math.max(self.filename_font_max - sizedec, self.filename_font_min)), face = Font:getFace("cfont", math.max(self.filename_font_max - sizedec, self.filename_font_min)),
width = text_width, width = text_width,
alignment = "center", alignment = "center",
@ -429,6 +464,7 @@ function MosaicMenuItem:update()
if text:match('/$') then -- remove /, more readable if text:match('/$') then -- remove /, more readable
text = text:sub(1, -2) text = text:sub(1, -2)
end end
text = BD.directory(text)
local nbitems = TextBoxWidget:new{ local nbitems = TextBoxWidget:new{
text = self.mandatory, text = self.mandatory,
face = Font:getFace("infont", 15), face = Font:getFace("infont", 15),
@ -553,25 +589,27 @@ function MosaicMenuItem:update()
else else
-- add Series metadata if requested -- add Series metadata if requested
local series_mode = BookInfoManager:getSetting("series_mode") local series_mode = BookInfoManager:getSetting("series_mode")
local title_add, authors_add
if bookinfo.series then if bookinfo.series then
-- Shorten calibre series decimal number (#4.0 => #4) -- Shorten calibre series decimal number (#4.0 => #4)
bookinfo.series = bookinfo.series:gsub("(#%d+)%.0$", "%1") bookinfo.series = bookinfo.series:gsub("(#%d+)%.0$", "%1")
bookinfo.series = BD.auto(bookinfo.series)
if series_mode == "append_series_to_title" then if series_mode == "append_series_to_title" then
if bookinfo.title then if bookinfo.title then
bookinfo.title = bookinfo.title .. " - " .. bookinfo.series title_add = " - " .. bookinfo.series
else else
bookinfo.title = bookinfo.series title_add = bookinfo.series
end end
end end
if not bookinfo.authors then if not bookinfo.authors then
if series_mode == "append_series_to_authors" or series_mode == "series_in_separate_line" then if series_mode == "append_series_to_authors" or series_mode == "series_in_separate_line" then
bookinfo.authors = bookinfo.series authors_add = bookinfo.series
end end
else else
if series_mode == "append_series_to_authors" then if series_mode == "append_series_to_authors" then
bookinfo.authors = bookinfo.authors .. " - " .. bookinfo.series authors_add = " - " .. bookinfo.series
elseif series_mode == "series_in_separate_line" then elseif series_mode == "series_in_separate_line" then
bookinfo.authors = bookinfo.authors .. "\n \n" .. bookinfo.series authors_add = "\n \n" .. bookinfo.series
end end
end end
end end
@ -585,6 +623,8 @@ function MosaicMenuItem:update()
filename = self.text, filename = self.text,
title = not bookinfo.ignore_meta and bookinfo.title, title = not bookinfo.ignore_meta and bookinfo.title,
authors = not bookinfo.ignore_meta and bookinfo.authors, authors = not bookinfo.ignore_meta and bookinfo.authors,
title_add = not bookinfo.ignore_meta and title_add,
authors_add = not bookinfo.ignore_meta and authors_add,
file_deleted = self.file_deleted, file_deleted = self.file_deleted,
} }
} }
@ -622,7 +662,8 @@ function MosaicMenuItem:update()
width = dimen.w, width = dimen.w,
height = dimen.h, height = dimen.h,
bordersize = border_size, bordersize = border_size,
filename = self.text .. "\n" .. hint, filename = self.text,
filename_add = "\n" .. hint,
initial_sizedec = 4, -- start with a smaller font when filenames only initial_sizedec = 4, -- start with a smaller font when filenames only
file_deleted = self.file_deleted, file_deleted = self.file_deleted,
} }

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local FFIUtil = require("ffi/util") local FFIUtil = require("ffi/util")
local InputDialog = require("ui/widget/inputdialog") local InputDialog = require("ui/widget/inputdialog")
@ -48,7 +49,7 @@ function DocSettingTweak:editDirectoryDefaults()
directory_defaults_file:close() directory_defaults_file:close()
local config_editor local config_editor
config_editor = InputDialog:new{ config_editor = InputDialog:new{
title = T(_("Directory Defaults: %1"),directory_defaults_path), title = T(_("Directory Defaults: %1"), BD.filepath(directory_defaults_path)),
input = defaults, input = defaults,
input_type = "string", input_type = "string",
para_direction_rtl = false, -- force LTR para_direction_rtl = false, -- force LTR

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local LoginDialog = require("ui/widget/logindialog") local LoginDialog = require("ui/widget/logindialog")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
@ -242,7 +243,7 @@ For Windows: netsh interface portproxy add listeningaddress:0.0.0.0 listeningpor
For Linux: $socat tcp-listen:41185,reuseaddr,fork tcp:localhost:41184 For Linux: $socat tcp-listen:41185,reuseaddr,fork tcp:localhost:41184
For more information, please visit https://github.com/koreader/koreader/wiki/Evernote-export.]]) For more information, please visit https://github.com/koreader/koreader/wiki/Evernote-export.]])
,DataStorage:getDataDir()) , BD.dirpath(DataStorage:getDataDir()))
}) })
end end
} }
@ -595,7 +596,7 @@ function EvernoteExporter:exportClippings(clippings)
end end
end end
if (self.html_export or self.txt_export) and export_count > 0 then if (self.html_export or self.txt_export) and export_count > 0 then
msg = msg .. T(_("\nNotes can be found in %1/."), realpath(self.clipping_dir)) msg = msg .. T(_("\nNotes can be found in %1/."), BD.dirpath(realpath(self.clipping_dir)))
end end
UIManager:show(InfoMessage:new{ text = msg }) UIManager:show(InfoMessage:new{ text = msg })
end end

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
--local DownloadBackend = require("internaldownloadbackend") --local DownloadBackend = require("internaldownloadbackend")
--local DownloadBackend = require("luahttpdownloadbackend") --local DownloadBackend = require("luahttpdownloadbackend")
@ -125,7 +126,7 @@ function NewsDownloader:addToMainMenu(menu_items)
callback = function() callback = function()
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("News downloader retrieves RSS and Atom news entries and stores them to:\n%1\n\nEach entry is a separate html file, that can be browsed by KOReader file manager.\nItems download limit can be configured in Settings."), text = T(_("News downloader retrieves RSS and Atom news entries and stores them to:\n%1\n\nEach entry is a separate html file, that can be browsed by KOReader file manager.\nItems download limit can be configured in Settings."),
news_download_dir_path) BD.dirpath(news_download_dir_path))
}) })
end, end,
}, },
@ -183,7 +184,7 @@ function NewsDownloader:loadConfigAndProcessFeeds()
local download_full_article = feed.download_full_article == nil or feed.download_full_article local download_full_article = feed.download_full_article == nil or feed.download_full_article
local include_images = feed.include_images local include_images = feed.include_images
if url and limit then if url and limit then
local feed_message = T(_("Processing %1/%2:\n%3"), idx, total_feed_entries, url) local feed_message = T(_("Processing %1/%2:\n%3"), idx, total_feed_entries, BD.url(url))
UI:info(feed_message) UI:info(feed_message)
NewsDownloader:processFeedSource(url, tonumber(limit), unsupported_feeds_urls, download_full_article, include_images, feed_message) NewsDownloader:processFeedSource(url, tonumber(limit), unsupported_feeds_urls, download_full_article, include_images, feed_message)
else else
@ -198,7 +199,7 @@ function NewsDownloader:loadConfigAndProcessFeeds()
for k,url in pairs(unsupported_feeds_urls) do for k,url in pairs(unsupported_feeds_urls) do
unsupported_urls = unsupported_urls .. url unsupported_urls = unsupported_urls .. url
if k ~= #unsupported_feeds_urls then if k ~= #unsupported_feeds_urls then
unsupported_urls = unsupported_urls .. ", " unsupported_urls = BD.url(unsupported_urls) .. ", "
end end
end end
UI:info(T(_("Downloading news finished. Could not process some feeds. Unsupported format in: %1"), unsupported_urls)) UI:info(T(_("Downloading news finished. Could not process some feeds. Unsupported format in: %1"), unsupported_urls))
@ -408,7 +409,7 @@ function NewsDownloader:changeFeedConfig()
feed_config_file:close() feed_config_file:close()
local config_editor local config_editor
config_editor = InputDialog:new{ config_editor = InputDialog:new{
title = T(_("Config: %1"),feed_config_path), title = T(_("Config: %1"), BD.filepath(feed_config_path)),
input = config, input = config,
input_type = "string", input_type = "string",
para_direction_rtl = false, -- force LTR para_direction_rtl = false, -- force LTR

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local DocSettings = require("frontend/docsettings") local DocSettings = require("frontend/docsettings")
local ReadHistory = require("readhistory") local ReadHistory = require("readhistory")
@ -103,7 +104,7 @@ function Send2Ebook:addToMainMenu(menu_items)
keep_menu_open = true, keep_menu_open = true,
callback = function() callback = function()
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_('Send2Ebook lets you send articles found on PC/Android phone to your Ebook reader (using ftp server).\n\nMore details: https://github.com/mwoz123/send2ebook\n\nDownloads to local folder: %1'), download_dir_path) text = T(_('Send2Ebook lets you send articles found on PC/Android phone to your Ebook reader (using ftp server).\n\nMore details: https://github.com/mwoz123/send2ebook\n\nDownloads to local folder: %1'), BD.dirpath(download_dir_path))
}) })
end, end,
}, },
@ -143,7 +144,7 @@ function Send2Ebook:process()
local ftp_files_table = FtpApi:listFolder(connection_url .. ftp_config.folder, ftp_config.folder) --args looks strange but otherwise resonse with invalid paths local ftp_files_table = FtpApi:listFolder(connection_url .. ftp_config.folder, ftp_config.folder) --args looks strange but otherwise resonse with invalid paths
if not ftp_files_table then if not ftp_files_table then
info = InfoMessage:new{ text = T(_("Could not get file list for server: %1, user: %2, folder: %3"), ftp_config.address, ftp_config.username, ftp_config.folder) } info = InfoMessage:new{ text = T(_("Could not get file list for server: %1, user: %2, folder: %3"), BD.ltr(ftp_config.address), ftp_config.username, BD.dirpath(ftp_config.folder)) }
else else
local total_entries = table.getn(ftp_files_table) local total_entries = table.getn(ftp_files_table)
logger.dbg("Send2Ebook: total_entries ", total_entries) logger.dbg("Send2Ebook: total_entries ", total_entries)

@ -1,3 +1,4 @@
local BD = require("ui/bidi")
local BookStatusWidget = require("ui/widget/bookstatuswidget") local BookStatusWidget = require("ui/widget/bookstatuswidget")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
@ -235,7 +236,7 @@ Cannot open database in %1.
The database may have been moved or deleted. The database may have been moved or deleted.
Do you want to create an empty database? Do you want to create an empty database?
]]), ]]),
db_location), BD.filepath(db_location)),
cancel_text = _("Close"), cancel_text = _("Close"),
cancel_callback = function() cancel_callback = function()
return return

@ -171,7 +171,7 @@ Enable this if you are mostly editing code, HTML, CSS…]]),
local file_path = self.history[i] local file_path = self.history[i]
local directory, filename = util.splitFilePathName(file_path) -- luacheck: no unused local directory, filename = util.splitFilePathName(file_path) -- luacheck: no unused
table.insert(sub_item_table, { table.insert(sub_item_table, {
text = T("%1. %2", i, filename), text = T("%1. %2", i, BD.filename(filename)),
keep_menu_open = true, keep_menu_open = true,
callback = function(touchmenu_instance) callback = function(touchmenu_instance)
self:setupWhenDoneFunc(touchmenu_instance) self:setupWhenDoneFunc(touchmenu_instance)
@ -186,10 +186,10 @@ Enable this if you are mostly editing code, HTML, CSS…]]),
local filesize = util.getFormattedSize(attr.size) local filesize = util.getFormattedSize(attr.size)
local lastmod = os.date("%Y-%m-%d %H:%M", attr.modification) local lastmod = os.date("%Y-%m-%d %H:%M", attr.modification)
text = T(_("File path:\n%1\n\nFile size: %2 bytes\nLast modified: %3\n\nRemove this file from text editor history?"), text = T(_("File path:\n%1\n\nFile size: %2 bytes\nLast modified: %3\n\nRemove this file from text editor history?"),
file_path, filesize, lastmod) BD.filepath(file_path), filesize, lastmod)
else else
text = T(_("File path:\n%1\n\nThis file does not exist anymore.\n\nRemove it from text editor history?"), text = T(_("File path:\n%1\n\nThis file does not exist anymore.\n\nRemove it from text editor history?"),
file_path) BD.filepath(file_path))
end end
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = text, text = text,
@ -332,7 +332,7 @@ function TextEditor:checkEditFile(file_path, from_history, possibly_new_file)
local attr = lfs.attributes(file_path) local attr = lfs.attributes(file_path)
if not possibly_new_file and not attr then if not possibly_new_file and not attr then
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("This file does not exist anymore:\n\n%1\n\nDo you want to create it and start editing it?"), file_path), text = T(_("This file does not exist anymore:\n\n%1\n\nDo you want to create it and start editing it?"), BD.filepath(file_path)),
ok_text = _("Yes"), ok_text = _("Yes"),
cancel_text = _("No"), cancel_text = _("No"),
ok_callback = function() ok_callback = function()
@ -350,7 +350,7 @@ function TextEditor:checkEditFile(file_path, from_history, possibly_new_file)
if attr then -- File exists if attr then -- File exists
if attr.mode ~= "file" then if attr.mode ~= "file" then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("This file is not a regular file:\n\n%1"), file_path) text = T(_("This file is not a regular file:\n\n%1"), BD.filepath(file_path))
}) })
return return
end end
@ -368,7 +368,7 @@ function TextEditor:checkEditFile(file_path, from_history, possibly_new_file)
if not from_history and attr.size > self.min_file_size_warn then if not from_history and attr.size > self.min_file_size_warn then
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = T(_("This file is %2:\n\n%1\n\nAre you sure you want to open it?\n\nOpening big files may take some time."), text = T(_("This file is %2:\n\n%1\n\nAre you sure you want to open it?\n\nOpening big files may take some time."),
file_path, util.getFriendlySize(attr.size)), BD.filepath(file_path), util.getFriendlySize(attr.size)),
ok_text = _("Yes"), ok_text = _("Yes"),
cancel_text = _("No"), cancel_text = _("No"),
ok_callback = function() ok_callback = function()
@ -389,7 +389,7 @@ function TextEditor:checkEditFile(file_path, from_history, possibly_new_file)
self:editFile(file_path) self:editFile(file_path)
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("This file can not be created:\n\n%1\n\nReason: %2"), file_path, err) text = T(_("This file can not be created:\n\n%1\n\nReason: %2"), BD.filepath(file_path), err)
}) })
return return
end end
@ -526,7 +526,7 @@ Lua syntax check failed:
KOReader may crash if this is saved. KOReader may crash if this is saved.
Do you really want to save to this file? Do you really want to save to this file?
%2]]), parse_error, file_path), _("Do not save"), _("Save anyway")) %2]]), parse_error, BD.filepath(file_path)), _("Do not save"), _("Save anyway"))
-- we'll get the safer "Do not save" on tap outside -- we'll get the safer "Do not save" on tap outside
if save_anyway then if save_anyway then
local ok, err = self:saveFileContent(file_path, content) local ok, err = self:saveFileContent(file_path, content)
@ -543,7 +543,7 @@ Do you really want to save to this file?
Text content is empty. Text content is empty.
Do you want to keep this file as empty, or do you prefer to delete it? Do you want to keep this file as empty, or do you prefer to delete it?
%1]]), file_path), _("Keep empty file"), _("Delete file")) %1]]), BD.filepath(file_path)), _("Keep empty file"), _("Delete file"))
-- we'll get the safer "Keep empty file" on tap outside -- we'll get the safer "Keep empty file" on tap outside
if delete_file then if delete_file then
local ok, err = self:deleteFile(file_path) local ok, err = self:deleteFile(file_path)

@ -2,6 +2,7 @@
@module koplugin.wallabag @module koplugin.wallabag
]] ]]
local BD = require("ui/bidi")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local DocSettings = require("docsettings") local DocSettings = require("docsettings")
local Event = require("ui/event") local Event = require("ui/event")
@ -163,7 +164,7 @@ function Wallabag:addToMainMenu(menu_items)
else else
path = filemanagerutil.abbreviate(self.directory) path = filemanagerutil.abbreviate(self.directory)
end end
return T(_("Set download directory (%1)"), path) return T(_("Set download directory (%1)"), BD.dirpath(path))
end, end,
keep_menu_open = true, keep_menu_open = true,
callback = function(touchmenu_instance) callback = function(touchmenu_instance)
@ -261,7 +262,7 @@ The 'Synchronize remotely deleted files' option will remove local files that no
More details: https://wallabag.org More details: https://wallabag.org
Downloads to directory: %1]]), filemanagerutil.abbreviate(self.directory)) Downloads to directory: %1]]), BD.dirpath(filemanagerutil.abbreviate(self.directory)))
}) })
end, end,
}, },
@ -824,7 +825,7 @@ Enter the details of your Wallabag server and account.
Client ID and client secret are long strings so you might prefer to save the empty settings and edit the config file directly in your installation directory: Client ID and client secret are long strings so you might prefer to save the empty settings and edit the config file directly in your installation directory:
%1/wallabag.lua %1/wallabag.lua
Restart KOReader after editing the config file.]]), DataStorage:getSettingsDir()) Restart KOReader after editing the config file.]]), BD.dirpath(DataStorage:getSettingsDir()))
self.settings_dialog = MultiInputDialog:new { self.settings_dialog = MultiInputDialog:new {
title = _("Wallabag settings"), title = _("Wallabag settings"),
@ -994,11 +995,11 @@ function Wallabag:onAddWallabagArticle(article_url)
local wallabag_result = self:addArticle(article_url) local wallabag_result = self:addArticle(article_url)
if wallabag_result then if wallabag_result then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Article added to Wallabag:\n%1"), article_url), text = T(_("Article added to Wallabag:\n%1"), BD.url(article_url)),
}) })
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Error adding link to Wallabag:\n%1"), article_url), text = T(_("Error adding link to Wallabag:\n%1"), BD.url(article_url)),
}) })
end end

Loading…
Cancel
Save