mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
29
Makefile
29
Makefile
@@ -1,4 +1,4 @@
|
||||
PHONY = all android-ndk android-sdk base clean distclean doc fetchthirdparty po pot re static-check
|
||||
PHONY = all android-ndk android-sdk base clean distclean doc fetchthirdparty re static-check
|
||||
SOUND = $(INSTALL_DIR)/%
|
||||
|
||||
# koreader-base directory
|
||||
@@ -81,6 +81,7 @@ ev_replay.py
|
||||
help
|
||||
history
|
||||
l10n/templates
|
||||
l10n/*/*.po
|
||||
ota
|
||||
resources/fonts*
|
||||
resources/icons/src*
|
||||
@@ -104,6 +105,7 @@ endef
|
||||
define UPDATE_PATH_EXCLUDES +=
|
||||
dummy-test-file*
|
||||
file.sdr*
|
||||
i18n-test
|
||||
readerbookmark.*
|
||||
readerhighlight.*
|
||||
testdata
|
||||
@@ -134,7 +136,7 @@ $(foreach a,$1,'$(if $(filter --%,$a),$a,$(abspath $a))') $(or $2,koreader)
|
||||
$(call release_excludes,$(or $2,koreader)/)
|
||||
endef
|
||||
|
||||
all: base
|
||||
all: base mo
|
||||
install -d $(INSTALL_DIR)/koreader
|
||||
rm -f $(INSTALL_DIR)/koreader/git-rev; echo "$(VERSION)" > $(INSTALL_DIR)/koreader/git-rev
|
||||
ifdef ANDROID
|
||||
@@ -190,7 +192,7 @@ else
|
||||
git submodule update --jobs 3 --init --recursive
|
||||
endif
|
||||
|
||||
clean: base-clean
|
||||
clean: base-clean mo-clean
|
||||
rm -rf $(INSTALL_DIR)
|
||||
|
||||
distclean: clean base-distclean
|
||||
@@ -209,31 +211,14 @@ ifneq (,$(wildcard make/$(TARGET).mk))
|
||||
include make/$(TARGET).mk
|
||||
endif
|
||||
|
||||
include make/gettext.mk
|
||||
|
||||
android-ndk:
|
||||
$(MAKE) -C $(KOR_BASE)/toolchain $(ANDROID_NDK_HOME)
|
||||
|
||||
android-sdk:
|
||||
$(MAKE) -C $(KOR_BASE)/toolchain $(ANDROID_HOME)
|
||||
|
||||
# for gettext
|
||||
DOMAIN=koreader
|
||||
TEMPLATE_DIR=l10n/templates
|
||||
XGETTEXT_BIN=xgettext
|
||||
|
||||
pot: po
|
||||
mkdir -p $(TEMPLATE_DIR)
|
||||
$(XGETTEXT_BIN) --from-code=utf-8 \
|
||||
--keyword=C_:1c,2 --keyword=N_:1,2 --keyword=NC_:1c,2,3 \
|
||||
--add-comments=@translators \
|
||||
reader.lua `find frontend -iname "*.lua" | sort` \
|
||||
`find plugins -iname "*.lua" | sort` \
|
||||
`find tools -iname "*.lua" | sort` \
|
||||
-o $(TEMPLATE_DIR)/$(DOMAIN).pot
|
||||
|
||||
po:
|
||||
git submodule update --remote l10n
|
||||
|
||||
|
||||
static-check:
|
||||
@if which luacheck > /dev/null; then \
|
||||
luacheck -q {reader,setupkoenv,datastorage}.lua frontend plugins spec; \
|
||||
|
||||
2
base
2
base
Submodule base updated: ca3f8fcda4...63f95f0f2a
@@ -14,6 +14,7 @@ local KeyValuePage = require("ui/widget/keyvaluepage")
|
||||
local LuaData = require("luadata")
|
||||
local MultiConfirmBox = require("ui/widget/multiconfirmbox")
|
||||
local NetworkMgr = require("ui/network/manager")
|
||||
local Presets = require("ui/presets")
|
||||
local SortWidget = require("ui/widget/sortwidget")
|
||||
local Trapper = require("ui/trapper")
|
||||
local UIManager = require("ui/uimanager")
|
||||
@@ -165,6 +166,17 @@ function ReaderDictionary:init()
|
||||
if not lookup_history then
|
||||
lookup_history = LuaData:open(DataStorage:getSettingsDir() .. "/lookup_history.lua", "LookupHistory")
|
||||
end
|
||||
|
||||
self.preset_obj = {
|
||||
presets = G_reader_settings:readSetting("dict_presets", {}),
|
||||
cycle_index = G_reader_settings:readSetting("dict_presets_cycle_index"),
|
||||
dispatcher_name = "load_dictionary_preset",
|
||||
saveCycleIndex = function(this)
|
||||
G_reader_settings:saveSetting("dict_presets_cycle_index", this.cycle_index)
|
||||
end,
|
||||
buildPreset = function() return self:buildPreset() end,
|
||||
loadPreset = function(preset) self:loadPreset(preset) end,
|
||||
}
|
||||
end
|
||||
|
||||
function ReaderDictionary:registerKeyEvents()
|
||||
@@ -281,9 +293,18 @@ function ReaderDictionary:addToMainMenu(menu_items)
|
||||
end)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Dictionary presets"),
|
||||
help_text = _("This feature allows you to organize dictionaries into presets (for example, by language). You can quickly switch between these presets to change which dictionaries are used for lookups.\n\nNote: presets only store dictionaries, no other settings."),
|
||||
sub_item_table_func = function()
|
||||
return Presets.genPresetMenuItemTable(self.preset_obj, _("Create new preset from enabled dictionaries"),
|
||||
function() return self.enabled_dict_names and #self.enabled_dict_names > 0 end)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Download dictionaries"),
|
||||
sub_item_table_func = function() return self:_genDownloadDictionariesMenu() end,
|
||||
separator = true,
|
||||
},
|
||||
{
|
||||
text_func = function()
|
||||
@@ -313,32 +334,40 @@ function ReaderDictionary:addToMainMenu(menu_items)
|
||||
separator = true,
|
||||
},
|
||||
{
|
||||
text = _("Enable dictionary lookup history"),
|
||||
text = _("Dictionary lookup history"),
|
||||
checked_func = function()
|
||||
return not self.disable_lookup_history
|
||||
end,
|
||||
callback = function()
|
||||
self.disable_lookup_history = not self.disable_lookup_history
|
||||
G_reader_settings:saveSetting("disable_lookup_history", self.disable_lookup_history)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Clean dictionary lookup history"),
|
||||
enabled_func = function()
|
||||
return lookup_history:has("lookup_history")
|
||||
end,
|
||||
keep_menu_open = true,
|
||||
callback = function(touchmenu_instance)
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = _("Clean dictionary lookup history?"),
|
||||
ok_text = _("Clean"),
|
||||
ok_callback = function()
|
||||
-- empty data table to replace current one
|
||||
lookup_history:reset{}
|
||||
touchmenu_instance:updateItems()
|
||||
sub_item_table = {
|
||||
{
|
||||
text = _("Enable dictionary lookup history"),
|
||||
checked_func = function()
|
||||
return not self.disable_lookup_history
|
||||
end,
|
||||
})
|
||||
end,
|
||||
callback = function()
|
||||
self.disable_lookup_history = not self.disable_lookup_history
|
||||
G_reader_settings:saveSetting("disable_lookup_history", self.disable_lookup_history)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Clean dictionary lookup history"),
|
||||
enabled_func = function()
|
||||
return lookup_history:has("lookup_history")
|
||||
end,
|
||||
keep_menu_open = true,
|
||||
callback = function(touchmenu_instance)
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = _("Clean dictionary lookup history?"),
|
||||
ok_text = _("Clean"),
|
||||
ok_callback = function()
|
||||
-- empty data table to replace current one
|
||||
lookup_history:reset{}
|
||||
touchmenu_instance:updateItems()
|
||||
end,
|
||||
})
|
||||
end,
|
||||
},
|
||||
},
|
||||
separator = true,
|
||||
},
|
||||
{ -- setting used by dictquicklookup
|
||||
@@ -385,7 +414,7 @@ function ReaderDictionary:addToMainMenu(menu_items)
|
||||
}
|
||||
}
|
||||
if not is_docless then
|
||||
table.insert(menu_items.dictionary_settings.sub_item_table, 3, {
|
||||
table.insert(menu_items.dictionary_settings.sub_item_table, 2, {
|
||||
keep_menu_open = true,
|
||||
text = _("Set dictionary priority for this book"),
|
||||
help_text = _("This feature enables you to specify dictionary priorities on a per-book basis. Results from higher-priority dictionaries will be displayed first when looking up words. Only dictionaries that are currently active can be selected and prioritized."),
|
||||
@@ -859,31 +888,70 @@ function ReaderDictionary:dismissLookupInfo()
|
||||
end
|
||||
|
||||
function ReaderDictionary:onShowDictionaryLookup()
|
||||
local buttons = {}
|
||||
local preset_names = Presets.getPresets(self.preset_obj)
|
||||
if preset_names and #preset_names > 0 then
|
||||
table.insert(buttons, {
|
||||
{
|
||||
text = _("Search with preset"),
|
||||
callback = function()
|
||||
local text = self.dictionary_lookup_dialog:getInputText()
|
||||
if text == "" or text:match("^%s*$") then return end
|
||||
local current_dict_state = self:buildPreset()
|
||||
local button_dialog, dialog_buttons = nil, {} -- CI won't like it if we call it buttons :( so dialog_buttons
|
||||
for _, preset_name in ipairs(preset_names) do
|
||||
table.insert(dialog_buttons, {
|
||||
{
|
||||
align = "left",
|
||||
text = preset_name,
|
||||
callback = function()
|
||||
self:loadPreset(self.preset_obj.presets[preset_name], true)
|
||||
UIManager:close(button_dialog)
|
||||
UIManager:close(self.dictionary_lookup_dialog)
|
||||
self:onLookupWord(text, true, nil, nil, nil,
|
||||
function()
|
||||
self:loadPreset(current_dict_state, true)
|
||||
end
|
||||
)
|
||||
end,
|
||||
}
|
||||
})
|
||||
end
|
||||
button_dialog = ButtonDialog:new{
|
||||
buttons = dialog_buttons,
|
||||
shrink_unneeded_width = true,
|
||||
}
|
||||
UIManager:show(button_dialog)
|
||||
end,
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
table.insert(buttons, {
|
||||
{
|
||||
text = _("Cancel"),
|
||||
id = "close",
|
||||
callback = function()
|
||||
UIManager:close(self.dictionary_lookup_dialog)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Search dictionary"),
|
||||
is_enter_default = true,
|
||||
callback = function()
|
||||
if self.dictionary_lookup_dialog:getInputText() == "" then return end
|
||||
UIManager:close(self.dictionary_lookup_dialog)
|
||||
-- Trust that input text does not need any cleaning (allows querying for "-suffix")
|
||||
self:onLookupWord(self.dictionary_lookup_dialog:getInputText(), true)
|
||||
end,
|
||||
},
|
||||
})
|
||||
|
||||
self.dictionary_lookup_dialog = InputDialog:new{
|
||||
title = _("Enter a word or phrase to look up"),
|
||||
input = "",
|
||||
input_type = "text",
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Cancel"),
|
||||
id = "close",
|
||||
callback = function()
|
||||
UIManager:close(self.dictionary_lookup_dialog)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Search dictionary"),
|
||||
is_enter_default = true,
|
||||
callback = function()
|
||||
if self.dictionary_lookup_dialog:getInputText() == "" then return end
|
||||
UIManager:close(self.dictionary_lookup_dialog)
|
||||
-- Trust that input text does not need any cleaning (allows querying for "-suffix")
|
||||
self:onLookupWord(self.dictionary_lookup_dialog:getInputText(), true)
|
||||
end,
|
||||
},
|
||||
}
|
||||
},
|
||||
buttons = buttons,
|
||||
}
|
||||
UIManager:show(self.dictionary_lookup_dialog)
|
||||
self.dictionary_lookup_dialog:onShowKeyboard()
|
||||
@@ -1459,4 +1527,64 @@ The current default (★) is enabled.]])
|
||||
})
|
||||
end
|
||||
|
||||
function ReaderDictionary:buildPreset()
|
||||
local preset = { enabled_dict_names = {} } -- Only store the names of enabled dictionaries.
|
||||
for _, name in ipairs(self.enabled_dict_names) do
|
||||
preset.enabled_dict_names[name] = true
|
||||
end
|
||||
return preset
|
||||
end
|
||||
|
||||
function ReaderDictionary:loadPreset(preset, skip_notification)
|
||||
if not preset.enabled_dict_names then return end
|
||||
-- build a list of currently available dictionary names for validation
|
||||
local available_dict_names = {}
|
||||
for _, ifo in ipairs(available_ifos) do
|
||||
available_dict_names[ifo.name] = true
|
||||
end
|
||||
-- Only enable dictionaries from the preset that are still available, and re-build self.dicts_disabled
|
||||
-- to make sure dicts added after the creation of the preset, are disabled as well.
|
||||
local dicts_disabled, valid_enabled_names = {}, {}
|
||||
for _, ifo in ipairs(available_ifos) do
|
||||
if preset.enabled_dict_names[ifo.name] then
|
||||
table.insert(valid_enabled_names, ifo.name)
|
||||
else
|
||||
dicts_disabled[ifo.file] = true
|
||||
end
|
||||
end
|
||||
-- update both settings and save
|
||||
self.dicts_disabled = dicts_disabled
|
||||
self.enabled_dict_names = valid_enabled_names
|
||||
G_reader_settings:saveSetting("dicts_disabled", self.dicts_disabled)
|
||||
self:onSaveSettings()
|
||||
self:updateSdcvDictNamesOptions()
|
||||
-- Show a message if any dictionaries from the preset are missing.
|
||||
if not skip_notification and util.tableSize(preset.enabled_dict_names) > #valid_enabled_names then
|
||||
local missing_dicts = {}
|
||||
for preset_name, _ in pairs(preset.enabled_dict_names) do
|
||||
if not available_dict_names[preset_name] then
|
||||
table.insert(missing_dicts, preset_name)
|
||||
end
|
||||
end
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("Some dictionaries from this preset have been deleted or are no longer available:") .. "\n\n• " .. table.concat(missing_dicts, "\n• "),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function ReaderDictionary:onCycleDictionaryPresets()
|
||||
return Presets.cycleThroughPresets(self.preset_obj, true)
|
||||
end
|
||||
|
||||
function ReaderDictionary:onLoadDictionaryPreset(preset_name)
|
||||
return Presets.onLoadPreset(self.preset_obj, preset_name, true)
|
||||
end
|
||||
|
||||
function ReaderDictionary.getPresets() -- for Dispatcher
|
||||
local dict_config = {
|
||||
presets = G_reader_settings:readSetting("dict_presets", {})
|
||||
}
|
||||
return Presets.getPresets(dict_config)
|
||||
end
|
||||
|
||||
return ReaderDictionary
|
||||
|
||||
@@ -9,12 +9,10 @@ local FrameContainer = require("ui/widget/container/framecontainer")
|
||||
local Geom = require("ui/geometry")
|
||||
local HorizontalGroup = require("ui/widget/horizontalgroup")
|
||||
local HorizontalSpan = require("ui/widget/horizontalspan")
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
local InputDialog = require("ui/widget/inputdialog")
|
||||
local LeftContainer = require("ui/widget/container/leftcontainer")
|
||||
local LineWidget = require("ui/widget/linewidget")
|
||||
local MultiConfirmBox = require("ui/widget/multiconfirmbox")
|
||||
local MultiInputDialog = require("ui/widget/multiinputdialog")
|
||||
local Presets = require("ui/presets")
|
||||
local ProgressWidget = require("ui/widget/progresswidget")
|
||||
local RightContainer = require("ui/widget/container/rightcontainer")
|
||||
local Size = require("ui/size")
|
||||
@@ -25,7 +23,6 @@ local VerticalGroup = require("ui/widget/verticalgroup")
|
||||
local VerticalSpan = require("ui/widget/verticalspan")
|
||||
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
||||
local datetime = require("datetime")
|
||||
local ffiUtil = require("ffi/util")
|
||||
local logger = require("logger")
|
||||
local util = require("util")
|
||||
local T = require("ffi/util").template
|
||||
@@ -665,6 +662,13 @@ function ReaderFooter:init()
|
||||
self.custom_text = G_reader_settings:readSetting("reader_footer_custom_text", "KOReader")
|
||||
self.custom_text_repetitions =
|
||||
tonumber(G_reader_settings:readSetting("reader_footer_custom_text_repetitions", "1"))
|
||||
|
||||
self.preset_obj = {
|
||||
presets = G_reader_settings:readSetting("footer_presets", {}),
|
||||
dispatcher_name = "load_footer_preset",
|
||||
buildPreset = function() return self:buildPreset() end,
|
||||
loadPreset = function(preset) self:loadPreset(preset) end,
|
||||
}
|
||||
end
|
||||
|
||||
function ReaderFooter:set_custom_text(touchmenu_instance)
|
||||
@@ -1710,7 +1714,7 @@ With this feature enabled, the current page is factored in, resulting in the cou
|
||||
text = _("Status bar presets"),
|
||||
separator = true,
|
||||
sub_item_table_func = function()
|
||||
return self:genPresetMenuItemTable()
|
||||
return Presets.genPresetMenuItemTable(self.preset_obj, nil, nil)
|
||||
end,
|
||||
})
|
||||
table.insert(sub_items, {
|
||||
@@ -1930,91 +1934,6 @@ function ReaderFooter:genAlignmentMenuItems(value)
|
||||
}
|
||||
end
|
||||
|
||||
function ReaderFooter:genPresetMenuItemTable()
|
||||
local footer_presets = G_reader_settings:readSetting("footer_presets", {})
|
||||
local items = {
|
||||
{
|
||||
text = _("Create new preset from current settings"),
|
||||
keep_menu_open = true,
|
||||
callback = function(touchmenu_instance)
|
||||
self:createPresetFromCurrentSettings(touchmenu_instance)
|
||||
end,
|
||||
separator = true,
|
||||
},
|
||||
}
|
||||
for preset_name in ffiUtil.orderedPairs(footer_presets) do
|
||||
table.insert(items, {
|
||||
text = preset_name,
|
||||
keep_menu_open = true,
|
||||
callback = function()
|
||||
self:loadPreset(footer_presets[preset_name])
|
||||
end,
|
||||
hold_callback = function(touchmenu_instance, item)
|
||||
UIManager:show(MultiConfirmBox:new{
|
||||
text = T(_("What would you like to do with preset '%1'?"), preset_name),
|
||||
choice1_text = _("Delete"),
|
||||
choice1_callback = function()
|
||||
footer_presets[preset_name] = nil
|
||||
UIManager:broadcastEvent(Event:new("DispatcherActionValueChanged",
|
||||
{ name = "load_footer_preset", old_value = preset_name, new_value = nil }))
|
||||
table.remove(touchmenu_instance.item_table, item.idx)
|
||||
touchmenu_instance:updateItems()
|
||||
end,
|
||||
choice2_text = _("Update"),
|
||||
choice2_callback = function()
|
||||
footer_presets[preset_name] = self:buildPreset()
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = T(_("Preset '%1' was updated with current settings"), preset_name),
|
||||
timeout = 2,
|
||||
})
|
||||
end,
|
||||
})
|
||||
end,
|
||||
})
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
function ReaderFooter:createPresetFromCurrentSettings(touchmenu_instance)
|
||||
local input_dialog
|
||||
input_dialog = InputDialog:new{
|
||||
title = _("Enter preset name"),
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Cancel"),
|
||||
id = "close",
|
||||
callback = function()
|
||||
UIManager:close(input_dialog)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Save"),
|
||||
is_enter_default = true,
|
||||
callback = function()
|
||||
local preset_name = input_dialog:getInputText()
|
||||
if preset_name == "" or preset_name:match("^%s*$") then return end
|
||||
local footer_presets = G_reader_settings:readSetting("footer_presets")
|
||||
if footer_presets[preset_name] then
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = T(_("A preset named '%1' already exists. Please choose a different name."), preset_name),
|
||||
timeout = 2,
|
||||
})
|
||||
else
|
||||
footer_presets[preset_name] = self:buildPreset()
|
||||
UIManager:close(input_dialog)
|
||||
touchmenu_instance.item_table = self:genPresetMenuItemTable()
|
||||
touchmenu_instance:updateItems()
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
UIManager:show(input_dialog)
|
||||
input_dialog:onShowKeyboard()
|
||||
end
|
||||
|
||||
function ReaderFooter:buildPreset()
|
||||
return {
|
||||
footer = util.tableDeepCopy(self.settings),
|
||||
@@ -2048,25 +1967,14 @@ function ReaderFooter:loadPreset(preset)
|
||||
end
|
||||
|
||||
function ReaderFooter:onLoadFooterPreset(preset_name)
|
||||
local footer_presets = G_reader_settings:readSetting("footer_presets")
|
||||
if footer_presets and footer_presets[preset_name] then
|
||||
self:loadPreset(footer_presets[preset_name])
|
||||
end
|
||||
return true
|
||||
return Presets.onLoadPreset(self.preset_obj, preset_name, true)
|
||||
end
|
||||
|
||||
function ReaderFooter.getPresets() -- for Dispatcher
|
||||
local footer_presets = G_reader_settings:readSetting("footer_presets")
|
||||
local actions = {}
|
||||
if footer_presets and next(footer_presets) then
|
||||
for preset_name in pairs(footer_presets) do
|
||||
table.insert(actions, preset_name)
|
||||
end
|
||||
if #actions > 1 then
|
||||
table.sort(actions)
|
||||
end
|
||||
end
|
||||
return actions, actions
|
||||
local footer_config = {
|
||||
presets = G_reader_settings:readSetting("footer_presets", {})
|
||||
}
|
||||
return Presets.getPresets(footer_config)
|
||||
end
|
||||
|
||||
function ReaderFooter:addAdditionalFooterContent(content_func)
|
||||
|
||||
@@ -75,6 +75,9 @@ function ReaderHighlight:init()
|
||||
callback = function()
|
||||
this:startSelection(index)
|
||||
this:onClose()
|
||||
if not Device:isTouchDevice() then
|
||||
self:onStartHighlightIndicator()
|
||||
end
|
||||
end,
|
||||
}
|
||||
end,
|
||||
@@ -245,20 +248,18 @@ function ReaderHighlight:registerKeyEvents()
|
||||
self.key_events.RightHighlightIndicator = { { "Right" }, event = "MoveHighlightIndicator", args = {1, 0} }
|
||||
self.key_events.HighlightPress = { { "Press" } }
|
||||
end
|
||||
if Device:hasKeyboard() then
|
||||
if Device:hasScreenKB() or Device:hasKeyboard() then
|
||||
local modifier = Device:hasScreenKB() and "ScreenKB" or "Shift"
|
||||
-- Used for text selection with dpad/keys
|
||||
local QUICK_INDICATOR_MOVE = true
|
||||
self.key_events.QuickUpHighlightIndicator = { { "Shift", "Up" }, event = "MoveHighlightIndicator", args = {0, -1, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.QuickDownHighlightIndicator = { { "Shift", "Down" }, event = "MoveHighlightIndicator", args = {0, 1, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.QuickLeftHighlightIndicator = { { "Shift", "Left" }, event = "MoveHighlightIndicator", args = {-1, 0, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.QuickRightHighlightIndicator = { { "Shift", "Right" }, event = "MoveHighlightIndicator", args = {1, 0, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.StartHighlightIndicator = { { "H" } }
|
||||
elseif Device:hasScreenKB() then
|
||||
local QUICK_INDICATOR_MOVE = true
|
||||
self.key_events.QuickUpHighlightIndicator = { { "ScreenKB", "Up" }, event = "MoveHighlightIndicator", args = {0, -1, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.QuickDownHighlightIndicator = { { "ScreenKB", "Down" }, event = "MoveHighlightIndicator", args = {0, 1, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.QuickLeftHighlightIndicator = { { "ScreenKB", "Left" }, event = "MoveHighlightIndicator", args = {-1, 0, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.QuickRightHighlightIndicator = { { "ScreenKB", "Right" }, event = "MoveHighlightIndicator", args = {1, 0, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.QuickUpHighlightIndicator = { { modifier, "Up" }, event = "MoveHighlightIndicator", args = {0, -1, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.QuickDownHighlightIndicator = { { modifier, "Down" }, event = "MoveHighlightIndicator", args = {0, 1, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.QuickLeftHighlightIndicator = { { modifier, "Left" }, event = "MoveHighlightIndicator", args = {-1, 0, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.QuickRightHighlightIndicator = { { modifier, "Right" }, event = "MoveHighlightIndicator", args = {1, 0, QUICK_INDICATOR_MOVE} }
|
||||
self.key_events.HighlightModifierPress = { { modifier, "Press" } }
|
||||
if Device:hasKeyboard() then
|
||||
self.key_events.StartHighlightIndicator = { { "H" } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2673,15 +2674,22 @@ end
|
||||
|
||||
-- dpad/keys support
|
||||
|
||||
function ReaderHighlight:onHighlightPress()
|
||||
function ReaderHighlight:onHighlightPress(skip_tap_check)
|
||||
if not self._current_indicator_pos then return false end
|
||||
if self._start_indicator_highlight then
|
||||
self:onHoldRelease(nil, self:_createHighlightGesture("hold_release"))
|
||||
self:onStopHighlightIndicator()
|
||||
return true
|
||||
end
|
||||
-- Check if we're in select mode (or extending an existing highlight)
|
||||
if self.select_mode and self.highlight_idx then
|
||||
self:onHold(nil, self:_createHighlightGesture("hold"))
|
||||
self:onHoldRelease(nil, self:_createHighlightGesture("hold_release"))
|
||||
self:onStopHighlightIndicator()
|
||||
return true
|
||||
end
|
||||
-- Attempt to open an existing highlight
|
||||
if self:onTap(nil, self:_createHighlightGesture("tap")) then
|
||||
if not skip_tap_check and self:onTap(nil, self:_createHighlightGesture("tap")) then
|
||||
self:onStopHighlightIndicator(true) -- need_clear_selection=true
|
||||
return true
|
||||
end
|
||||
@@ -2764,6 +2772,19 @@ function ReaderHighlight:onHighlightPress()
|
||||
return true
|
||||
end
|
||||
|
||||
function ReaderHighlight:onHighlightModifierPress()
|
||||
if not self._current_indicator_pos then return false end -- let event propagate to hotkeys
|
||||
if not self._start_indicator_highlight then
|
||||
self:onHighlightPress(true)
|
||||
return true -- don't trigger hotkeys during text selection
|
||||
end
|
||||
-- Simulate very long-long press by setting the long hold flag. This will trigger the long-press dialog.
|
||||
self.long_hold_reached = true
|
||||
self:onHoldRelease(nil, self:_createHighlightGesture("hold_release"))
|
||||
self:onStopHighlightIndicator()
|
||||
return true
|
||||
end
|
||||
|
||||
function ReaderHighlight:onStartHighlightIndicator()
|
||||
-- disable long-press icon (poke-ball), as it is triggered constantly due to NT devices needing a workaround for text selection to work.
|
||||
self.long_hold_reached_action = function() end
|
||||
@@ -2786,6 +2807,16 @@ function ReaderHighlight:onStartHighlightIndicator()
|
||||
end
|
||||
|
||||
function ReaderHighlight:onStopHighlightIndicator(need_clear_selection)
|
||||
-- If we're in select mode and user presses back, end the selection
|
||||
if self.select_mode and self.highlight_idx then
|
||||
self.select_mode = false
|
||||
if self.ui.annotation.annotations[self.highlight_idx].is_tmp then
|
||||
self:deleteHighlight(self.highlight_idx) -- temporary highlight, delete it
|
||||
else
|
||||
UIManager:setDirty(self.dialog, "ui", self.view.flipping:getRefreshRegion())
|
||||
end
|
||||
self.highlight_idx = nil
|
||||
end
|
||||
if self._current_indicator_pos then
|
||||
local rect = self._current_indicator_pos
|
||||
self._previous_indicator_pos = rect
|
||||
|
||||
@@ -260,32 +260,40 @@ You can choose an existing folder, or use a default folder named "Wikipedia" in
|
||||
separator = true,
|
||||
},
|
||||
{
|
||||
text = _("Enable Wikipedia history"),
|
||||
text = _("Wikipedia lookup history"),
|
||||
checked_func = function()
|
||||
return not self.disable_history
|
||||
end,
|
||||
callback = function()
|
||||
self.disable_history = not self.disable_history
|
||||
G_reader_settings:saveSetting("wikipedia_disable_history", self.disable_history)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Clean Wikipedia history"),
|
||||
enabled_func = function()
|
||||
return wikipedia_history:has("wikipedia_history")
|
||||
end,
|
||||
keep_menu_open = true,
|
||||
callback = function(touchmenu_instance)
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = _("Clean Wikipedia history?"),
|
||||
ok_text = _("Clean"),
|
||||
ok_callback = function()
|
||||
-- empty data table to replace current one
|
||||
wikipedia_history:reset{}
|
||||
touchmenu_instance:updateItems()
|
||||
sub_item_table = {
|
||||
{
|
||||
text = _("Enable Wikipedia history"),
|
||||
checked_func = function()
|
||||
return not self.disable_history
|
||||
end,
|
||||
})
|
||||
end,
|
||||
callback = function()
|
||||
self.disable_history = not self.disable_history
|
||||
G_reader_settings:saveSetting("wikipedia_disable_history", self.disable_history)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Clean Wikipedia history"),
|
||||
enabled_func = function()
|
||||
return wikipedia_history:has("wikipedia_history")
|
||||
end,
|
||||
keep_menu_open = true,
|
||||
callback = function(touchmenu_instance)
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = _("Clean Wikipedia history?"),
|
||||
ok_text = _("Clean"),
|
||||
ok_callback = function()
|
||||
-- empty data table to replace current one
|
||||
wikipedia_history:reset{}
|
||||
touchmenu_instance:updateItems()
|
||||
end,
|
||||
})
|
||||
end,
|
||||
},
|
||||
},
|
||||
separator = true,
|
||||
},
|
||||
{ -- setting used in wikipedia.lua
|
||||
|
||||
@@ -34,6 +34,7 @@ local Device = require("device")
|
||||
local Event = require("ui/event")
|
||||
local FileManager = require("apps/filemanager/filemanager")
|
||||
local Notification = require("ui/widget/notification")
|
||||
local ReaderDictionary = require("apps/reader/modules/readerdictionary")
|
||||
local ReaderFooter = require("apps/reader/modules/readerfooter")
|
||||
local ReaderHighlight = require("apps/reader/modules/readerhighlight")
|
||||
local ReaderZooming = require("apps/reader/modules/readerzooming")
|
||||
@@ -60,6 +61,8 @@ local settingsList = {
|
||||
collections_search = {category="none", event="ShowCollectionsSearchDialog", title=_("Collections search"), general=true, separator=true},
|
||||
----
|
||||
dictionary_lookup = {category="none", event="ShowDictionaryLookup", title=_("Dictionary lookup"), general=true},
|
||||
load_dictionary_preset = {category="string", event="LoadDictionaryPreset", title=_("Load dictionary preset"), args_func=ReaderDictionary.getPresets, general=true},
|
||||
cycle_dictionary_preset = {category="none", event="CycleDictionaryPresets", title=_("Cycle through dictionary presets"), general=true,},
|
||||
wikipedia_lookup = {category="none", event="ShowWikipediaLookup", title=_("Wikipedia lookup"), general=true, separator=true},
|
||||
----
|
||||
show_menu = {category="none", event="ShowMenu", title=_("Show menu"), general=true},
|
||||
@@ -300,6 +303,8 @@ local dispatcher_menu_order = {
|
||||
"collections_search",
|
||||
----
|
||||
"dictionary_lookup",
|
||||
"load_dictionary_preset",
|
||||
"cycle_dictionary_preset",
|
||||
"wikipedia_lookup",
|
||||
----
|
||||
"show_menu",
|
||||
|
||||
@@ -21,6 +21,12 @@ See @{ffi.util.template}() for more information about the template function.
|
||||
|
||||
local isAndroid, android = pcall(require, "android")
|
||||
local logger = require("logger")
|
||||
local buffer = require("string.buffer")
|
||||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
|
||||
require "table.new"
|
||||
require "ffi/posix_h"
|
||||
|
||||
local GetText = {
|
||||
context = {},
|
||||
@@ -61,21 +67,6 @@ function GetText_mt.__call(gettext, msgid)
|
||||
return gettext.translation[msgid] and gettext.translation[msgid][0] or gettext.translation[msgid] or gettext.wrapUntranslated(msgid)
|
||||
end
|
||||
|
||||
local function c_escape(what_full, what)
|
||||
if what == "\n" then return ""
|
||||
elseif what == "a" then return "\a"
|
||||
elseif what == "b" then return "\b"
|
||||
elseif what == "f" then return "\f"
|
||||
elseif what == "n" then return "\n"
|
||||
elseif what == "r" then return "\r"
|
||||
elseif what == "t" then return "\t"
|
||||
elseif what == "v" then return "\v"
|
||||
elseif what == "0" then return "\0" -- shouldn't happen, though
|
||||
else
|
||||
return what_full
|
||||
end
|
||||
end
|
||||
|
||||
--- Converts C logical operators to Lua.
|
||||
local function logicalCtoLua(logical_str)
|
||||
logical_str = logical_str:gsub("&&", "and")
|
||||
@@ -137,9 +128,13 @@ local function getPluralFunc(pl_tests, nplurals, plural_default)
|
||||
end
|
||||
|
||||
local function addTranslation(msgctxt, msgid, msgstr, n)
|
||||
-- translated string
|
||||
local unescaped_string = string.gsub(msgstr, "(\\(.))", c_escape)
|
||||
if msgctxt and msgctxt ~= "" then
|
||||
assert(not msgctxt or msgctxt ~= "")
|
||||
assert(msgid and msgid ~= "")
|
||||
assert(msgstr)
|
||||
if msgstr == "" then
|
||||
return
|
||||
end
|
||||
if msgctxt then
|
||||
if not GetText.context[msgctxt] then
|
||||
GetText.context[msgctxt] = {}
|
||||
end
|
||||
@@ -147,26 +142,22 @@ local function addTranslation(msgctxt, msgid, msgstr, n)
|
||||
if not GetText.context[msgctxt][msgid] then
|
||||
GetText.context[msgctxt][msgid] = {}
|
||||
end
|
||||
GetText.context[msgctxt][msgid][n] = unescaped_string ~= "" and unescaped_string or nil
|
||||
GetText.context[msgctxt][msgid][n] = msgstr
|
||||
else
|
||||
GetText.context[msgctxt][msgid] = unescaped_string ~= "" and unescaped_string or nil
|
||||
GetText.context[msgctxt][msgid] = msgstr
|
||||
end
|
||||
else
|
||||
if n then
|
||||
if not GetText.translation[msgid] then
|
||||
GetText.translation[msgid] = {}
|
||||
end
|
||||
GetText.translation[msgid][n] = unescaped_string ~= "" and unescaped_string or nil
|
||||
GetText.translation[msgid][n] = msgstr
|
||||
else
|
||||
GetText.translation[msgid] = unescaped_string ~= "" and unescaped_string or nil
|
||||
GetText.translation[msgid] = msgstr
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- for PO file syntax, see
|
||||
-- https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
|
||||
-- we only implement a sane subset for now
|
||||
|
||||
function GetText_mt.__index.changeLang(new_lang)
|
||||
GetText.context = {}
|
||||
GetText.translation = {}
|
||||
@@ -180,119 +171,210 @@ function GetText_mt.__index.changeLang(new_lang)
|
||||
-- strip encoding suffix in locale like "zh_CN.utf8"
|
||||
new_lang = new_lang:sub(1, new_lang:find(".%."))
|
||||
|
||||
local file = GetText.dirname .. "/" .. new_lang .. "/" .. GetText.textdomain .. ".po"
|
||||
local po = io.open(file, "r")
|
||||
|
||||
if not po then
|
||||
logger.dbg("cannot open translation file:", file)
|
||||
local mo = GetText.dirname .. "/" .. new_lang .. "/" .. GetText.textdomain .. ".mo"
|
||||
if not GetText.loadMO(mo) then
|
||||
return false
|
||||
end
|
||||
|
||||
local data = {}
|
||||
local in_comments = false
|
||||
local fuzzy = false
|
||||
local headers
|
||||
local what = nil
|
||||
while true do
|
||||
local line = po:read("*l")
|
||||
if line == nil or line == "" then
|
||||
if data.msgid and data.msgid_plural and data["msgstr[0]"] then
|
||||
for k, v in pairs(data) do
|
||||
local n = tonumber(k:match("msgstr%[([0-9]+)%]"))
|
||||
local msgstr = v
|
||||
GetText.current_lang = new_lang
|
||||
return true
|
||||
end
|
||||
|
||||
if n and msgstr and msgstr ~= "" then
|
||||
addTranslation(data.msgctxt, data.msgid, msgstr, n)
|
||||
end
|
||||
end
|
||||
elseif data.msgid and data.msgstr and data.msgstr ~= "" then
|
||||
-- header
|
||||
if not headers and data.msgid == "" then
|
||||
headers = data.msgstr
|
||||
local plural_forms = data.msgstr:match("Plural%-Forms: (.*)")
|
||||
local nplurals = plural_forms:match("nplurals=([0-9]+);") or 2
|
||||
local plurals = plural_forms:match("plural=%((.*)%);")
|
||||
local function parse_headers(headers)
|
||||
local plural_forms = headers:match("Plural%-Forms: (.*)")
|
||||
local nplurals = plural_forms:match("nplurals=([0-9]+);") or 2
|
||||
local plurals = plural_forms:match("plural=%((.*)%);")
|
||||
|
||||
-- Hardcoded workaround for Hebrew which has 4 plural forms.
|
||||
if plurals == "n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && n % 10 == 0) ? 2 : 3)" then
|
||||
plurals = "n == 1 ? 0 : (n == 2) ? 1 : (n > 10 && n % 10 == 0) ? 2 : 3"
|
||||
end
|
||||
-- Hardcoded workaround for Latvian.
|
||||
if plurals == "n % 10 == 0 || n % 100 >= 11 && n % 100 <= 19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2" then
|
||||
plurals = "n % 10 == 0 || n % 100 >= 11 && n % 100 <= 19 ? 0 : (n % 10 == 1 && n % 100 != 11) ? 1 : 2"
|
||||
end
|
||||
-- Hardcoded workaround for Romanian which has 3 plural forms.
|
||||
if plurals == "n == 1) ? 0 : ((n == 0 || n != 1 && n % 100 >= 1 && n % 100 <= 19) ? 1 : 2" then
|
||||
plurals = "n == 1 ? 0 : (n == 0 || n != 1 && n % 100 >= 1 && n % 100 <= 19) ? 1 : 2"
|
||||
end
|
||||
-- Hardcoded workaround for Hebrew which has 4 plural forms.
|
||||
if plurals == "n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && n % 10 == 0) ? 2 : 3)" then
|
||||
plurals = "n == 1 ? 0 : (n == 2) ? 1 : (n > 10 && n % 10 == 0) ? 2 : 3"
|
||||
end
|
||||
-- Hardcoded workaround for Latvian.
|
||||
if plurals == "n % 10 == 0 || n % 100 >= 11 && n % 100 <= 19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2" then
|
||||
plurals = "n % 10 == 0 || n % 100 >= 11 && n % 100 <= 19 ? 0 : (n % 10 == 1 && n % 100 != 11) ? 1 : 2"
|
||||
end
|
||||
-- Hardcoded workaround for Romanian which has 3 plural forms.
|
||||
if plurals == "n == 1) ? 0 : ((n == 0 || n != 1 && n % 100 >= 1 && n % 100 <= 19) ? 1 : 2" then
|
||||
plurals = "n == 1 ? 0 : (n == 0 || n != 1 && n % 100 >= 1 && n % 100 <= 19) ? 1 : 2"
|
||||
end
|
||||
|
||||
if not plurals then
|
||||
-- Some languages (e.g., Arabic) may not use parentheses.
|
||||
-- However, the following more inclusive match is more likely
|
||||
-- to accidentally include junk and seldom relevant.
|
||||
-- We might also be dealing with a language without plurals.
|
||||
-- That would look like `plural=0`.
|
||||
plurals = plural_forms:match("plural=(.*);")
|
||||
end
|
||||
if not plurals then
|
||||
-- Some languages (e.g., Arabic) may not use parentheses.
|
||||
-- However, the following more inclusive match is more likely
|
||||
-- to accidentally include junk and seldom relevant.
|
||||
-- We might also be dealing with a language without plurals.
|
||||
-- That would look like `plural=0`.
|
||||
plurals = plural_forms:match("plural=(.*);")
|
||||
end
|
||||
|
||||
if plurals:find("[^n!=%%<>&:%(%)|?0-9 ]") then
|
||||
-- we don't trust this input, go with default instead
|
||||
plurals = GetText.plural_default
|
||||
end
|
||||
if plurals:find("[^n!=%%<>&:%(%)|?0-9 ]") then
|
||||
-- we don't trust this input, go with default instead
|
||||
plurals = GetText.plural_default
|
||||
end
|
||||
|
||||
local pl_tests = {}
|
||||
for pl_test in plurals:gmatch("[^:]+") do
|
||||
table.insert(pl_tests, pl_test)
|
||||
end
|
||||
local pl_tests = {}
|
||||
for pl_test in plurals:gmatch("[^:]+") do
|
||||
table.insert(pl_tests, pl_test)
|
||||
end
|
||||
|
||||
GetText.getPlural = getPluralFunc(pl_tests, nplurals, GetText.plural_default)
|
||||
if not GetText.getPlural then
|
||||
GetText.getPlural = getDefaultPlural
|
||||
end
|
||||
end
|
||||
GetText.getPlural = getPluralFunc(pl_tests, nplurals, GetText.plural_default)
|
||||
if not GetText.getPlural then
|
||||
GetText.getPlural = getDefaultPlural
|
||||
end
|
||||
end
|
||||
|
||||
addTranslation(data.msgctxt, data.msgid, data.msgstr)
|
||||
-- for MO file format, see
|
||||
-- https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
|
||||
|
||||
ffi.cdef[[
|
||||
struct __attribute__((packed)) mo_header {
|
||||
uint32_t magic;
|
||||
uint16_t revision_major;
|
||||
uint16_t revision_minor;
|
||||
uint32_t nb_strings;
|
||||
uint32_t original_strings_table_offset;
|
||||
uint32_t translated_strings_table_offset;
|
||||
uint32_t hash_table_size;
|
||||
uint32_t hash_table_offset;
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) mo_string_table {
|
||||
uint32_t length;
|
||||
uint32_t offset;
|
||||
};
|
||||
]]
|
||||
local MO_MAGIC = 0x950412de
|
||||
|
||||
function GetText_mt.__index.loadMO(file)
|
||||
local fd = C.open(file, C.O_RDONLY)
|
||||
if fd < 0 then
|
||||
logger.dbg(string.format("cannot open translation file: %s", file))
|
||||
return false
|
||||
end
|
||||
local strerror = function()
|
||||
return ffi.string(C.strerror(ffi.errno()))
|
||||
end
|
||||
local seek_and_read = function(off, ptr, len)
|
||||
local ret
|
||||
ret = C.lseek(fd, off, C.SEEK_SET)
|
||||
if ret ~= off then
|
||||
logger.err(string.format("loading translation file failed: %s [%s]", file, ret < 0 and strerror() or "lseek"))
|
||||
return false
|
||||
end
|
||||
ret = C.read(fd, ptr, len)
|
||||
if ret ~= len then
|
||||
logger.err(string.format("loading translation file failed: %s [%s]", file), ret < 0 and strerror() or "short read")
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
local mo_hdr = ffi.new("struct mo_header")
|
||||
if not seek_and_read(0, mo_hdr, ffi.sizeof(mo_hdr)) then
|
||||
C.close(fd)
|
||||
return false
|
||||
end
|
||||
if mo_hdr.magic ~= MO_MAGIC then
|
||||
logger.err(string.format("bad translation file: %s [magic]", file))
|
||||
C.close(fd)
|
||||
return false
|
||||
end
|
||||
if mo_hdr.revision_major ~= 0 then
|
||||
logger.err(string.format("bad translation file: %s [revision]", file))
|
||||
C.close(fd)
|
||||
return false
|
||||
end
|
||||
local table_buf = buffer:new()
|
||||
local table_size = mo_hdr.nb_strings * ffi.sizeof("struct mo_string_table")
|
||||
local table_ptr = table_buf:reserve(table_size)
|
||||
local read_strings_count
|
||||
local read_strings = function(check_for_context)
|
||||
local m_str_tbl = ffi.cast("struct mo_string_table *", table_ptr)
|
||||
local str_buf = buffer:new()
|
||||
read_strings_count = -1
|
||||
return function()
|
||||
read_strings_count = read_strings_count + 1
|
||||
if read_strings_count >= mo_hdr.nb_strings then
|
||||
return
|
||||
end
|
||||
-- stop at EOF:
|
||||
if line == nil then break end
|
||||
data = {}
|
||||
what = nil
|
||||
else
|
||||
-- comment
|
||||
if line:match("^#") then
|
||||
if not in_comments then
|
||||
in_comments = true
|
||||
fuzzy = false
|
||||
local str_len = m_str_tbl[read_strings_count].length
|
||||
local str_off = m_str_tbl[read_strings_count].offset
|
||||
local str_ptr = str_buf:reserve(str_len)
|
||||
if not seek_and_read(str_off, str_ptr, str_len) then
|
||||
return
|
||||
end
|
||||
local ctx
|
||||
local pos = 0
|
||||
if check_for_context then
|
||||
-- 4: ␄ (End of Transmission).
|
||||
local p = C.memchr(str_ptr, 4, str_len)
|
||||
if p ~= nil then
|
||||
local l = ffi.cast("ssize_t", p) - ffi.cast("ssize_t", str_ptr)
|
||||
ctx = ffi.string(str_ptr, l)
|
||||
pos = l + 1
|
||||
end
|
||||
if line:match(", fuzzy") then
|
||||
fuzzy = true
|
||||
end
|
||||
local l = C.strnlen(str_ptr + pos, str_len - pos)
|
||||
if l + pos < str_len then
|
||||
-- Plurals!
|
||||
local strings = {ffi.string(str_ptr + pos, l)}
|
||||
pos = pos + l + 1
|
||||
while pos < str_len do
|
||||
l = C.strnlen(str_ptr + pos, str_len - pos)
|
||||
table.insert(strings, ffi.string(str_ptr + pos, l))
|
||||
pos = pos + l + 1
|
||||
end
|
||||
elseif fuzzy then
|
||||
in_comments = false
|
||||
return read_strings_count + 1, strings, ctx
|
||||
else
|
||||
in_comments = false
|
||||
-- new data item (msgid, msgstr, ...
|
||||
local w, s = line:match("^%s*([%a_%[%]0-9]+)%s+\"(.*)\"%s*$")
|
||||
if w then
|
||||
what = w
|
||||
else
|
||||
-- string continuation
|
||||
s = line:match("^%s*\"(.*)\"%s*$")
|
||||
end
|
||||
if what and s then
|
||||
-- unescape \n or msgid won't match
|
||||
s = s:gsub("\\n", "\n")
|
||||
-- unescape " or msgid won't match
|
||||
s = s:gsub('\\"', '"')
|
||||
-- unescape \\ or msgid won't match
|
||||
s = s:gsub("\\\\", "\\")
|
||||
data[what] = (data[what] or "") .. s
|
||||
end
|
||||
return read_strings_count + 1, ffi.string(str_ptr + pos, str_len - pos), ctx
|
||||
end
|
||||
end
|
||||
end
|
||||
po:close()
|
||||
GetText.current_lang = new_lang
|
||||
-- Read original strings.
|
||||
if not seek_and_read(mo_hdr.original_strings_table_offset, table_ptr, table_size) then
|
||||
C.close(fd)
|
||||
return false
|
||||
end
|
||||
local original_context = {}
|
||||
local original_strings = table.new(mo_hdr.nb_strings, 0)
|
||||
for n, s, ctx in read_strings(true) do
|
||||
if ctx then
|
||||
original_context[n] = ctx
|
||||
end
|
||||
original_strings[n] = s
|
||||
end
|
||||
if read_strings_count ~= mo_hdr.nb_strings then
|
||||
C.close(fd)
|
||||
return false
|
||||
end
|
||||
-- Read translated strings.
|
||||
if not seek_and_read(mo_hdr.translated_strings_table_offset, table_ptr, table_size) then
|
||||
C.close(fd)
|
||||
return false
|
||||
end
|
||||
for n, ts in read_strings() do
|
||||
local ctx = original_context[n]
|
||||
local os = original_strings[n]
|
||||
if type(os) == "table" then
|
||||
if type(ts) == "table" then
|
||||
for pn, pts in ipairs(ts) do
|
||||
addTranslation(ctx, os[1], pts, pn - 1)
|
||||
end
|
||||
else
|
||||
addTranslation(ctx, os[1], ts, 0)
|
||||
end
|
||||
elseif type(ts) == "table" then
|
||||
logger.warn(string.format("bad translation file: %s [singular / plurals mismatch]", file))
|
||||
else
|
||||
if n == 1 and #os == 0 then
|
||||
parse_headers(ts)
|
||||
else
|
||||
addTranslation(ctx, os, ts)
|
||||
end
|
||||
end
|
||||
end
|
||||
local ok = read_strings_count == mo_hdr.nb_strings
|
||||
C.close(fd)
|
||||
return ok
|
||||
end
|
||||
|
||||
GetText_mt.__index.getPlural = getDefaultPlural
|
||||
@@ -412,7 +494,6 @@ elseif os.getenv("LANG") then
|
||||
end
|
||||
|
||||
if isAndroid then
|
||||
local ffi = require("ffi")
|
||||
local buf = ffi.new("char[?]", 16)
|
||||
android.lib.AConfiguration_getLanguage(android.app.config, buf)
|
||||
local lang = ffi.string(buf)
|
||||
|
||||
325
frontend/ui/presets.lua
Normal file
325
frontend/ui/presets.lua
Normal file
@@ -0,0 +1,325 @@
|
||||
--[[--
|
||||
This module provides a unified interface for managing presets across different KOReader modules.
|
||||
It handles creation, loading, updating, and deletion of presets, as well as menu generation.
|
||||
|
||||
Usage:
|
||||
local Presets = require("ui/presets")
|
||||
|
||||
-- 1. In your module's init() method, set up a preset object:
|
||||
self.preset_obj = {
|
||||
presets = G_reader_settings:readSetting("my_module_presets", {}), -- or custom storage
|
||||
cycle_index = G_reader_settings:readSetting("my_module_presets_cycle_index"), -- optional, only needed if cycling through presets
|
||||
dispatcher_name = "load_my_module_preset", -- must match dispatcher.lua entry
|
||||
saveCycleIndex = function(this) -- Save cycle index to persistent storage
|
||||
G_reader_settings:saveSetting("my_module_presets_cycle_index", this.cycle_index)
|
||||
end,
|
||||
buildPreset = function() return self:buildPreset() end, -- Closure to build a preset from current state
|
||||
loadPreset = function(preset) self:loadPreset(preset) end, -- Closure to apply a preset to the module
|
||||
}
|
||||
|
||||
-- 2. Implement required methods in your module:
|
||||
function MyModule:buildPreset()
|
||||
return {
|
||||
-- Return a table with the settings you want to save in the preset
|
||||
setting1 = self.setting1,
|
||||
setting2 = self.setting2,
|
||||
enabled_features = self.enabled_features,
|
||||
}
|
||||
end
|
||||
|
||||
function MyModule:loadPreset(preset)
|
||||
-- Apply the preset settings to your module
|
||||
self.setting1 = preset.setting1
|
||||
self.setting2 = preset.setting2
|
||||
self.enabled_features = preset.enabled_features
|
||||
-- Update UI or perform other necessary changes
|
||||
self:refresh()
|
||||
end
|
||||
|
||||
-- 3. Create menu items for presets: (Alternatively, you could call Presets.genPresetMenuItemTable directly from touchmenu_instance)
|
||||
function MyModule:genPresetMenuItemTable(touchmenu_instance)
|
||||
return Presets.genPresetMenuItemTable(
|
||||
self.preset_obj, -- preset object
|
||||
_("Create new preset from current settings"), -- optional: custom text for UI menu
|
||||
function() return self:hasValidSettings() end, -- optional: function to enable/disable creating presets
|
||||
)
|
||||
end
|
||||
|
||||
-- 4. Load a preset by name (for dispatcher/event handling):
|
||||
function MyModule:onLoadMyModulePreset(preset_name)
|
||||
return Presets.onLoadPreset(
|
||||
self.preset_obj,
|
||||
preset_name,
|
||||
true -- show notification
|
||||
)
|
||||
end
|
||||
|
||||
-- 5. Cycle through presets (for dispatcher/event handling):
|
||||
function MyModule:onCycleMyModulePresets()
|
||||
return Presets.cycleThroughPresets(
|
||||
self.preset_obj,
|
||||
true -- show notification
|
||||
)
|
||||
end
|
||||
|
||||
-- 6. Get list of available presets (for dispatcher):
|
||||
function MyModule.getPresets() -- Note: This is a static method on MyModule
|
||||
local config = {
|
||||
presets = G_reader_settings:readSetting("my_module_presets", {})
|
||||
}
|
||||
return Presets.getPresets(config)
|
||||
end
|
||||
|
||||
-- 7. Add to dispatcher.lua:
|
||||
load_my_module_preset = {
|
||||
category = "string",
|
||||
event = "LoadMyModulePreset",
|
||||
title = _("Load my module preset"),
|
||||
args_func = MyModule.getPresets,
|
||||
reader = true
|
||||
},
|
||||
cycle_my_module_preset = {
|
||||
category = "none",
|
||||
event = "CycleMyModulePresets",
|
||||
title = _("Cycle through my module presets"),
|
||||
reader = true
|
||||
},
|
||||
|
||||
Required preset_obj fields:
|
||||
- presets: table containing saved presets
|
||||
- cycle_index: current index for cycling through presets (optional, defaults to 0)
|
||||
- dispatcher_name: string matching the dispatcher action name (for dispatcher integration)
|
||||
- saveCycleIndex(this): function to save cycle index (optional, only needed if cycling is used)
|
||||
|
||||
Required module methods:
|
||||
- buildPreset(): returns a table with the current settings to save as a preset
|
||||
- loadPreset(preset): applies the settings from the preset table to the module
|
||||
|
||||
The preset system handles:
|
||||
- Creating, updating, deleting, and renaming presets through UI dialogs
|
||||
- Generating menu items with hold actions for preset management
|
||||
- Saving/loading presets to/from G_reader_settings (or custom storage)
|
||||
- Cycling through presets with wrap-around
|
||||
- User notifications when presets are loaded/updated/created
|
||||
- Integration with Dispatcher for gesture/hotkey/profile support
|
||||
- Broadcasting events to update dispatcher when presets change
|
||||
- Input validation and duplicate name prevention
|
||||
--]]
|
||||
|
||||
local ConfirmBox = require("ui/widget/confirmbox")
|
||||
local Event = require("ui/event")
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
local InputDialog = require("ui/widget/inputdialog")
|
||||
local Notification = require("ui/widget/notification")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local ffiUtil = require("ffi/util")
|
||||
local T = require("ffi/util").template
|
||||
local _ = require("gettext")
|
||||
|
||||
local Presets = {}
|
||||
|
||||
function Presets.editPresetName(options, preset_obj, on_success_callback)
|
||||
local input_dialog
|
||||
input_dialog = InputDialog:new{
|
||||
title = options.title or _("Enter preset name"),
|
||||
input = options.initial_value or "",
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Cancel"),
|
||||
id = "close",
|
||||
callback = function()
|
||||
UIManager:close(input_dialog)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = options.confirm_button_text or _("Create"),
|
||||
is_enter_default = true,
|
||||
callback = function()
|
||||
local entered_preset_name = input_dialog:getInputText()
|
||||
if entered_preset_name == "" or entered_preset_name:match("^%s*$") then
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("Invalid preset name. Please choose a different name."),
|
||||
timeout = 2,
|
||||
})
|
||||
return false
|
||||
end
|
||||
if options.initial_value and entered_preset_name == options.initial_value then
|
||||
UIManager:close(input_dialog)
|
||||
return false
|
||||
end
|
||||
if preset_obj.presets[entered_preset_name] then
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = T(_("A preset named '%1' already exists. Please choose a different name."), entered_preset_name),
|
||||
timeout = 2,
|
||||
})
|
||||
return false
|
||||
end
|
||||
|
||||
-- If all validation passes, call the success callback
|
||||
on_success_callback(entered_preset_name)
|
||||
UIManager:close(input_dialog)
|
||||
end,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
UIManager:show(input_dialog)
|
||||
input_dialog:onShowKeyboard()
|
||||
end
|
||||
|
||||
function Presets.genPresetMenuItemTable(preset_obj, text, enabled_func)
|
||||
local presets = preset_obj.presets
|
||||
local items = {
|
||||
{
|
||||
text = text or _("Create new preset from current settings"),
|
||||
keep_menu_open = true,
|
||||
enabled_func = enabled_func,
|
||||
callback = function(touchmenu_instance)
|
||||
Presets.editPresetName({}, preset_obj,
|
||||
function(entered_preset_name)
|
||||
local preset_data = preset_obj.buildPreset()
|
||||
preset_obj.presets[entered_preset_name] = preset_data
|
||||
touchmenu_instance.item_table = Presets.genPresetMenuItemTable(preset_obj)
|
||||
touchmenu_instance:updateItems()
|
||||
end
|
||||
)
|
||||
end,
|
||||
separator = true,
|
||||
},
|
||||
}
|
||||
for preset_name in ffiUtil.orderedPairs(presets) do
|
||||
table.insert(items, {
|
||||
text = preset_name,
|
||||
keep_menu_open = true,
|
||||
callback = function()
|
||||
preset_obj.loadPreset(presets[preset_name])
|
||||
-- There's no guarantee that it'll be obvious to the user that the preset was loaded so, we show a notification.
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = T(_("Preset '%1' loaded successfully."), preset_name),
|
||||
timeout = 2,
|
||||
})
|
||||
end,
|
||||
hold_callback = function(touchmenu_instance, item)
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = T(_("What would you like to do with preset '%1'?"), preset_name),
|
||||
icon = "notice-question",
|
||||
ok_text = _("Update"),
|
||||
ok_callback = function()
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = T(_("Are you sure you want to overwrite preset '%1' with current settings?"), preset_name),
|
||||
ok_callback = function()
|
||||
presets[preset_name] = preset_obj.buildPreset()
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = T(_("Preset '%1' was updated with current settings"), preset_name),
|
||||
timeout = 2,
|
||||
})
|
||||
end,
|
||||
})
|
||||
end,
|
||||
other_buttons_first = true,
|
||||
other_buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Delete"),
|
||||
callback = function()
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = T(_("Are you sure you want to delete preset '%1'?"), preset_name),
|
||||
ok_text = _("Delete"),
|
||||
ok_callback = function()
|
||||
presets[preset_name] = nil
|
||||
local action_key = preset_obj.dispatcher_name
|
||||
if action_key then
|
||||
UIManager:broadcastEvent(Event:new("DispatcherActionValueChanged", {
|
||||
name = action_key,
|
||||
old_value = preset_name,
|
||||
new_value = nil -- delete the action
|
||||
}))
|
||||
end
|
||||
table.remove(touchmenu_instance.item_table, item.idx)
|
||||
touchmenu_instance:updateItems()
|
||||
end,
|
||||
})
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Rename"),
|
||||
callback = function()
|
||||
Presets.editPresetName({
|
||||
title = _("Enter new preset name"),
|
||||
initial_value = preset_name,
|
||||
confirm_button_text = _("Rename"),
|
||||
}, preset_obj,
|
||||
function(new_name)
|
||||
presets[new_name] = presets[preset_name]
|
||||
presets[preset_name] = nil
|
||||
local action_key = preset_obj.dispatcher_name
|
||||
if action_key then
|
||||
UIManager:broadcastEvent(Event:new("DispatcherActionValueChanged", {
|
||||
name = action_key,
|
||||
old_value = preset_name,
|
||||
new_value = new_name
|
||||
}))
|
||||
end
|
||||
touchmenu_instance.item_table = Presets.genPresetMenuItemTable(preset_obj)
|
||||
touchmenu_instance:updateItems()
|
||||
end) -- editPresetName
|
||||
end, -- rename callback
|
||||
},
|
||||
},
|
||||
}, -- end of other_buttons
|
||||
}) -- end of ConfirmBox
|
||||
end, -- hold_callback
|
||||
}) -- end of table.insert
|
||||
end -- for each preset
|
||||
return items
|
||||
end
|
||||
|
||||
|
||||
function Presets.onLoadPreset(preset_obj, preset_name, show_notification)
|
||||
local presets = preset_obj.presets
|
||||
if presets and presets[preset_name] then
|
||||
preset_obj.loadPreset(presets[preset_name])
|
||||
if show_notification then
|
||||
Notification:notify(T(_("Preset '%1' was loaded"), preset_name))
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Presets.cycleThroughPresets(preset_obj, show_notification)
|
||||
local preset_names = Presets.getPresets(preset_obj)
|
||||
if #preset_names == 0 then
|
||||
Notification:notify(_("No presets available"), Notification.SOURCE_ALWAYS_SHOW)
|
||||
return true -- we *must* return true here to prevent further event propagation, i.e multiple notifications
|
||||
end
|
||||
-- Get and increment index, wrap around if needed
|
||||
local index = (preset_obj.cycle_index or 0) + 1
|
||||
if index > #preset_names then
|
||||
index = 1
|
||||
end
|
||||
local next_preset_name = preset_names[index]
|
||||
preset_obj.loadPreset(preset_obj.presets[next_preset_name])
|
||||
preset_obj.cycle_index = index
|
||||
preset_obj:saveCycleIndex()
|
||||
if show_notification then
|
||||
Notification:notify(T(_("Loaded preset: %1"), next_preset_name))
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Presets.getPresets(preset_obj)
|
||||
local presets = preset_obj.presets
|
||||
local actions = {}
|
||||
if presets and next(presets) then
|
||||
for preset_name in pairs(presets) do
|
||||
table.insert(actions, preset_name)
|
||||
end
|
||||
if #actions > 1 then
|
||||
table.sort(actions)
|
||||
end
|
||||
end
|
||||
return actions, actions
|
||||
end
|
||||
|
||||
return Presets
|
||||
@@ -21,6 +21,7 @@ local Size = require("ui/size")
|
||||
local TextWidget = require("ui/widget/textwidget")
|
||||
local TitleBar = require("ui/widget/titlebar")
|
||||
local Translator = require("ui/translator")
|
||||
local Presets = require("ui/presets")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local VerticalGroup = require("ui/widget/verticalgroup")
|
||||
local VerticalSpan = require("ui/widget/verticalspan")
|
||||
@@ -1159,12 +1160,7 @@ function DictQuickLookup:changeDictionary(index, skip_update)
|
||||
-- add queried word to 1st result's definition, so we can see
|
||||
-- what was the selected text and if we selected wrong
|
||||
if index == 1 then
|
||||
if self.is_html then
|
||||
self.definition = self.definition.."<br/>_______<br/>"
|
||||
else
|
||||
self.definition = self.definition.."\n_______\n"
|
||||
end
|
||||
self.definition = self.definition..T(_("(query : %1)"), self.word)
|
||||
self:addQueryWordToResult()
|
||||
end
|
||||
end
|
||||
self.displaydictname = self.dictionary
|
||||
@@ -1187,6 +1183,16 @@ function DictQuickLookup:changeDictionary(index, skip_update)
|
||||
end
|
||||
end
|
||||
|
||||
function DictQuickLookup:addQueryWordToResult()
|
||||
-- Extracted to a separate method so it can be removed by user patches.
|
||||
if self.is_html then
|
||||
self.definition = self.definition.."<br/>_______<br/>"
|
||||
else
|
||||
self.definition = self.definition.."\n_______\n"
|
||||
end
|
||||
self.definition = self.definition..T(_("(query : %1)"), self.word)
|
||||
end
|
||||
|
||||
--[[ No longer used
|
||||
function DictQuickLookup:changeToDefaultDict()
|
||||
if self.dictionary then
|
||||
@@ -1404,57 +1410,96 @@ function DictQuickLookup:onForwardingPanRelease(arg, ges)
|
||||
end
|
||||
|
||||
function DictQuickLookup:onLookupInputWord(hint)
|
||||
local buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Translate"),
|
||||
callback = function()
|
||||
local text = self.input_dialog:getInputText()
|
||||
if text ~= "" then
|
||||
UIManager:close(self.input_dialog)
|
||||
Translator:showTranslation(text, true)
|
||||
end
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Search Wikipedia"),
|
||||
is_enter_default = self.is_wiki,
|
||||
callback = function()
|
||||
local text = self.input_dialog:getInputText()
|
||||
if text ~= "" then
|
||||
UIManager:close(self.input_dialog)
|
||||
self.is_wiki = true
|
||||
self:lookupWikipedia(false, text, true)
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
text = _("Cancel"),
|
||||
id = "close",
|
||||
callback = function()
|
||||
UIManager:close(self.input_dialog)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Search dictionary"),
|
||||
is_enter_default = not self.is_wiki,
|
||||
callback = function()
|
||||
local text = self.input_dialog:getInputText()
|
||||
if text ~= "" then
|
||||
UIManager:close(self.input_dialog)
|
||||
self.is_wiki = false
|
||||
self.ui:handleEvent(Event:new("LookupWord", text, true))
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
local preset_names = Presets.getPresets(self.ui.dictionary.preset_obj)
|
||||
if preset_names and #preset_names > 0 then
|
||||
table.insert(buttons, 2, {
|
||||
{
|
||||
text = _("Search with preset"),
|
||||
callback = function()
|
||||
local text = self.input_dialog:getInputText()
|
||||
if text == "" or text:match("^%s*$") then return end
|
||||
local current_dict_state = self.ui.dictionary:buildPreset()
|
||||
local button_dialog, dialog_buttons = nil, {} -- CI won't like it if we call it buttons :( so dialog_buttons
|
||||
for _, preset_name in ipairs(preset_names) do
|
||||
table.insert(dialog_buttons, {
|
||||
{
|
||||
align = "left",
|
||||
text = preset_name,
|
||||
callback = function()
|
||||
self.ui.dictionary:loadPreset(self.ui.dictionary.preset_obj.presets[preset_name], true)
|
||||
UIManager:close(button_dialog)
|
||||
UIManager:close(self.input_dialog)
|
||||
self.ui:handleEvent(Event:new("LookupWord", text, true, nil, nil, nil,
|
||||
function()
|
||||
-- Restore original preset _after_ lookup is complete
|
||||
self.ui.dictionary:loadPreset(current_dict_state, true)
|
||||
end
|
||||
))
|
||||
end
|
||||
}
|
||||
})
|
||||
end
|
||||
button_dialog = ButtonDialog:new{
|
||||
buttons = dialog_buttons,
|
||||
shrink_unneeded_width = true,
|
||||
}
|
||||
UIManager:show(button_dialog)
|
||||
end,
|
||||
}
|
||||
})
|
||||
end
|
||||
self.input_dialog = InputDialog:new{
|
||||
title = _("Enter a word or phrase to look up"),
|
||||
input = hint,
|
||||
input_hint = hint,
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Translate"),
|
||||
callback = function()
|
||||
local text = self.input_dialog:getInputText()
|
||||
if text ~= "" then
|
||||
UIManager:close(self.input_dialog)
|
||||
Translator:showTranslation(text, true)
|
||||
end
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Search Wikipedia"),
|
||||
is_enter_default = self.is_wiki,
|
||||
callback = function()
|
||||
local text = self.input_dialog:getInputText()
|
||||
if text ~= "" then
|
||||
UIManager:close(self.input_dialog)
|
||||
self.is_wiki = true
|
||||
self:lookupWikipedia(false, text, true)
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
text = _("Cancel"),
|
||||
id = "close",
|
||||
callback = function()
|
||||
UIManager:close(self.input_dialog)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Search dictionary"),
|
||||
is_enter_default = not self.is_wiki,
|
||||
callback = function()
|
||||
local text = self.input_dialog:getInputText()
|
||||
if text ~= "" then
|
||||
UIManager:close(self.input_dialog)
|
||||
self.is_wiki = false
|
||||
self.ui:handleEvent(Event:new("LookupWord", text, true))
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
},
|
||||
buttons = buttons,
|
||||
}
|
||||
UIManager:show(self.input_dialog)
|
||||
self.input_dialog:onShowKeyboard()
|
||||
|
||||
33
make/gettext.mk
Normal file
33
make/gettext.mk
Normal file
@@ -0,0 +1,33 @@
|
||||
PHONY += mo mo-clean po pot
|
||||
|
||||
SELF := $(lastword $(MAKEFILE_LIST))
|
||||
|
||||
DOMAIN = koreader
|
||||
TEMPLATE_DIR = l10n/templates
|
||||
MSGFMT_BIN = msgfmt
|
||||
XGETTEXT_BIN = xgettext
|
||||
|
||||
PO_FILES = $(wildcard l10n/*/*.po)
|
||||
MO_FILES = $(PO_FILES:%.po=%.mo)
|
||||
|
||||
%.mo: %.po
|
||||
@$(MSGFMT_BIN) --no-hash -o $@ $<
|
||||
|
||||
mo:
|
||||
$(MAKE) $(if $(PARALLEL_JOBS),--jobs=$(PARALLEL_JOBS)) $(if $(PARALLEL_LOAD),--load-average=$(PARALLEL_LOAD)) --silent --file=$(SELF) $(MO_FILES)
|
||||
|
||||
mo-clean:
|
||||
rm -f $(MO_FILES)
|
||||
|
||||
pot: po
|
||||
mkdir -p $(TEMPLATE_DIR)
|
||||
$(XGETTEXT_BIN) --from-code=utf-8 \
|
||||
--keyword=C_:1c,2 --keyword=N_:1,2 --keyword=NC_:1c,2,3 \
|
||||
--add-comments=@translators \
|
||||
reader.lua `find frontend -iname "*.lua" | sort` \
|
||||
`find plugins -iname "*.lua" | sort` \
|
||||
`find tools -iname "*.lua" | sort` \
|
||||
-o $(TEMPLATE_DIR)/$(DOMAIN).pot
|
||||
|
||||
po:
|
||||
git submodule update --remote l10n
|
||||
@@ -40,9 +40,9 @@ return {
|
||||
alt_plus_a = nil,
|
||||
alt_plus_b = nil,
|
||||
alt_plus_c = nil,
|
||||
alt_plus_d = {dictionary_lookup = true,},
|
||||
alt_plus_d = Device:hasKeyboard() and {dictionary_lookup = true,} or {},
|
||||
alt_plus_e = nil,
|
||||
alt_plus_f = {file_search = true,},
|
||||
alt_plus_f = Device:hasKeyboard() and {file_search = true,} or {},
|
||||
alt_plus_g = nil,
|
||||
alt_plus_h = nil,
|
||||
alt_plus_i = nil,
|
||||
@@ -59,7 +59,7 @@ return {
|
||||
alt_plus_t = nil,
|
||||
alt_plus_u = nil,
|
||||
alt_plus_v = nil,
|
||||
alt_plus_w = {wikipedia_lookup = true,},
|
||||
alt_plus_w = Device:hasKeyboard() and {wikipedia_lookup = true,} or {},
|
||||
alt_plus_x = nil,
|
||||
alt_plus_y = nil,
|
||||
alt_plus_z = nil,
|
||||
@@ -96,9 +96,9 @@ return {
|
||||
alt_plus_a = nil,
|
||||
alt_plus_b = nil,
|
||||
alt_plus_c = nil,
|
||||
alt_plus_d = {dictionary_lookup = true,},
|
||||
alt_plus_d = Device:hasKeyboard() and {dictionary_lookup = true,} or {},
|
||||
alt_plus_e = nil,
|
||||
alt_plus_f = {file_search = true,},
|
||||
alt_plus_f = Device:hasKeyboard() and {file_search = true,} or {},
|
||||
alt_plus_g = nil,
|
||||
alt_plus_h = nil,
|
||||
alt_plus_i = nil,
|
||||
@@ -111,11 +111,11 @@ return {
|
||||
alt_plus_p = nil,
|
||||
alt_plus_q = nil,
|
||||
alt_plus_r = nil,
|
||||
alt_plus_s = {fulltext_search = true,},
|
||||
alt_plus_s = Device:hasKeyboard() and {fulltext_search = true,} or {},
|
||||
alt_plus_t = nil,
|
||||
alt_plus_u = nil,
|
||||
alt_plus_v = nil,
|
||||
alt_plus_w = {wikipedia_lookup = true,},
|
||||
alt_plus_w = Device:hasKeyboard() and {wikipedia_lookup = true,} or {},
|
||||
alt_plus_x = nil,
|
||||
alt_plus_y = nil,
|
||||
alt_plus_z = nil,
|
||||
|
||||
@@ -106,75 +106,44 @@ msgstr "Fuzzy translated"
|
||||
|
||||
describe("GetText module", function()
|
||||
local GetText
|
||||
local test_po_ar
|
||||
local test_po_nl, test_po_ru
|
||||
local test_po_none, test_po_simple
|
||||
local test_po_many
|
||||
|
||||
setup(function()
|
||||
require("commonrequire")
|
||||
GetText = require("gettext")
|
||||
GetText.dirname = "i18n-test"
|
||||
GetText.dirname = (os.getenv("KO_HOME") or ".").."/i18n-test"
|
||||
|
||||
local lfs = require("libs/libkoreader-lfs")
|
||||
|
||||
lfs.mkdir(GetText.dirname)
|
||||
lfs.mkdir(GetText.dirname.."/nl_NL")
|
||||
lfs.mkdir(GetText.dirname.."/none")
|
||||
lfs.mkdir(GetText.dirname.."/ar")
|
||||
lfs.mkdir(GetText.dirname.."/ru")
|
||||
lfs.mkdir(GetText.dirname.."/simple")
|
||||
lfs.mkdir(GetText.dirname.."/many")
|
||||
|
||||
test_po_nl = GetText.dirname.."/nl_NL/koreader.po"
|
||||
local f = io.open(test_po_nl, "w")
|
||||
f:write(test_po_part1, test_plurals_nl, test_po_part2)
|
||||
f:close()
|
||||
local pocreate = function(lang, ...)
|
||||
local dir = GetText.dirname.."/"..lang
|
||||
local po = dir.."/koreader.po"
|
||||
local mo = dir.."/koreader.mo"
|
||||
lfs.mkdir(dir)
|
||||
local f = io.open(po, "w")
|
||||
f:write(...)
|
||||
f:close()
|
||||
local ok = os.execute(string.format("msgfmt --no-hash -o %s %s", mo, po))
|
||||
assert(ok == 0)
|
||||
end
|
||||
|
||||
pocreate("nl_NL", test_po_part1, test_plurals_nl, test_po_part2)
|
||||
|
||||
-- same file, just different plural for testing
|
||||
test_po_none = GetText.dirname.."/none/koreader.po"
|
||||
f = io.open(test_po_none, "w")
|
||||
f:write(test_po_part1, test_plurals_none, test_po_part2)
|
||||
f:close()
|
||||
pocreate("none", test_po_part1, test_plurals_none, test_po_part2)
|
||||
|
||||
-- same file, just different plural for testing
|
||||
test_po_ar = GetText.dirname.."/ar/koreader.po"
|
||||
f = io.open(test_po_ar, "w")
|
||||
f:write(test_po_part1, test_plurals_ar, test_po_part2)
|
||||
f:close()
|
||||
pocreate("ar", test_po_part1, test_plurals_ar, test_po_part2)
|
||||
|
||||
-- same file, just different plural for testing
|
||||
test_po_ru = GetText.dirname.."/ru/koreader.po"
|
||||
f = io.open(test_po_ru, "w")
|
||||
f:write(test_po_part1, test_plurals_ru, test_po_part2)
|
||||
f:close()
|
||||
pocreate("ru", test_po_part1, test_plurals_ru, test_po_part2)
|
||||
|
||||
-- same file, just different plural for testing
|
||||
test_po_simple = GetText.dirname.."/simple/koreader.po"
|
||||
f = io.open(test_po_simple, "w")
|
||||
f:write(test_po_part1, test_plurals_simple, test_po_part2)
|
||||
f:close()
|
||||
pocreate("simple", test_po_part1, test_plurals_simple, test_po_part2)
|
||||
|
||||
-- same file, just different plural for testing
|
||||
test_po_many = GetText.dirname.."/many/koreader.po"
|
||||
f = io.open(test_po_many, "w")
|
||||
f:write(test_po_part1, test_plurals_many, test_po_part2)
|
||||
f:close()
|
||||
end)
|
||||
|
||||
teardown(function()
|
||||
os.remove(test_po_nl)
|
||||
os.remove(test_po_none)
|
||||
os.remove(test_po_ar)
|
||||
os.remove(test_po_ru)
|
||||
os.remove(test_po_simple)
|
||||
os.remove(test_po_many)
|
||||
os.remove(GetText.dirname.."/nl_NL")
|
||||
os.remove(GetText.dirname.."/none")
|
||||
os.remove(GetText.dirname.."/ar")
|
||||
os.remove(GetText.dirname.."/ru")
|
||||
os.remove(GetText.dirname.."/simple")
|
||||
os.remove(GetText.dirname.."/many")
|
||||
os.remove(GetText.dirname)
|
||||
pocreate("many", test_po_part1, test_plurals_many, test_po_part2)
|
||||
end)
|
||||
|
||||
describe("changeLang", function()
|
||||
|
||||
Reference in New Issue
Block a user