1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Implemented basic support for plural forms in translations

This commit is contained in:
Ivan Savenko 2023-11-10 16:10:01 +02:00
parent 7391f2a6ee
commit 7d54f6a9c0
3 changed files with 121 additions and 32 deletions

View File

@ -239,6 +239,18 @@
"vcmi.optionsTab.simturns.help" : "Play simultaneously at least for specified number of days, after which simultaneous turns will stay active until specified day or until players make contact. Contacts between players during this period are blocked",
"vcmi.optionsTab.simturnsAI.help" : "",
// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
// Using this information, VCMI will automatically select correct plural form for every possible amount
"vcmi.optionsTab.simturns.days.0" : " %d days",
"vcmi.optionsTab.simturns.days.1" : " %d day",
"vcmi.optionsTab.simturns.days.2" : " %d days",
"vcmi.optionsTab.simturns.weeks.0" : " %d weeks",
"vcmi.optionsTab.simturns.weeks.1" : " %d week",
"vcmi.optionsTab.simturns.weeks.2" : " %d weeks",
"vcmi.optionsTab.simturns.months.0" : " %d months",
"vcmi.optionsTab.simturns.months.1" : " %d month",
"vcmi.optionsTab.simturns.months.2" : " %d months",
// Custom victory conditions for H3 campaigns and HotA maps
"vcmi.map.victoryCondition.daysPassed.toOthers" : "The enemy has managed to survive till this day. Victory is theirs!",
"vcmi.map.victoryCondition.daysPassed.toSelf" : "Congratulations! You have managed to survive. Victory is yours!",

View File

@ -18,6 +18,7 @@
#include "../CGameInfo.h"
#include "../../lib/StartInfo.h"
#include "../../lib/Languages.h"
#include "../../lib/MetaString.h"
#include "../../lib/CGeneralTextHandler.h"
@ -189,6 +190,32 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
void OptionsTabBase::recreate()
{
auto const & generateSimturnsDurationText = [](int days) -> std::string
{
bool canUseMonth = days % 28 == 0 && days >= 28*2;
bool canUseWeek = days % 7 == 0 && days >= 7*2;
MetaString message;
int value = days;
std::string text = "vcmi.optionsTab.simturns.days";
if (canUseWeek)
{
value = days / 7;
text = "vcmi.optionsTab.simturns.weeks";
}
if (canUseMonth)
{
value = days / 28;
text = "vcmi.optionsTab.simturns.months";
}
message.appendTextID(Languages::getPluralFormTextID( CGI->generaltexth->getPreferredLanguage(), value, text));
message.replaceNumber(value);
return message.toString();
};
//Simultaneous turns
if(auto turnSlider = widget<CSlider>("simturnsDurationMin"))
turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.requiredTurns);
@ -197,20 +224,10 @@ void OptionsTabBase::recreate()
turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.optionalTurns);
if(auto w = widget<CLabel>("labelSimturnsDurationValueMin"))
{
MetaString message;
message.appendRawString("%d days");
message.replaceNumber(SEL->getStartInfo()->simturnsInfo.requiredTurns);
w->setText(message.toString());
}
w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.requiredTurns));
if(auto w = widget<CLabel>("labelSimturnsDurationValueMax"))
{
MetaString message;
message.appendRawString("%d days");
message.replaceNumber(SEL->getStartInfo()->simturnsInfo.optionalTurns);
w->setText(message.toString());
}
w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.optionalTurns));
const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo;

View File

