From bb4f242e0b77ff17133532ee4dae1bdaecbed445 Mon Sep 17 00:00:00 2001 From: mniip Date: Tue, 24 Mar 2020 19:01:51 +0300 Subject: [PATCH] Add support for plural forms --- data/localization/EN.cpp | 8 +++++- data/localization/List.h | 2 ++ src/common/Internationalization.cpp | 8 +++++- src/common/Internationalization.h | 43 +++++++++++++++++++++++++++-- src/common/Localization.h | 32 +++++++++++++++++---- 5 files changed, 83 insertions(+), 10 deletions(-) diff --git a/data/localization/EN.cpp b/data/localization/EN.cpp index 8c2ac1c19..87319a868 100644 --- a/data/localization/EN.cpp +++ b/data/localization/EN.cpp @@ -6,9 +6,16 @@ struct LocaleEN : public Locale { String GetName() const { return "English"_ascii; } + size_t GetPluralIndex(size_t n) const + { + // 0: singular, 1: plural + return n == 1 ? 0 : 1; + } + void Set() const { using i18n::translation; + using i18n::pluralForm; } String GetIntroText() const @@ -132,7 +139,6 @@ struct LocaleEN : public Locale "\n" "If you have any questions about what is and isn't against the rules, feel free to contact a moderator."; } - }; Locale const &Locale_EN = LocaleEN{}; diff --git a/data/localization/List.h b/data/localization/List.h index c370d6b71..7fa07c466 100644 --- a/data/localization/List.h +++ b/data/localization/List.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "common/Localization.h" extern Locale const &Locale_EN; diff --git a/src/common/Internationalization.cpp b/src/common/Internationalization.cpp index d5e05c8d9..bd2a34321 100644 --- a/src/common/Internationalization.cpp +++ b/src/common/Internationalization.cpp @@ -32,9 +32,15 @@ namespace i18n #ifdef I18N_DEBUG std::set > &activeKeys() { - static std::set > activeKeys; + static std::set > activeKeys{}; return activeKeys; } + + std::set &activePlurals() + { + static std::set activePlurals{}; + return activePlurals; + } #endif // I18N_DEBUG } diff --git a/src/common/Internationalization.h b/src/common/Internationalization.h index ae23730dc..12b580011 100644 --- a/src/common/Internationalization.h +++ b/src/common/Internationalization.h @@ -8,6 +8,8 @@ #include "String.h" #include "Localization.h" +extern Locale const *currentLocale; + /* We handle internationalization by maintaining a map from "key" strings, to localized versions of those strings. The "keys" are strings in English, the @@ -67,6 +69,7 @@ namespace i18n #ifdef I18N_DEBUG std::set > &activeKeys(); + std::set &activePlurals(); template struct Chars { @@ -84,8 +87,14 @@ namespace i18n MultiKeyUsage() { activeKeys().insert({Ts::chars...}); } }; + template struct PluralUsage + { + PluralUsage() { activePlurals().insert(T::chars); } + }; + template KeyUsage keyUsage; template MultiKeyUsage multiKeyUsage; + template PluralUsage pluralUsage; #endif template struct TranslationMap @@ -99,6 +108,17 @@ namespace i18n } }; + inline std::map> &pluralForms() + { + static std::map> pluralForms{}; + return pluralForms; + } + + template std::vector &pluralForm(char const (&str)[N]) + { + return pluralForms()[Canonicalize(str)]; + } + template String &translation(char const (&str)[N]) { return TranslationMap<1>::Map()[{Canonicalize(str)}][0]; @@ -145,9 +165,15 @@ namespace i18n strs[i] = lit; return getMultiTranslation(strs, i + 1, std::forward(args)...); } -} -extern struct Locale const *currentLocale; + inline String getPlural(LiteralPtr str, size_t n) + { + auto it = pluralForms().find(Canonicalize(str)); + if(it == pluralForms().end() || !currentLocale) + return ByteString(str).FromUtf8(); + return it->second[currentLocale->GetPluralIndex(n)]; + } +} #ifndef I18N_DEBUG @@ -162,6 +188,11 @@ template std::array i18nMulti(Ts&&... arg return i18n::getMultiTranslation(strs, 0, std::forward(args)...); } +inline String i18nPlural(char const *str, size_t n) +{ + return i18n::getPlural(str, n); +} + #else // I18N_DEBUG template inline i18n::Chars operator""_chars() @@ -202,4 +233,12 @@ template std::array i18nMultiImpl() return i18n::getMultiTranslation(strs, 0, Ts::chars...); } +#define i18nPlural(str, n) i18nPluralImpl(n) + +template String i18nPluralImpl(size_t n) +{ + (void)i18n::pluralUsage; + return i18n::getPlural(T::chars, n); +} + #endif // I18N_DEBUG diff --git a/src/common/Localization.h b/src/common/Localization.h index 1e6dc9247..bc722e635 100644 --- a/src/common/Localization.h +++ b/src/common/Localization.h @@ -2,18 +2,38 @@ #include -#include "String.h" - struct Locale { // The name of the language this locale is for, readable in both the native // language and in English; - virtual String GetName() const = 0; + virtual class String GetName() const = 0; // Populate the translations map. virtual void Set() const = 0; - virtual String GetIntroText() const = 0; - virtual String GetSavePublishingInfo() const = 0; - virtual String GetRules() const = 0; + virtual class String GetIntroText() const = 0; + virtual class String GetSavePublishingInfo() const = 0; + virtual class String GetRules() const = 0; + + /* + English only has two choices for spelling a noun based on amount: + singular when there's exactly 1 object, and plural when there's 0 or 2 + or more objects. + + In Russian there are three choices, based on the last 2 digits of the + amount: singular nominative if the number ends in 1, except 11; + singular genitive if the number ends in 2,3,4, except 12 through 14; + plural genitive if the for everything else. + + In other languages things can get more complicated, for example in + Arabic a noun can have as many as 6 spellings. + + What we can do is give these various forms indices, e.g. for Russian + singular nominative=0, singular genitive=1, plural genitive=2; + and then use these indices to index into an array of spellings for the + respective word. + + GetPluralIndex(n) returns the index of the word form used for n objects. + */ + virtual size_t GetPluralIndex(size_t n) const = 0; };