lang_instance.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "lang/lang_instance.h"
  8. #include "core/application.h"
  9. #include "storage/serialize_common.h"
  10. #include "storage/localstorage.h"
  11. #include "ui/boxes/confirm_box.h"
  12. #include "lang/lang_file_parser.h"
  13. #include "lang/lang_tag.h" // kTextCommandLangTag.
  14. #include "base/platform/base_platform_info.h"
  15. #include "base/qthelp_regex.h"
  16. namespace Lang {
  17. namespace {
  18. const auto kSerializeVersionTag = u"#new"_q;
  19. constexpr auto kSerializeVersion = 1;
  20. constexpr auto kCloudLangPackName = "tdesktop"_cs;
  21. constexpr auto kCustomLanguage = "#custom"_cs;
  22. constexpr auto kLangValuesLimit = 20000;
  23. std::vector<QString> PrepareDefaultValues() {
  24. auto result = std::vector<QString>();
  25. result.reserve(kKeysCount);
  26. for (auto i = 0; i != kKeysCount; ++i) {
  27. result.emplace_back(GetOriginalValue(ushort(i)));
  28. }
  29. return result;
  30. }
  31. class ValueParser {
  32. public:
  33. ValueParser(
  34. const QByteArray &key,
  35. ushort keyIndex,
  36. const QByteArray &value);
  37. QString takeResult() {
  38. Expects(!_failed);
  39. return std::move(_result);
  40. }
  41. bool parse();
  42. private:
  43. void appendToResult(const char *nextBegin);
  44. bool logError(const QString &text);
  45. bool readTag();
  46. const QByteArray &_key;
  47. ushort _keyIndex = kKeysCount;
  48. QLatin1String _currentTag;
  49. ushort _currentTagIndex = 0;
  50. QString _currentTagReplacer;
  51. bool _failed = true;
  52. const char *_begin = nullptr;
  53. const char *_ch = nullptr;
  54. const char *_end = nullptr;
  55. QString _result;
  56. OrderedSet<ushort> _tagsUsed;
  57. };
  58. ValueParser::ValueParser(
  59. const QByteArray &key,
  60. ushort keyIndex,
  61. const QByteArray &value)
  62. : _key(key)
  63. , _keyIndex(keyIndex)
  64. , _currentTag("")
  65. , _begin(value.constData())
  66. , _ch(_begin)
  67. , _end(_begin + value.size()) {
  68. }
  69. void ValueParser::appendToResult(const char *nextBegin) {
  70. if (_ch > _begin) _result.append(QString::fromUtf8(_begin, _ch - _begin));
  71. _begin = nextBegin;
  72. }
  73. bool ValueParser::logError(const QString &text) {
  74. _failed = true;
  75. auto loggedKey = (_currentTag.size() > 0) ? (_key + QString(':') + _currentTag) : QString(_key);
  76. LOG(("Lang Error: %1 (key '%2')").arg(text, loggedKey));
  77. return false;
  78. }
  79. bool ValueParser::readTag() {
  80. auto tagStart = _ch;
  81. auto isTagChar = [](QChar ch) {
  82. if (ch >= 'a' && ch <= 'z') {
  83. return true;
  84. } else if (ch >= 'A' && ch <= 'Z') {
  85. return true;
  86. } else if (ch >= '0' && ch <= '9') {
  87. return true;
  88. }
  89. return (ch == '_');
  90. };
  91. while (_ch != _end && isTagChar(*_ch)) {
  92. ++_ch;
  93. }
  94. if (_ch == tagStart) {
  95. return logError("Expected tag name");
  96. }
  97. _currentTag = QLatin1String(tagStart, _ch - tagStart);
  98. if (_ch == _end || *_ch != '}') {
  99. return logError("Expected '}' after tag name");
  100. }
  101. _currentTagIndex = GetTagIndex(_currentTag);
  102. if (_currentTagIndex == kTagsCount) {
  103. return logError("Unknown tag");
  104. }
  105. if (!IsTagReplaced(_keyIndex, _currentTagIndex)) {
  106. return logError("Unexpected tag");
  107. }
  108. if (_tagsUsed.contains(_currentTagIndex)) {
  109. return logError("Repeated tag");
  110. }
  111. _tagsUsed.insert(_currentTagIndex);
  112. if (_currentTagReplacer.isEmpty()) {
  113. _currentTagReplacer = QString(4, QChar(kTextCommand));
  114. _currentTagReplacer[1] = QChar(kTextCommandLangTag);
  115. }
  116. _currentTagReplacer[2] = QChar(0x0020 + _currentTagIndex);
  117. return true;
  118. }
  119. bool ValueParser::parse() {
  120. _failed = false;
  121. _result.reserve(_end - _begin);
  122. for (; _ch != _end; ++_ch) {
  123. if (*_ch == '{') {
  124. appendToResult(_ch);
  125. ++_ch;
  126. if (!readTag()) {
  127. return false;
  128. }
  129. _result.append(_currentTagReplacer);
  130. _begin = _ch + 1;
  131. _currentTag = QLatin1String("");
  132. }
  133. }
  134. appendToResult(_end);
  135. return true;
  136. }
  137. QString PrepareTestValue(const QString &current, QChar filler) {
  138. auto size = current.size();
  139. auto result = QString(size + 1, filler);
  140. auto inCommand = false;
  141. for (auto i = 0; i != size; ++i) {
  142. const auto ch = current[i];
  143. const auto newInCommand = (ch.unicode() == kTextCommand)
  144. ? (!inCommand)
  145. : inCommand;
  146. if (inCommand || newInCommand || ch.isSpace()) {
  147. result[i + 1] = ch;
  148. }
  149. inCommand = newInCommand;
  150. }
  151. return result;
  152. }
  153. QString PluralCodeForCustom(
  154. const QString &absolutePath,
  155. const QString &relativePath) {
  156. const auto path = !absolutePath.isEmpty()
  157. ? absolutePath
  158. : relativePath;
  159. const auto name = QFileInfo(path).fileName();
  160. if (const auto match = qthelp::regex_match(
  161. "_([a-z]{2,3}_[A-Z]{2,3}|\\-[a-z]{2,3})?\\.",
  162. name)) {
  163. return match->captured(1);
  164. }
  165. return DefaultLanguageId();
  166. }
  167. template <typename Save>
  168. void ParseKeyValue(
  169. const QByteArray &key,
  170. const QByteArray &value,
  171. Save &&save) {
  172. const auto index = GetKeyIndex(QLatin1String(key));
  173. if (index != kKeysCount) {
  174. ValueParser parser(key, index, value);
  175. if (parser.parse()) {
  176. save(index, parser.takeResult());
  177. }
  178. } else if (!key.startsWith("cloud_")) {
  179. DEBUG_LOG(("Lang Warning: Unknown key '%1'"
  180. ).arg(QString::fromLatin1(key)));
  181. }
  182. }
  183. } // namespace
  184. QString CloudLangPackName() {
  185. return kCloudLangPackName.utf16();
  186. }
  187. QString CustomLanguageId() {
  188. return kCustomLanguage.utf16();
  189. }
  190. Language DefaultLanguage() {
  191. return Language{
  192. u"en"_q,
  193. QString(),
  194. QString(),
  195. u"English"_q,
  196. u"English"_q,
  197. };
  198. }
  199. struct Instance::PrivateTag {
  200. };
  201. Instance::Instance()
  202. : _values(PrepareDefaultValues())
  203. , _nonDefaultSet(kKeysCount, 0) {
  204. }
  205. Instance::Instance(not_null<Instance*> derived, const PrivateTag &)
  206. : _derived(derived)
  207. , _nonDefaultSet(kKeysCount, 0) {
  208. }
  209. void Instance::switchToId(const Language &data) {
  210. reset(data);
  211. if (_id == u"#TEST_X"_q || _id == u"#TEST_0"_q) {
  212. for (auto &value : _values) {
  213. value = PrepareTestValue(value, _id[5]);
  214. }
  215. if (!_derived) {
  216. _updated.fire({});
  217. }
  218. }
  219. updatePluralRules();
  220. }
  221. void Instance::setBaseId(const QString &baseId, const QString &pluralId) {
  222. if (baseId.isEmpty()) {
  223. _base = nullptr;
  224. } else {
  225. if (!_base) {
  226. _base = std::make_unique<Instance>(this, PrivateTag{});
  227. }
  228. _base->switchToId({ baseId, pluralId });
  229. }
  230. }
  231. void Instance::switchToCustomFile(const QString &filePath) {
  232. if (loadFromCustomFile(filePath)) {
  233. Local::writeLangPack();
  234. _updated.fire({});
  235. }
  236. }
  237. void Instance::reset(const Language &data) {
  238. const auto computedPluralId = !data.pluralId.isEmpty()
  239. ? data.pluralId
  240. : !data.baseId.isEmpty()
  241. ? data.baseId
  242. : data.id;
  243. setBaseId(data.baseId, computedPluralId);
  244. _id = LanguageIdOrDefault(data.id);
  245. _pluralId = computedPluralId;
  246. _name = data.name;
  247. _nativeName = data.nativeName;
  248. _customFilePathAbsolute = QString();
  249. _customFilePathRelative = QString();
  250. _customFileContent = QByteArray();
  251. _version = 0;
  252. _nonDefaultValues.clear();
  253. for (auto i = 0, count = int(_values.size()); i != count; ++i) {
  254. _values[i] = GetOriginalValue(ushort(i));
  255. }
  256. ranges::fill(_nonDefaultSet, 0);
  257. updateChoosingStickerReplacement();
  258. _idChanges.fire_copy(_id);
  259. }
  260. QString Instance::systemLangCode() const {
  261. if (_systemLanguage.isEmpty()) {
  262. _systemLanguage = Platform::SystemLanguage();
  263. if (_systemLanguage.isEmpty()) {
  264. auto uiLanguages = QLocale::system().uiLanguages();
  265. if (!uiLanguages.isEmpty()) {
  266. _systemLanguage = uiLanguages.front();
  267. }
  268. if (_systemLanguage.isEmpty()) {
  269. _systemLanguage = DefaultLanguageId();
  270. }
  271. }
  272. }
  273. return _systemLanguage;
  274. }
  275. QString Instance::cloudLangCode(Pack pack) const {
  276. return (isCustom() || id().isEmpty())
  277. ? DefaultLanguageId()
  278. : id(pack);
  279. }
  280. QString Instance::id() const {
  281. return id(Pack::Current);
  282. }
  283. rpl::producer<QString> Instance::idChanges() const {
  284. return _idChanges.events();
  285. }
  286. QString Instance::baseId() const {
  287. return id(Pack::Base);
  288. }
  289. QString Instance::name() const {
  290. return _name.isEmpty()
  291. ? getValue(tr::lng_language_name.base)
  292. : _name;
  293. }
  294. QString Instance::nativeName() const {
  295. return _nativeName.isEmpty()
  296. ? getValue(tr::lng_language_name.base)
  297. : _nativeName;
  298. }
  299. QString Instance::id(Pack pack) const {
  300. return (pack != Pack::Base)
  301. ? _id
  302. : _base
  303. ? _base->id(Pack::Current)
  304. : QString();
  305. }
  306. bool Instance::isCustom() const {
  307. return (_id == CustomLanguageId())
  308. || (_id == u"#TEST_X"_q)
  309. || (_id == u"#TEST_0"_q);
  310. }
  311. int Instance::version(Pack pack) const {
  312. return (pack != Pack::Base)
  313. ? _version
  314. : _base
  315. ? _base->version(Pack::Current)
  316. : 0;
  317. }
  318. QString Instance::langPackName() const {
  319. return isCustom() ? QString() : CloudLangPackName();
  320. }
  321. QByteArray Instance::serialize() const {
  322. auto size = Serialize::stringSize(kSerializeVersionTag)
  323. + sizeof(qint32) // serializeVersion
  324. + Serialize::stringSize(_id)
  325. + Serialize::stringSize(_pluralId)
  326. + Serialize::stringSize(_name)
  327. + Serialize::stringSize(_nativeName)
  328. + sizeof(qint32) // version
  329. + Serialize::stringSize(_customFilePathAbsolute)
  330. + Serialize::stringSize(_customFilePathRelative)
  331. + Serialize::bytearraySize(_customFileContent)
  332. + sizeof(qint32); // _nonDefaultValues.size()
  333. for (auto &nonDefault : _nonDefaultValues) {
  334. size += Serialize::bytearraySize(nonDefault.first)
  335. + Serialize::bytearraySize(nonDefault.second);
  336. }
  337. const auto base = _base ? _base->serialize() : QByteArray();
  338. size += Serialize::bytearraySize(base);
  339. auto result = QByteArray();
  340. result.reserve(size);
  341. {
  342. QDataStream stream(&result, QIODevice::WriteOnly);
  343. stream.setVersion(QDataStream::Qt_5_1);
  344. stream
  345. << kSerializeVersionTag
  346. << qint32(kSerializeVersion)
  347. << _id
  348. << _pluralId
  349. << _name
  350. << _nativeName
  351. << qint32(_version)
  352. << _customFilePathAbsolute
  353. << _customFilePathRelative
  354. << _customFileContent
  355. << qint32(_nonDefaultValues.size());
  356. for (const auto &nonDefault : _nonDefaultValues) {
  357. stream << nonDefault.first << nonDefault.second;
  358. }
  359. stream << base;
  360. }
  361. return result;
  362. }
  363. void Instance::fillFromSerialized(
  364. const QByteArray &data,
  365. int dataAppVersion) {
  366. QDataStream stream(data);
  367. stream.setVersion(QDataStream::Qt_5_1);
  368. qint32 serializeVersion = 0;
  369. QString serializeVersionTag;
  370. QString id, pluralId, name, nativeName;
  371. qint32 version = 0;
  372. QString customFilePathAbsolute, customFilePathRelative;
  373. QByteArray customFileContent;
  374. qint32 nonDefaultValuesCount = 0;
  375. stream >> serializeVersionTag;
  376. const auto legacyFormat = (serializeVersionTag != kSerializeVersionTag);
  377. if (legacyFormat) {
  378. id = serializeVersionTag;
  379. stream
  380. >> version
  381. >> customFilePathAbsolute
  382. >> customFilePathRelative
  383. >> customFileContent
  384. >> nonDefaultValuesCount;
  385. } else {
  386. stream >> serializeVersion;
  387. if (serializeVersion == kSerializeVersion) {
  388. stream
  389. >> id
  390. >> pluralId
  391. >> name
  392. >> nativeName
  393. >> version
  394. >> customFilePathAbsolute
  395. >> customFilePathRelative
  396. >> customFileContent
  397. >> nonDefaultValuesCount;
  398. } else {
  399. LOG(("Lang Error: Unsupported serialize version."));
  400. return;
  401. }
  402. }
  403. if (stream.status() != QDataStream::Ok) {
  404. LOG(("Lang Error: Could not read data from serialized langpack."));
  405. return;
  406. }
  407. if (nonDefaultValuesCount > kLangValuesLimit) {
  408. LOG(("Lang Error: Values count limit exceeded: %1"
  409. ).arg(nonDefaultValuesCount));
  410. return;
  411. }
  412. if (!customFilePathAbsolute.isEmpty()) {
  413. id = CustomLanguageId();
  414. auto currentCustomFileContent = Lang::FileParser::ReadFile(
  415. customFilePathAbsolute,
  416. customFilePathRelative);
  417. if (!currentCustomFileContent.isEmpty()
  418. && currentCustomFileContent != customFileContent) {
  419. fillFromCustomContent(
  420. customFilePathAbsolute,
  421. customFilePathRelative,
  422. currentCustomFileContent);
  423. Local::writeLangPack();
  424. return;
  425. }
  426. }
  427. std::vector<QByteArray> nonDefaultStrings;
  428. nonDefaultStrings.reserve(2 * nonDefaultValuesCount);
  429. for (auto i = 0; i != nonDefaultValuesCount; ++i) {
  430. QByteArray key, value;
  431. stream >> key >> value;
  432. if (stream.status() != QDataStream::Ok) {
  433. LOG(("Lang Error: "
  434. "Could not read data from serialized langpack."));
  435. return;
  436. }
  437. nonDefaultStrings.push_back(key);
  438. nonDefaultStrings.push_back(value);
  439. }
  440. _base = nullptr;
  441. QByteArray base;
  442. if (legacyFormat) {
  443. if (!stream.atEnd()) {
  444. stream >> pluralId;
  445. } else {
  446. pluralId = id;
  447. }
  448. if (!stream.atEnd()) {
  449. stream >> base;
  450. if (base.isEmpty()) {
  451. stream.setStatus(QDataStream::ReadCorruptData);
  452. }
  453. }
  454. if (stream.status() != QDataStream::Ok) {
  455. LOG(("Lang Error: "
  456. "Could not read data from serialized langpack."));
  457. return;
  458. }
  459. } else {
  460. stream >> base;
  461. }
  462. if (!base.isEmpty()) {
  463. _base = std::make_unique<Instance>(this, PrivateTag{});
  464. _base->fillFromSerialized(base, dataAppVersion);
  465. }
  466. _id = id;
  467. _pluralId = (id == CustomLanguageId())
  468. ? PluralCodeForCustom(
  469. customFilePathAbsolute,
  470. customFilePathRelative)
  471. : pluralId;
  472. _name = name;
  473. _nativeName = nativeName;
  474. _version = version;
  475. _customFilePathAbsolute = customFilePathAbsolute;
  476. _customFilePathRelative = customFilePathRelative;
  477. _customFileContent = customFileContent;
  478. LOG(("Lang Info: Loaded cached, keys: %1").arg(nonDefaultValuesCount));
  479. for (auto i = 0, count = nonDefaultValuesCount * 2; i != count; i += 2) {
  480. applyValue(nonDefaultStrings[i], nonDefaultStrings[i + 1]);
  481. }
  482. updatePluralRules();
  483. updateChoosingStickerReplacement();
  484. _idChanges.fire_copy(_id);
  485. }
  486. void Instance::loadFromContent(const QByteArray &content) {
  487. Lang::FileParser loader(content, [this](QLatin1String key, const QByteArray &value) {
  488. applyValue(QByteArray(key.data(), key.size()), value);
  489. });
  490. if (!loader.errors().isEmpty()) {
  491. LOG(("Lang load errors: %1").arg(loader.errors()));
  492. } else if (!loader.warnings().isEmpty()) {
  493. LOG(("Lang load warnings: %1").arg(loader.warnings()));
  494. }
  495. }
  496. void Instance::fillFromCustomContent(
  497. const QString &absolutePath,
  498. const QString &relativePath,
  499. const QByteArray &content) {
  500. setBaseId(QString(), QString());
  501. _id = CustomLanguageId();
  502. _pluralId = PluralCodeForCustom(absolutePath, relativePath);
  503. _name = _nativeName = QString();
  504. loadFromCustomContent(absolutePath, relativePath, content);
  505. updateChoosingStickerReplacement();
  506. _idChanges.fire_copy(_id);
  507. }
  508. void Instance::loadFromCustomContent(
  509. const QString &absolutePath,
  510. const QString &relativePath,
  511. const QByteArray &content) {
  512. _version = 0;
  513. _customFilePathAbsolute = absolutePath;
  514. _customFilePathRelative = relativePath;
  515. _customFileContent = content;
  516. loadFromContent(_customFileContent);
  517. }
  518. bool Instance::loadFromCustomFile(const QString &filePath) {
  519. auto absolutePath = QFileInfo(filePath).absoluteFilePath();
  520. auto relativePath = QDir().relativeFilePath(filePath);
  521. auto content = Lang::FileParser::ReadFile(absolutePath, relativePath);
  522. if (!content.isEmpty()) {
  523. reset({
  524. CustomLanguageId(),
  525. PluralCodeForCustom(absolutePath, relativePath) });
  526. loadFromCustomContent(absolutePath, relativePath, content);
  527. updatePluralRules();
  528. return true;
  529. }
  530. return false;
  531. }
  532. void Instance::updateChoosingStickerReplacement() {
  533. // A language changing in the runtime is not supported.
  534. const auto replacement = kChoosingStickerReplacement.utf8();
  535. const auto phrase = tr::lng_send_action_choose_sticker(tr::now);
  536. const auto first = phrase.indexOf(replacement);
  537. const auto support = (first != -1);
  538. const auto phraseNamed = tr::lng_user_action_choose_sticker(
  539. tr::now,
  540. lt_user,
  541. QString());
  542. const auto firstNamed = phraseNamed.indexOf(replacement);
  543. const auto supportNamed = (firstNamed != -1);
  544. _choosingStickerReplacement.support = (supportNamed && support);
  545. _choosingStickerReplacement.rightIndex = phrase.size() - first;
  546. _choosingStickerReplacement.rightIndexNamed = phraseNamed.size()
  547. - firstNamed;
  548. }
  549. bool Instance::supportChoosingStickerReplacement() const {
  550. return _choosingStickerReplacement.support;
  551. }
  552. int Instance::rightIndexChoosingStickerReplacement(bool named) const {
  553. return named
  554. ? _choosingStickerReplacement.rightIndexNamed
  555. : _choosingStickerReplacement.rightIndex;
  556. }
  557. // SetCallback takes two QByteArrays: key, value.
  558. // It is called for all key-value pairs in string.
  559. // ResetCallback takes one QByteArray: key.
  560. template <typename SetCallback, typename ResetCallback>
  561. void HandleString(
  562. const MTPLangPackString &string,
  563. SetCallback setCallback,
  564. ResetCallback resetCallback) {
  565. string.match([&](const MTPDlangPackString &data) {
  566. setCallback(qba(data.vkey()), qba(data.vvalue()));
  567. }, [&](const MTPDlangPackStringPluralized &data) {
  568. const auto key = qba(data.vkey());
  569. setCallback(key + "#zero", data.vzero_value().value_or_empty());
  570. setCallback(key + "#one", data.vone_value().value_or_empty());
  571. setCallback(key + "#two", data.vtwo_value().value_or_empty());
  572. setCallback(key + "#few", data.vfew_value().value_or_empty());
  573. setCallback(key + "#many", data.vmany_value().value_or_empty());
  574. setCallback(key + "#other", qba(data.vother_value()));
  575. }, [&](const MTPDlangPackStringDeleted &data) {
  576. auto key = qba(data.vkey());
  577. resetCallback(key);
  578. const auto postfixes = {
  579. "#zero",
  580. "#one",
  581. "#two",
  582. "#few",
  583. "#many",
  584. "#other"
  585. };
  586. for (const auto plural : postfixes) {
  587. resetCallback(key + plural);
  588. }
  589. });
  590. }
  591. void Instance::applyDifference(
  592. Pack pack,
  593. const MTPDlangPackDifference &difference) {
  594. switch (pack) {
  595. case Pack::Current:
  596. applyDifferenceToMe(difference);
  597. break;
  598. case Pack::Base:
  599. Assert(_base != nullptr);
  600. _base->applyDifference(Pack::Current, difference);
  601. break;
  602. default:
  603. Unexpected("Pack in Instance::applyDifference.");
  604. }
  605. }
  606. void Instance::applyDifferenceToMe(
  607. const MTPDlangPackDifference &difference) {
  608. Expects(LanguageIdOrDefault(_id) == qs(difference.vlang_code()));
  609. Expects(difference.vfrom_version().v <= _version);
  610. _version = difference.vversion().v;
  611. for (const auto &string : difference.vstrings().v) {
  612. HandleString(string, [&](auto &&key, auto &&value) {
  613. applyValue(key, value);
  614. }, [&](auto &&key) {
  615. resetValue(key);
  616. });
  617. }
  618. if (!_derived) {
  619. _updated.fire({});
  620. } else {
  621. _derived->_updated.fire({});
  622. }
  623. }
  624. std::map<ushort, QString> Instance::ParseStrings(
  625. const MTPVector<MTPLangPackString> &strings) {
  626. auto result = std::map<ushort, QString>();
  627. for (const auto &string : strings.v) {
  628. HandleString(string, [&](auto &&key, auto &&value) {
  629. ParseKeyValue(key, value, [&](ushort key, QString &&value) {
  630. result[key] = std::move(value);
  631. });
  632. }, [&](auto &&key) {
  633. auto keyIndex = GetKeyIndex(QLatin1String(key));
  634. if (keyIndex != kKeysCount) {
  635. result.erase(keyIndex);
  636. }
  637. });
  638. }
  639. return result;
  640. }
  641. QString Instance::getNonDefaultValue(const QByteArray &key) const {
  642. const auto i = _nonDefaultValues.find(key);
  643. return (i != end(_nonDefaultValues))
  644. ? QString::fromUtf8(i->second)
  645. : _base
  646. ? _base->getNonDefaultValue(key)
  647. : QString();
  648. }
  649. void Instance::applyValue(const QByteArray &key, const QByteArray &value) {
  650. _nonDefaultValues[key] = value;
  651. ParseKeyValue(key, value, [&](ushort key, QString &&value) {
  652. _nonDefaultSet[key] = 1;
  653. if (!_derived) {
  654. _values[key] = std::move(value);
  655. } else if (!_derived->_nonDefaultSet[key]) {
  656. _derived->_values[key] = std::move(value);
  657. }
  658. if (key == tr::lng_send_action_choose_sticker.base
  659. || key == tr::lng_user_action_choose_sticker.base) {
  660. if (!_derived) {
  661. updateChoosingStickerReplacement();
  662. } else {
  663. _derived->updateChoosingStickerReplacement();
  664. }
  665. }
  666. });
  667. }
  668. void Instance::updatePluralRules() {
  669. if (_pluralId.isEmpty()) {
  670. _pluralId = isCustom()
  671. ? PluralCodeForCustom(
  672. _customFilePathAbsolute,
  673. _customFilePathRelative)
  674. : LanguageIdOrDefault(_id);
  675. }
  676. UpdatePluralRules(_pluralId);
  677. }
  678. void Instance::resetValue(const QByteArray &key) {
  679. _nonDefaultValues.erase(key);
  680. const auto keyIndex = GetKeyIndex(QLatin1String(key));
  681. if (keyIndex != kKeysCount) {
  682. _nonDefaultSet[keyIndex] = 0;
  683. if (!_derived) {
  684. const auto base = _base
  685. ? _base->getNonDefaultValue(key)
  686. : QString();
  687. _values[keyIndex] = !base.isEmpty()
  688. ? base
  689. : GetOriginalValue(keyIndex);
  690. } else if (!_derived->_nonDefaultSet[keyIndex]) {
  691. _derived->_values[keyIndex] = GetOriginalValue(keyIndex);
  692. }
  693. if (keyIndex == tr::lng_send_action_choose_sticker.base
  694. || keyIndex == tr::lng_user_action_choose_sticker.base) {
  695. if (!_derived) {
  696. updateChoosingStickerReplacement();
  697. } else {
  698. _derived->updateChoosingStickerReplacement();
  699. }
  700. }
  701. }
  702. }
  703. Instance &GetInstance() {
  704. return Core::App().langpack();
  705. }
  706. QString Id() {
  707. return GetInstance().id();
  708. }
  709. rpl::producer<> Updated() {
  710. return GetInstance().updated();
  711. }
  712. QString GetNonDefaultValue(const QByteArray &key) {
  713. return GetInstance().getNonDefaultValue(key);
  714. }
  715. namespace details {
  716. QString Current(ushort key) {
  717. return GetInstance().getValue(key);
  718. }
  719. rpl::producer<QString> Value(ushort key) {
  720. return rpl::single(
  721. Current(key)
  722. ) | then(
  723. Updated() | rpl::map([=] { return Current(key); })
  724. );
  725. }
  726. bool IsNonDefaultPlural(ushort keyBase) {
  727. return GetInstance().isNonDefaultPlural(keyBase);
  728. }
  729. } // namespace details
  730. } // namespace Lang