From 2334ba1fe15ce5983d8c901a0a0265e1386241d4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Apr 2017 11:45:58 +0300 Subject: [PATCH] Use QString + Lang::Tag() instead of Lang::String. --- Telegram/SourceFiles/boxes/about_box.cpp | 4 +- Telegram/SourceFiles/boxes/language_box.cpp | 14 +- .../SourceFiles/codegen/lang/generator.cpp | 163 ++++--- Telegram/SourceFiles/historywidget.cpp | 2 +- Telegram/SourceFiles/intro/introwidget.cpp | 4 +- .../SourceFiles/lang/lang_file_parser.cpp | 414 ++++++++++-------- Telegram/SourceFiles/lang/lang_file_parser.h | 44 +- Telegram/SourceFiles/lang/lang_keys.cpp | 137 +----- Telegram/SourceFiles/lang/lang_keys.h | 87 +--- Telegram/SourceFiles/lang/lang_tag.cpp | 81 ++++ Telegram/SourceFiles/lang/lang_tag.h | 28 ++ Telegram/SourceFiles/lang/lang_translator.cpp | 55 +++ Telegram/SourceFiles/lang/lang_translator.h | 31 ++ Telegram/SourceFiles/mainwidget.cpp | 37 +- Telegram/SourceFiles/messenger.cpp | 7 +- Telegram/SourceFiles/messenger.h | 6 +- .../platform/win/main_window_win.cpp | 2 +- .../settings/settings_general_widget.cpp | 4 +- .../SourceFiles/settings/settings_widget.cpp | 4 + Telegram/gyp/telegram_sources.txt | 8 +- 20 files changed, 598 insertions(+), 534 deletions(-) create mode 100644 Telegram/SourceFiles/lang/lang_tag.cpp create mode 100644 Telegram/SourceFiles/lang/lang_tag.h create mode 100644 Telegram/SourceFiles/lang/lang_translator.cpp create mode 100644 Telegram/SourceFiles/lang/lang_translator.h diff --git a/Telegram/SourceFiles/boxes/about_box.cpp b/Telegram/SourceFiles/boxes/about_box.cpp index 663037f3a..d9908e645 100644 --- a/Telegram/SourceFiles/boxes/about_box.cpp +++ b/Telegram/SourceFiles/boxes/about_box.cpp @@ -88,11 +88,11 @@ void AboutBox::keyPressEvent(QKeyEvent *e) { } QString telegramFaqLink() { - QString result = qsl("https://telegram.org/faq"); + auto result = qsl("https://telegram.org/faq"); if (cLang() > languageDefault && cLang() < languageCount) { const char *code = LanguageCodes[cLang()].c_str(); if (qstr("de") == code || qstr("es") == code || qstr("it") == code || qstr("ko") == code) { - result += qsl("/") + code; + result += '/' + code; } else if (qstr("pt_BR") == code) { result += qsl("/br"); } diff --git a/Telegram/SourceFiles/boxes/language_box.cpp b/Telegram/SourceFiles/boxes/language_box.cpp index ee72a10f6..9c76cb066 100644 --- a/Telegram/SourceFiles/boxes/language_box.cpp +++ b/Telegram/SourceFiles/boxes/language_box.cpp @@ -46,9 +46,9 @@ void LanguageBox::prepare() { y += _langs.back()->heightNoMargins() + st::boxOptionListSkip; } for (auto i = 0; i != languageCount; ++i) { - LangLoaderResult result; + Lang::FileParser::Result result; if (i) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), { lng_language_name }); + Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), { lng_language_name }); result = loader.found(); } else { result.insert(lng_language_name, langOriginal(lng_language_name)); @@ -66,12 +66,12 @@ void LanguageBox::prepare() { void LanguageBox::mousePressEvent(QMouseEvent *e) { if ((e->modifiers() & Qt::CTRL) && (e->modifiers() & Qt::ALT) && (e->modifiers() & Qt::SHIFT)) { for (int32 i = 1; i < languageCount; ++i) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), { lngkeys_cnt }); + Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), { kLangKeysCount }); if (!loader.errors().isEmpty()) { Ui::show(Box(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" error :(\n\nError: ") + loader.errors())); return; } else if (!loader.warnings().isEmpty()) { - QString warn = loader.warnings(); + auto warn = loader.warnings(); if (warn.size() > 256) warn = warn.mid(0, 253) + qsl("..."); Ui::show(Box(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" warnings :(\n\nWarnings: ") + warn)); return; @@ -88,12 +88,12 @@ void LanguageBox::languageChanged(int languageId) { return; } - LangLoaderResult result; + Lang::FileParser::Result result; if (languageId > 0) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[languageId].c_str() + qsl(".strings"), { lng_sure_save_language, lng_cancel, lng_box_ok }); + Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[languageId].c_str() + qsl(".strings"), { lng_sure_save_language, lng_cancel, lng_box_ok }); result = loader.found(); } else if (languageId == languageTest) { - LangLoaderPlain loader(cLangFile(), { lng_sure_save_language, lng_cancel, lng_box_ok }); + Lang::FileParser loader(cLangFile(), { lng_sure_save_language, lng_cancel, lng_box_ok }); result = loader.found(); } auto text = result.value(lng_sure_save_language, langOriginal(lng_sure_save_language)), diff --git a/Telegram/SourceFiles/codegen/lang/generator.cpp b/Telegram/SourceFiles/codegen/lang/generator.cpp index 3dc0426ba..f34e9a56b 100644 --- a/Telegram/SourceFiles/codegen/lang/generator.cpp +++ b/Telegram/SourceFiles/codegen/lang/generator.cpp @@ -118,71 +118,71 @@ Generator::Generator(const Langpack &langpack, const QString &destBasePath, cons bool Generator::writeHeader() { header_ = std::make_unique(basePath_ + ".h", project_); - header_->stream() << "\ -class LangString : public QString {\n\ -public:\n\ - LangString() = default;\n\ - LangString(const QString &str) : QString(str) {\n\ - }\n\ - LangString &operator=(const QString &str) {\n\ - QString::operator=(str);\n\ - return *this;\n\ - }\n\ + header_->include("lang/lang_tag.h").newline().pushNamespace("Lang").stream() << "\ \n\ - LangString tag(ushort tag, const QString &replacement);\n\ -\n\ -};\n\ -\n\ -LangString langCounted(ushort key0, ushort tag, float64 value);\n\ +constexpr auto kTagsCount = " << langpack_.tags.size() << ";\n\ +constexpr auto kTagsPluralVariants = " << kMaxPluralVariants << ";\n\ \n"; + + header_->popNamespace().newline(); auto index = 0; for (auto &tag : langpack_.tags) { header_->stream() << "enum lngtag_" << tag.tag << " { lt_" << tag.tag << " = " << index++ << " };\n"; } header_->stream() << "\ \n\ -constexpr auto lngtags_cnt = " << langpack_.tags.size() << ";\n\ -constexpr auto lngtags_max_counted_values = " << kMaxPluralVariants << ";\n\ -\n\ enum LangKey {\n"; for (auto &entry : langpack_.entries) { header_->stream() << "\t" << getFullKey(entry) << ",\n"; } header_->stream() << "\ \n\ - lngkeys_cnt,\n\ + kLangKeysCount,\n\ };\n\ \n\ -LangString lang(LangKey key);\n\ +QString lang(LangKey key);\n\ \n\ -LangString langOriginal(LangKey key);\n\ +QString langOriginal(LangKey key);\n\ \n"; for (auto &entry : langpack_.entries) { if (!entry.tags.empty()) { auto &key = entry.key; auto params = QStringList(); - auto invokations = QStringList(); + auto applyTags = QStringList(); for (auto &tagData : entry.tags) { auto &tag = tagData.tag; auto isPlural = isTagPlural(key, tag); params.push_back("lngtag_" + tag + ", " + (isPlural ? "float64 " : "const QString &") + tag + "__val"); - invokations.push_back("tag(lt_" + tag + ", " + (isPlural ? ("langCounted(" + key + "__" + tag + "0, lt_" + tag + ", " + tag + "__val)") : (tag + "__val")) + ")"); + applyTags.push_back("\tresult = Lang::Tag(result, lt_" + tag + ", " + (isPlural ? ("Lang::Plural(" + key + "__" + tag + "0, lt_" + tag + ", " + tag + "__val)") : (tag + "__val")) + ");"); } header_->stream() << "\ -inline LangString " << entry.key << "(" << params.join(QString(", ")) << ") {\n\ - return lang(" << entry.key << "__tagged)." << invokations.join('.') << ";\n\ +inline QString " << entry.key << "(" << params.join(QString(", ")) << ") {\n\ + auto result = lang(" << entry.key << "__tagged);\n\ +" << applyTags.join('\n') << ";\n\ + return result;\n\ }\n\ \n"; } } + + header_->pushNamespace("Lang").stream() << "\ +\n\ +const char *GetKeyName(LangKey key);\n\ +ushort GetTagIndex(QLatin1String tag);\n\ +LangKey GetKeyIndex(QLatin1String key);\n\ +LangKey GetSubkeyIndex(LangKey key, ushort tag, ushort index);\n\ +bool IsTagReplaced(LangKey key, ushort tag);\n\ +void FeedKeyValue(LangKey key, const QString &value);\n\ +\n"; + return header_->finalize(); } bool Generator::writeSource() { source_ = std::make_unique(basePath_ + ".cpp", project_); - source_->include("lang/lang_keys.h").pushNamespace().stream() << "\ -const char *_langKeyNames[lngkeys_cnt] = {\n\ + source_->include("lang/lang_keys.h").pushNamespace("Lang").pushNamespace().stream() << "\ +const char *KeyNames[kLangKeysCount] = {\n\ \n"; for (auto &entry : langpack_.entries) { source_->stream() << "\"" << entry.key << "\",\n"; @@ -191,15 +191,15 @@ const char *_langKeyNames[lngkeys_cnt] = {\n\ \n\ };\n\ \n\ -LangString _langValues[lngkeys_cnt], _langValuesOriginal[lngkeys_cnt];\n\ +QString Values[kLangKeysCount], OriginalValues[kLangKeysCount];\n\ \n\ void set(LangKey key, const QString &val) {\n\ - _langValues[key] = val;\n\ + Values[key] = val;\n\ }\n\ \n\ -class LangInit {\n\ +class Initializer {\n\ public:\n\ - LangInit() {\n"; + Initializer() {\n"; for (auto &entry : langpack_.entries) { source_->stream() << "\t\tset(" << getFullKey(entry) << ", QString::fromUtf8(" << stringToEncodedString(entry.value) << "));\n"; } @@ -208,24 +208,16 @@ public:\n\ \n\ };\n\ \n\ -LangInit _langInit;\n\ +Initializer Instance;\n\ \n"; source_->popNamespace().stream() << "\ \n\ -LangString lang(LangKey key) {\n\ - return (key < 0 || key > lngkeys_cnt) ? QString() : _langValues[key];\n\ +const char *GetKeyName(LangKey key) {\n\ + return (key < 0 || key >= kLangKeysCount) ? \"\" : KeyNames[key];\n\ }\n\ \n\ -LangString langOriginal(LangKey key) {\n\ - return (key < 0 || key > lngkeys_cnt || _langValuesOriginal[key] == qsl(\"{}\")) ? QString() : (_langValuesOriginal[key].isEmpty() ? _langValues[key] : _langValuesOriginal[key]);\n\ -}\n\ -\n\ -const char *langKeyName(LangKey key) {\n\ - return (key < 0 || key > lngkeys_cnt) ? \"\" : _langKeyNames[key];\n\ -}\n\ -\n\ -ushort LangLoader::tagIndex(QLatin1String tag) const {\n\ +ushort GetTagIndex(QLatin1String tag) {\n\ auto size = tag.size();\n\ auto data = tag.data();\n"; @@ -236,12 +228,12 @@ ushort LangLoader::tagIndex(QLatin1String tag) const {\n\ writeSetSearch(tagsSet, [](const QString &tag) { return "lt_" + tag; - }, "lngtags_cnt"); + }, "kTagsCount"); source_->stream() << "\ }\n\ \n\ -LangKey LangLoader::keyIndex(QLatin1String key) const {\n\ +LangKey GetKeyIndex(QLatin1String key) {\n\ auto size = key.size();\n\ auto data = key.data();\n"; @@ -262,12 +254,41 @@ LangKey LangLoader::keyIndex(QLatin1String key) const {\n\ writeSetSearch(keysSet, [&taggedKeys](const QString &key) { auto it = taggedKeys.find(key); return (it != taggedKeys.end()) ? it->second : key; - }, "lngkeys_cnt"); + }, "kLangKeysCount"); source_->stream() << "\ }\n\ \n\ -bool LangLoader::tagReplaced(LangKey key, ushort tag) const {\n\ +LangKey GetSubkeyIndex(LangKey key, ushort tag, ushort index) {\n\ + if (index >= kTagsPluralVariants) return kLangKeysCount;\n\ +\n\ + switch (key) {\n"; + + for (auto &entry : langpack_.entries) { + auto cases = QString(); + for (auto &tag : entry.tags) { + if (isTagPlural(entry.key, tag.tag)) { + cases += "\t\t\tcase lt_" + tag.tag + ": return LangKey(" + entry.key + "__" + tag.tag + "0 + index);\n"; + } + } + if (cases.isEmpty()) { + continue; + } + source_->stream() << "\ + case " << entry.key << "__tagged: {\n\ + switch (tag) {\n\ +" << cases << "\ + }\n\ + } break;\n"; + } + + source_->stream() << "\ + }\n\ +\n\ + return kLangKeysCount;\n\ +}\n\ +\n\ +bool IsTagReplaced(LangKey key, ushort tag) {\n\ switch (key) {\n"; for (auto &entry : langpack_.entries) { @@ -293,45 +314,23 @@ bool LangLoader::tagReplaced(LangKey key, ushort tag) const {\n\ return false;\n\ }\n\ \n\ -LangKey LangLoader::subkeyIndex(LangKey key, ushort tag, ushort index) const {\n\ - if (index >= lngtags_max_counted_values) return lngkeys_cnt;\n\ -\n\ - switch (key) {\n"; - - for (auto &entry : langpack_.entries) { - auto cases = QString(); - for (auto &tag : entry.tags) { - if (isTagPlural(entry.key, tag.tag)) { - cases += "\t\t\tcase lt_" + tag.tag + ": return LangKey(" + entry.key + "__" + tag.tag + "0 + index);\n"; - } - } - if (cases.isEmpty()) { - continue; - } - source_->stream() << "\ - case " << entry.key << "__tagged: {\n\ - switch (tag) {\n\ -" << cases << "\ - }\n\ - } break;\n"; - } - - source_->stream() << "\ +void FeedKeyValue(LangKey key, const QString &value) {\n\ + Expects(key >= 0 && key < kLangKeysCount);\n\ + if (OriginalValues[key].isEmpty()) {\n\ + OriginalValues[key] = Values[key].isEmpty() ? qsl(\"{}\") : Values[key];\n\ }\n\ + Values[key] = value;\n\ +}\n\ +\n"; + + source_->popNamespace().stream() << "\ \n\ - return lngkeys_cnt;\n\ +QString lang(LangKey key) {\n\ + return (key < 0 || key >= kLangKeysCount) ? QString() : Lang::Values[key];\n\ }\n\ \n\ -bool LangLoader::feedKeyValue(LangKey key, const QString &value) {\n\ - if (key < lngkeys_cnt) {\n\ - _found[key] = 1;\n\ - if (_langValuesOriginal[key].isEmpty()) {\n\ - _langValuesOriginal[key] = _langValues[key].isEmpty() ? qsl(\"{}\") : _langValues[key];\n\ - }\n\ - _langValues[key] = value;\n\ - return true;\n\ - }\n\ - return false;\n\ +QString langOriginal(LangKey key) {\n\ + return (key < 0 || key >= kLangKeysCount || Lang::OriginalValues[key] == qsl(\"{}\")) ? QString() : (Lang::OriginalValues[key].isEmpty() ? Lang::Values[key] : Lang::OriginalValues[key]);\n\ }\n"; return source_->finalize(); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index dd37764e0..eb3d1db32 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -353,7 +353,7 @@ bool HistoryHider::offerPeer(PeerId peer) { return false; } _offered = App::peer(peer); - LangString phrase; + auto phrase = QString(); QString recipient = _offered->isUser() ? _offered->name : '\xAB' + _offered->name + '\xBB'; if (_sharedContact) { phrase = lng_forward_share_contact(lt_recipient, recipient); diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index 84362407d..48639864a 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -64,8 +64,8 @@ Widget::Widget(QWidget *parent) : TWidget(parent) if (cLang() == languageDefault) { auto systemLangId = Sandbox::LangSystem(); if (systemLangId != languageDefault) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[systemLangId].c_str() + qsl(".strings"), { lng_switch_to_this }); - QString text = loader.found().value(lng_switch_to_this); + Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[systemLangId].c_str() + qsl(".strings"), { lng_switch_to_this }); + auto text = loader.found().value(lng_switch_to_this); if (!text.isEmpty()) { _changeLanguage.create(this, object_ptr(this, text), st::introCoverDuration); _changeLanguage->entity()->setClickedCallback([this, systemLangId] { changeLanguage(systemLangId); }); diff --git a/Telegram/SourceFiles/lang/lang_file_parser.cpp b/Telegram/SourceFiles/lang/lang_file_parser.cpp index 437136c55..ca8fde6ad 100644 --- a/Telegram/SourceFiles/lang/lang_file_parser.cpp +++ b/Telegram/SourceFiles/lang/lang_file_parser.cpp @@ -22,192 +22,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "base/parse_helper.h" -bool LangLoaderPlain::readKeyValue(const char *&from, const char *end) { - using base::parse::skipWhitespaces; - if (!skipWhitespaces(from, end)) return false; +namespace Lang { - if (*from != '"') throw Exception(QString("Expected quote before key name!")); - ++from; - const char *nameStart = from; - while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { - ++from; - } - - auto varName = QLatin1String(nameStart, from - nameStart); - - if (from == end || *from != '"') throw Exception(QString("Expected quote after key name '%1'!").arg(varName)); - ++from; - - if (!skipWhitespaces(from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); - if (*from != '=') throw Exception(QString("'=' expected in key '%1'!").arg(varName)); - - if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); - if (*from != '"') throw Exception(QString("Expected string after '=' in key '%1'!").arg(varName)); - - LangKey varKey = keyIndex(varName); - bool feedingValue = request.empty(); - if (feedingValue) { - if (varKey == lngkeys_cnt) { - warning(QString("Unknown key '%1'!").arg(varName)); - } - } else if (!readingAll && request.find(varKey) == request.end()) { - varKey = lngkeys_cnt; - } - bool readingValue = (varKey != lngkeys_cnt); - - QByteArray varValue; - QMap tagsUsed; - const char *start = ++from; - while (from < end && *from != '"') { - if (*from == '\n') { - throw Exception(QString("Unexpected end of string in key '%1'!").arg(varName)); - } - if (*from == '\\') { - if (from + 1 >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); - if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{') { - if (readingValue && from > start) varValue.append(start, from - start); - start = ++from; - } else if (*(from + 1) == 'n') { - if (readingValue) { - if (from > start) varValue.append(start, int(from - start)); - varValue.append('\n'); - } - start = (++from) + 1; - } - } else if (readingValue && *from == '{') { - if (from > start) varValue.append(start, int(from - start)); - - const char *tagStart = ++from; - while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { - ++from; - } - if (from == tagStart) { - readingValue = false; - warning(QString("Expected tag name in key '%1'!").arg(varName)); - continue; - } - auto tagName = QLatin1String(tagStart, int(from - tagStart)); - - if (from == end || (*from != '}' && *from != ':')) throw Exception(QString("Expected '}' or ':' after tag name in key '%1'!").arg(varName)); - - ushort index = tagIndex(tagName); - if (index == lngtags_cnt) { - readingValue = false; - warning(QString("Tag '%1' not found in key '%2', not using value.").arg(tagName).arg(varName)); - continue; - } - - if (!tagReplaced(varKey, index)) { - readingValue = false; - warning(QString("Unexpected tag '%1' in key '%2', not using value.").arg(tagName).arg(varName)); - continue; - } - if (tagsUsed.contains(index)) throw Exception(QString("Tag '%1' double used in key '%2'!").arg(tagName).arg(varName)); - tagsUsed.insert(index, true); - - QString tagReplacer(4, TextCommand); - tagReplacer[1] = TextCommandLangTag; - tagReplacer[2] = QChar(0x0020 + index); - varValue.append(tagReplacer.toUtf8()); - - if (*from == ':') { - start = ++from; - - QByteArray subvarValue; - bool foundtag = false; - int countedIndex = 0; - while (from < end && *from != '"' && *from != '}') { - if (*from == '|') { - if (from > start) subvarValue.append(start, int(from - start)); - if (countedIndex >= lngtags_max_counted_values) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); - LangKey subkey = subkeyIndex(varKey, index, countedIndex++); - if (subkey == lngkeys_cnt) { - readingValue = false; - warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(tagName).arg(varName)); - break; - } else { - if (feedingValue) { - if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(tagName).arg(varName)); - } else { - foundKeyValue(subkey); - } - } - subvarValue = QByteArray(); - foundtag = false; - start = from + 1; - } - if (*from == '\n') { - throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); - } - if (*from == '\\') { - if (from + 1 >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); - if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{' || *(from + 1) == '#') { - if (from > start) subvarValue.append(start, int(from - start)); - start = ++from; - } else if (*(from + 1) == 'n') { - if (from > start) subvarValue.append(start, int(from - start)); - - subvarValue.append('\n'); - - start = (++from) + 1; - } - } else if (*from == '{') { - throw Exception(QString("Unexpected tag inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); - } else if (*from == '#') { - if (foundtag) throw Exception(QString("Replacement '#' double used inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); - foundtag = true; - if (from > start) subvarValue.append(start, int(from - start)); - subvarValue.append(tagReplacer.toUtf8()); - start = from + 1; - } - ++from; - } - if (!readingValue) continue; - if (from >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); - if (*from == '"') throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); - - if (from > start) subvarValue.append(start, int(from - start)); - if (countedIndex >= lngtags_max_counted_values) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); - - LangKey subkey = subkeyIndex(varKey, index, countedIndex++); - if (subkey == lngkeys_cnt) { - readingValue = false; - warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(tagName).arg(varName)); - break; - } else { - if (feedingValue) { - if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(tagName).arg(varName)); - } else { - foundKeyValue(subkey); - } - } - } - start = from + 1; - } - ++from; - } - if (from >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); - if (readingValue && from > start) varValue.append(start, from - start); - - if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); - if (*from != ';') throw Exception(QString("';' expected after \"value\" in key '%1'!").arg(varName)); - - skipWhitespaces(++from, end); - - if (readingValue) { - if (feedingValue) { - if (!feedKeyValue(varKey, QString::fromUtf8(varValue))) throw Exception(QString("Could not write value in key '%1'!").arg(varName)); - } else { - foundKeyValue(varKey); - result.insert(varKey, QString::fromUtf8(varValue)); - } - } - - return true; -} - -LangLoaderPlain::LangLoaderPlain(const QString &file, const std::set &request) : file(file), request(request), readingAll(request.find(lngkeys_cnt) != request.end()) { - QFile f(file); +FileParser::FileParser(const QString &file, const std::set &request) +: _filePath(file) +, _request(request) +, _readingAll(request.find(kLangKeysCount) != request.end()) { + QFile f(_filePath); if (!f.open(QIODevice::ReadOnly)) { error(qsl("Could not open input file!")); return; @@ -271,3 +92,226 @@ LangLoaderPlain::LangLoaderPlain(const QString &file, const std::set &r return; } } + +const QString &FileParser::errors() const { + if (_errors.isEmpty() && !_errorsList.isEmpty()) { + _errors = _errorsList.join('\n'); + } + return _errors; +} + +const QString &FileParser::warnings() const { + if (!_checked) { + for (auto i = 0; i < kLangKeysCount; ++i) { + if (!_found[i]) { + _warningsList.push_back(qsl("No value found for key '%1'").arg(GetKeyName(LangKey(i)))); + } + } + _checked = true; + } + if (_warnings.isEmpty() && !_warningsList.isEmpty()) { + _warnings = _warningsList.join('\n'); + } + return _warnings; +} + +void FileParser::foundKeyValue(LangKey key) { + if (key < kLangKeysCount) { + _found[key] = true; + } +} + +bool FileParser::readKeyValue(const char *&from, const char *end) { + using base::parse::skipWhitespaces; + if (!skipWhitespaces(from, end)) return false; + + if (*from != '"') throw Exception(QString("Expected quote before key name!")); + ++from; + const char *nameStart = from; + while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { + ++from; + } + + auto varName = QLatin1String(nameStart, from - nameStart); + + if (from == end || *from != '"') throw Exception(QString("Expected quote after key name '%1'!").arg(varName)); + ++from; + + if (!skipWhitespaces(from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); + if (*from != '=') throw Exception(QString("'=' expected in key '%1'!").arg(varName)); + + if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); + if (*from != '"') throw Exception(QString("Expected string after '=' in key '%1'!").arg(varName)); + + auto varKey = GetKeyIndex(varName); + bool feedingValue = _request.empty(); + if (feedingValue) { + if (varKey == kLangKeysCount) { + warning(QString("Unknown key '%1'!").arg(varName)); + } + } else if (!_readingAll && _request.find(varKey) == _request.end()) { + varKey = kLangKeysCount; + } + bool readingValue = (varKey != kLangKeysCount); + + QByteArray varValue; + QMap tagsUsed; + const char *start = ++from; + while (from < end && *from != '"') { + if (*from == '\n') { + throw Exception(QString("Unexpected end of string in key '%1'!").arg(varName)); + } + if (*from == '\\') { + if (from + 1 >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); + if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{') { + if (readingValue && from > start) varValue.append(start, from - start); + start = ++from; + } else if (*(from + 1) == 'n') { + if (readingValue) { + if (from > start) varValue.append(start, int(from - start)); + varValue.append('\n'); + } + start = (++from) + 1; + } + } else if (readingValue && *from == '{') { + if (from > start) varValue.append(start, int(from - start)); + + const char *tagStart = ++from; + while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { + ++from; + } + if (from == tagStart) { + readingValue = false; + warning(QString("Expected tag name in key '%1'!").arg(varName)); + continue; + } + auto tagName = QLatin1String(tagStart, int(from - tagStart)); + + if (from == end || (*from != '}' && *from != ':')) throw Exception(QString("Expected '}' or ':' after tag name in key '%1'!").arg(varName)); + + auto index = GetTagIndex(tagName); + if (index == kTagsCount) { + readingValue = false; + warning(QString("Tag '%1' not found in key '%2', not using value.").arg(tagName).arg(varName)); + continue; + } + + if (!IsTagReplaced(varKey, index)) { + readingValue = false; + warning(QString("Unexpected tag '%1' in key '%2', not using value.").arg(tagName).arg(varName)); + continue; + } + if (tagsUsed.contains(index)) throw Exception(QString("Tag '%1' double used in key '%2'!").arg(tagName).arg(varName)); + tagsUsed.insert(index, true); + + QString tagReplacer(4, TextCommand); + tagReplacer[1] = TextCommandLangTag; + tagReplacer[2] = QChar(0x0020 + index); + varValue.append(tagReplacer.toUtf8()); + + if (*from == ':') { + start = ++from; + + QByteArray subvarValue; + bool foundtag = false; + int countedIndex = 0; + while (from < end && *from != '"' && *from != '}') { + if (*from == '|') { + if (from > start) subvarValue.append(start, int(from - start)); + if (countedIndex >= kTagsPluralVariants) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); + auto subkey = GetSubkeyIndex(varKey, index, countedIndex++); + if (subkey == kLangKeysCount) { + readingValue = false; + warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(tagName).arg(varName)); + break; + } else { + if (feedingValue) { + if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(tagName).arg(varName)); + } else { + foundKeyValue(subkey); + } + } + subvarValue = QByteArray(); + foundtag = false; + start = from + 1; + } + if (*from == '\n') { + throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); + } + if (*from == '\\') { + if (from + 1 >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); + if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{' || *(from + 1) == '#') { + if (from > start) subvarValue.append(start, int(from - start)); + start = ++from; + } else if (*(from + 1) == 'n') { + if (from > start) subvarValue.append(start, int(from - start)); + + subvarValue.append('\n'); + + start = (++from) + 1; + } + } else if (*from == '{') { + throw Exception(QString("Unexpected tag inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); + } else if (*from == '#') { + if (foundtag) throw Exception(QString("Replacement '#' double used inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); + foundtag = true; + if (from > start) subvarValue.append(start, int(from - start)); + subvarValue.append(tagReplacer.toUtf8()); + start = from + 1; + } + ++from; + } + if (!readingValue) continue; + if (from >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); + if (*from == '"') throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); + + if (from > start) subvarValue.append(start, int(from - start)); + if (countedIndex >= kTagsPluralVariants) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName)); + + auto subkey = GetSubkeyIndex(varKey, index, countedIndex++); + if (subkey == kLangKeysCount) { + readingValue = false; + warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(tagName).arg(varName)); + break; + } else { + if (feedingValue) { + if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(tagName).arg(varName)); + } else { + foundKeyValue(subkey); + } + } + } + start = from + 1; + } + ++from; + } + if (from >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); + if (readingValue && from > start) varValue.append(start, from - start); + + if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); + if (*from != ';') throw Exception(QString("';' expected after \"value\" in key '%1'!").arg(varName)); + + skipWhitespaces(++from, end); + + if (readingValue) { + if (feedingValue) { + if (!feedKeyValue(varKey, QString::fromUtf8(varValue))) throw Exception(QString("Could not write value in key '%1'!").arg(varName)); + } else { + foundKeyValue(varKey); + _result.insert(varKey, QString::fromUtf8(varValue)); + } + } + + return true; +} + +bool FileParser::feedKeyValue(LangKey key, const QString &value) { + if (key < kLangKeysCount) { + _found[key] = 1; + FeedKeyValue(key, value); + return true; + } + return false; +} + +} // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_file_parser.h b/Telegram/SourceFiles/lang/lang_file_parser.h index 027d7f96a..b7bc2b561 100644 --- a/Telegram/SourceFiles/lang/lang_file_parser.h +++ b/Telegram/SourceFiles/lang/lang_file_parser.h @@ -22,22 +22,44 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "lang/lang_keys.h" -using LangLoaderResult = QMap; -class LangLoaderPlain : public LangLoader { -public: - LangLoaderPlain(const QString &file, const std::set &request = std::set()); +namespace Lang { - LangLoaderResult found() const { - return result; +class FileParser { +public: + using Result = QMap; + + FileParser(const QString &file, const std::set &request = std::set()); + + const QString &errors() const; + const QString &warnings() const; + + Result found() const { + return _result; } -protected: - QString file; - std::set request; +private: + bool feedKeyValue(LangKey key, const QString &value); + void foundKeyValue(LangKey key); + void error(const QString &text) { + _errorsList.push_back(text); + } + void warning(const QString &text) { + _warningsList.push_back(text); + } bool readKeyValue(const char *&from, const char *end); - bool readingAll; - LangLoaderResult result; + mutable QStringList _errorsList, _warningsList; + mutable QString _errors, _warnings; + mutable bool _checked = false; + std::array _found = { { false } }; + + QString _filePath; + std::set _request; + + bool _readingAll = false; + Result _result; }; + +} // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_keys.cpp b/Telegram/SourceFiles/lang/lang_keys.cpp index ba530a73b..7e73d6510 100644 --- a/Telegram/SourceFiles/lang/lang_keys.cpp +++ b/Telegram/SourceFiles/lang/lang_keys.cpp @@ -22,58 +22,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "lang/lang_file_parser.h" -LangString LangString::tag(ushort tag, const QString &replacement) { - for (const QChar *s = constData(), *ch = s, *e = ch + size(); ch != e;) { - if (*ch == TextCommand) { - if (ch + 3 < e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) { - if ((ch + 2)->unicode() == 0x0020 + tag) { - LangString result; - result.reserve(size() + replacement.size() - 4); - if (ch > s) result.append(midRef(0, ch - s)); - result.append(replacement); - if (ch + 4 < e) result.append(midRef(ch - s + 4)); - return result; - } else { - ch += 4; - } - } else { - const QChar *next = textSkipCommand(ch, e); - if (next == ch) { - ++ch; - } else { - ch = next; - } - } - } else { - ++ch; - } - } - return *this; -} - -LangString langCounted(ushort key0, ushort tag, float64 value) { // current lang dependent - int v = qFloor(value); - QString sv; - ushort key = key0; - if (v != qCeil(value)) { - key += 2; - sv = QString::number(value); - } else { - if (v == 1) { - key += 1; - } else if (v) { - key += 2; - } - sv = QString::number(v); - } - while (key > key0) { - LangString v = lang(LangKey(key)); - if (!v.isEmpty()) return v.tag(tag, sv); - --key; - } - return lang(LangKey(key0)).tag(tag, sv); -} - //#define NEW_VER_TAG lt_link //#define NEW_VER_TAG_VALUE "https://telegram.org/blog/desktop-1-0" @@ -85,84 +33,21 @@ QString langNewVersionText() { #endif // NEW_VER_TAG } -#ifdef NEW_VER_TAG -#define NEW_VER_KEY lng_new_version_text__tagged -#define NEW_VER_POSTFIX .tag(NEW_VER_TAG, QString::fromUtf8(NEW_VER_TAG_VALUE)) -#else // NEW_VER_TAG -#define NEW_VER_KEY lng_new_version_text -#define NEW_VER_POSTFIX -#endif // NEW_VER_TAG - -QString langNewVersionTextForLang(int langId) { - LangLoaderResult result; - if (langId) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[langId].c_str() + qsl(".strings"), { lng_language_name, NEW_VER_KEY }); - result = loader.found(); - } else { - result.insert(lng_language_name, langOriginal(lng_language_name)); - result.insert(NEW_VER_KEY, langOriginal(NEW_VER_KEY)); - } - return result.value(lng_language_name, LanguageCodes[langId].c_str() + qsl(" language")) + qsl(":\n\n") + LangString(result.value(NEW_VER_KEY, qsl("--none--")))NEW_VER_POSTFIX; -} - -#undef NEW_VER_POSTFIX -#undef NEW_VER_KEY - #undef NEW_VER_TAG_VALUE #undef NEW_VER_TAG -const QString &LangLoader::errors() const { - if (_errors.isEmpty() && !_err.isEmpty()) { - _errors = _err.join('\n'); - } - return _errors; -} - -const QString &LangLoader::warnings() const { - if (!_checked) { - for (int32 i = 0; i < lngkeys_cnt; ++i) { - if (!_found[i]) { - _warn.push_back(qsl("No value found for key '%1'").arg(langKeyName(LangKey(i)))); +bool langFirstNameGoesSecond() { + auto fullname = lang(lng_full_name__tagged); + for (auto begin = fullname.constData(), ch = begin, end = ch + fullname.size(); ch != end;) { + if (*ch == TextCommand) { + if (ch + 3 < end && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) { + if ((ch + 2)->unicode() == 0x0020 + lt_last_name) { + return true; + } else if ((ch + 2)->unicode() == 0x0020 + lt_first_name) { + break; + } } } - _checked = true; } - if (_warnings.isEmpty() && !_warn.isEmpty()) { - _warnings = _warn.join('\n'); - } - return _warnings; -} - -void LangLoader::foundKeyValue(LangKey key) { - if (key < lngkeys_cnt) { - _found[key] = true; - } -} - -QString Translator::translate(const char *context, const char *sourceText, const char *disambiguation, int n) const { - if (qstr("QMenuBar") == context) { - if (qstr("Services") == sourceText) return lang(lng_mac_menu_services); - if (qstr("Hide %1") == sourceText) return lng_mac_menu_hide_telegram(lt_telegram, qsl("%1")); - if (qstr("Hide Others") == sourceText) return lang(lng_mac_menu_hide_others); - if (qstr("Show All") == sourceText) return lang(lng_mac_menu_show_all); - if (qstr("Preferences...") == sourceText) return lang(lng_mac_menu_preferences); - if (qstr("Quit %1") == sourceText) return lng_mac_menu_quit_telegram(lt_telegram, qsl("%1")); - if (qstr("About %1") == sourceText) return lng_mac_menu_about_telegram(lt_telegram, qsl("%1")); - return QString(); - } - if (qstr("QWidgetTextControl") == context || qstr("QLineEdit") == context) { - if (qstr("&Undo") == sourceText) return lang((cPlatform() == dbipWindows) ? lng_wnd_menu_undo : ((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_mac_menu_undo : lng_linux_menu_undo)); - if (qstr("&Redo") == sourceText) return lang((cPlatform() == dbipWindows) ? lng_wnd_menu_redo : ((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_mac_menu_redo : lng_linux_menu_redo)); - if (qstr("Cu&t") == sourceText) return lang(lng_mac_menu_cut); - if (qstr("&Copy") == sourceText) return lang(lng_mac_menu_copy); - if (qstr("&Paste") == sourceText) return lang(lng_mac_menu_paste); - if (qstr("Delete") == sourceText) return lang(lng_mac_menu_delete); - if (qstr("Select All") == sourceText) return lang(lng_mac_menu_select_all); - return QString(); - } - if (qstr("QUnicodeControlCharacterMenu") == context) { - if (qstr("Insert Unicode control character") == sourceText) return lang(lng_menu_insert_unicode); - return QString(); - } - return QString(); + return false; } diff --git a/Telegram/SourceFiles/lang/lang_keys.h b/Telegram/SourceFiles/lang/lang_keys.h index f935b93b6..fafeae4e1 100644 --- a/Telegram/SourceFiles/lang/lang_keys.h +++ b/Telegram/SourceFiles/lang/lang_keys.h @@ -33,10 +33,8 @@ constexpr const str_const LanguageCodes[] = { }; constexpr const int languageTest = -1, languageDefault = 0, languageCount = base::array_size(LanguageCodes); -const char *langKeyName(LangKey key); - template -inline LangString langDateMaybeWithYear(QDate date, WithYear withYear, WithoutYear withoutYear) { +inline QString langDateMaybeWithYear(QDate date, WithYear withYear, WithoutYear withoutYear) { auto month = date.month(); if (month <= 0 || month > 12) { return qsl("MONTH_ERR"); @@ -63,7 +61,7 @@ inline LangString langDateMaybeWithYear(QDate date, WithYear withYear, WithoutYe return withoutYear(month, year); } -inline LangString langDayOfMonth(const QDate &date) { +inline QString langDayOfMonth(const QDate &date) { auto day = date.day(); return langDateMaybeWithYear(date, [day](int month, int year) { return lng_month_day_year(lt_month, lang(LangKey(lng_month1_small + month - 1)), lt_day, QString::number(day), lt_year, QString::number(year)); @@ -72,7 +70,7 @@ inline LangString langDayOfMonth(const QDate &date) { }); } -inline LangString langDayOfMonthFull(const QDate &date) { +inline QString langDayOfMonthFull(const QDate &date) { auto day = date.day(); return langDateMaybeWithYear(date, [day](int month, int year) { return lng_month_day_year(lt_month, lang(LangKey(lng_month1 + month - 1)), lt_day, QString::number(day), lt_year, QString::number(year)); @@ -81,11 +79,11 @@ inline LangString langDayOfMonthFull(const QDate &date) { }); } -inline LangString langMonthOfYear(int month, int year) { +inline QString langMonthOfYear(int month, int year) { return (month > 0 && month <= 12) ? lng_month_year(lt_month, lang(LangKey(lng_month1_small + month - 1)), lt_year, QString::number(year)) : qsl("MONTH_ERR"); } -inline LangString langMonth(const QDate &date) { +inline QString langMonth(const QDate &date) { return langDateMaybeWithYear(date, [](int month, int year) { return langMonthOfYear(month, year); }, [](int month, int year) { @@ -93,11 +91,11 @@ inline LangString langMonth(const QDate &date) { }); } -inline LangString langMonthOfYearFull(int month, int year) { +inline QString langMonthOfYearFull(int month, int year) { return (month > 0 && month <= 12) ? lng_month_year(lt_month, lang(LangKey(lng_month1 + month - 1)), lt_year, QString::number(year)) : qsl("MONTH_ERR"); } -inline LangString langMonthFull(const QDate &date) { +inline QString langMonthFull(const QDate &date) { return langDateMaybeWithYear(date, [](int month, int year) { return langMonthOfYearFull(month, year); }, [](int month, int year) { @@ -105,87 +103,30 @@ inline LangString langMonthFull(const QDate &date) { }); } -inline LangString langDayOfWeek(int index) { +inline QString langDayOfWeek(int index) { return (index > 0 && index <= 7) ? lang(LangKey(lng_weekday1 + index - 1)) : qsl("DAY_ERR"); } -inline LangString langDayOfWeek(const QDate &date) { +inline QString langDayOfWeek(const QDate &date) { return langDayOfWeek(date.dayOfWeek()); } -inline LangString langDayOfWeekFull(int index) { +inline QString langDayOfWeekFull(int index) { return (index > 0 && index <= 7) ? lang(LangKey(lng_weekday1_full + index - 1)) : qsl("DAY_ERR"); } -inline LangString langDayOfWeekFull(const QDate &date) { +inline QString langDayOfWeekFull(const QDate &date) { return langDayOfWeekFull(date.dayOfWeek()); } -inline LangString langDateTime(const QDateTime &date) { +inline QString langDateTime(const QDateTime &date) { return lng_mediaview_date_time(lt_date, langDayOfMonth(date.date()), lt_time, date.time().toString(cTimeFormat())); } -inline LangString langDateTimeFull(const QDateTime &date) { +inline QString langDateTimeFull(const QDateTime &date) { return lng_mediaview_date_time(lt_date, langDayOfMonthFull(date.date()), lt_time, date.time().toString(cTimeFormat())); } QString langNewVersionText(); -QString langNewVersionTextForLang(int langId); -class LangLoader { -public: - const QString &errors() const; - const QString &warnings() const; - -protected: - LangLoader() : _checked(false) { - memset(_found, 0, sizeof(_found)); - } - - ushort tagIndex(QLatin1String tag) const; - LangKey keyIndex(QLatin1String key) const; - bool tagReplaced(LangKey key, ushort tag) const; - LangKey subkeyIndex(LangKey key, ushort tag, ushort index) const; - - bool feedKeyValue(LangKey key, const QString &value); - void foundKeyValue(LangKey key); - - void error(const QString &text) { - _err.push_back(text); - } - void warning(const QString &text) { - _warn.push_back(text); - } - -private: - mutable QStringList _err, _warn; - mutable QString _errors, _warnings; - mutable bool _checked; - bool _found[lngkeys_cnt]; - - LangLoader(const LangLoader &); - LangLoader &operator=(const LangLoader &); - -}; - -class Translator : public QTranslator { -public: - QString translate(const char *context, const char *sourceText, const char *disambiguation = 0, int n = -1) const override; - -}; - -inline bool langFirstNameGoesSecond() { - QString fullname = lang(lng_full_name__tagged); - for (const QChar *s = fullname.constData(), *ch = s, *e = ch + fullname.size(); ch != e;) { - if (*ch == TextCommand) { - if (ch + 3 < e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) { - if ((ch + 2)->unicode() == 0x0020 + lt_last_name) { - return true; - } else if ((ch + 2)->unicode() == 0x0020 + lt_first_name) { - break; - } - } - } - } - return false; -} +bool langFirstNameGoesSecond(); diff --git a/Telegram/SourceFiles/lang/lang_tag.cpp b/Telegram/SourceFiles/lang/lang_tag.cpp new file mode 100644 index 000000000..1790a0057 --- /dev/null +++ b/Telegram/SourceFiles/lang/lang_tag.cpp @@ -0,0 +1,81 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "lang/lang_tag.h" + +#include "lang/lang_keys.h" + +namespace Lang { + +QString Tag(const QString &original, ushort tag, const QString &replacement) { + for (auto s = original.constData(), ch = s, e = ch + original.size(); ch != e;) { + if (*ch == TextCommand) { + if (ch + 3 < e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) { + if ((ch + 2)->unicode() == 0x0020 + tag) { + auto result = QString(); + result.reserve(original.size() + replacement.size() - 4); + if (ch > s) result.append(original.midRef(0, ch - s)); + result.append(replacement); + if (ch + 4 < e) result.append(original.midRef(ch - s + 4)); + return result; + } else { + ch += 4; + } + } else { + auto next = textSkipCommand(ch, e); + if (next == ch) { + ++ch; + } else { + ch = next; + } + } + } else { + ++ch; + } + } + return original; +} + +QString Plural(ushort key0, ushort tag, float64 value) { // current lang dependent + int v = qFloor(value); + QString sv; + ushort key = key0; + if (v != qCeil(value)) { + key += 2; + sv = QString::number(value); + } else { + if (v == 1) { + key += 1; + } else if (v) { + key += 2; + } + sv = QString::number(v); + } + while (key > key0) { + auto v = lang(LangKey(key)); + if (!v.isEmpty()) { + return Tag(v, tag, sv); + } + --key; + } + return Tag(lang(LangKey(key0)), tag, sv); +} + +} // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_tag.h b/Telegram/SourceFiles/lang/lang_tag.h new file mode 100644 index 000000000..218746243 --- /dev/null +++ b/Telegram/SourceFiles/lang/lang_tag.h @@ -0,0 +1,28 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace Lang { + +QString Tag(const QString &original, ushort tag, const QString &replacement); +QString Plural(ushort key0, ushort tag, float64 value); + +} // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_translator.cpp b/Telegram/SourceFiles/lang/lang_translator.cpp new file mode 100644 index 000000000..4a54db885 --- /dev/null +++ b/Telegram/SourceFiles/lang/lang_translator.cpp @@ -0,0 +1,55 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "lang/lang_translator.h" + +#include "lang/lang_keys.h" + +namespace Lang { + +QString Translator::translate(const char *context, const char *sourceText, const char *disambiguation, int n) const { + if (qstr("QMenuBar") == context) { + if (qstr("Services") == sourceText) return lang(lng_mac_menu_services); + if (qstr("Hide %1") == sourceText) return lng_mac_menu_hide_telegram(lt_telegram, qsl("%1")); + if (qstr("Hide Others") == sourceText) return lang(lng_mac_menu_hide_others); + if (qstr("Show All") == sourceText) return lang(lng_mac_menu_show_all); + if (qstr("Preferences...") == sourceText) return lang(lng_mac_menu_preferences); + if (qstr("Quit %1") == sourceText) return lng_mac_menu_quit_telegram(lt_telegram, qsl("%1")); + if (qstr("About %1") == sourceText) return lng_mac_menu_about_telegram(lt_telegram, qsl("%1")); + return QString(); + } + if (qstr("QWidgetTextControl") == context || qstr("QLineEdit") == context) { + if (qstr("&Undo") == sourceText) return lang((cPlatform() == dbipWindows) ? lng_wnd_menu_undo : ((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_mac_menu_undo : lng_linux_menu_undo)); + if (qstr("&Redo") == sourceText) return lang((cPlatform() == dbipWindows) ? lng_wnd_menu_redo : ((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_mac_menu_redo : lng_linux_menu_redo)); + if (qstr("Cu&t") == sourceText) return lang(lng_mac_menu_cut); + if (qstr("&Copy") == sourceText) return lang(lng_mac_menu_copy); + if (qstr("&Paste") == sourceText) return lang(lng_mac_menu_paste); + if (qstr("Delete") == sourceText) return lang(lng_mac_menu_delete); + if (qstr("Select All") == sourceText) return lang(lng_mac_menu_select_all); + return QString(); + } + if (qstr("QUnicodeControlCharacterMenu") == context) { + if (qstr("Insert Unicode control character") == sourceText) return lang(lng_menu_insert_unicode); + return QString(); + } + return QString(); +} + +} // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_translator.h b/Telegram/SourceFiles/lang/lang_translator.h new file mode 100644 index 000000000..b5938aec6 --- /dev/null +++ b/Telegram/SourceFiles/lang/lang_translator.h @@ -0,0 +1,31 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace Lang { + +class Translator : public QTranslator { +public: + QString translate(const char *context, const char *sourceText, const char *disambiguation = 0, int n = -1) const override; + +}; + +} // namespace Lang diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 4d46f68ee..b5c985ee2 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1455,38 +1455,6 @@ Dialogs::IndexedList *MainWidget::contactsNoDialogsList() { return _dialogs->contactsNoDialogsList(); } -namespace { -QString parseCommandFromMessage(History *history, const QString &message) { - if (history->peer->id != peerFromUser(ServiceUserId)) { - return QString(); - } - if (message.size() < 3 || message.at(0) != '*' || message.at(message.size() - 1) != '*') { - return QString(); - } - QString command = message.mid(1, message.size() - 2); - QStringList commands; - commands.push_back(qsl("new_version_text")); - commands.push_back(qsl("all_new_version_texts")); - if (commands.indexOf(command) < 0) { - return QString(); - } - return command; -} - -void executeParsedCommand(const QString &command) { - if (command.isEmpty() || !App::wnd()) { - return; - } - if (command == qsl("new_version_text")) { - App::wnd()->serviceNotificationLocal(langNewVersionText()); - } else if (command == qsl("all_new_version_texts")) { - for (int i = 0; i < languageCount; ++i) { - App::wnd()->serviceNotificationLocal(langNewVersionTextForLang(i)); - } - } -} -} // namespace - void MainWidget::sendMessage(const MessageToSend &message) { auto history = message.history; auto &textWithTags = message.textWithTags; @@ -1503,11 +1471,10 @@ void MainWidget::sendMessage(const MessageToSend &message) { auto prepareFlags = itemTextOptions(history, App::self()).flags; QString sendingText, leftText = prepareTextWithEntities(textWithTags.text, prepareFlags, &leftEntities); - QString command = parseCommandFromMessage(history, textWithTags.text); HistoryItem *lastMessage = nullptr; MsgId replyTo = (message.replyTo < 0) ? _history->replyToId() : message.replyTo; - while (command.isEmpty() && textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) { + while (textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) { FullMsgId newId(peerToChannel(history->peer->id), clientMsgId()); uint64 randomId = rand_value(); @@ -1561,8 +1528,6 @@ void MainWidget::sendMessage(const MessageToSend &message) { history->lastSentMsg = lastMessage; finishForwarding(history, message.silent); - - executeParsedCommand(command); } void MainWidget::saveRecentHashtags(const QString &text) { diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index 308910eaf..a83a026ac 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -30,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "calls/calls_instance.h" #include "lang/lang_file_parser.h" +#include "lang/lang_translator.h" #include "observer_peer.h" #include "storage/file_upload.h" #include "mainwidget.h" @@ -380,7 +381,7 @@ void Messenger::loadLanguage() { } if (cLang() == languageTest) { if (QFileInfo(cLangFile()).exists()) { - LangLoaderPlain loader(cLangFile()); + Lang::FileParser loader(cLangFile()); cSetLangErrors(loader.errors()); if (!cLangErrors().isEmpty()) { LOG(("Lang load errors: %1").arg(cLangErrors())); @@ -391,14 +392,14 @@ void Messenger::loadLanguage() { cSetLang(languageDefault); } } else if (cLang() > languageDefault && cLang() < languageCount) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[cLang()].c_str() + qsl(".strings")); + Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[cLang()].c_str() + qsl(".strings")); if (!loader.errors().isEmpty()) { LOG(("Lang load errors: %1").arg(loader.errors())); } else if (!loader.warnings().isEmpty()) { LOG(("Lang load warnings: %1").arg(loader.warnings())); } } - _translator = std::make_unique(); + _translator = std::make_unique(); QCoreApplication::instance()->installTranslator(_translator.get()); } diff --git a/Telegram/SourceFiles/messenger.h b/Telegram/SourceFiles/messenger.h index eaafa9a0a..bb2d3cdad 100644 --- a/Telegram/SourceFiles/messenger.h +++ b/Telegram/SourceFiles/messenger.h @@ -47,6 +47,10 @@ class Instance; } // namespace Audio } // namespace Media +namespace Lang { +class Translator; +} // namespace Lang + class Messenger final : public QObject, public RPCSender, private base::Subscriber { Q_OBJECT @@ -189,7 +193,7 @@ private: std::unique_ptr _window; FileUploader *_uploader = nullptr; - std::unique_ptr _translator; + std::unique_ptr _translator; std::unique_ptr _dcOptions; std::unique_ptr _mtproto; std::unique_ptr _mtprotoForKeysDestroy; diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index 22507ceaa..04a7b2a58 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -776,7 +776,7 @@ void MainWindow::updateIconCounters() { iconOverlay.addPixmap(App::pixmapFromImageInPlace(iconWithCounter(-32, counter, bg, fg, false))); ps_iconOverlay = createHIconFromQIcon(iconOverlay, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); } - auto description = (counter > 0) ? lng_unread_bar(lt_count, counter) : LangString(); + auto description = (counter > 0) ? lng_unread_bar(lt_count, counter) : QString(); taskbarList->SetOverlayIcon(ps_hWnd, ps_iconOverlay, description.toStdWString().c_str()); } SetWindowPos(ps_hWnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); diff --git a/Telegram/SourceFiles/settings/settings_general_widget.cpp b/Telegram/SourceFiles/settings/settings_general_widget.cpp index 9f2097e0a..b56a120d6 100644 --- a/Telegram/SourceFiles/settings/settings_general_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_general_widget.cpp @@ -213,9 +213,9 @@ void GeneralWidget::chooseCustomLang() { } _testLanguage = QFileInfo(result.paths.front()).absoluteFilePath(); - LangLoaderPlain loader(_testLanguage, { lng_sure_save_language, lng_cancel, lng_box_ok }); + Lang::FileParser loader(_testLanguage, { lng_sure_save_language, lng_cancel, lng_box_ok }); if (loader.errors().isEmpty()) { - LangLoaderResult result = loader.found(); + auto result = loader.found(); auto text = result.value(lng_sure_save_language, langOriginal(lng_sure_save_language)), save = result.value(lng_box_ok, langOriginal(lng_box_ok)), cancel = result.value(lng_cancel, langOriginal(lng_cancel)); diff --git a/Telegram/SourceFiles/settings/settings_widget.cpp b/Telegram/SourceFiles/settings/settings_widget.cpp index c976cf303..5f46a0298 100644 --- a/Telegram/SourceFiles/settings/settings_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_widget.cpp @@ -121,6 +121,10 @@ void fillCodes() { } }); }); + Codes.insert(qsl("newversiontext"), [] { + App::wnd()->serviceNotificationLocal(langNewVersionText()); + }); + auto audioFilters = qsl("Audio files (*.wav *.mp3);;") + FileDialog::AllFilesFilter(); auto audioKeys = { qsl("msg_incoming"), diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 96928b759..52dd111b6 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -177,10 +177,14 @@ <(src_loc)/intro/introsignup.h <(src_loc)/intro/introstart.cpp <(src_loc)/intro/introstart.h -<(src_loc)/lang/lang_keys.cpp -<(src_loc)/lang/lang_keys.h <(src_loc)/lang/lang_file_parser.cpp <(src_loc)/lang/lang_file_parser.h +<(src_loc)/lang/lang_keys.cpp +<(src_loc)/lang/lang_keys.h +<(src_loc)/lang/lang_tag.cpp +<(src_loc)/lang/lang_tag.h +<(src_loc)/lang/lang_translator.cpp +<(src_loc)/lang/lang_translator.h <(src_loc)/media/player/media_player_button.cpp <(src_loc)/media/player/media_player_button.h <(src_loc)/media/player/media_player_cover.cpp