summaryrefslogtreecommitdiff
path: root/gettext.lua
diff options
context:
space:
mode:
authorDiego Martínez <kaeza@users.noreply.github.com>2017-01-21 01:04:03 -0300
committerDiego Martínez <kaeza@users.noreply.github.com>2017-01-24 00:24:57 -0300
commitb2551f6a2209b8a11b42834cb0d63f5c03a2b95f (patch)
tree93e1ac391a3146431b273c94805f543b63b64fb1 /gettext.lua
parent4e067ec21906e9a27ec704dd5f34297b2592d6de (diff)
Add support for gettext message catalogs.
Diffstat (limited to 'gettext.lua')
-rw-r--r--gettext.lua288
1 files changed, 288 insertions, 0 deletions
diff --git a/gettext.lua b/gettext.lua
new file mode 100644
index 0000000..6f3a1cb
--- /dev/null
+++ b/gettext.lua
@@ -0,0 +1,288 @@
+
+local strfind, strsub, strrep = string.find, string.sub, string.rep
+local strmatch, strgsub = string.match, string.gsub
+local floor = math.floor
+
+local function split(str, sep)
+ local pos, endp = 1, #str+1
+ return function()
+ if (not pos) or pos > endp then return end
+ local s, e = strfind(str, sep, pos, true)
+ local part = strsub(str, pos, s and s-1)
+ pos = e and e + 1
+ return part
+ end
+end
+
+local function trim(str)
+ return strmatch(str, "^%s*(.-)%s*$")
+end
+
+local escapes = { n="\n", r="\r", t="\t" }
+
+local function unescape(str)
+ return (strgsub(str, "(\\+)([nrt]?)", function(bs, c)
+ local bsl = #bs
+ local realbs = strrep("\\", bsl/2)
+ if bsl%2 == 1 then
+ c = escapes[c]
+ end
+ return realbs..c
+ end))
+end
+
+local function parse_po(str)
+ local state, msgid, msgid_plural, msgstrind
+ local texts = { }
+ local lineno = 0
+ local function perror(msg)
+ return error(msg.." at line "..lineno)
+ end
+ for line in split(str, "\n") do repeat
+ lineno = lineno + 1
+ line = trim(line)
+
+ if line == "" or strmatch(line, "^#") then
+ state, msgid, msgid_plural = nil, nil, nil
+ break -- continue
+ end
+
+ local mid = strmatch(line, "^%s*msgid%s*\"(.*)\"%s*$")
+ if mid then
+ if state == "id" then
+ return perror("unexpected msgid")
+ end
+ state, msgid = "id", unescape(mid)
+ break -- continue
+ end
+
+ mid = strmatch(line, "^%s*msgid_plural%s*\"(.*)\"%s*$")
+ if mid then
+ if state ~= "id" then
+ return perror("unexpected msgid_plural")
+ end
+ state, msgid_plural = "idp", unescape(mid)
+ break -- continue
+ end
+
+ local ind, mstr = strmatch(line,
+ "^%s*msgstr([0-9%[%]]*)%s*\"(.*)\"%s*$")
+ if ind then
+ if not msgid then
+ return perror("missing msgid")
+ elseif ind == "" then
+ msgstrind = 0
+ elseif strmatch(ind, "%[[0-9]+%]") then
+ msgstrind = tonumber(strsub(ind, 2, -2))
+ else
+ return perror("malformed msgstr")
+ end
+ texts[msgid] = texts[msgid] or { }
+ if msgid_plural then
+ texts[msgid_plural] = texts[msgid]
+ end
+ texts[msgid][msgstrind] = unescape(mstr)
+ state = "str"
+ break -- continue
+ end
+
+ mstr = strmatch(line, "^%s*\"(.*)\"%s*$")
+ if mstr then
+ if state == "id" then
+ msgid = msgid..unescape(mstr)
+ break -- continue
+ elseif state == "idp" then
+ msgid_plural = msgid_plural..unescape(mstr)
+ break -- continue
+ elseif state == "str" then
+ local text = texts[msgid][msgstrind]
+ texts[msgid][msgstrind] = text..unescape(mstr)
+ break -- continue
+ end
+ end
+
+ return perror("malformed line")
+
+ until true end -- end for
+
+ return texts
+end
+
+local M = { }
+
+local domains = { }
+local dgettext_cache = { }
+local dngettext_cache = { }
+local langs
+
+local function detect_languages()
+ if langs then return langs end
+
+ langs = { }
+
+ local function addlang(l)
+ local sep
+ langs[#langs+1] = l
+ sep = strfind(l, ".", 1, true)
+ if sep then
+ l = strsub(l, 1, sep-1)
+ langs[#langs+1] = l
+ end
+ sep = strfind(l, "_", 1, true)
+ if sep then
+ langs[#langs+1] = strsub(l, 1, sep-1)
+ end
+ end
+
+ local v
+
+ v = rawget(_G, "minetest") and minetest.setting_get("language")
+ if v and v~="" then
+ addlang(v)
+ end
+
+ v = os.getenv("LANGUAGE")
+ if v then
+ for item in split(v, ":") do
+ addlang(item)
+ end
+ end
+
+ v = os.getenv("LANG")
+ if v then
+ addlang(v)
+ end
+
+ return langs
+end
+
+local function warn(msg)
+ if rawget(_G, "minetest") then
+ minetest.log("warning", msg)
+ else
+ io.stderr:write("WARNING: ", msg, "\n")
+ end
+end
+
+-- hax!
+-- This function converts a C expression to an equivalent Lua expression.
+-- It handles enough stuff to parse the `Plural-Forms` header correctly.
+-- Note that it assumes the C expression is valid to begin with.
+local function compile_plural_forms(str)
+ local plural = strmatch(str, "plural=([^;]+);?$")
+ local function replace_ternary(str)
+ local c, t, f = strmatch(str, "^(.-)%?(.-):(.*)")
+ if c then
+ return ("__if("
+ ..replace_ternary(c)
+ ..","..replace_ternary(t)
+ ..","..replace_ternary(f)
+ ..")")
+ end
+ return str
+ end
+ plural = replace_ternary(plural)
+ plural = strgsub(plural, "&&", " and ")
+ plural = strgsub(plural, "||", " or ")
+ plural = strgsub(plural, "!=", "~=")
+ plural = strgsub(plural, "!", " not ")
+ local f, err = loadstring([[
+ local function __if(c, t, f)
+ if c and c~=0 then return t else return f end
+ end
+ local function __f(n)
+ return (]]..plural..[[)
+ end
+ return (__f(...))
+ ]])
+ if not f then return nil, err end
+ local env = { }
+ env._ENV, env._G = env, env
+ setfenv(f, env)
+ return function(n)
+ local v = f(n)
+ if type(v) == "boolean" then
+ -- Handle things like a plain `n != 1`
+ v = v and 1 or 0
+ end
+ return v
+ end
+end
+
+local function parse_headers(str)
+ local headers = { }
+ for line in split(str, "\n") do
+ local k, v = strmatch(line, "^([^:]+):%s*(.*)")
+ if k then
+ headers[k] = v
+ end
+ end
+ return headers
+end
+
+local function load_catalog(filename)
+ local f, data, err
+
+ local function bail(msg)
+ warn(msg..(err and ": ")..(err or ""))
+ return nil
+ end
+
+ f, err = io.open(filename, "rb")
+ if not f then
+ return --bail("failed to open catalog")
+ end
+
+ data, err = f:read("*a")
+
+ f:close()
+
+ if not data then
+ return bail("failed to read catalog")
+ end
+
+ data, err = parse_po(data)
+ if not data then
+ return bail("failed to parse catalog")
+ end
+
+ err = nil
+ local hdrs = data[""]
+ if not (hdrs and hdrs[0]) then
+ print(dump(hdrs))
+ return bail("catalog has no headers")
+ end
+
+ hdrs = parse_headers(hdrs[0])
+
+ local pf = hdrs["Plural-Forms"]
+ if not pf then
+ return bail("failed to load catalog:"
+ .." catalog has no Plural-Forms header")
+ end
+
+ data.plural_index, err = compile_plural_forms(pf)
+ if not data.plural_index then
+ return bail("failed to compile plural forms")
+ end
+
+ --warn("loaded: "..filename)
+
+ return data
+end
+
+function M.load_catalogs(path)
+ detect_languages()
+
+ local cats = { }
+ for _, lang in ipairs(langs) do
+ local cat = load_catalog(path.."/"..lang..".po")
+ if cat then
+ cats[#cats+1] = cat
+ end
+ end
+
+ return cats
+end
+
+return M