2
0
mirror of https://github.com/koreader/koreader synced 2024-10-31 21:20:20 +00:00
koreader/frontend/gettext.lua
Frans de Jonge 0067c8f29e
[i18n, feat] Add basic context (msgctxt) support (#5234)
References https://github.com/koreader/koreader/issues/5232

Given an entry in the PO file like the following:

```
msgctxt "systemstat"
msgid "    Total"
msgstr "Totaal"
```

It can be addressed using:

```lua
local _ = require("gettext")
local C_ = _.pgettext
C_("systemstat", "    Total")
```

This allows to distinguish between separate instances of the same string, for example "Pages" meaning "Number of pages" and "Pages" meaning "Display of pages".

Extraction of this code pattern is not yet implemented by nightswatcher. xgettext didn't yet support Lua back in 2013 when all this was first added to the program, but now it does. Therefore it might make the most sense to replace the current Python extraction script with xgettext itself.

By default it only understands gettext.pgettext(), but that can be addressed by passing some extra command line arguments, for example:

```
xgettext -l lua -c --keyword=C_:1c,2 *.lua
```
2019-08-21 19:40:15 +02:00

132 lines
4.0 KiB
Lua

local isAndroid, android = pcall(require, "android")
local logger = require("logger")
local GetText = {
translation = {},
current_lang = "C",
dirname = "l10n",
textdomain = "koreader"
}
local GetText_mt = {
__index = {}
}
function GetText_mt.__call(gettext, msgstr)
return gettext.translation[msgstr] or msgstr
end
local function c_escape(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
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 = {}
GetText.current_lang = "C"
-- the "C" locale disables localization altogether
-- can be various things such as `en_US` or `en_US:en`
if new_lang == "C" or new_lang == nil or new_lang == ""
or new_lang:match("^en_US") == "en_US" then return end
-- 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)
return false
end
local data = {}
local what = nil
while true do
local line = po:read("*l")
if line == nil or line == "" then
if data.msgid and data.msgstr and data.msgstr ~= "" then
local unescaped_string = string.gsub(data.msgstr, "\\(.)", c_escape)
if data.msgctxt and data.msgctxt ~= "" then
if not GetText.context[data.msgctxt] then
GetText.context[data.msgctxt] = {}
end
GetText.context[data.msgctxt][data.msgid] = unescaped_string
else
GetText.translation[data.msgid] = unescaped_string
end
end
-- stop at EOF:
if line == nil then break end
data = {}
what = nil
else
-- comment
if not line:match("^#") then
-- new data item (msgid, msgstr, ...
local w, s = line:match("^%s*(%a+)%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('\\"', '"')
data[what] = (data[what] or "") .. s
end
end
end
end
GetText.current_lang = new_lang
end
function GetText_mt.__index.pgettext(msgctxt, msgstr)
return GetText.context[msgctxt] and GetText.context[msgctxt][msgstr] or msgstr
end
setmetatable(GetText, GetText_mt)
if os.getenv("LANGUAGE") then
GetText.changeLang(os.getenv("LANGUAGE"))
elseif os.getenv("LC_ALL") then
GetText.changeLang(os.getenv("LC_ALL"))
elseif os.getenv("LC_MESSAGES") then
GetText.changeLang(os.getenv("LC_MESSAGES"))
elseif os.getenv("LANG") then
GetText.changeLang(os.getenv("LANG"))
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)
android.lib.AConfiguration_getCountry(android.app.config, buf)
local country = ffi.string(buf)
if lang and country then
GetText.changeLang(lang.."_"..country)
end
end
return GetText