Skip to content

Instantly share code, notes, and snippets.

@kaeza kaeza/lgettext_mo.lua Secret
Created Jan 21, 2017

Embed
What would you like to do?
local strfind, strsub, strbyte = string.find, string.sub, string.byte
local strmatch = string.match
local floor = math.floor
local function isplit(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 split(str, sep)
local t, n = { }, 0
for part in isplit(str, sep) do
n = n+1
t[n] = part
end
t.n = n
return t
end
local function usplit(str, sep)
local t = split(str, sep)
return unpack(t, 1, t.n)
end
-- From: http://lua-users.org/lists/lua-l/2010-04/msg00005.html
-- Retrieved: 2017-01-20 05:11:36
--
-- Small modifications by kaeza <https://github.com/kaeza> to
-- support `ngettext`.
--
-- Original header follows:
-- -------------------------------------------------------------
-- load an mo file and return a lua table
-- @param mo_file name of the file to load
-- @return table on success
-- @return nil,string on failure
-- @copyright J.Jørgen von Bargen
-- @licence I provide this as public domain
-- @see http://www.gnu.org/software/hello/manual/gettext/MO-Files.html
-- -------------------------------------------------------------
local function parse_mo(mo_data)
--------------------------------
-- check format
--------------------------------
local peek_long --localize
local magic = strsub(mo_data, 1, 4)
-- intel magic 0xde120495
if magic == "\222\018\004\149" then
peek_long = function(offs)
local a, b, c, d = strbyte(mo_data, offs+1, offs+4)
return ((d*256+c)*256+b)*256+a
end
-- motorola magic = 0x950412de
elseif magic == "\149\004\018\222" then
peek_long = function(offs)
local a, b, c, d = strbyte(mo_data, offs+1, offs+4)
return ((a*256+b)*256+c)*256+d
end
else
return nil, "not a valid mo-file"
end
--------------------------------
-- version
--------------------------------
local V = peek_long(4)
if V ~= 0 then
return nil, "unsupported version"
end
------------------------------
-- get number of offsets of table
------------------------------
local N, O, T = peek_long(8), peek_long(12), peek_long(16)
------------------------------
-- traverse and get strings
------------------------------
local hash = { }
for nstr = 1, N do
local ol, oo = peek_long(O), peek_long(O+4) O = O+8
local tl, to = peek_long(T), peek_long(T+4) T = T+8
local key = strsub(mo_data, oo+1, oo+ol)
local val = strsub(mo_data, to+1, to+tl)
local nul = strfind(key, "\0", 1, true)
if nul then
local k = strsub(key, 1, nul-1)
local msgstr, nmsgstr = { }, 0
hash[k] = msgstr
for v in isplit(val, "\0") do
msgstr[nmsgstr] = v
nmsgstr = nmsgstr + 1
end
else
hash[key] = val
end
end
return hash -- return table
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)
langs[#langs+1] = l
local 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)
return langs
end
v = os.getenv("LANGUAGE")
if v then
for item in isplit(v, ":") do
addlang(item)
end
return langs
end
v = os.getenv("LANG")
if v then
addlang(v)
return langs
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 = (plural:gsub("&&", " and ")
:gsub("||", " or ")
:gsub("!=", "~=")
:gsub("!", " not "))
local f, err = loadstring([[
local function __if(c, t, f)
if c 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 isplit(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_mo(data)
if not data then
return bail("failed to parse catalog")
end
err = nil
local hdrs = data[""]
if not hdrs then
return bail("catalog has no headers")
end
hdrs = parse_headers(data[""])
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.bindtextdomain(domain, path)
detect_languages()
--print(unpack(langs))
local old = domains[domain]
local cats = { }
local d = {
path = path,
catalogs = cats,
}
domains[domain] = d
for _, l in ipairs(langs) do
local full = path.."/"..l.."/LC_MESSAGES/"..domain..".mo"
cats[l] = load_catalog(full)
end
dgettext_cache, dngettext_cache = { }, { }
return old and old.path
end
function M.dgettext(domain, msgid)
assert(langs, "you must call bindtextdomain first")
local hash = domain.."\0"..msgid
local cached = dgettext_cache[hash]
if cached then
return cached
end
local msgstr = msgid
local d = domains[domain]
if d then
for _, l in ipairs(langs) do
local cat = d.catalogs[l]
local mstr = cat and cat[msgid]
if mstr and type(mstr) == "string" then
msgstr = mstr
break
end
end
end
dgettext_cache[hash] = msgstr
return msgstr
end
function M.dngettext(domain, msgid, msgid_plural, n)
assert(langs, "you must call bindtextdomain first")
n = floor(n)
local hash = domain.."\0"..msgid.."\0"..n
local cached = dngettext_cache[hash]
if cached then
return cached
end
local msgstr
local d = domains[domain]
if d then
for _, l in ipairs(langs) do
local cat = d.catalogs[l]
local mstr = cat and cat[msgid]
if mstr and type(mstr) == "table" then
local index = cat.plural_index(n)
print(index)
msgstr = mstr[index]
break
end
end
end
if not msgstr then
msgstr = n==1 and msgid or msgid_plural
end
dngettext_cache[hash] = msgstr
return msgstr
end
return M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.