local unescape_string do local schartbl = { -- https://en.wikipedia.org/wiki/Escape_sequences_in_C a = "\a", b = "\b", e = string.char(0x1b), f = "\f", n = "\n", r = "\r", t = "\t", v = "\v", } local function replace_single(pfx, c) local pl = #pfx if pl % 2 == 0 then return pfx .. c end return string.sub(pfx, 1, math.floor(pl/2)) .. (schartbl[c] or c) end unescape_string = function(str) return (string.gsub(str, [[(\+)([abefnrtv'"?])]], replace_single):gsub([[\\]], [[\]])) end end local function readstring_aux(str, pos) local _, spos = string.find(str, [[^%s*"]], pos) if not spos then return nil end local ipos = spos while true do local _, epos, m = string.find(str, [[(\*)"]], ipos+1) if not epos then return error("String extends beyond the end of input") end ipos = epos if #m % 2 == 0 then return unescape_string(string.sub(str, spos+1, epos-1)), epos+1 end end end local function readstring(str, pos) local st = {} local nxt = pos while true do local s, npos = readstring_aux(str, nxt) if not s then if not st[1] then return nil, nxt else return table.concat(st), nxt end end nxt = npos table.insert(st, s) end end local function readtoken(str, pos) local _, epos, tok = string.find(str, [[^%s*(%S+)]], pos) if not epos then return nil, pos end return tok, epos+1 end local function readcomment_aux(str, pos) local _, epos, sval = string.find(str, "^\n*#%s*([^\n]*)", pos) if not epos then return nil end return sval, epos+1 end local function readcomment(str, pos) local st = {} local nxt = pos while true do local s, npos = readcomment_aux(str, nxt) if not npos then return table.concat(st, "\n"), nxt end table.insert(st, s) nxt = npos end end local function readpo(str) local st = {} local pos = 1 while true do local tok local _, npos = readcomment(str, pos) tok, npos = readtoken(str, npos) if not tok then return st end assert(tok == "msgid", "Invalid token: " .. tok) local orig, tr orig, npos = readstring(str, npos) assert(orig ~= nil, "Missing untranslated string") tok, npos = readtoken(str, npos) assert(tok == "msgstr", "Invalid token: " .. tok) tr, npos = readstring(str, npos) assert(tr ~= nil, "Missing translated string") if not (orig == "" or tr == "") then st[orig] = tr end pos = npos end end local escape_lookup = { ["="] = "@=", ["\n"] = "@n" } local function escape_string(st) return (string.gsub(st, "[=\n]", escape_lookup)) end local function convert_po_string(textdomain, str) local st = {string.format("# textdomain: %s", textdomain)} for k, v in pairs(readpo(str)) do table.insert(st, ("%s=%s"):format(escape_string(k), escape_string(v))) end return table.concat(st, "\n") end local function convert_po_file(textdomain, inpath, outpath) local f, err = io.open(inpath, "rb") assert(f, err) local str = convert_po_string(textdomain, f:read("*a")) f:close() minetest.safe_file_write(outpath, str) return str end local function convert_flat_po_directory(textdomain, modpath) assert(textdomain, "No textdomain specified for po file conversion") local mp = modpath or minetest.get_modpath(textdomain) assert(mp ~= nil, "No path to write for " .. textdomain) local popath = mp .. "/po" local trpath = mp .. "/locale" for _, infile in pairs(minetest.get_dir_list(popath, false)) do local lang = string.match(infile, [[^([^%.]+)%.po$]]) if lang then local inpath = popath .. "/" .. infile local outpath = ("%s/%s.%s.tr"):format(trpath, textdomain, lang) convert_po_file(textdomain, inpath, outpath) end end end return { from_string = convert_po_string, from_file = convert_po_file, from_flat = convert_flat_po_directory, }