diff --git a/Telegram/Resources/langs/rewrites/ru.json b/Telegram/Resources/langs/rewrites/ru.json new file mode 100644 index 000000000..91471f245 --- /dev/null +++ b/Telegram/Resources/langs/rewrites/ru.json @@ -0,0 +1,92 @@ +// This file is used to override translation in Kotatogram. +// Please note that changes here won't be saved, because +// this file is bundled into application. +// Suggest any new translations or edits for existing here: +// https://github.com/kotatogram/kotatogram-desktop/issues + +[ + { + "key": "ktg_intro_about", + "value": "Добро пожаловать в неофициальный мессенджер\nна основе Telegram Desktop." + }, + { + "key": "ktg_about_text1", + "value": "Экспериментальный неофициальный форк {tdesktop_link}." + }, + { + "key": "ktg_about_text3", + "value": "Посетите {channel_link} или {faq_link} (англ.), чтобы узнать больше." + }, + { + "key": "ktg_about_text3_channel", + "value": "канал Kotatogram" + }, + { + "key": "ktg_copy_btn_callback", + "value": "Копировать callback-данные" + }, + { + "key": "ktg_profile_copy_id", + "value": "Копировать ID" + }, + { + "key": "ktg_profile_bot_id", + "value": "ID бота" + }, + { + "key": "ktg_profile_user_id", + "value": "ID пользователя" + }, + { + "key": "ktg_profile_group_id", + "value": "ID группы" + }, + { + "key": "ktg_profile_supergroup_id", + "value": "ID супергруппы" + }, + { + "key": "ktg_profile_channel_id", + "value": "ID канала" + }, + { + "key": "ktg_pinned_message_show", + "value": "Показать закреплённое сообщение" + }, + { + "key": "ktg_pinned_message_hide", + "value": "Скрыть закреплённое сообщение" + }, + { + "key": "ktg_restricted_send_message_until", + "value": "Администраторы группы запретили вам писать в ней до {date}, {time}." + }, + { + "key": "ktg_restricted_send_media_until", + "value": "Администраторы группы запретили вам публиковать здесь фото и видео до {date}, {time}." + }, + { + "key": "ktg_restricted_send_stickers_until", + "value": "Администраторы группы запретили вам публиковать здесь стикеры до {date}, {time}." + }, + { + "key": "ktg_restricted_send_gifs_until", + "value": "Администраторы группы запретили вам отправлять в нее файлы GIF до {date}, {time}." + }, + { + "key": "ktg_restricted_send_inline_until", + "value": "Администраторы группы запретили вам отправлять сообщения с помощью ботов до {date}, {time}." + }, + { + "key": "ktg_restricted_send_polls_until", + "value": "Администраторы группы запретили Вам публиковать в ней опросы до {date}, {time}." + }, + { + "key": "ktg_settings_show_json_settings", + "value": "Показать файл настроек" + }, + { + "key": "ktg_settings_restart", + "value": "Перезапустить Kotatogram" + } +] \ No newline at end of file diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index 25ab0b68a..f197964ad 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -58,4 +58,7 @@ ../../default_shortcuts-custom.json ../../default_kotato-settings-custom.json + + ../../langs/rewrites/ru.json + diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index e3d24bfa5..a03246834 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -183,6 +183,8 @@ void Application::run() { refreshGlobalProxy(); // Depends on Global::started(). startLocalStorage(); + Lang::Current().fillDefaultJson(); + Lang::Current().fillFromJson(); ValidateScale(); if (Local::oldSettingsVersion() < AppVersion) { diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 2435a1631..8cac56980 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_folder.h" #include "data/data_location.h" +#include "lang/lang_keys.h" #include "base/unixtime.h" #include "history/history.h" #include "observer_peer.h" diff --git a/Telegram/SourceFiles/lang/lang_instance.cpp b/Telegram/SourceFiles/lang/lang_instance.cpp index f6177f0fd..e8767866c 100644 --- a/Telegram/SourceFiles/lang/lang_instance.cpp +++ b/Telegram/SourceFiles/lang/lang_instance.cpp @@ -14,6 +14,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/confirm_box.h" #include "lang/lang_file_parser.h" #include "base/qthelp_regex.h" +#include "base/parse_helper.h" + +#include +#include +#include +#include namespace Lang { namespace { @@ -552,6 +558,100 @@ void Instance::fillFromSerialized( _idChanges.fire_copy(_id); } +QString Instance::jsonLangDir() { + return cWorkingDir() + "tdata/ktg_lang/"; +} + +void Instance::fillDefaultJson() { + if (!QDir().exists(jsonLangDir())) QDir().mkpath(jsonLangDir()); + + const auto path = jsonLangDir() + "ru.json"; + const auto pathRaw = jsonLangDir() + "ru-raw.json"; + auto input = QFile(":/ktg_lang/ru.json"); + auto output = QFile(path); + auto outputRaw = QFile(pathRaw); + if (input.open(QIODevice::ReadOnly)) { + auto inputData = input.readAll(); + if (output.open(QIODevice::WriteOnly)) { + output.write(inputData); + output.close(); + } + + if (outputRaw.open(QIODevice::WriteOnly)) { + outputRaw.write(inputData); + outputRaw.close(); + } + input.close(); + } +} + +void Instance::fillFromJson() { + if (id() != baseId()) { + const auto langBaseDir = jsonLangDir() + (qsl("%1.json").arg(baseId())); + loadFromJson(langBaseDir); + } + + const auto langCustomDir = jsonLangDir() + (qsl("%1.json").arg(id())); + loadFromJson(langCustomDir); + + _idChanges.fire_copy(_id); +} + +void Instance::loadFromJson(const QString &filename) { + QFile file(filename); + if (!file.exists()) { + return; + } + if (!file.open(QIODevice::ReadOnly)) { + LOG(("Lang Info: file %1 could not be read.").arg(filename)); + return; + } + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + const auto document = QJsonDocument::fromJson( + base::parse::stripComments(file.readAll()), + &error); + file.close(); + + if (error.error != QJsonParseError::NoError) { + LOG(("Lang Info: file %1 has failed to parse. Error: %2" + ).arg(filename + ).arg(error.errorString())); + return; + } else if (!document.isArray()) { + LOG(("Lang Info: file %1 has failed to parse. Error: array expected" + ).arg(filename)); + return; + } + const auto langKeys = document.array(); + auto limit = kLangValuesLimit; + for (auto i = langKeys.constBegin(), e = langKeys.constEnd(); i != e; ++i) { + if (!(*i).isObject()) { + LOG(("Lang Info: Bad entry in file %1, object expected" + ).arg(filename)); + continue; + } + const auto entry = (*i).toObject(); + const auto key = entry.constFind(qsl("key")); + const auto value = entry.constFind(qsl("value")); + if (key == entry.constEnd() + || value == entry.constEnd() + || !(*key).isString() + || !(*value).isString()) { + LOG(("Lang Info: Bad entry in file %1, %2 expected" + ).arg(filename).arg("{\"key\": \"...\", \"value\": \"...\" }" + )); + } else { + const auto name = QByteArray().append((*key).toString()); + const auto translation = QByteArray().append((*value).toString()); + + applyValue(name, translation); + } + if (!--limit) { + break; + } + } +} + void Instance::loadFromContent(const QByteArray &content) { Lang::FileParser loader(content, [this](QLatin1String key, const QByteArray &value) { applyValue(QByteArray(key.data(), key.size()), value); diff --git a/Telegram/SourceFiles/lang/lang_instance.h b/Telegram/SourceFiles/lang/lang_instance.h index 10c5c69b4..50617e8da 100644 --- a/Telegram/SourceFiles/lang/lang_instance.h +++ b/Telegram/SourceFiles/lang/lang_instance.h @@ -73,6 +73,9 @@ public: QByteArray serialize() const; void fillFromSerialized(const QByteArray &data, int dataAppVersion); + QString jsonLangDir(); + void fillDefaultJson(); + void fillFromJson(); void applyDifference( Pack pack, @@ -112,6 +115,7 @@ private: const QString &absolutePath, const QString &relativePath, const QByteArray &content); + void loadFromJson(const QString &filename); bool loadFromCustomFile(const QString &filePath); void loadFromContent(const QByteArray &content); void loadFromCustomContent(