Skip to content
Permalink
Browse files

Add clientside translations.

  • Loading branch information...
Ekdohibs committed Jan 31, 2017
1 parent b28af0e commit b24e6433df3c3b2926568aff9c0173459e3e8eab
@@ -680,6 +680,44 @@ function core.strip_colors(str)
return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", ""))
end

function core.translate(textdomain, str, ...)
local start_seq
if textdomain == "" then
start_seq = ESCAPE_CHAR .. "T"
else
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")"
end
local arg = {n=select('#', ...), ...}
local end_seq = ESCAPE_CHAR .. "E"
local arg_index = 1
local translated = str:gsub("@(.)", function(matched)
local c = string.byte(matched)
if string.byte("1") <= c and c <= string.byte("9") then
local a = c - string.byte("0")
if a ~= arg_index then
error("Escape sequences in string given to core.translate " ..
"are not in the correct order: got @" .. matched ..
"but expected @" .. tostring(arg_index))
end
if a > arg.n then
error("Not enough arguments provided to core.translate")
end
arg_index = arg_index + 1
return ESCAPE_CHAR .. "F" .. arg[a] .. ESCAPE_CHAR .. "E"
else
return matched
end
end)
if arg_index < arg.n + 1 then
error("Too many arguments provided to core.translate")
end
return start_seq .. translated .. end_seq
end

function core.get_translator(textdomain)
return function(str, ...) return core.translate(textdomain or "", str, ...) end
end

--------------------------------------------------------------------------------
-- Returns the exact coordinate of a pointed surface
--------------------------------------------------------------------------------
@@ -139,6 +139,7 @@ Mod directory structure
| | `-- modname_something_else.png
| |-- sounds
| |-- media
| |-- locale
| `-- <custom data>
`-- another

@@ -182,6 +183,9 @@ Models for entities or meshnodes.
Media files (textures, sounds, whatever) that will be transferred to the
client and will be available for use by the mod.

### `locale`
Translation files for the clients. (See `Translations`)

Naming convention for registered textual names
----------------------------------------------
Registered names should generally be in this format:
@@ -2152,6 +2156,68 @@ Helper functions
* `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a position
* returns the exact position on the surface of a pointed node

Translations
------------

Texts can be translated client-side with the help of `minetest.translate` and translation files.

### Translating a string
Two functions are provided to translate strings: `minetest.translate` and `minetest.get_translator`.

* `minetest.get_translator(textdomain)` is a simple wrapper around `minetest.translate`, and
`minetest.get_translator(textdomain)(str, ...)` is equivalent to `minetest.translate(textdomain, str, ...)`.
It is intended to be used in the following way, so that it avoids verbose repetitions of `minetest.translate`:

local S = minetest.get_translator(textdomain)
S(str, ...)

As an extra commodity, if `textdomain` is nil, it is assumed to be "" instead.

* `minetest.translate(textdomain, str, ...)` translates the string `str` with the given `textdomain`
for disambiguation. The textdomain must match the textdomain specified in the translation file in order
to get the string translated. This can be used so that a string is translated differently in different contexts.
It is advised to use the name of the mod as textdomain whenever possible, to avoid clashes with other mods.
This function must be given a number of arguments equal to the number of arguments the translated string expects.
Arguments are literal strings -- they will not be translated, so if you want them to be, they need to come as
outputs of `minetest.translate` as well.

For instance, suppose we want to translate "@1 Wool" with "@1" being replaced by the translation of "Red".
We can do the following:

local S = minetest.get_translator()
S("@1 Wool", S("Red"))

This will be displayed as "Red Wool" on old clients and on clients that do not have localization enabled.
However, if we have for instance a translation file named `wool.fr.tr` containing the following:

@1 Wool=Laine @1
Red=Rouge

this will be displayed as "Laine Rouge" on clients with a French locale.

### Translation file format
A translation file has the suffix `.[lang].tr`, where `[lang]` is the language it corresponds to.
The file should be a text file, with the following format:

