diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index 57db21c69..8318d92df 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -1,5 +1,6 @@ #include "utils/language.h" +#include #include #include #include @@ -20,7 +21,7 @@ struct cstring_cmp { } }; -std::map> map; +std::vector>> translation = { {}, {} }; std::map meta; struct mo_head { @@ -63,11 +64,101 @@ char *strtrim_right(char *s) return s; } -bool parse_metadata(char *data) +bool IsUTF8 = true; + +// English, Danish, Spanish, Italian, Swedish +int PluralForms = 2; +std::function GetLocalPluralId = [](int n) -> int { return n != 1 ? 1 : 0; }; + +/** + * Match plural=(n != 1);" + */ +void SetPluralForm(char *string) +{ + char *expression = strstr(string, "plural"); + if (expression == nullptr) + return; + + expression = strstr(expression, "="); + if (expression == nullptr) + return; + expression += 1; + + for (unsigned i = 0; i < strlen(expression); i++) { + if (expression[i] == ';') { + expression[i] = '\0'; + break; + } + } + + expression = strtrim_right(expression); + expression = strtrim_left(expression); + + // Chinese + if (strcmp(expression, "0") == 0) { + GetLocalPluralId = [](int n) -> int { return 0; }; + return; + } + + // Portuguese + if (strcmp(expression, "(n > 1)") == 0) { + GetLocalPluralId = [](int n) -> int { return n > 1 ? 1 : 0; }; + return; + } + + // Russian, Croatian + if (strcmp(expression, "(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2)") == 0) { + GetLocalPluralId = [](int n) -> int { + if (n % 10 == 1 && n % 100 != 11) + return 0; + if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) + return 1; + return 2; + }; + return; + } + + // Polish + if (strcmp(expression, "(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)") == 0) { + GetLocalPluralId = [](int n) -> int { + if (n == 1) + return 0; + if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) + return 1; + return 2; + }; + return; + } +} + +/** + * Parse "nplurals=2;" + */ +void ParsePluralForms(char *string) +{ + char *value = strstr(string, "nplurals"); + if (value == nullptr) + return; + + value = strstr(value, "="); + if (value == nullptr) + return; + + value += 1; + + int nplurals = SDL_atoi(value); + if (nplurals == 0) + return; + + PluralForms = nplurals; + + SetPluralForm(value); +} + +void parse_metadata(char *data) { char *key, *delim, *val; char *ptr = data; - bool utf8 = false; while (ptr && (delim = strstr(ptr, ":"))) { key = strtrim_left(ptr); @@ -87,11 +178,16 @@ bool parse_metadata(char *data) // Match "Content-Type: text/plain; charset=UTF-8" if (!strcmp("Content-Type", key) && (delim = strstr(val, "="))) { - utf8 = !strcasecmp(delim + 1, "utf-8"); + IsUTF8 = !strcasecmp(delim + 1, "utf-8"); + continue; } - } - return utf8; + // Match "Plural-Forms: nplurals=2; plural=(n != 1);" + if (!strcmp("Plural-Forms", key)) { + ParsePluralForms(val); + continue; + } + } } bool read_entry(FILE *fp, mo_entry *e, std::vector &result) @@ -105,12 +201,27 @@ bool read_entry(FILE *fp, mo_entry *e, std::vector &result) } // namespace +const std::string &LanguagePluralTranslate(const char *key, const char *key2, int count) +{ + int n = GetLocalPluralId(count); + + auto it = translation[n].find(key); + if (it == translation[n].end()) { + if (count != 1) + it = translation[1].insert({ key, utf8_to_latin1(key2) }).first; + else + it = translation[0].insert({ key, utf8_to_latin1(key) }).first; + } + + return it->second; +} const std::string &LanguageTranslate(const char *key) { - auto it = map.find(key); - if (it == map.end()) { - return map.insert({key, utf8_to_latin1(key)}).first->second; + auto it = translation[0].find(key); + if (it == translation[0].end()) { + it = translation[0].insert({ key, utf8_to_latin1(key) }).first; } + return it->second; } @@ -128,7 +239,6 @@ void LanguageInitialize() { mo_head head; FILE *fp; - bool utf8; auto path = paths::LangPath() + "./" + sgOptions.Language.szCode + ".gmo"; if (!(fp = fopen(path.c_str(), "rb"))) { @@ -168,19 +278,34 @@ void LanguageInitialize() if (fread(dst.get(), sizeof(mo_entry), head.nb_mappings, fp) != head.nb_mappings) return; + std::vector key; + std::vector value; + + // MO header + if (!read_entry(fp, &src[0], key) && read_entry(fp, &dst[0], value)) + return; + + if (key.data()[0] != '\0') + return; + + parse_metadata(value.data()); + + translation.resize(PluralForms); + for (int i = 0; i < PluralForms; i++) + translation[i] = {}; + // Read strings described by entries - for (uint32_t i = 0; i < head.nb_mappings; i++) { - std::vector key; - std::vector value; + for (uint32_t i = 1; i < head.nb_mappings; i++) { if (read_entry(fp, &src[i], key) && read_entry(fp, &dst[i], value)) { - if (key.data()[0] == '\0') { - utf8 = parse_metadata(value.data()); - } else { - if (utf8) { - map.emplace(key.data(), utf8_to_latin1(value.data())); - } else { - map.emplace(key.data(), value.data()); - } + int offset = 0; + for (int j = 0; j < PluralForms; j++) { + const char *text = value.data() + offset; + translation[j].emplace(key.data(), IsUTF8 ? utf8_to_latin1(text) : text); + + if (dst[i].length <= offset + strlen(value.data())) + break; + + offset += strlen(text) + 1; } } } diff --git a/Source/utils/language.h b/Source/utils/language.h index 8f1a87ac2..1f92b1d91 100644 --- a/Source/utils/language.h +++ b/Source/utils/language.h @@ -3,8 +3,10 @@ #include #define _(x) LanguageTranslate(x).c_str() +#define ngettext(x, y, z) LanguagePluralTranslate(x, y, z).c_str() #define N_(x) (x) void LanguageInitialize(); -const std::string &LanguageTranslate(const char* key); +const std::string &LanguagePluralTranslate(const char *singular, const char *plural, int count); +const std::string &LanguageTranslate(const char *key); const char *LanguageMetadata(const char *key); diff --git a/Translations/es.po b/Translations/es.po index 02fc4fbf4..70e948c84 100644 --- a/Translations/es.po +++ b/Translations/es.po @@ -10,8 +10,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.1\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-KeywordsList: _;N_\n" "X-Poedit-Basepath: ..\n" diff --git a/Translations/hr.po b/Translations/hr.po index fc5a94f0f..eec9ee2f0 100644 --- a/Translations/hr.po +++ b/Translations/hr.po @@ -15,7 +15,8 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.1\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-KeywordsList: _;N_\n" "X-Poedit-Basepath: ..\n" diff --git a/Translations/pt_BR.po b/Translations/pt_BR.po index f45f22528..3701b2ee4 100644 --- a/Translations/pt_BR.po +++ b/Translations/pt_BR.po @@ -9,8 +9,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.4.2\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 2.4.1\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-KeywordsList: _;N_\n" "X-Poedit-Basepath: ..\n" diff --git a/Translations/sv.po b/Translations/sv.po index 74455a51a..566ee5101 100644 --- a/Translations/sv.po +++ b/Translations/sv.po @@ -13,6 +13,7 @@ msgstr "" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-KeywordsList: _;N_\n" "X-Poedit-Basepath: ..\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-SearchPath-0: Source\n" #: Source/DiabloUI/mainmenu.cpp:36