lang_cloud_manager.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  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_cloud_manager.h"
  8. #include "lang/lang_instance.h"
  9. #include "lang/lang_file_parser.h"
  10. #include "lang/lang_text_entity.h"
  11. #include "mtproto/mtp_instance.h"
  12. #include "storage/localstorage.h"
  13. #include "core/application.h"
  14. #include "main/main_account.h"
  15. #include "main/main_domain.h"
  16. #include "ui/boxes/confirm_box.h"
  17. #include "ui/wrap/padding_wrap.h"
  18. #include "ui/widgets/labels.h"
  19. #include "ui/text/text_utilities.h"
  20. #include "core/file_utilities.h"
  21. #include "core/click_handler_types.h"
  22. #include "boxes/abstract_box.h" // Ui::hideLayer().
  23. #include "styles/style_layers.h"
  24. namespace Lang {
  25. namespace {
  26. class ConfirmSwitchBox : public Ui::BoxContent {
  27. public:
  28. ConfirmSwitchBox(
  29. QWidget*,
  30. const MTPDlangPackLanguage &data,
  31. Fn<void()> apply);
  32. protected:
  33. void prepare() override;
  34. private:
  35. QString _name;
  36. int _percent = 0;
  37. bool _official = false;
  38. QString _editLink;
  39. Fn<void()> _apply;
  40. };
  41. class NotReadyBox : public Ui::BoxContent {
  42. public:
  43. NotReadyBox(
  44. QWidget*,
  45. const MTPDlangPackLanguage &data);
  46. protected:
  47. void prepare() override;
  48. private:
  49. QString _name;
  50. QString _editLink;
  51. };
  52. ConfirmSwitchBox::ConfirmSwitchBox(
  53. QWidget*,
  54. const MTPDlangPackLanguage &data,
  55. Fn<void()> apply)
  56. : _name(qs(data.vnative_name()))
  57. , _percent(data.vtranslated_count().v * 100 / data.vstrings_count().v)
  58. , _official(data.is_official())
  59. , _editLink(qs(data.vtranslations_url()))
  60. , _apply(std::move(apply)) {
  61. }
  62. void ConfirmSwitchBox::prepare() {
  63. setTitle(tr::lng_language_switch_title());
  64. auto text = (_official
  65. ? tr::lng_language_switch_about_official
  66. : tr::lng_language_switch_about_unofficial)(
  67. lt_lang_name,
  68. rpl::single(Ui::Text::Bold(_name)),
  69. lt_percent,
  70. rpl::single(Ui::Text::Bold(QString::number(_percent))),
  71. lt_link,
  72. tr::lng_language_switch_link() | Ui::Text::ToLink(_editLink),
  73. Ui::Text::WithEntities);
  74. const auto content = Ui::CreateChild<Ui::PaddingWrap<Ui::FlatLabel>>(
  75. this,
  76. object_ptr<Ui::FlatLabel>(
  77. this,
  78. std::move(text),
  79. st::boxLabel),
  80. QMargins{ st::boxPadding.left(), 0, st::boxPadding.right(), 0 });
  81. content->entity()->setLinksTrusted();
  82. addButton(tr::lng_language_switch_apply(), [=] {
  83. const auto apply = _apply;
  84. closeBox();
  85. apply();
  86. });
  87. addButton(tr::lng_cancel(), [=] { closeBox(); });
  88. content->resizeToWidth(st::boxWideWidth);
  89. content->heightValue(
  90. ) | rpl::start_with_next([=](int height) {
  91. setDimensions(st::boxWideWidth, height);
  92. }, lifetime());
  93. }
  94. NotReadyBox::NotReadyBox(
  95. QWidget*,
  96. const MTPDlangPackLanguage &data)
  97. : _name(qs(data.vnative_name()))
  98. , _editLink(qs(data.vtranslations_url())) {
  99. }
  100. void NotReadyBox::prepare() {
  101. setTitle(tr::lng_language_not_ready_title());
  102. auto text = tr::lng_language_not_ready_about(
  103. lt_lang_name,
  104. rpl::single(_name) | Ui::Text::ToWithEntities(),
  105. lt_link,
  106. tr::lng_language_not_ready_link() | Ui::Text::ToLink(_editLink),
  107. Ui::Text::WithEntities);
  108. const auto content = Ui::CreateChild<Ui::PaddingWrap<Ui::FlatLabel>>(
  109. this,
  110. object_ptr<Ui::FlatLabel>(
  111. this,
  112. std::move(text),
  113. st::boxLabel),
  114. QMargins{ st::boxPadding.left(), 0, st::boxPadding.right(), 0 });
  115. content->entity()->setLinksTrusted();
  116. addButton(tr::lng_box_ok(), [=] { closeBox(); });
  117. content->resizeToWidth(st::boxWidth);
  118. content->heightValue(
  119. ) | rpl::start_with_next([=](int height) {
  120. setDimensions(st::boxWidth, height);
  121. }, lifetime());
  122. }
  123. } // namespace
  124. Language ParseLanguage(const MTPLangPackLanguage &data) {
  125. return data.match([](const MTPDlangPackLanguage &data) {
  126. return Language{
  127. qs(data.vlang_code()),
  128. qs(data.vplural_code()),
  129. qs(data.vbase_lang_code().value_or_empty()),
  130. qs(data.vname()),
  131. qs(data.vnative_name())
  132. };
  133. });
  134. }
  135. CloudManager::CloudManager(Instance &langpack)
  136. : _langpack(langpack) {
  137. const auto mtpLifetime = _lifetime.make_state<rpl::lifetime>();
  138. Core::App().domain().activeValue(
  139. ) | rpl::filter([=](Main::Account *account) {
  140. return (account != nullptr);
  141. }) | rpl::start_with_next_done([=](Main::Account *account) {
  142. *mtpLifetime = account->mtpMainSessionValue(
  143. ) | rpl::start_with_next([=](not_null<MTP::Instance*> instance) {
  144. _api.emplace(instance);
  145. resendRequests();
  146. });
  147. }, [=] {
  148. _api.reset();
  149. }, _lifetime);
  150. }
  151. Pack CloudManager::packTypeFromId(const QString &id) const {
  152. if (id == LanguageIdOrDefault(_langpack.id())) {
  153. return Pack::Current;
  154. } else if (id == _langpack.baseId()) {
  155. return Pack::Base;
  156. }
  157. return Pack::None;
  158. }
  159. rpl::producer<> CloudManager::languageListChanged() const {
  160. return _languageListChanged.events();
  161. }
  162. rpl::producer<> CloudManager::firstLanguageSuggestion() const {
  163. return _firstLanguageSuggestion.events();
  164. }
  165. void CloudManager::requestLangPackDifference(const QString &langId) {
  166. Expects(!langId.isEmpty());
  167. if (langId == LanguageIdOrDefault(_langpack.id())) {
  168. requestLangPackDifference(Pack::Current);
  169. } else {
  170. requestLangPackDifference(Pack::Base);
  171. }
  172. }
  173. mtpRequestId &CloudManager::packRequestId(Pack pack) {
  174. return (pack != Pack::Base)
  175. ? _langPackRequestId
  176. : _langPackBaseRequestId;
  177. }
  178. mtpRequestId CloudManager::packRequestId(Pack pack) const {
  179. return (pack != Pack::Base)
  180. ? _langPackRequestId
  181. : _langPackBaseRequestId;
  182. }
  183. void CloudManager::requestLangPackDifference(Pack pack) {
  184. if (!_api) {
  185. return;
  186. }
  187. _api->request(base::take(packRequestId(pack))).cancel();
  188. if (_langpack.isCustom()) {
  189. return;
  190. }
  191. const auto version = _langpack.version(pack);
  192. const auto code = _langpack.cloudLangCode(pack);
  193. if (code.isEmpty()) {
  194. return;
  195. }
  196. if (version > 0) {
  197. packRequestId(pack) = _api->request(MTPlangpack_GetDifference(
  198. MTP_string(CloudLangPackName()),
  199. MTP_string(code),
  200. MTP_int(version)
  201. )).done([=](const MTPLangPackDifference &result) {
  202. packRequestId(pack) = 0;
  203. applyLangPackDifference(result);
  204. }).fail([=] {
  205. packRequestId(pack) = 0;
  206. }).send();
  207. } else {
  208. packRequestId(pack) = _api->request(MTPlangpack_GetLangPack(
  209. MTP_string(CloudLangPackName()),
  210. MTP_string(code)
  211. )).done([=](const MTPLangPackDifference &result) {
  212. packRequestId(pack) = 0;
  213. applyLangPackDifference(result);
  214. }).fail([=] {
  215. packRequestId(pack) = 0;
  216. }).send();
  217. }
  218. }
  219. void CloudManager::setSuggestedLanguage(const QString &langCode) {
  220. if (Lang::LanguageIdOrDefault(langCode) != Lang::DefaultLanguageId()) {
  221. _suggestedLanguage = langCode;
  222. } else {
  223. _suggestedLanguage = QString();
  224. }
  225. if (!_languageWasSuggested) {
  226. _languageWasSuggested = true;
  227. _firstLanguageSuggestion.fire({});
  228. if (Core::App().offerLegacyLangPackSwitch()
  229. && _langpack.id().isEmpty()
  230. && !_suggestedLanguage.isEmpty()) {
  231. _offerSwitchToId = _suggestedLanguage;
  232. offerSwitchLangPack();
  233. }
  234. }
  235. }
  236. void CloudManager::setCurrentVersions(int version, int baseVersion) {
  237. const auto check = [&](Pack pack, int version) {
  238. if (version > _langpack.version(pack) && !packRequestId(pack)) {
  239. requestLangPackDifference(pack);
  240. }
  241. };
  242. check(Pack::Current, version);
  243. check(Pack::Base, baseVersion);
  244. }
  245. void CloudManager::applyLangPackDifference(
  246. const MTPLangPackDifference &difference) {
  247. Expects(difference.type() == mtpc_langPackDifference);
  248. if (_langpack.isCustom()) {
  249. return;
  250. }
  251. const auto &langpack = difference.c_langPackDifference();
  252. const auto langpackId = qs(langpack.vlang_code());
  253. const auto pack = packTypeFromId(langpackId);
  254. if (pack != Pack::None) {
  255. applyLangPackData(pack, langpack);
  256. if (_restartAfterSwitch) {
  257. restartAfterSwitch();
  258. }
  259. } else {
  260. LOG(("Lang Warning: "
  261. "Ignoring update for '%1' because our language is '%2'").arg(
  262. langpackId,
  263. _langpack.id()));
  264. }
  265. }
  266. void CloudManager::requestLanguageList() {
  267. if (!_api) {
  268. _languagesRequestId = -1;
  269. return;
  270. }
  271. _api->request(base::take(_languagesRequestId)).cancel();
  272. _languagesRequestId = _api->request(MTPlangpack_GetLanguages(
  273. MTP_string(CloudLangPackName())
  274. )).done([=](const MTPVector<MTPLangPackLanguage> &result) {
  275. auto languages = Languages();
  276. for (const auto &language : result.v) {
  277. languages.push_back(ParseLanguage(language));
  278. }
  279. if (_languages != languages) {
  280. _languages = languages;
  281. _languageListChanged.fire({});
  282. }
  283. _languagesRequestId = 0;
  284. }).fail([=] {
  285. _languagesRequestId = 0;
  286. }).send();
  287. }
  288. void CloudManager::offerSwitchLangPack() {
  289. Expects(!_offerSwitchToId.isEmpty());
  290. Expects(_offerSwitchToId != DefaultLanguageId());
  291. if (!showOfferSwitchBox()) {
  292. languageListChanged(
  293. ) | rpl::start_with_next([=] {
  294. showOfferSwitchBox();
  295. }, _lifetime);
  296. requestLanguageList();
  297. }
  298. }
  299. Language CloudManager::findOfferedLanguage() const {
  300. for (const auto &language : _languages) {
  301. if (language.id == _offerSwitchToId) {
  302. return language;
  303. }
  304. }
  305. return {};
  306. }
  307. bool CloudManager::showOfferSwitchBox() {
  308. const auto language = findOfferedLanguage();
  309. if (language.id.isEmpty()) {
  310. return false;
  311. }
  312. const auto confirm = [=] {
  313. Ui::hideLayer();
  314. if (_offerSwitchToId.isEmpty()) {
  315. return;
  316. }
  317. performSwitchAndRestart(language);
  318. };
  319. const auto cancel = [=] {
  320. Ui::hideLayer();
  321. changeIdAndReInitConnection(DefaultLanguage());
  322. Local::writeLangPack();
  323. };
  324. Ui::show(
  325. Ui::MakeConfirmBox({
  326. .text = QString("Do you want to switch your language to ")
  327. + language.nativeName
  328. + QString("? You can always change your language in Settings."),
  329. .confirmed = confirm,
  330. .cancelled = cancel,
  331. .confirmText = QString("Change"),
  332. }),
  333. Ui::LayerOption::KeepOther);
  334. return true;
  335. }
  336. void CloudManager::applyLangPackData(
  337. Pack pack,
  338. const MTPDlangPackDifference &data) {
  339. if (_langpack.version(pack) < data.vfrom_version().v) {
  340. requestLangPackDifference(pack);
  341. } else if (!data.vstrings().v.isEmpty()) {
  342. _langpack.applyDifference(pack, data);
  343. Local::writeLangPack();
  344. } else if (_restartAfterSwitch) {
  345. Local::writeLangPack();
  346. } else {
  347. LOG(("Lang Info: Up to date."));
  348. }
  349. }
  350. bool CloudManager::canApplyWithoutRestart(const QString &id) const {
  351. if (id == u"#TEST_X"_q || id == u"#TEST_0"_q) {
  352. return true;
  353. }
  354. return Core::App().canApplyLangPackWithoutRestart();
  355. }
  356. void CloudManager::resetToDefault() {
  357. performSwitch(DefaultLanguage());
  358. }
  359. void CloudManager::switchToLanguage(const QString &id) {
  360. requestLanguageAndSwitch(id, false);
  361. }
  362. void CloudManager::switchWithWarning(const QString &id) {
  363. requestLanguageAndSwitch(id, true);
  364. }
  365. void CloudManager::requestLanguageAndSwitch(
  366. const QString &id,
  367. bool warning) {
  368. Expects(!id.isEmpty());
  369. if (LanguageIdOrDefault(_langpack.id()) == id) {
  370. Ui::show(Ui::MakeInformBox(tr::lng_language_already()));
  371. return;
  372. } else if (id == u"#custom"_q) {
  373. performSwitchToCustom();
  374. return;
  375. }
  376. _switchingToLanguageId = id;
  377. _switchingToLanguageWarning = warning;
  378. sendSwitchingToLanguageRequest();
  379. }
  380. void CloudManager::sendSwitchingToLanguageRequest() {
  381. if (!_api) {
  382. _switchingToLanguageRequest = -1;
  383. return;
  384. }
  385. _api->request(_switchingToLanguageRequest).cancel();
  386. _switchingToLanguageRequest = _api->request(MTPlangpack_GetLanguage(
  387. MTP_string(Lang::CloudLangPackName()),
  388. MTP_string(_switchingToLanguageId)
  389. )).done([=](const MTPLangPackLanguage &result) {
  390. _switchingToLanguageRequest = 0;
  391. const auto language = Lang::ParseLanguage(result);
  392. const auto finalize = [=] {
  393. if (canApplyWithoutRestart(language.id)) {
  394. performSwitchAndAddToRecent(language);
  395. } else {
  396. performSwitchAndRestart(language);
  397. }
  398. };
  399. if (!_switchingToLanguageWarning) {
  400. finalize();
  401. return;
  402. }
  403. result.match([=](const MTPDlangPackLanguage &data) {
  404. if (data.vstrings_count().v > 0) {
  405. Ui::show(Box<ConfirmSwitchBox>(data, finalize));
  406. } else {
  407. Ui::show(Box<NotReadyBox>(data));
  408. }
  409. });
  410. }).fail([=](const MTP::Error &error) {
  411. _switchingToLanguageRequest = 0;
  412. if (error.type() == "LANG_CODE_NOT_SUPPORTED") {
  413. Ui::show(Ui::MakeInformBox(tr::lng_language_not_found()));
  414. }
  415. }).send();
  416. }
  417. void CloudManager::switchToLanguage(const Language &data) {
  418. if (_langpack.id() == data.id && data.id != u"#custom"_q) {
  419. return;
  420. } else if (!_api) {
  421. return;
  422. }
  423. _api->request(base::take(_getKeysForSwitchRequestId)).cancel();
  424. if (data.id == u"#custom"_q) {
  425. performSwitchToCustom();
  426. } else if (canApplyWithoutRestart(data.id)) {
  427. performSwitchAndAddToRecent(data);
  428. } else {
  429. QVector<MTPstring> keys;
  430. keys.reserve(3);
  431. keys.push_back(MTP_string("lng_sure_save_language"));
  432. _getKeysForSwitchRequestId = _api->request(MTPlangpack_GetStrings(
  433. MTP_string(Lang::CloudLangPackName()),
  434. MTP_string(data.id),
  435. MTP_vector<MTPstring>(std::move(keys))
  436. )).done([=](const MTPVector<MTPLangPackString> &result) {
  437. _getKeysForSwitchRequestId = 0;
  438. const auto values = Instance::ParseStrings(result);
  439. const auto getValue = [&](ushort key) {
  440. auto it = values.find(key);
  441. return (it == values.cend())
  442. ? GetOriginalValue(key)
  443. : it->second;
  444. };
  445. const auto text = tr::lng_sure_save_language(tr::now)
  446. + "\n\n"
  447. + getValue(tr::lng_sure_save_language.base);
  448. Ui::show(
  449. Ui::MakeConfirmBox({
  450. .text = text,
  451. .confirmed = [=] { performSwitchAndRestart(data); },
  452. .confirmText = tr::lng_box_ok(),
  453. }),
  454. Ui::LayerOption::KeepOther);
  455. }).fail([=] {
  456. _getKeysForSwitchRequestId = 0;
  457. }).send();
  458. }
  459. }
  460. void CloudManager::performSwitchToCustom() {
  461. auto filter = u"Language files (*.strings)"_q;
  462. auto title = u"Choose language .strings file"_q;
  463. FileDialog::GetOpenPath(Core::App().getFileDialogParent(), title, filter, [=, weak = base::make_weak(this)](const FileDialog::OpenResult &result) {
  464. if (!weak || result.paths.isEmpty()) {
  465. return;
  466. }
  467. const auto filePath = result.paths.front();
  468. auto loader = Lang::FileParser(
  469. filePath,
  470. { tr::lng_sure_save_language.base });
  471. if (loader.errors().isEmpty()) {
  472. if (_api) {
  473. _api->request(
  474. base::take(_switchingToLanguageRequest)
  475. ).cancel();
  476. }
  477. if (canApplyWithoutRestart(u"#custom"_q)) {
  478. _langpack.switchToCustomFile(filePath);
  479. } else {
  480. const auto values = loader.found();
  481. const auto getValue = [&](ushort key) {
  482. const auto it = values.find(key);
  483. return (it == values.cend())
  484. ? GetOriginalValue(key)
  485. : it.value();
  486. };
  487. const auto text = tr::lng_sure_save_language(tr::now)
  488. + "\n\n"
  489. + getValue(tr::lng_sure_save_language.base);
  490. const auto change = [=] {
  491. _langpack.switchToCustomFile(filePath);
  492. Core::Restart();
  493. };
  494. Ui::show(
  495. Ui::MakeConfirmBox({
  496. .text = text,
  497. .confirmed = change,
  498. .confirmText = tr::lng_box_ok(),
  499. }),
  500. Ui::LayerOption::KeepOther);
  501. }
  502. } else {
  503. Ui::show(
  504. Ui::MakeInformBox(
  505. "Custom lang failed :(\n\nError: " + loader.errors()),
  506. Ui::LayerOption::KeepOther);
  507. }
  508. });
  509. }
  510. void CloudManager::switchToTestLanguage() {
  511. const auto testLanguageId = (_langpack.id() == u"#TEST_X"_q)
  512. ? u"#TEST_0"_q
  513. : u"#TEST_X"_q;
  514. performSwitch({ testLanguageId });
  515. }
  516. void CloudManager::performSwitch(const Language &data) {
  517. _restartAfterSwitch = false;
  518. switchLangPackId(data);
  519. requestLangPackDifference(Pack::Current);
  520. requestLangPackDifference(Pack::Base);
  521. }
  522. void CloudManager::performSwitchAndAddToRecent(const Language &data) {
  523. Local::pushRecentLanguage(data);
  524. performSwitch(data);
  525. }
  526. void CloudManager::performSwitchAndRestart(const Language &data) {
  527. performSwitchAndAddToRecent(data);
  528. restartAfterSwitch();
  529. }
  530. void CloudManager::restartAfterSwitch() {
  531. if (_langPackRequestId || _langPackBaseRequestId) {
  532. _restartAfterSwitch = true;
  533. } else {
  534. Core::Restart();
  535. }
  536. }
  537. void CloudManager::switchLangPackId(const Language &data) {
  538. const auto currentId = _langpack.id();
  539. const auto currentBaseId = _langpack.baseId();
  540. const auto notChanged = (currentId == data.id
  541. && currentBaseId == data.baseId)
  542. || (currentId.isEmpty()
  543. && currentBaseId.isEmpty()
  544. && data.id == DefaultLanguageId());
  545. if (!notChanged) {
  546. changeIdAndReInitConnection(data);
  547. }
  548. }
  549. void CloudManager::changeIdAndReInitConnection(const Language &data) {
  550. _langpack.switchToId(data);
  551. if (_api) {
  552. const auto mtproto = &_api->instance();
  553. mtproto->reInitConnection(mtproto->mainDcId());
  554. }
  555. }
  556. void CloudManager::resendRequests() {
  557. if (packRequestId(Pack::Base)) {
  558. requestLangPackDifference(Pack::Base);
  559. }
  560. if (packRequestId(Pack::Current)) {
  561. requestLangPackDifference(Pack::Current);
  562. }
  563. if (_languagesRequestId) {
  564. requestLanguageList();
  565. }
  566. if (_switchingToLanguageRequest) {
  567. sendSwitchingToLanguageRequest();
  568. }
  569. }
  570. CloudManager &CurrentCloudManager() {
  571. auto result = Core::App().langCloudManager();
  572. Assert(result != nullptr);
  573. return *result;
  574. }
  575. } // namespace Lang