Browse Source

Support for plural forms in translations

The implementation isn't robust, dynamic or pretty, but works for now.
pull/1799/head
Anders Jenbo 5 years ago
parent
commit
18f3a0c5ea
  1. 167
      Source/utils/language.cpp
  2. 4
      Source/utils/language.h
  3. 3
      Translations/es.po
  4. 3
      Translations/hr.po
  5. 4
      Translations/pt_BR.po
  6. 1
      Translations/sv.po

167
Source/utils/language.cpp

@ -1,5 +1,6 @@
#include "utils/language.h"
#include <functional>
#include <map>
#include <memory>
#include <vector>
@ -20,7 +21,7 @@ struct cstring_cmp {
}
};
std::map<std::string, std::string, std::less<>> map;
std::vector<std::map<std::string, std::string, std::less<>>> translation = { {}, {} };
std::map<const char *, const char *, cstring_cmp> 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<int(int n)> 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<char> &result)
@ -105,12 +201,27 @@ bool read_entry(FILE *fp, mo_entry *e, std::vector<char> &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<char> key;
std::vector<char> 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<char> key;
std::vector<char> 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;
}
}
}

4
Source/utils/language.h

@ -3,8 +3,10 @@
#include <string>
#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);

3
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"

3
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"

4
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"

1
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

Loading…
Cancel
Save