* Lines beginning with `# textdomain:` (the space is significant) can be used to specify the text
domain of all following translations in the file.
* All other empty lines or lines beginning with `#` are ignored.
* Other lines should be in the format `original=translated`. Both `original` and `translated` can
contain escape sequences beginning with `@` to insert arguments, literal `@`, `=` or newline
(See ### Escapes below). There must be no extraneous whitespace around the `=` or at the beginning
or the end of the line.

### Escapes
Strings that need to be translated can contain several escapes, preceded by `@`.
* `@@` acts as a literal `@`.
* `@n`, where `n` is a digit between 1 and 9, is an argument for the translated string that will be inlined
when translation. Due to how translations are implemented, the original translation string **must** have
its arguments in increasing order, without gaps or repetitions, starting from 1.
* `@=` acts as a literal `=`. It is not required in strings given to `minetest.translate`, but is in translation
files to avoid begin confused with the `=` separating the original from the translation.
* `@\n` (where the `\n` is a literal newline) acts as a literal newline. As with `@=`, this escape is not required
in strings given to `minetest.translate`, but is in translation files.

`minetest` namespace reference
------------------------------

@@ -450,6 +450,7 @@ set(common_SRCS
terminal_chat_console.cpp
tileanimation.cpp
tool.cpp
translation.cpp
treegen.cpp
version.cpp
voxel.cpp
@@ -620,10 +620,11 @@ void Camera::drawNametags()
f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f };
trans.multiplyWith1x4Matrix(transformed_pos);
if (transformed_pos[3] > 0) {
std::string nametag_colorless = unescape_enriched(nametag->nametag_text);
std::wstring nametag_colorless =
unescape_translate(utf8_to_wide(nametag->nametag_text));
core::dimension2d<u32> textsize =
g_fontengine->getFont()->getDimension(
utf8_to_wide(nametag_colorless).c_str());
nametag_colorless.c_str());
f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
core::reciprocal(transformed_pos[3]);
v2u32 screensize = RenderingEngine::get_video_driver()->getScreenSize();
@@ -633,8 +634,9 @@ void Camera::drawNametags()
screen_pos.Y = screensize.Y *
(0.5 - transformed_pos[1] * zDiv * 0.5) - textsize.Height / 2;
core::rect<s32> size(0, 0, textsize.Width, textsize.Height);
g_fontengine->getFont()->draw(utf8_to_wide(nametag->nametag_text).c_str(),
size + screen_pos, nametag->nametag_color);
g_fontengine->getFont()->draw(
translate_string(utf8_to_wide(nametag->nametag_text)).c_str(),
size + screen_pos, nametag->nametag_color);
}
}
}
@@ -650,6 +650,7 @@ ChatBackend::ChatBackend():
void ChatBackend::addMessage(std::wstring name, std::wstring text)
{
// Note: A message may consist of multiple lines, for example the MOTD.
text = translate_string(text);
WStrfnd fnd(text);
while (!fnd.at_end())
{
@@ -51,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "script/scripting_client.h"
#include "game.h"
#include "chatmessage.h"
#include "translation.h"

extern gui::IGUIEnvironment* guienv;

@@ -684,8 +685,19 @@ bool Client::loadMedia(const std::string &data, const std::string &filename)
return true;
}

errorstream<<"Client: Don't know how to load file \""
<<filename<<"\""<<std::endl;
const char *translate_ext[] = {
".tr", NULL
};
name = removeStringEnd(filename, translate_ext);
if (!name.empty()) {
verbosestream << "Client: Loading translation: "
<< "\"" << filename << "\"" << std::endl;
g_translations->loadTranslation(data);
return true;
}

errorstream << "Client: Don't know how to load file \""
<< filename << "\"" << std::endl;
return false;
}

@@ -58,6 +58,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "sky.h"
#include "subgame.h"
#include "tool.h"
#include "translation.h"
#include "util/basic_macros.h"
#include "util/directiontables.h"
#include "util/pointedthing.h"
@@ -242,7 +243,7 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, FontEngine *fe,

std::ostringstream os(std::ios_base::binary);
g_profiler->printPage(os, show_profiler, show_profiler_max);
std::wstring text = utf8_to_wide(os.str());
std::wstring text = translate_string(utf8_to_wide(os.str()));
setStaticText(guitext_profiler, text.c_str());
guitext_profiler->setVisible(true);

@@ -1619,6 +1620,8 @@ bool Game::startup(bool *kill,
m_invert_mouse = g_settings->getBool("invert_mouse");
m_first_loop_after_window_activation = true;

g_translations->clear();

if (!init(map_dir, address, port, gamespec))
return false;

@@ -3781,7 +3784,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
NodeMetadata *meta = map.getNodeMetadata(nodepos);

if (meta) {
infotext = unescape_enriched(utf8_to_wide(meta->getString("infotext")));
infotext = unescape_translate(utf8_to_wide(meta->getString("infotext")));
} else {
MapNode n = map.getNodeNoEx(nodepos);

@@ -3858,15 +3861,14 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
const v3f &player_position, bool show_debug)
{
infotext = unescape_enriched(
infotext = unescape_translate(
utf8_to_wide(runData.selected_object->infoText()));

if (show_debug) {
if (!infotext.empty()) {
infotext += L"\n";
}
infotext += unescape_enriched(utf8_to_wide(
runData.selected_object->debugInfoText()));
infotext += utf8_to_wide(runData.selected_object->debugInfoText());
}

if (isLeftPressed()) {
@@ -4399,7 +4401,7 @@ void Game::updateGui(const RunStats &stats, f32 dtime, const CameraOrientation &
guitext3->setRelativePosition(rect);
}

setStaticText(guitext_info, infotext.c_str());
setStaticText(guitext_info, translate_string(infotext).c_str());
guitext_info->setVisible(flags.show_hud && g_menumgr.menuCount() == 0);

float statustext_time_max = 1.5;
@@ -4413,7 +4415,7 @@ void Game::updateGui(const RunStats &stats, f32 dtime, const CameraOrientation &
}
}

setStaticText(guitext_status, m_statustext.c_str());
setStaticText(guitext_status, translate_string(m_statustext).c_str());
guitext_status->setVisible(!m_statustext.empty());

if (!m_statustext.empty()) {
@@ -547,7 +547,7 @@ bool GUIEngine::downloadFile(const std::string &url, const std::string &target)
/******************************************************************************/
void GUIEngine::setTopleftText(const std::string &text)
{
m_toplefttext = utf8_to_wide(text);
m_toplefttext = translate_string(utf8_to_wide(text));

updateTopLeftTextSize();
}
Oops, something went wrong.

0 comments on commit b24e643

Please sign in to comment.
You can’t perform that action at this time.