Translate builtin (#10693)

This PR is the second attempt to translate builtin.
Server-sent translation files can be added to `builtin/locale/`, whereas client-side translations depend on gettext.
This commit is contained in:
Wuzzy 2021-03-05 15:27:33 +00:00 committed by SmallJoker
parent ac8ac19169
commit cafad6ac03
12 changed files with 623 additions and 334 deletions

3
.gitignore vendored

@ -86,8 +86,7 @@ src/test_config.h
src/cmake_config.h
src/cmake_config_githash.h
src/unittest/test_world/world.mt
src/lua/build/
locale/
/locale/
.directory
*.cbp
*.layout

@ -1,6 +1,5 @@
-- Minetest: builtin/client/chatcommands.lua
core.register_on_sending_chat_message(function(message)
if message:sub(1,2) == ".." then
return false
@ -8,7 +7,7 @@ core.register_on_sending_chat_message(function(message)
local first_char = message:sub(1,1)
if first_char == "/" or first_char == "." then
core.display_chat_message(core.gettext("issued command: ") .. message)
core.display_chat_message(core.gettext("Issued command: ") .. message)
end
if first_char ~= "." then
@ -19,7 +18,7 @@ core.register_on_sending_chat_message(function(message)
param = param or ""
if not cmd then
core.display_chat_message(core.gettext("-!- Empty command"))
core.display_chat_message("-!- " .. core.gettext("Empty command."))
return true
end
@ -36,7 +35,7 @@ core.register_on_sending_chat_message(function(message)
core.display_chat_message(result)
end
else
core.display_chat_message(core.gettext("-!- Invalid command: ") .. cmd)
core.display_chat_message("-!- " .. core.gettext("Invalid command: ") .. cmd)
end
return true
@ -66,7 +65,7 @@ core.register_chatcommand("clear_chat_queue", {
description = core.gettext("Clear the out chat queue"),
func = function(param)
core.clear_out_chat_queue()
return true, core.gettext("The out chat queue is now empty")
return true, core.gettext("The out chat queue is now empty.")
end,
})

@ -2,7 +2,7 @@
-- handled by the engine.
core.register_on_death(function()
core.display_chat_message("You died.")
core.display_chat_message(core.gettext("You died."))
local formspec = "size[11,5.5]bgcolor[#320000b4;true]" ..
"label[4.85,1.35;" .. fgettext("You died") ..
"]button_exit[4,3;3,0.5;btn_respawn;".. fgettext("Respawn") .."]"

@ -1,5 +1,9 @@
-- Minetest: builtin/common/chatcommands.lua
-- For server-side translations (if INIT == "game")
-- Otherwise, use core.gettext
local S = core.get_translator("__builtin")
core.registered_chatcommands = {}
function core.register_chatcommand(cmd, def)
@ -29,25 +33,12 @@ function core.override_chatcommand(name, redefinition)
core.registered_chatcommands[name] = chatcommand
end
local cmd_marker = "/"
local function gettext(...)
return ...
end
local function gettext_replace(text, replace)
return text:gsub("$1", replace)
end
if INIT == "client" then
cmd_marker = "."
gettext = core.gettext
gettext_replace = fgettext_ne
end
local function do_help_cmd(name, param)
local function format_help_line(cmd, def)
local cmd_marker = "/"
if INIT == "client" then
cmd_marker = "."
end
local msg = core.colorize("#00ffff", cmd_marker .. cmd)
if def.params and def.params ~= "" then
msg = msg .. " " .. def.params
@ -65,9 +56,21 @@ local function do_help_cmd(name, param)
end
end
table.sort(cmds)
return true, gettext("Available commands: ") .. table.concat(cmds, " ") .. "\n"
.. gettext_replace("Use '$1help <cmd>' to get more information,"
.. " or '$1help all' to list everything.", cmd_marker)
local msg
if INIT == "game" then
msg = S("Available commands: @1",
table.concat(cmds, " ")) .. "\n"
.. S("Use '/help <cmd>' to get more "
.. "information, or '/help all' to list "
.. "everything.")
else
msg = core.gettext("Available commands: ")
.. table.concat(cmds, " ") .. "\n"
.. core.gettext("Use '.help <cmd>' to get more "
.. "information, or '.help all' to list "
.. "everything.")
end
return true, msg
elseif param == "all" then
local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do
@ -76,19 +79,31 @@ local function do_help_cmd(name, param)
end
end
table.sort(cmds)
return true, gettext("Available commands:").."\n"..table.concat(cmds, "\n")
local msg
if INIT == "game" then
msg = S("Available commands:")
else
msg = core.gettext("Available commands:")
end
return true, msg.."\n"..table.concat(cmds, "\n")
elseif INIT == "game" and param == "privs" then
local privs = {}
for priv, def in pairs(core.registered_privileges) do
privs[#privs + 1] = priv .. ": " .. def.description
end
table.sort(privs)
return true, "Available privileges:\n"..table.concat(privs, "\n")
return true, S("Available privileges:").."\n"..table.concat(privs, "\n")
else
local cmd = param
local def = core.registered_chatcommands[cmd]
if not def then
return false, gettext("Command not available: ")..cmd
local msg
if INIT == "game" then
msg = S("Command not available: @1", cmd)
else
msg = core.gettext("Command not available: ") .. cmd
end
return false, msg
else
return true, format_help_line(cmd, def)
end
@ -97,16 +112,16 @@ end
if INIT == "client" then
core.register_chatcommand("help", {
params = gettext("[all | <cmd>]"),
description = gettext("Get help for commands"),
params = core.gettext("[all | <cmd>]"),
description = core.gettext("Get help for commands"),
func = function(param)
return do_help_cmd(nil, param)
end,
})
else
core.register_chatcommand("help", {
params = "[all | privs | <cmd>]",
description = "Get help for commands or list privileges",
params = S("[all | privs | <cmd>]"),
description = S("Get help for commands or list privileges"),
func = do_help_cmd,
})
end

@ -20,7 +20,8 @@ local LIST_FORMSPEC_DESCRIPTION = [[
button_exit[5,7;3,1;quit;%s]
]]
local formspec_escape = core.formspec_escape
local F = core.formspec_escape
local S = core.get_translator("__builtin")
local check_player_privs = core.check_player_privs
@ -51,22 +52,23 @@ core.after(0, load_mod_command_tree)
local function build_chatcommands_formspec(name, sel, copy)
local rows = {}
rows[1] = "#FFF,0,Command,Parameters"
rows[1] = "#FFF,0,"..F(S("Command"))..","..F(S("Parameters"))
local description = "For more information, click on any entry in the list.\n" ..
"Double-click to copy the entry to the chat history."
local description = S("For more information, click on "
.. "any entry in the list.").. "\n" ..
S("Double-click to copy the entry to the chat history.")
for i, data in ipairs(mod_cmds) do
rows[#rows + 1] = COLOR_BLUE .. ",0," .. formspec_escape(data[1]) .. ","
rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. ","
for j, cmds in ipairs(data[2]) do
local has_priv = check_player_privs(name, cmds[2].privs)
rows[#rows + 1] = ("%s,1,%s,%s"):format(
has_priv and COLOR_GREEN or COLOR_GRAY,
cmds[1], formspec_escape(cmds[2].params))
cmds[1], F(cmds[2].params))
if sel == #rows then
description = cmds[2].description
if copy then
core.chat_send_player(name, ("Command: %s %s"):format(
core.chat_send_player(name, S("Command: @1 @2",
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params))
end
end
@ -74,9 +76,9 @@ local function build_chatcommands_formspec(name, sel, copy)
end
return LIST_FORMSPEC_DESCRIPTION:format(
"Available commands: (see also: /help <cmd>)",
F(S("Available commands: (see also: /help <cmd>)")),
table.concat(rows, ","), sel or 0,
description, "Close"
F(description), F(S("Close"))
)
end
@ -91,19 +93,19 @@ local function build_privs_formspec(name)
table.sort(privs, function(a, b) return a[1] < b[1] end)
local rows = {}
rows[1] = "#FFF,0,Privilege,Description"
rows[1] = "#FFF,0,"..F(S("Privilege"))..","..F(S("Description"))
local player_privs = core.get_player_privs(name)
for i, data in ipairs(privs) do
rows[#rows + 1] = ("%s,0,%s,%s"):format(
player_privs[data[1]] and COLOR_GREEN or COLOR_GRAY,
data[1], formspec_escape(data[2].description))
data[1], F(data[2].description))
end
return LIST_FORMSPEC:format(
"Available privileges:",
F(S("Available privileges:")),
table.concat(rows, ","),
"Close"
F(S("Close"))
)
end

File diff suppressed because it is too large Load Diff

@ -1,5 +1,7 @@
-- Minetest: builtin/privileges.lua
local S = core.get_translator("__builtin")
--
-- Privileges
--
@ -15,7 +17,7 @@ function core.register_privilege(name, param)
def.give_to_admin = def.give_to_singleplayer
end
if def.description == nil then
def.description = "(no description)"
def.description = S("(no description)")
end
end
local def
@ -28,69 +30,69 @@ function core.register_privilege(name, param)
core.registered_privileges[name] = def
end
core.register_privilege("interact", "Can interact with things and modify the world")
core.register_privilege("shout", "Can speak in chat")
core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges")
core.register_privilege("privs", "Can modify privileges")
core.register_privilege("interact", S("Can interact with things and modify the world"))
core.register_privilege("shout", S("Can speak in chat"))
core.register_privilege("basic_privs", S("Can modify 'shout' and 'interact' privileges"))
core.register_privilege("privs", S("Can modify privileges"))
core.register_privilege("teleport", {
description = "Can teleport self",
description = S("Can teleport self"),
give_to_singleplayer = false,
})
core.register_privilege("bring", {
description = "Can teleport other players",
description = S("Can teleport other players"),
give_to_singleplayer = false,
})
core.register_privilege("settime", {
description = "Can set the time of day using /time",
description = S("Can set the time of day using /time"),
give_to_singleplayer = false,
})
core.register_privilege("server", {
description = "Can do server maintenance stuff",
description = S("Can do server maintenance stuff"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("protection_bypass", {
description = "Can bypass node protection in the world",
description = S("Can bypass node protection in the world"),
give_to_singleplayer = false,
})
core.register_privilege("ban", {
description = "Can ban and unban players",
description = S("Can ban and unban players"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("kick", {
description = "Can kick players",
description = S("Can kick players"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("give", {
description = "Can use /give and /giveme",
description = S("Can use /give and /giveme"),
give_to_singleplayer = false,
})
core.register_privilege("password", {
description = "Can use /setpassword and /clearpassword",
description = S("Can use /setpassword and /clearpassword"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("fly", {
description = "Can use fly mode",
description = S("Can use fly mode"),
give_to_singleplayer = false,
})
core.register_privilege("fast", {
description = "Can use fast mode",
description = S("Can use fast mode"),
give_to_singleplayer = false,
})
core.register_privilege("noclip", {
description = "Can fly through solid nodes using noclip mode",
description = S("Can fly through solid nodes using noclip mode"),
give_to_singleplayer = false,
})
core.register_privilege("rollback", {
description = "Can use the rollback functionality",
description = S("Can use the rollback functionality"),
give_to_singleplayer = false,
})
core.register_privilege("debug", {
description = "Allows enabling various debug options that may affect gameplay",
description = S("Allows enabling various debug options that may affect gameplay"),
give_to_singleplayer = false,
give_to_admin = true,
})

@ -1,5 +1,7 @@
-- Minetest: builtin/misc_register.lua
local S = core.get_translator("__builtin")
--
-- Make raw registration functions inaccessible to anyone except this file
--
@ -326,7 +328,7 @@ end
core.register_item(":unknown", {
type = "none",
description = "Unknown Item",
description = S("Unknown Item"),
inventory_image = "unknown_item.png",
on_place = core.item_place,
on_secondary_use = core.item_secondary_use,
@ -336,7 +338,7 @@ core.register_item(":unknown", {
})
core.register_node(":air", {
description = "Air",
description = S("Air"),
inventory_image = "air.png",
wield_image = "air.png",
drawtype = "airlike",
@ -353,7 +355,7 @@ core.register_node(":air", {
})
core.register_node(":ignore", {
description = "Ignore",
description = S("Ignore"),
inventory_image = "ignore.png",
wield_image = "ignore.png",
drawtype = "airlike",
@ -370,7 +372,7 @@ core.register_node(":ignore", {
core.chat_send_player(
placer:get_player_name(),
core.colorize("#FF0000",
"You can't place 'ignore' nodes!"))
S("You can't place 'ignore' nodes!")))
return ""
end,
})

224
builtin/locale/template.txt Normal file

@ -0,0 +1,224 @@
# textdomain: __builtin
Empty command.=
Invalid command: @1=
Invalid command usage.=
You don't have permission to run this command (missing privileges: @1).=
Unable to get position of player @1.=
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=
<action>=
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=
Show the name of the server owner=
The administrator of this server is @1.=
There's no administrator named in the config file.=
[<name>]=
Show privileges of yourself or another player=
Player @1 does not exist.=
Privileges of @1: @2=
<privilege>=
Return list of all online players with privilege=
Invalid parameters (see /help haspriv).=
Unknown privilege!=
Players online with the "@1" privilege: @2=
Your privileges are insufficient.=
Unknown privilege: @1=
@1 granted you privileges: @2=
<name> (<privilege> | all)=
Give privileges to player=
Invalid parameters (see /help grant).=
<privilege> | all=
Grant privileges to yourself=
Invalid parameters (see /help grantme).=
@1 revoked privileges from you: @2=
Remove privileges from player=
Invalid parameters (see /help revoke).=
Revoke privileges from yourself=
Invalid parameters (see /help revokeme).=
<name> <password>=
Set player's password=
Name field required.=
Your password was cleared by @1.=
Password of player "@1" cleared.=
Your password was set by @1.=
Password of player "@1" set.=
<name>=
Set empty password for a player=
Reload authentication data=
Done.=
Failed.=
Remove a player's data=
Player "@1" removed.=
No such player "@1" to remove.=
Player "@1" is connected, cannot remove.=
Unhandled remove_player return code @1.=
Cannot teleport out of map bounds!=
Cannot get player with name @1.=
Cannot teleport, @1 is attached to an object!=
Teleporting @1 to @2.=
One does not teleport to oneself.=
Cannot get teleportee with name @1.=
Cannot get target player with name @1.=
Teleporting @1 to @2 at @3.=
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=
Teleport to position or player=
You don't have permission to teleport other players (missing privilege: @1).=
([-n] <name> <value>) | <name>=
Set or read server configuration setting=
Failed. Use '/set -n <name> <value>' to create a new setting.=
@1 @= @2=
<not set>=
Invalid parameters (see /help set).=
Finished emerging @1 blocks in @2ms.=
emergeblocks update: @1/@2 blocks emerged (@3%)=
(here [<radius>]) | (<pos1> <pos2>)=
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=
Started emerge of area ranging from @1 to @2.=
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=
Successfully cleared area ranging from @1 to @2.=
Failed to clear one or more blocks in area.=
Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=
Successfully reset light in the area ranging from @1 to @2.=
Failed to load one or more blocks in area.=
List mods installed on the server=
Cannot give an empty item.=
Cannot give an unknown item.=
Giving 'ignore' is not allowed.=
@1 is not a known player.=
@1 partially added to inventory.=
@1 could not be added to inventory.=
@1 added to inventory.=
@1 partially added to inventory of @2.=
@1 could not be added to inventory of @2.=
@1 added to inventory of @2.=
<name> <ItemString> [<count> [<wear>]]=
Give item to player=
Name and ItemString required.=
<ItemString> [<count> [<wear>]]=
Give item to yourself=
ItemString required.=
<EntityName> [<X>,<Y>,<Z>]=
Spawn entity at given (or your) position=
EntityName required.=
Unable to spawn entity, player is nil.=
Cannot spawn an unknown entity.=
Invalid parameters (@1).=
@1 spawned.=
@1 failed to spawn.=
Destroy item in hand=
Unable to pulverize, no player.=
Unable to pulverize, no item in hand.=
An item was pulverized.=
[<range>] [<seconds>] [<limit>]=
Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=
Rollback functions are disabled.=
That limit is too high!=
Checking @1 ...=
Nobody has touched the specified location in @1 seconds.=
@1 @2 @3 -> @4 @5 seconds ago.=
Punch a node (range@=@1, seconds@=@2, limit@=@3).=
(<name> [<seconds>]) | (:<actor> [<seconds>])=
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=
Invalid parameters. See /help rollback and /help rollback_check.=
Reverting actions of player '@1' since @2 seconds.=
Reverting actions of @1 since @2 seconds.=
(log is too long to show)=
Reverting actions succeeded.=
Reverting actions FAILED.=
Show server status=
This command was disabled by a mod or game.=
[<0..23>:<0..59> | <0..24000>]=
Show or set time of day=
Current time is @1:@2.=
You don't have permission to run this command (missing privilege: @1).=
Invalid time.=
Time of day changed.=
Invalid hour (must be between 0 and 23 inclusive).=
Invalid minute (must be between 0 and 59 inclusive).=
Show day count since world creation=
Current day is @1.=
[<delay_in_seconds> | -1] [reconnect] [<message>]=
Shutdown server (-1 cancels a delayed shutdown)=
Server shutting down (operator request).=
Ban the IP of a player or show the ban list=
The ban list is empty.=
Ban list: @1=
Player is not online.=
Failed to ban player.=
Banned @1.=
<name> | <IP_address>=
Remove IP ban belonging to a player/IP=
Failed to unban player/IP.=
Unbanned @1.=
<name> [<reason>]=
Kick a player=
Failed to kick player @1.=
Kicked @1.=
[full | quick]=
Clear all objects in world=
Invalid usage, see /help clearobjects.=
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=
Cleared all objects.=
<name> <message>=
Send a direct message to a player=
Invalid usage, see /help msg.=
The player @1 is not online.=
DM from @1: @2=
Message sent.=
Get the last login time of a player or yourself=
@1's last login time was @2.=
@1's last login time is unknown.=
Clear the inventory of yourself or another player=
You don't have permission to clear another player's inventory (missing privilege: @1).=
@1 cleared your inventory.=
Cleared @1's inventory.=
Player must be online to clear inventory!=
Players can't be killed, damage has been disabled.=
Player @1 is not online.=
You are already dead.=
@1 is already dead.=
@1 has been killed.=
Kill player or yourself=
Available commands: @1=
Use '/help <cmd>' to get more information, or '/help all' to list everything.=
Available commands:=
Command not available: @1=
[all | privs | <cmd>]=
Get help for commands or list privileges=
Available privileges:=
Command=
Parameters=
For more information, click on any entry in the list.=
Double-click to copy the entry to the chat history.=
Command: @1 @2=
Available commands: (see also: /help <cmd>)=
Close=
Privilege=
Description=
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=
Handle the profiler and profiling data=
Statistics written to action log.=
Statistics were reset.=
Usage: @1=
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=
(no description)=
Can interact with things and modify the world=
Can speak in chat=
Can modify 'shout' and 'interact' privileges=
Can modify privileges=
Can teleport self=
Can teleport other players=
Can set the time of day using /time=
Can do server maintenance stuff=
Can bypass node protection in the world=
Can ban and unban players=
Can kick players=
Can use /give and /giveme=
Can use /setpassword and /clearpassword=
Can use fly mode=
Can use fast mode=
Can fly through solid nodes using noclip mode=
Can use the rollback functionality=
Allows enabling various debug options that may affect gameplay=
Unknown Item=
Air=
Ignore=
You can't place 'ignore' nodes!=

@ -15,6 +15,8 @@
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local S = core.get_translator("__builtin")
local function get_bool_default(name, default)
local val = core.settings:get_bool(name)
if val == nil then
@ -40,9 +42,9 @@ function profiler.init_chatcommand()
instrumentation.init_chatcommand()
end
local param_usage = "print [filter] | dump [filter] | save [format [filter]] | reset"
local param_usage = S("print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset")
core.register_chatcommand("profiler", {
description = "handle the profiler and profiling data",
description = S("Handle the profiler and profiling data"),
params = param_usage,
privs = { server=true },
func = function(name, param)
@ -51,21 +53,19 @@ function profiler.init_chatcommand()
if command == "dump" then
core.log("action", reporter.print(sampler.profile, arg0))
return true, "Statistics written to action log"
return true, S("Statistics written to action log.")
elseif command == "print" then
return true, reporter.print(sampler.profile, arg0)
elseif command == "save" then
return reporter.save(sampler.profile, args[1] or "txt", args[2])
elseif command == "reset" then
sampler.reset()
return true, "Statistics were reset"
return true, S("Statistics were reset.")
end
return false, string.format(
"Usage: %s\n" ..
"Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).",
param_usage
)
return false,
S("Usage: @1", param_usage) .. "\n" ..
S("Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).")
end
})

@ -2496,7 +2496,9 @@ void Server::fillMediaCache()
// Collect all media file paths
std::vector<std::string> paths;
// The paths are ordered in descending priority
// ordered in descending priority
paths.push_back(getBuiltinLuaPath() + DIR_DELIM + "locale");
fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server");
fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures");
m_modmgr->getModsMediaPaths(paths);

@ -58,6 +58,7 @@ xgettext --package-name=minetest \
--keyword=fgettext_ne \
--keyword=strgettext \
--keyword=wstrgettext \
--keyword=core.gettext \
--keyword=showTranslatedStatusText \
--output $potfile \
--from-code=utf-8 \