1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-07-22 17:18:39 +00:00

Add clientside translations.

This commit is contained in:
Ekdohibs 2017-01-31 18:05:03 +01:00
parent b28af0ed07
commit b24e6433df
21 changed files with 629 additions and 46 deletions

View file

@ -36,19 +36,19 @@ EnrichedString::EnrichedString(const std::wstring &string,
EnrichedString::EnrichedString(const std::wstring &s, const SColor &color)
{
clear();
addAtEnd(s, color);
addAtEnd(translate_string(s), color);
}
EnrichedString::EnrichedString(const wchar_t *str, const SColor &color)
{
clear();
addAtEnd(std::wstring(str), color);
addAtEnd(translate_string(std::wstring(str)), color);
}
void EnrichedString::operator=(const wchar_t *str)
{
clear();
addAtEnd(std::wstring(str), SColor(255, 255, 255, 255));
addAtEnd(translate_string(std::wstring(str)), SColor(255, 255, 255, 255));
}
void EnrichedString::addAtEnd(const std::wstring &s, const SColor &initial_color)

View file

@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "hex.h"
#include "../porting.h"
#include "../translation.h"
#include <algorithm>
#include <sstream>
@ -743,3 +744,196 @@ void str_replace(std::string &str, char from, char to)
{
std::replace(str.begin(), str.end(), from, to);
}
/* Translated strings have the following format:
* \x1bT marks the beginning of a translated string
* \x1bE marks its end
*
* \x1bF marks the beginning of an argument, and \x1bE its end.
*
* Arguments are *not* translated, as they may contain escape codes.
* Thus, if you want a translated argument, it should be inside \x1bT/\x1bE tags as well.
*
* This representation is chosen so that clients ignoring escape codes will
* see untranslated strings.
*
* For instance, suppose we have a string such as "@1 Wool" with the argument "White"
* The string will be sent as "\x1bT\x1bF\x1bTWhite\x1bE\x1bE Wool\x1bE"
* To translate this string, we extract what is inside \x1bT/\x1bE tags.
* When we notice the \x1bF tag, we recursively extract what is there up to the \x1bE end tag,
* translating it as well.
* We get the argument "White", translated, and create a template string with "@1" instead of it.
* We finally get the template "@1 Wool" that was used in the beginning, which we translate
* before filling it again.
*/
void translate_all(const std::wstring &s, size_t &i, std::wstring &res);
void translate_string(const std::wstring &s, const std::wstring &textdomain,
size_t &i, std::wstring &res) {
std::wostringstream output;
std::vector<std::wstring> args;
int arg_number = 1;
while (i < s.length()) {
// Not an escape sequence: just add the character.
if (s[i] != '\x1b') {
output.put(s[i]);
// The character is a literal '@'; add it twice
// so that it is not mistaken for an argument.
if (s[i] == L'@')
output.put(L'@');
++i;
continue;
}
// We have an escape sequence: locate it and its data
// It is either a single character, or it begins with '('
// and extends up to the following ')', with '\' as an escape character.
++i;
size_t start_index = i;
size_t length;
if (i == s.length()) {
length = 0;
} else if (s[i] == L'(') {
++i;
++start_index;
while (i < s.length() && s[i] != L')') {
if (s[i] == L'\\')
++i;
++i;
}
length = i - start_index;
++i;
if (i > s.length())
i = s.length();
} else {
++i;
length = 1;
}
std::wstring escape_sequence(s, start_index, length);
// The escape sequence is now reconstructed.
std::vector<std::wstring> parts = split(escape_sequence, L'@');
if (parts[0] == L"E") {
// "End of translation" escape sequence. We are done locating the string to translate.
break;
} else if (parts[0] == L"F") {
// "Start of argument" escape sequence.
// Recursively translate the argument, and add it to the argument list.
// Add an "@n" instead of the argument to the template to translate.
if (arg_number >= 10) {
errorstream << "Ignoring too many arguments to translation" << std::endl;
std::wstring arg;
translate_all(s, i, arg);
args.push_back(arg);
continue;
}
output.put(L'@');
output << arg_number;
++arg_number;
std::wstring arg;
translate_all(s, i, arg);
args.push_back(arg);
} else {
// This is an escape sequence *inside* the template string to translate itself.
// This should not happen, show an error message.
errorstream << "Ignoring escape sequence '" << wide_to_narrow(escape_sequence) << "' in translation" << std::endl;
}
}
// Translate the template.
std::wstring toutput = g_translations->getTranslation(textdomain, output.str());
// Put back the arguments in the translated template.
std::wostringstream result;
size_t j = 0;
while (j < toutput.length()) {
// Normal character, add it to output and continue.
if (toutput[j] != L'@' || j == toutput.length() - 1) {
result.put(toutput[j]);
++j;
continue;
}
++j;
// Literal escape for '@'.
if (toutput[j] == L'@') {
result.put(L'@');
++j;
continue;
}
// Here we have an argument; get its index and add the translated argument to the output.
int arg_index = toutput[j] - L'1';
++j;
result << args[arg_index];
}
res = result.str();
}
void translate_all(const std::wstring &s, size_t &i, std::wstring &res) {
std::wostringstream output;
while (i < s.length()) {
// Not an escape sequence: just add the character.
if (s[i] != '\x1b') {
output.put(s[i]);
++i;
continue;
}
// We have an escape sequence: locate it and its data
// It is either a single character, or it begins with '('
// and extends up to the following ')', with '\' as an escape character.
size_t escape_start = i;
++i;
size_t start_index = i;
size_t length;
if (i == s.length()) {
length = 0;
} else if (s[i] == L'(') {
++i;
++start_index;
while (i < s.length() && s[i] != L')') {
if (s[i] == L'\\') {
++i;
}
++i;
}
length = i - start_index;
++i;
if (i > s.length())
i = s.length();
} else {
++i;
length = 1;
}
std::wstring escape_sequence(s, start_index, length);
// The escape sequence is now reconstructed.
std::vector<std::wstring> parts = split(escape_sequence, L'@');
if (parts[0] == L"E") {
// "End of argument" escape sequence. Exit.
break;
} else if (parts[0] == L"T") {
// Beginning of translated string.
std::wstring textdomain;
if (parts.size() > 1)
textdomain = parts[1];
std::wstring translated;
translate_string(s, textdomain, i, translated);
output << translated;
} else {
// Another escape sequence, such as colors. Preserve it.
output << std::wstring(s, escape_start, i - escape_start);
}
}
res = output.str();
}
std::wstring translate_string(const std::wstring &s) {
size_t i = 0;
std::wstring res;
translate_all(s, i, res);
return res;
}

View file

@ -203,6 +203,56 @@ inline bool str_starts_with(const std::basic_string<T> &str,
case_insensitive);
}
/**
* Check whether \p str ends with the string suffix. If \p case_insensitive
* is true then the check is case insensitve (default is false; i.e. case is
* significant).
*
* @param str
* @param suffix
* @param case_insensitive
* @return true if the str begins with suffix
*/
template <typename T>
inline bool str_ends_with(const std::basic_string<T> &str,
const std::basic_string<T> &suffix,
bool case_insensitive = false)
{
if (str.size() < suffix.size())
return false;
size_t start = str.size() - suffix.size();
if (!case_insensitive)
return str.compare(start, suffix.size(), suffix) == 0;
for (size_t i = 0; i < suffix.size(); ++i)
if (tolower(str[start + i]) != tolower(suffix[i]))
return false;
return true;
}
/**
* Check whether \p str ends with the string suffix. If \p case_insensitive
* is true then the check is case insensitve (default is false; i.e. case is
* significant).
*
* @param str
* @param suffix
* @param case_insensitive
* @return true if the str begins with suffix
*/
template <typename T>
inline bool str_ends_with(const std::basic_string<T> &str,
const T *suffix,
bool case_insensitive = false)
{
return str_ends_with(str, std::basic_string<T>(suffix),
case_insensitive);
}
/**
* Splits a string into its component parts separated by the character
* \p delimiter.
@ -598,6 +648,12 @@ std::vector<std::basic_string<T> > split(const std::basic_string<T> &s, T delim)
return tokens;
}
std::wstring translate_string(const std::wstring &s);
inline std::wstring unescape_translate(const std::wstring &s) {
return unescape_enriched(translate_string(s));
}
/**
* Checks that all characters in \p to_check are a decimal digits.
*