aboutsummaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/enriched_string.cpp6
-rw-r--r--src/util/string.cpp194
-rw-r--r--src/util/string.h56
3 files changed, 253 insertions, 3 deletions
diff --git a/src/util/enriched_string.cpp b/src/util/enriched_string.cpp
index f79e77d5a..642188a52 100644
--- a/src/util/enriched_string.cpp
+++ b/src/util/enriched_string.cpp
@@ -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)
diff --git a/src/util/string.cpp b/src/util/string.cpp
index fc2a2057f..6335aeafe 100644
--- a/src/util/string.cpp
+++ b/src/util/string.cpp
@@ -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;
+}
diff --git a/src/util/string.h b/src/util/string.h
index 122262af8..2840f1192 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -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.
*