This commit is contained in:
sfan5 2020-06-14 00:37:16 +02:00
parent d03d7e9bdc
commit fbe62fc84e
3 changed files with 508 additions and 0 deletions

299
findtext.lua Normal file
View File

@ -0,0 +1,299 @@
#! /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
table.sort(messages[textdomain])
local last_msg
printf("# textdomain: %s\n", textdomain)
for _, msg in ipairs(messages[textdomain]) do
if msg ~= last_msg then
printf("%s=\n", msg)
end
last_msg = msg
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 #")
]]

36
updatepo.sh Executable file
View File

@ -0,0 +1,36 @@
#!/bin/bash -e
p=$PWD
if ! [[ -f "$p/findtext.lua" && -f "$p/updatetext.lua" ]]; then
echo "Missing findtext.lua and updatetext.lua"
exit 1
fi
luafile=$(mktemp -u).lua
trap 'rm -f $luafile' EXIT
if [ ! -d mods ]; then
echo "Current directory needs to be the repository root"
exit 1
fi
pushd mods
for name in *; do
echo
[ -d "$name/locale" ] || { echo "Skipping $name (no locale folder)"; continue; }
echo "Updating template for $name"
printf 'local S = minetest.get_translator("%s")\n' "$name" >"$luafile"
cat $(find "$name/" -name '*.lua') >>"$luafile"
lua "$p/findtext.lua" -o "$name/locale/template.txt" "$luafile"
echo "Updating translations for $name"
pushd "$name/locale"
for tl in *.tr; do
echo " $tl"
lua "$p/updatetext.lua" template.txt "$tl" >/dev/null
done
popd
done
popd
echo "All done."
exit 0

173
updatetext.lua Normal file
View File

@ -0,0 +1,173 @@
#! /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, outfile, template
local catalogs = { }
local function usage()
print([[
Usage: ]]..me..[[ [OPTIONS] TEMPLATE CATALOG...
Update a catalog with new strings from a template.
Available options:
-h,--help Show this help screen and exit.
-o,--output X Set output file (default: stdout).
Messages in the template that are not on the catalog are added to the
catalog at the end.
This tool also checks messages that are in the catalog but not in the
template, and reports such lines. It's up to the user to remove such
lines, if so desired.
]])
os.exit(0)
end
local i = 1
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:sub(1, 1) ~= "-" then
if not template then
template = a
else
table.insert(catalogs, a)
end
else
err("unrecognized option `%s'", a)
end
i = i + 1
end
if not template then
err("no template specified")
elseif #catalogs == 0 then
err("no catalogs specified")
end
local f, e = io.open(template, "r")
if not f then
err("error opening template: %s", e)
end
local escapes = {
["\n"] = "@n",
["="] = "@=",
}
local function escape(s)
local r = s:gsub("\\n", "@n"):gsub("[\n=]", escapes)
if r == "" or not r:sub(1, 1) == "#" then
return r
else
-- Escape '#' at beginning of line
return "@" .. r
end
end
if output then
outfile, e = io.open(output, "w")
if not outfile then
err("error opening file for writing: %s", e)
end
end
local function load_strings(file)
local infile, e = io.open(file, "r")
local messages = {}
local textdomain = ""
messages[""] = {}
if infile then
for line in infile:lines() do
for td in line:gmatch('# textdomain:%s*(%S+)') do
textdomain = td
messages[textdomain] = messages[textdomain] or {}
end
if not (line == "" or line:sub(1, 1) == "#") then
local i = 1
while i < line:len() do
if line:sub(i, i) == "@" then
i = i + 2
elseif line:sub(i, i) == "=" then
break
else
i = i + 1
end
end
local untranslated = line:sub(1, i - 1)
local translated = line:sub(i + 1)
messages[textdomain][untranslated] = translated
print(file, textdomain, untranslated, translated)
end
end
infile:close()
else
io.stderr:write(("%s: WARNING: error opening file: %s\n"):format(me, e))
end
return messages
end
local template_msgs = load_strings(template)
for _, file in ipairs(catalogs) do
print("Processing: "..file)
local catalog_msgs = load_strings(file)
local dirty_lines = {}
local dirty = false
if catalog_msgs then
-- Add new entries from template.
for textdomain, tm in pairs(template_msgs) do
for k in pairs(tm) do
if not catalog_msgs[textdomain][k] then
print("NEW: "..textdomain.." "..k)
dirty_lines[textdomain] = dirty_lines[textdomain] or {}
table.insert(dirty_lines[textdomain], k.."=")
dirty = true
end
end
end
-- Check for old messages.
for textdomain, cm in pairs(catalog_msgs) do
for k, v in pairs(cm) do
if not template_msgs[textdomain][k] then
print("OLD: "..textdomain.." "..k)
dirty_lines[textdomain] = dirty_lines[textdomain] or {}
table.insert(dirty_lines[textdomain], "# OLD: "..k.."="..v)
dirty = true
end
end
end
if dirty then
local outf
outf, e = io.open(file, "a+")
if outf then
for textdomain, dl in pairs(dirty_lines) do
for _, line in ipairs(dl) do
outf:write(line)
outf:write("\n")
end
end
outf:close()
else
io.stderr:write(("%s: WARNING: cannot write: %s\n"):format(me, e))
end
end
else
io.stderr:write(("%s: WARNING: could not load catalog\n"):format(me))
end
end