minetest_game/findtext.lua

298 lines
6.3 KiB
Lua

#! /usr/bin/env lua
local me = arg[0]:gsub(".*[/\\](.*)$", "%1")
local function err(fmt, ...)
io.stderr:write(("%s: %s\n"):format(me, fmt:format(...)))
os.exit(1)
end
local output
local inputs = { }
local lang
local author
local i = 1
local function usage()
print([[
Usage: ]]..me..[[ [OPTIONS] FILE...
Extract translatable strings from the given FILE(s).
Available options:
-h,--help Show this help screen and exit.
-o,--output X Set output file (default: stdout).
-a,--author X Set author.
-l,--lang X Set language name.
]])
os.exit(0)
end
while i <= #arg do
local a = arg[i]
if (a == "-h") or (a == "--help") then
usage()
elseif (a == "-o") or (a == "--output") then
i = i + 1
if i > #arg then
err("missing required argument to `%s'", a)
end
output = arg[i]
elseif (a == "-a") or (a == "--author") then
i = i + 1
if i > #arg then
err("missing required argument to `%s'", a)
end
author = arg[i]
elseif (a == "-l") or (a == "--lang") then
i = i + 1
if i > #arg then
err("missing required argument to `%s'", a)
end
lang = arg[i]
elseif a:sub(1, 1) ~= "-" then
table.insert(inputs, a)
else
err("unrecognized option `%s'", a)
end
i = i + 1
end
if #inputs == 0 then
err("no input files")
end
local outfile = io.stdout
local function printf(fmt, ...)
outfile:write(fmt:format(...))
end
if output then
local e
outfile, e = io.open(output, "w")
if not outfile then
err("error opening file for writing: %s", e)
end
end
if author or lang then
outfile:write("\n")
end
if lang then
printf("# Language: %s\n", lang)
end
if author then
printf("# Author: %s\n", author)
end
if author or lang then
outfile:write("\n")
end
local c_escapes = {
[('a'):byte(1)] = '\a',
[('b'):byte(1)] = '\b',
[('f'):byte(1)] = '\f',
[('r'):byte(1)] = '\r',
[('t'):byte(1)] = '\t',
[('v'):byte(1)] = '\v',
-- \n is handled separately
}
local function parse_lua_string(s)
local esc = false
local i = 1
local len = #s
while i <= len do
local c = s:byte(i)
i = i + 1
if esc then
esc = false
if c >= 0x30 and c <= 0x39 then
-- 0x30 = 0
-- 0x39 = 9
local scode = s:match('%d%d?%d?', i - 1)
local ncode = tonumber(scode)
s = s:sub(1, i - 3) .. string.char(ncode) .. s:sub(i-1 + #scode)
-- Reevaluate the current character only if it isn't \
i = i - (ncode == 0x5C and 1 or 2)
len = #s
elseif c == 0x6E then
-- 0x6E = n
s = s:sub(1, i - 3) .. "@n" .. s:sub(i)
elseif c == 0x78 then
-- 0x78 = x
s = s:sub(1, i - 3) .. s:sub(i - 1)
i = i - 2
len = len - 1
io.stderr:write("Warning: Hex escape sequence is illegal in Lua 5.1\n")
elseif c_escapes[c] ~= nil then
s = s:sub(1, i - 3) .. c_escapes[c] .. s:sub(i)
len = len - 1
i = i - 1
else
s = s:sub(1, i - 3) .. s:sub(i - 1)
len = len - 1
-- Reevaluate the current character only if it isn't \
i = i - (c == 0x5C and 1 or 2)
end
elseif c == 0x5C then
-- 0x5C = \
esc = true
elseif c == 0x0A then
-- 0x0A = LF
s = s:sub(1, i - 2) .. "@n" .. s:sub(i)
len = len + 1
i = i + 1
elseif c == 0x3D then
-- 0x3D = =
s = s:sub(1, i - 2) .. "@=" .. s:sub(i)
len = len + 1
i = i + 1
elseif c == 0x23 and i == 2 then
-- 0x23 = #
s = '@' .. s
len = len + 1
i = i + 1
end
end
return s
end
local function replace_quote_in_quote(s)
--[[
state = 0: normal code, starting state
state = 1: seen -
state = 2: seen " or ' (begin string parsing)
state = 3: seen \ within string
--]]
local state = 0
local i = 1
local len = #s
local end_str
while i <= len do
local c = s:byte(i)
i = i + 1
if state == 0 then
if c == 0x2D then
-- 0x2D = -
state = 1
elseif c == 0x22 or c == 0x27 then
-- 0x22 = "
-- 0x27 = '
end_str = c
state = 2
-- else remain in state 0
end
elseif state == 1 then
if c == 0x2D then
-- 0x2D = -
-- Ignore the rest of the line. We don't parse --[[ ... ]].
return s:sub(1, i - 3)
elseif c == 0x22 or c == 0x27 then
-- 0x22 = "
-- 0x27 = '
end_str = c
state = 3
else
state = 0
end
elseif state == 2 then
if c == 0x5C then
-- 0x5C = \
state = 3
elseif c == end_str then
state = 0
elseif c == 0x22 or c == 0x27 or c == 0x28 then
-- " or ' or open parenthesis
s = s:sub(1, i - 2) .. ("\\%03d"):format(c) .. s:sub(i)
i = i + 3
len = len + 3
-- else remain in state 2
end
elseif state == 3 then
if c == 0x22 or c == 0x27 then
-- Escaped quote found - replace it
s = s:sub(1, i - 2) .. (c == 0x22 and "034" or "039") .. s:sub(i)
i = i + 2
len = len + 2
state = 2
else
state = 2
end
end
end
assert(#s == len)
return s
end
local messages = {}
for _, file in ipairs(inputs) do
local infile, e = io.open(file, "r")
local textdomains = {}
if infile then
for line in infile:lines() do
for translator_name, textdomain in line:gmatch('local (%w+)%s*=%s*%w+%.get_translator%("([^"]*)"%)') do
--print(translator_name, textdomain)
messages[textdomain] = messages[textdomain] or {}
textdomains[translator_name] = textdomain
end
line = replace_quote_in_quote(line)
for translator, s in line:gmatch('(%w+)%("([^"]*)"') do
s = parse_lua_string(s)
if textdomains[translator] then
local textdomain = textdomains[translator]
table.insert(messages[textdomain], s)
end
end
for textdomain, s in line:gmatch('%w+%.translate%("([^"]*)"%s*,%s*"([^"]*)"') do
s = parse_lua_string(s)
messages[textdomain] = messages[textdomain] or {}
table.insert(messages[textdomain], s)
end
end
infile:close()
else
io.stderr:write(("%s: WARNING: error opening file: %s\n"):format(me, e))
end
end
for textdomain, mtbl in pairs(messages) do
local last_msg = {}
printf("# textdomain: %s\n", textdomain)
for _, msg in ipairs(messages[textdomain]) do
if not last_msg[msg] then
printf("%s=\n", msg)
end
last_msg[msg] = true
end
end
if output then
outfile:close()
end
--[[
TESTS:
local S = minetest.get_translator("domain")
S("foo") S("bar")
S("bar")
S("foo") -- S("doesn't matter")
print("this is in a string S(") x=0 print(") still text") S('bar baz "this" \"\'that\' foobar')
minetest.translate("another_domain", "foo")
S("#foo=@1\n@2", "bar", "baz")
S("what's this? (oh, an apostrophe)")
S("\035 is a #")
S("\092 is a \\")
S("\\ is a \\")
S("\# is a #")
]]