aboutsummaryrefslogtreecommitdiff
path: root/src/util/string.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/string.cpp')
-rw-r--r--src/util/string.cpp194
1 files changed, 194 insertions, 0 deletions
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;
+}