intllib/gettext.lua

227 lines
4.9 KiB
Lua

local strsub, strrep = string.sub, string.rep
local strmatch, strgsub = string.match, string.gsub
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] or 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 ipairs(str:split("\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")
-- luacheck: ignore
until true end -- end for
return texts
end
local M = { }
local function warn(msg)
minetest.log("warning", "[intllib] "..msg)
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(s)
local c, t, f = strmatch(s, "^(.-)%?(.-):(.*)")
if c then
return ("__if("
..replace_ternary(c)
..","..replace_ternary(t)
..","..replace_ternary(f)
..")")
end
return s
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 ipairs(str:split("\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 ": " or "")..(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
return bail("catalog has no headers")
end
hdrs = parse_headers(hdrs[0])
local pf = hdrs["Plural-Forms"]
if not pf then
-- XXX: Is this right? Gettext assumes this if header not present.
pf = "nplurals=2; plural=n != 1"
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)
local langs = intllib.get_detected_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