diff --git a/Telegram/SourceFiles/codegen/lang/generator.cpp b/Telegram/SourceFiles/codegen/lang/generator.cpp index 7a144a12ec..de9fb85b7b 100644 --- a/Telegram/SourceFiles/codegen/lang/generator.cpp +++ b/Telegram/SourceFiles/codegen/lang/generator.cpp @@ -102,12 +102,30 @@ Generator::Generator(const LangPack &langpack, const QString &destBasePath, cons bool Generator::writeHeader() { header_ = std::make_unique(basePath_ + ".h", project_); - header_->include("lang/lang_tag.h").newline().pushNamespace("Lang").stream() << "\ + header_->include("lang/lang_tag.h").include("lang/lang_values.h").newline(); + + writeHeaderForwardDeclarations(); + writeHeaderTagTypes(); + writeHeaderKeyType(); + writeHeaderTaggedMethods(); + writeHeaderInterface(); + writeHeaderReactiveInterface(); + + return header_->finalize(); +} + +void Generator::writeHeaderForwardDeclarations() { + header_->pushNamespace("Lang").stream() << "\ \n\ -constexpr auto kTagsCount = " << langpack_.tags.size() << ";\n\ +inline constexpr auto kTagsCount = " << langpack_.tags.size() << ";\n\ \n"; - header_->popNamespace().newline(); + header_->popNamespace().newline().stream() << "\ +enum LangKey : int;\n\ +QString lang(LangKey key);\n\n"; +} + +void Generator::writeHeaderTagTypes() { auto index = 0; for (auto &tag : langpack_.tags) { if (tag.tag == kPluralTags[0]) { @@ -123,8 +141,11 @@ constexpr auto kTagsCount = " << langpack_.tags.size() << ";\n\ header_->stream() << "enum lngtag_" << tag.tag << " : int { lt_" << tag.tag << " = " << index++ << " };\n"; } } + header_->newline(); +} + +void Generator::writeHeaderKeyType() { header_->stream() << "\ -\n\ enum LangKey : int {\n"; for (auto &entry : langpack_.entries) { header_->stream() << "\t" << getFullKey(entry) << ",\n"; @@ -133,10 +154,10 @@ enum LangKey : int {\n"; \n\ kLangKeysCount,\n\ };\n\ -\n\ -QString lang(LangKey key);\n\ \n"; +} +void Generator::writeHeaderTaggedMethods() { for (auto &entry : langpack_.entries) { auto isPlural = !entry.keyBase.isEmpty(); auto &key = entry.key; @@ -144,7 +165,6 @@ QString lang(LangKey key);\n\ auto params = QStringList(); auto applyTags = QStringList(); auto plural = QString(); - auto nonPluralTagFound = false; for (auto &tagData : entry.tags) { auto &tag = tagData.tag; auto isPluralTag = isPlural && (tag == kPluralTags[0]); @@ -154,26 +174,27 @@ QString lang(LangKey key);\n\ plural = "\tauto plural = Lang::Plural(" + key + ", " + tag + "__val, type);\n"; applyTags.push_back("\tresult = Lang::ReplaceTag::Call(std::move(result), lt_" + tag + ", Lang::StartReplacements::Call(std::move(plural.replacement)));\n"); } else { - nonPluralTagFound = true; applyTags.push_back("\tresult = Lang::ReplaceTag::Call(std::move(result), lt_" + tag + ", " + tag + "__val);\n"); } } if (!entry.tags.empty() && (!isPlural || key == ComputePluralKey(entry.keyBase, 0))) { - auto initialString = isPlural ? ("std::move(plural.string)") : ("lang(" + getFullKey(entry) + ")"); + auto initialString = isPlural ? ("lang(LangKey(" + key + " + plural.keyShift))") : ("lang(" + getFullKey(entry) + ")"); header_->stream() << "\ template \n\ -inline ResultString " << (isPlural ? entry.keyBase : key) << "__generic(" << genericParams.join(QString(", ")) << ") {\n\ +ResultString " << (isPlural ? entry.keyBase : key) << "__generic(" << genericParams.join(QString(", ")) << ") {\n\ " << plural << "\ auto result = Lang::StartReplacements::Call(" << initialString << ");\n\ " << applyTags.join(QString()) << "\ return result;\n\ }\n\ -constexpr auto " << (isPlural ? entry.keyBase : key) << " = &" << (isPlural ? entry.keyBase : key) << "__generic;\n\ -constexpr auto " << (isPlural ? entry.keyBase : key) << "__rich = &" << (isPlural ? entry.keyBase : key) << "__generic;\n\ +inline constexpr auto " << (isPlural ? entry.keyBase : key) << " = &" << (isPlural ? entry.keyBase : key) << "__generic;\n\ +inline constexpr auto " << (isPlural ? entry.keyBase : key) << "__rich = &" << (isPlural ? entry.keyBase : key) << "__generic;\n\ \n"; } } +} +void Generator::writeHeaderInterface() { header_->pushNamespace("Lang").stream() << "\ \n\ const char *GetKeyName(LangKey key);\n\ @@ -182,8 +203,127 @@ LangKey GetKeyIndex(QLatin1String key);\n\ bool IsTagReplaced(LangKey key, ushort tag);\n\ QString GetOriginalValue(LangKey key);\n\ \n"; + writeHeaderTagValueLookup(); + header_->popNamespace().newline(); +} - return header_->finalize(); +void Generator::writeHeaderTagValueLookup() { + header_->pushNamespace("details").stream() << "\ +\n\ +template struct TagValue;\n\ +\n"; + + for (auto &tag : langpack_.tags) { + if (tag.tag != kPluralTags[0]) { + header_->stream() << "template <> struct TagValue : std::integral_constant {};\n"; + } + } + + header_->popNamespace(); +} + +void Generator::writeHeaderReactiveInterface() { + header_->pushNamespace("tr"); + + writeHeaderProducersInterface(); + writeHeaderProducersInstances(); + + header_->popNamespace().newline(); +} + +void Generator::writeHeaderProducersInterface() { + header_->pushNamespace("details").stream() << "\ +\n\ +struct Identity {\n\ + QString operator()(const QString &value) const {\n\ + return value;\n\ + }\n\ +};\n\ +\n"; + + header_->popNamespace().newline(); + header_->stream() << "\ +\n\ +struct now_t {\n\ +};\n\ +\n\ +inline constexpr now_t now{};\n\ +\n\ +template \n\ +struct Producer;\n\ +\n"; + std::set producersDeclared; + for (auto &entry : langpack_.entries) { + const auto isPlural = !entry.keyBase.isEmpty(); + const auto &key = entry.key; + auto tags = QStringList(); + auto producerArgs = QStringList(); + auto currentArgs = QStringList(); + auto values = QStringList(); + values.push_back("base"); + values.push_back("std::move(p)"); + for (auto &tagData : entry.tags) { + const auto &tag = tagData.tag; + const auto isPluralTag = isPlural && (tag == kPluralTags[0]); + tags.push_back("lngtag_" + tag); + const auto type1 = "lngtag_" + tag; + const auto arg1 = type1 + (isPluralTag ? " type" : ""); + const auto producerType2 = (isPluralTag ? "rpl::producer " : "rpl::producer "); + const auto producerArg2 = producerType2 + tag + "__val"; + const auto currentType2 = (isPluralTag ? "float64 " : "const T &"); + const auto currentArg2 = currentType2 + tag + "__val"; + producerArgs.push_back(arg1 + ", " + producerArg2); + currentArgs.push_back(arg1 + ", " + currentArg2); + if (isPluralTag) { + values.push_back("type"); + } + values.push_back(tag + "__val"); + } + producerArgs.push_back("P p = P()"); + currentArgs.push_back("P p = P()"); + if (!producersDeclared.emplace(tags).second) { + continue; + } + header_->stream() << "\ +template <>\n\ +struct Producer<" << tags.join(", ") << "> {\n\ + template <\n\ + typename P = details::Identity,\n\ + typename T = decltype(std::declval

()(QString()))>\n\ + rpl::producer operator()(" << producerArgs.join(", ") << ") const {\n\ + return ::Lang::details::template Producer<" << tags.join(", ") << ">::template Combine(" << values.join(", ") << ");\n\ + }\n\ +\n\ + template <\n\ + typename P = details::Identity,\n\ + typename T = decltype(std::declval

()(QString()))>\n\ + T operator()(now_t, " << currentArgs.join(", ") << ") const {\n\ + return ::Lang::details::template Producer<" << tags.join(", ") << ">::template Current(" << values.join(", ") << ");\n\ + }\n\ +\n\ + LangKey base;\n\ +};\n\ +\n"; + } +} + +void Generator::writeHeaderProducersInstances() { + auto index = 0; + for (auto &entry : langpack_.entries) { + const auto isPlural = !entry.keyBase.isEmpty(); + const auto &key = entry.key; + auto tags = QStringList(); + for (auto &tagData : entry.tags) { + const auto &tag = tagData.tag; + tags.push_back("lngtag_" + tag); + } + if (!isPlural || key == ComputePluralKey(entry.keyBase, 0)) { + header_->stream() << "\ +inline constexpr Producer<" << tags.join(", ") << "> " << (isPlural ? entry.keyBase : key) << "{ LangKey(" << index << ") };\n"; + } + ++index; + } + header_->newline(); } bool Generator::writeSource() { @@ -250,7 +390,7 @@ ushort GetTagIndex(QLatin1String tag) {\n\ auto size = tag.size();\n\ auto data = tag.data();\n"; - auto tagsSet = std::set>(); + auto tagsSet = std::set>(); for (auto &tag : langpack_.tags) { tagsSet.insert(tag.tag); } @@ -267,7 +407,7 @@ LangKey GetKeyIndex(QLatin1String key) {\n\ auto data = key.data();\n"; auto taggedKeys = std::map(); - auto keysSet = std::set>(); + auto keysSet = std::set>(); for (auto &entry : langpack_.entries) { if (!entry.keyBase.isEmpty()) { for (auto i = 0; i != kPluralPartCount; ++i) { @@ -342,7 +482,7 @@ QString GetOriginalValue(LangKey key) {\n\ } template -void Generator::writeSetSearch(const std::set> &set, ComputeResult computeResult, const QString &invalidResult) { +void Generator::writeSetSearch(const std::set> &set, ComputeResult computeResult, const QString &invalidResult) { auto tabs = [](int size) { return QString(size, '\t'); }; diff --git a/Telegram/SourceFiles/codegen/lang/generator.h b/Telegram/SourceFiles/codegen/lang/generator.h index fc1821faa4..958ac23a38 100644 --- a/Telegram/SourceFiles/codegen/lang/generator.h +++ b/Telegram/SourceFiles/codegen/lang/generator.h @@ -29,10 +29,20 @@ public: bool writeSource(); private: + void writeHeaderForwardDeclarations(); + void writeHeaderTagTypes(); + void writeHeaderKeyType(); + void writeHeaderTaggedMethods(); + void writeHeaderInterface(); + void writeHeaderTagValueLookup(); + void writeHeaderReactiveInterface(); + void writeHeaderProducersInterface(); + void writeHeaderProducersInstances(); + QString getFullKey(const LangPack::Entry &entry); template - void writeSetSearch(const std::set> &set, ComputeResult computeResult, const QString &invalidResult); + void writeSetSearch(const std::set> &set, ComputeResult computeResult, const QString &invalidResult); const LangPack &langpack_; QString basePath_, baseName_; diff --git a/Telegram/SourceFiles/lang/lang_instance.cpp b/Telegram/SourceFiles/lang/lang_instance.cpp index 6c5e67b45f..678d856584 100644 --- a/Telegram/SourceFiles/lang/lang_instance.cpp +++ b/Telegram/SourceFiles/lang/lang_instance.cpp @@ -780,6 +780,10 @@ Instance &Current() { return Core::App().langpack(); } +QString Current(LangKey key) { + return Current().getValue(key); +} + rpl::producer Viewer(LangKey key) { return rpl::single( Current().getValue(key) diff --git a/Telegram/SourceFiles/lang/lang_instance.h b/Telegram/SourceFiles/lang/lang_instance.h index 76d213c026..803eb4870f 100644 --- a/Telegram/SourceFiles/lang/lang_instance.h +++ b/Telegram/SourceFiles/lang/lang_instance.h @@ -56,6 +56,7 @@ Language DefaultLanguage(); class Instance; Instance &Current(); +QString Current(LangKey key); rpl::producer Viewer(LangKey key); enum class Pack { diff --git a/Telegram/SourceFiles/lang/lang_tag.cpp b/Telegram/SourceFiles/lang/lang_tag.cpp index 2495e74296..88fd4e5aa1 100644 --- a/Telegram/SourceFiles/lang/lang_tag.cpp +++ b/Telegram/SourceFiles/lang/lang_tag.cpp @@ -962,27 +962,25 @@ PluralResult Plural( const auto f = NonZeroPartToInt(fraction); const auto t = f; - auto &langpack = Lang::Current(); - auto useNonDefaultPlural = (ChoosePlural != ChoosePluralDefault) - && langpack.isNonDefaultPlural(LangKey(keyBase)); - auto shift = (useNonDefaultPlural ? ChoosePlural : ChoosePluralDefault)( + const auto useNonDefaultPlural = (ChoosePlural != ChoosePluralDefault) + && Lang::Current().isNonDefaultPlural(LangKey(keyBase)); + const auto shift = (useNonDefaultPlural ? ChoosePlural : ChoosePluralDefault)( (integer ? i : -1), i, v, w, f, t); - auto string = langpack.getValue(LangKey(keyBase + shift)); if (integer) { const auto round = qRound(value); if (type == lt_count_short) { - return { string, shortened.string }; + return { shift, shortened.string }; } else if (type == lt_count_decimal) { - return { string, QString("%L1").arg(round) }; + return { shift, QString("%L1").arg(round) }; } - return { string, QString::number(round) }; + return { shift, QString::number(round) }; } - return { string, FormatDouble(value) }; + return { shift, FormatDouble(value) }; } void UpdatePluralRules(const QString &languageId) { diff --git a/Telegram/SourceFiles/lang/lang_tag.h b/Telegram/SourceFiles/lang/lang_tag.h index 12fa99e651..8ce6d67580 100644 --- a/Telegram/SourceFiles/lang/lang_tag.h +++ b/Telegram/SourceFiles/lang/lang_tag.h @@ -22,7 +22,7 @@ struct ShortenedCount { ShortenedCount FormatCountToShort(int64 number); struct PluralResult { - QString string; + int keyShift = 0; QString replacement; }; PluralResult Plural( diff --git a/Telegram/SourceFiles/lang/lang_values.cpp b/Telegram/SourceFiles/lang/lang_values.cpp new file mode 100644 index 0000000000..b83ebd58c7 --- /dev/null +++ b/Telegram/SourceFiles/lang/lang_values.cpp @@ -0,0 +1,9 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "lang/lang_values.h" + diff --git a/Telegram/SourceFiles/lang/lang_values.h b/Telegram/SourceFiles/lang/lang_values.h new file mode 100644 index 0000000000..aa1c284196 --- /dev/null +++ b/Telegram/SourceFiles/lang/lang_values.h @@ -0,0 +1,181 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "lang/lang_tag.h" + +enum LangKey : int; +enum lngtag_count : int; + +namespace Lang { + +QString Current(LangKey key); +rpl::producer Viewer(LangKey key); + +namespace details { + +inline constexpr auto kPluralCount = 6; + +template struct TagValue; + +template +Type ReplaceUnwrapTuple(Type accumulated, const Tuple &tuple) { + return accumulated; +} + +template +Type ReplaceUnwrapTuple( + Type accumulated, + const Tuple &tuple, + Tag tag, + Tags ...tags) { + return ReplaceUnwrapTuple( + ReplaceTag::Call( + std::move(accumulated), + tag, + std::get(tuple)), + tuple, + tags...); +} + +template +struct ReplaceUnwrap; + +template <> +struct ReplaceUnwrap<> { + template + static Type Call(Type accumulated) { + return accumulated; + } +}; + +template +struct ReplaceUnwrap { + template + static Type Call( + Type accumulated, + const Value &value, + const Values &...values) { + return ReplaceUnwrap::template Call( + ReplaceTag::Call( + std::move(accumulated), + TagValue::value, + value), + values...); + } +}; + +template +struct Producer { + template < + typename P, + typename T = decltype(std::declval

()(QString())), + typename ...Values> + static rpl::producer Combine(LangKey base, P p, Values &...values) { + return rpl::combine( + Viewer(base), + std::move(values)... + ) | rpl::map([p = std::move(p)](auto tuple) { + return ReplaceUnwrapTuple<1>(p(std::get<0>(tuple)), tuple, TagValue::value...); + }); + } + + template < + typename P, + typename T = decltype(std::declval

()(QString())), + typename ...Values> + static T Current(LangKey base, P p, const Values &...values) { + return ReplaceUnwrap::template Call(p(Lang::Current(base)), values...); + } +}; + +template <> +struct Producer<> { + template < + typename P, + typename T = decltype(std::declval

()(QString()))> + static rpl::producer Combine(LangKey base, P p) { + return Viewer(base) | rpl::map(std::move(p)); + } + + template < + typename P, + typename T = decltype(std::declval

()(QString()))> + static T Current(LangKey base, P p) { + return p(Lang::Current(base)); + } +}; + +template +struct Producer { + template < + typename P, + typename T = decltype(std::declval

()(QString())), + typename ...Values> + static rpl::producer Combine( + LangKey base, + P p, + lngtag_count type, + rpl::producer &count, + Values &...values) { + return rpl::combine( + Viewer(base), + Viewer(LangKey(base + 1)), + Viewer(LangKey(base + 2)), + Viewer(LangKey(base + 3)), + Viewer(LangKey(base + 4)), + Viewer(LangKey(base + 5)), + std::move(count), + std::move(values)... + ) | rpl::map([base, type, p = std::move(p)](auto tuple) { + auto plural = Plural(base, std::get<6>(tuple), type); + const auto select = [&] { + switch (plural.keyShift) { + case 0: return std::get<0>(tuple); + case 1: return std::get<1>(tuple); + case 2: return std::get<2>(tuple); + case 3: return std::get<3>(tuple); + case 4: return std::get<4>(tuple); + case 5: return std::get<5>(tuple); + } + Unexpected("Lang shift value in Plural result."); + }; + return ReplaceUnwrapTuple<7>( + ReplaceTag::Call( + p(select()), + type, + StartReplacements::Call( + std::move(plural.replacement))), + tuple, + TagValue::value...); + }); + } + + template < + typename P, + typename T = decltype(std::declval

()(QString())), + typename ...Values> + static T Current( + LangKey base, + P p, + lngtag_count type, + float64 count, + const Values &...values) { + auto plural = Plural(base, count, type); + return ReplaceUnwrap::template Call( + ReplaceTag::Call( + p(Lang::Current(base + plural.keyShift)), + type, + StartReplacements::Call( + std::move(plural.replacement))), + values...); + } +}; + +} // namespace details +} // namespace Lang diff --git a/Telegram/SourceFiles/ui/text/text_utilities.h b/Telegram/SourceFiles/ui/text/text_utilities.h index 164d65eb6e..5f7efb9c07 100644 --- a/Telegram/SourceFiles/ui/text/text_utilities.h +++ b/Telegram/SourceFiles/ui/text/text_utilities.h @@ -21,6 +21,9 @@ TextWithEntities Link( const QString &text, const QString &url = "internal:action"); TextWithEntities RichLangValue(const QString &text); +inline TextWithEntities WithEntities(const QString &text) { + return { text }; +} inline auto ToBold() { return rpl::map(Bold); @@ -36,5 +39,9 @@ inline auto ToRichLangValue() { return rpl::map(RichLangValue); } +inline auto ToWithEntities() { + return rpl::map(WithEntities); +} + } // namespace Text } // namespace Ui diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index eb5a0e9b0c..0fdb696292 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -435,6 +435,8 @@ <(src_loc)/lang/lang_tag.h <(src_loc)/lang/lang_translator.cpp <(src_loc)/lang/lang_translator.h +<(src_loc)/lang/lang_values.cpp +<(src_loc)/lang/lang_values.h <(src_loc)/main/main_account.cpp <(src_loc)/main/main_account.h <(src_loc)/media/audio/media_audio.cpp