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