@ -12,6 +12,17 @@
namespace Languages
{
enum class EPluralForms
{
NONE,
VI_1, // Single plural form, (Vietnamese)
EN_2, // Two forms, singular used for one only (English)
FR_2, // Two forms, singular used for zero and one (French)
UK_3, // Three forms, special cases for numbers ending in 1 and 2, 3, 4, except those ending in 1[1-4] (Ukrainian)
CZ_3, // Three forms, special cases for 1 and 2, 3, 4 (Czech)
PL_3, // Three forms, special case for one and some numbers ending in 2, 3, or 4 (Polish)
};
enum class ELanguages
{
CZECH,
@ -57,6 +68,9 @@ struct Options
/// primary IETF language tag
std::string tagIETF;
/// Ruleset for plural forms in this language
EPluralForms pluralForms = EPluralForms::NONE;
/// VCMI supports translations into this language
bool hasTranslation = false;
};
@ -65,27 +79,27 @@ inline const auto & getLanguageList()
{
static const std::array<Options, 20> languages
{ {
{ "czech", "Czech", "Čeština", "CP1250", "cs", true },
{ "chinese", "Chinese", "简体中文", "GBK", "zh", true }, // Note: actually Simplified Chinese
{ "english", "English", "English", "CP1252", "en", true },
{ "finnish", "Finnish", "Suomi", "CP1252", "fi", true },
{ "french", "French", "Français", "CP1252", "fr", true },
{ "german", "German", "Deutsch", "CP1252", "de", true },
{ "hungarian", "Hungarian", "Magyar", "CP1250", "hu", true },
{ "italian", "Italian", "Italiano", "CP1250", "it", true },
{ "korean", "Korean", "한국어", "CP949", "ko", true },
{ "polish", "Polish", "Polski", "CP1250", "pl", true },
{ "portuguese", "Portuguese", "Português", "CP1252", "pt", true }, // Note: actually Brazilian Portuguese
{ "russian", "Russian", "Русский", "CP1251", "ru", true },
{ "spanish", "Spanish", "Español", "CP1252", "es", true },
{ "swedish", "Swedish", "Svenska", "CP1252", "sv", true },
{ "turkish", "Turkish", "Türkçe", "CP1254", "tr", true },
{ "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", true },
{ "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", true }, // Fan translation uses special encoding
{ "czech", "Czech", "Čeština", "CP1250", "cs", EPluralForms::CZ_3, true },
{ "chinese", "Chinese", "简体中文", "GBK", "zh", EPluralForms::VI_1, true }, // Note: actually Simplified Chinese
{ "english", "English", "English", "CP1252", "en", EPluralForms::EN_2, true },
{ "finnish", "Finnish", "Suomi", "CP1252", "fi", EPluralForms::EN_2, true },
{ "french", "French", "Français", "CP1252", "fr", EPluralForms::FR_2, true },
{ "german", "German", "Deutsch", "CP1252", "de", EPluralForms::EN_2, true },
{ "hungarian", "Hungarian", "Magyar", "CP1250", "hu", EPluralForms::EN_2, true },
{ "italian", "Italian", "Italiano", "CP1250", "it", EPluralForms::EN_2, true },
{ "korean", "Korean", "한국어", "CP949", "ko", EPluralForms::VI_1, true },
{ "polish", "Polish", "Polski", "CP1250", "pl", EPluralForms::PL_3, true },
{ "portuguese", "Portuguese", "Português", "CP1252", "pt", EPluralForms::EN_2, true }, // Note: actually Brazilian Portuguese
{ "russian", "Russian", "Русский", "CP1251", "ru", EPluralForms::UK_3, true },
{ "spanish", "Spanish", "Español", "CP1252", "es", EPluralForms::EN_2, true },
{ "swedish", "Swedish", "Svenska", "CP1252", "sv", EPluralForms::EN_2, true },
{ "turkish", "Turkish", "Türkçe", "CP1254", "tr", EPluralForms::EN_2, true },
{ "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", EPluralForms::UK_3, true },
{ "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", EPluralForms::VI_1, true }, // Fan translation uses special encoding
{ "other_cp1250", "Other (East European)", "", "CP1250", "", false },
{ "other_cp1251", "Other (Cyrillic Script)", "", "CP1251", "", false },
{ "other_cp1252", "Other (West European)", "", "CP1252", "", false }
{ "other_cp1250", "Other (East European)", "", "CP1250", "", EPluralForms::NONE, false },
{ "other_cp1251", "Other (Cyrillic Script)", "", "CP1251", "", EPluralForms::NONE, false },
{ "other_cp1252", "Other (West European)", "", "CP1252", "", EPluralForms::NONE, false }
} };
static_assert(languages.size() == static_cast<size_t>(ELanguages::COUNT), "Languages array is missing a value!");
@ -109,4 +123,50 @@ inline const Options & getLanguageOptions(const std::string & language)
return emptyValue;
}
template<typename Numeric>
inline constexpr int getPluralFormIndex(EPluralForms form, Numeric value)
{
// Based on https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html
switch(form)
{
case EPluralForms::NONE:
case EPluralForms::VI_1:
return 0;
case EPluralForms::EN_2:
if (value == 1)
return 1;
return 2;
case EPluralForms::FR_2:
if (value == 1 || value == 0)
return 1;
return 2;
case EPluralForms::UK_3:
if (value % 10 == 1 && value % 100 != 11)
return 1;
if (value%10>=2 && value%10<=4 && (value%100<10 || value%100>=20))
return 2;
return 0;
case EPluralForms::CZ_3:
if (value == 1)
return 1;
if (value>=2 && value<=4)
return 2;
return 0;
case EPluralForms::PL_3:
if (value == 1)
return 1;
if (value%10>=2 && value%10<=4 && (value%100<10 || value%100>=20))
return 2;
return 0;
}
throw std::runtime_error("Invalid plural form enumeration received!");
}
template<typename Numeric>
inline std::string getPluralFormTextID(std::string languageName, Numeric value, std::string textID)
{
int formIndex = getPluralFormIndex(getLanguageOptions(languageName).pluralForms, value);
return textID + '.' + std::to_string(formIndex);
}
}