From 10e75e653c7ce442641e7ef3127feb2f660b2b9b Mon Sep 17 00:00:00 2001 From: Artem226 Date: Sun, 5 Jul 2020 23:13:37 +0300 Subject: [PATCH] Addon titles and descriptions made translatable Modified the `translation` tag in the addon structure and made the translated names shown in the corresponding locale on the client side. See the related pull-request for more information. --- src/addon/client.cpp | 2 +- src/addon/info.cpp | 87 ++++++++++++++++++++++++- src/addon/info.hpp | 74 +++++++++++++++++++++ src/campaign_server/addon_utils.cpp | 24 ++++++- src/campaign_server/addon_utils.hpp | 1 + src/campaign_server/campaign_server.cpp | 12 +++- src/gui/dialogs/addon/manager.cpp | 21 ++++-- src/gui/widgets/addon_list.cpp | 10 +-- 8 files changed, 212 insertions(+), 19 deletions(-) diff --git a/src/addon/client.cpp b/src/addon/client.cpp index db0867ed9159..0d640d011025 100644 --- a/src/addon/client.cpp +++ b/src/addon/client.cpp @@ -308,7 +308,7 @@ bool addons_client::try_fetch_addon(const addon_info & addon) config archive; if(!( - download_addon(archive, addon.id, addon.title, !is_addon_installed(addon.id)) && + download_addon(archive, addon.id, addon.display_title_full(), !is_addon_installed(addon.id)) && install_addon(archive, addon) )) { const std::string& server_error = get_last_server_error(); diff --git a/src/addon/info.cpp b/src/addon/info.cpp index 6aa5333f02aa..0674a0b80be6 100644 --- a/src/addon/info.cpp +++ b/src/addon/info.cpp @@ -18,6 +18,7 @@ #include "config.hpp" #include "font/pango/escape.hpp" #include "gettext.hpp" +#include "language.hpp" #include "picture.hpp" #include "log.hpp" #include "serialization/string_utils.hpp" @@ -57,6 +58,22 @@ namespace { } } +void addon_info_translation::read(const config& cfg) +{ + this->language = cfg["language"].str(); + this->supported = cfg["supported"].to_bool(true); + this->title = cfg["title"].str(); + this->description = cfg["description"].str(); +} + +void addon_info_translation::write(config& cfg) const +{ + cfg["language"] = this->language; + cfg["supported"] = this->supported; + cfg["title"] = this->title; + cfg["description"] = this->description; +} + void addon_info::read(const config& cfg) { this->id = cfg["name"].str(); @@ -73,7 +90,9 @@ void addon_info::read(const config& cfg) const config::const_child_itors& locales_as_configs = cfg.child_range("translation"); for(const config& locale : locales_as_configs) { - this->locales.push_back(locale["language"].str()); + if(locale["supported"].to_bool(true)) + this->locales.push_back(locale["language"].str()); + this->info_translations.push_back(addon_info_translation(locale)); } this->core = cfg["core"].str(); @@ -100,8 +119,8 @@ void addon_info::write(config& cfg) const cfg["uploads"] = this->uploads; cfg["type"] = get_addon_type_string(this->type); - for (const std::string& locale_id : this->locales) { - cfg.add_child("translation")["language"] = locale_id; + for(const addon_info_translation& locale : this->info_translations) { + locale.write(cfg.add_child("translation")); } cfg["core"] = this->core; @@ -132,6 +151,68 @@ std::string addon_info::display_title() const } } +addon_info_translation addon_info_translation::invalid = {"en_US", false, "invalid_addon!", ""}; + +addon_info_translation addon_info::translated_info() const +{ + std::string locale = get_language().localename; + + if(locale != "en_US") { + for(const addon_info_translation& info : this->info_translations) { + if(info.language == locale || info.language == locale.substr(0, 2)) { + return info; + } + } + } + + return addon_info_translation::invalid; +} + +std::string addon_info::display_title_translated() const +{ + addon_info_translation info = this->translated_info(); + + if(info.valid()) { + return info.title; + } + + return ""; +} + +std::string addon_info::display_title_translated_or_original() const +{ + std::string title = display_title_translated(); + return title.empty() ? display_title() : title; +} + +std::string addon_info::description_translated() const +{ + addon_info_translation info = this->translated_info(); + + if(info.valid()) { + return info.description; + } + + return this->description; +} + +std::string addon_info::display_title_full() const +{ + std::string local_title = display_title_translated(); + if(local_title.empty()) + return display_title(); + return local_title + " (" + display_title() + ")"; +} + +std::string addon_info::display_title_full_shift() const +{ + std::string local_title = display_title_translated(); + if(local_title.empty()) + return display_title(); + return local_title + "\n" + + "(" + display_title() + ")"; +} + std::string addon_info::display_icon() const { std::string ret = icon; diff --git a/src/addon/info.hpp b/src/addon/info.hpp index 8f049f88c8ba..d5822c9fdd91 100644 --- a/src/addon/info.hpp +++ b/src/addon/info.hpp @@ -22,10 +22,67 @@ #include #include +struct addon_info_translation; struct addon_info; class config; typedef std::map addons_list; +struct addon_info_translation +{ + static addon_info_translation invalid; + + std::string language; + bool supported; + std::string title; + std::string description; + + addon_info_translation() + : language() + , supported(true) + , title() + , description() + {} + + addon_info_translation(std::string lang, bool sup, std::string titl, std::string desc) + : language(lang) + , supported(sup) + , title(titl) + , description(desc) + { + } + + explicit addon_info_translation(const config& cfg) + : language() + , supported(true) + , title() + , description() + { + this->read(cfg); + } + + addon_info_translation(const addon_info_translation&) = default; + + addon_info_translation& operator=(const addon_info_translation& o) + { + if(this != &o) { + this->language = o.language; + this->supported = o.supported; + this->title = o.title; + this->description = o.description; + } + return *this; + } + + void read(const config& cfg); + + void write(config& cfg) const; + + bool valid() + { + return title != "invalid_addon!"; + } +}; + struct addon_info { std::string id; @@ -61,6 +118,8 @@ struct addon_info // not previously published. bool local_only; + std::vector info_translations; + addon_info() : id(), title(), description(), icon() , version(), author(), size(), downloads() @@ -71,6 +130,7 @@ struct addon_info , updated() , created() , local_only(false) + , info_translations() {} explicit addon_info(const config& cfg) @@ -83,6 +143,7 @@ struct addon_info , updated() , created() , local_only(false) + , info_translations() { this->read(cfg); } @@ -109,6 +170,7 @@ struct addon_info this->updated = o.updated; this->created = o.created; this->local_only = o.local_only; + this->info_translations = o.info_translations; } return *this; } @@ -138,6 +200,18 @@ struct addon_info */ std::string display_title() const; + addon_info_translation translated_info() const; + + std::string display_title_translated() const; + + std::string display_title_translated_or_original() const; + + std::string display_title_full() const; + + std::string display_title_full_shift() const; + + std::string description_translated() const; + /** Get an icon path fixed for display (e.g. when TC is missing, or the image doesn't exist). */ std::string display_icon() const; diff --git a/src/campaign_server/addon_utils.cpp b/src/campaign_server/addon_utils.cpp index e945e147709d..adf0bba0559a 100644 --- a/src/campaign_server/addon_utils.cpp +++ b/src/campaign_server/addon_utils.cpp @@ -92,23 +92,43 @@ std::string format_addon_feedback_url(const std::string& format, const config& p return std::string(); } +void support_translation(config& addon, std::string locale_id) +{ + if(!locale_id.empty()) { + config& locale = addon.find_child("translation", "language", locale_id); + if(locale) { + locale["supported"] = true; + } + else { + addon.add_child("translation")["language"] = locale_id; //Doing this to circumvent a weird validation bug + addon.find_child("translation", "language", locale_id)["supported"] = true; + } + } +} + void find_translations(const config& base_dir, config& addon) { for(const config& file : base_dir.child_range("file")) { const std::string& fn = file["name"].str(); if(filesystem::ends_with(fn, ".po")) { - addon.add_child("translation")["language"] = filesystem::base_name(fn, true); + support_translation(addon, filesystem::base_name(fn, true)); } } for(const config &dir : base_dir.child_range("dir")) { if(dir["name"] == "LC_MESSAGES") { - addon.add_child("translation")["language"] = base_dir["name"]; + support_translation(addon, base_dir["name"]); } else { find_translations(dir, addon); } } + + for(config& locale : addon.child_range("translation")) { + if(!locale["supported"].to_bool(false)) { + locale["supported"] = false; + } + } } void add_license(config& cfg) diff --git a/src/campaign_server/addon_utils.hpp b/src/campaign_server/addon_utils.hpp index be4f6ef59a5a..0153b1b673fe 100644 --- a/src/campaign_server/addon_utils.hpp +++ b/src/campaign_server/addon_utils.hpp @@ -49,6 +49,7 @@ inline bool is_text_markup_char(char c) */ std::string format_addon_feedback_url(const std::string& format, const config& params); +void support_translation(config& addon, std::string locale_id); /** * Scans an add-on archive directory for translations. diff --git a/src/campaign_server/campaign_server.cpp b/src/campaign_server/campaign_server.cpp index c332c6bc02e6..3c53bc03d7ce 100644 --- a/src/campaign_server/campaign_server.cpp +++ b/src/campaign_server/campaign_server.cpp @@ -623,7 +623,7 @@ void server::handle_request_campaign_list(const server::request& req) for(const config& j : i.child_range("translation")) { - if(j["language"] == lang) { + if(j["language"] == lang && j["supported"].to_bool(true)) { found = true; break; } @@ -881,6 +881,11 @@ void server::handle_upload(const server::request& req) (*campaign).add_child("feedback", url_params); } + (*campaign).clear_children("translation"); + for(const config& locale_params : upload.child_range("translation")) { + (*campaign).add_child("translation", locale_params); + } + const std::string& filename = (*campaign)["filename"].str(); data["title"] = (*campaign)["title"]; data["name"] = ""; @@ -893,9 +898,12 @@ void server::handle_upload(const server::request& req) data["icon"] = (*campaign)["icon"]; data["type"] = (*campaign)["type"]; data["tags"] = (*campaign)["tags"]; - (*campaign).clear_children("translation"); find_translations(data, *campaign); + for(const config& locale_params : (*campaign).child_range("translation")) { + data.add_child("translation", locale_params);//Do we need it? + } + add_license(data); { diff --git a/src/gui/dialogs/addon/manager.cpp b/src/gui/dialogs/addon/manager.cpp index b8dd88ad4ba8..17949dd8f909 100644 --- a/src/gui/dialogs/addon/manager.cpp +++ b/src/gui/dialogs/addon/manager.cpp @@ -117,6 +117,15 @@ namespace { break; } } + for(const config& child : cfg.child_range("translation")) { + for(const auto& attribute : child.attribute_range()) { + std::string val = attribute.second.str(); + if(translation::ci_search(val, filter)) { + found = true; + break; + } + } + } if(!found) { return false; } @@ -159,7 +168,7 @@ namespace { str += ", "; } - str += addon_list::colorize_addon_state_string(dep.display_title(), depstate.state); + str += addon_list::colorize_addon_state_string(dep.display_title_translated_or_original(), depstate.state); } return str; @@ -672,14 +681,14 @@ void addon_manager::uninstall_addon(const addon_info& addon, window& window) if(have_addon_pbl_info(addon.id) || have_addon_in_vcs_tree(addon.id)) { show_error_message( _("The following add-on appears to have publishing or version control information stored locally, and will not be removed:") + " " + - addon.display_title()); + addon.display_title_full()); return; } bool success = remove_local_addon(addon.id); if(!success) { - gui2::show_error_message(_("The following add-on could not be deleted properly:") + " " + addon.display_title()); + gui2::show_error_message(_("The following add-on could not be deleted properly:") + " " + addon.display_title_full()); } else { need_wml_cache_refresh_ = true; @@ -805,7 +814,7 @@ void addon_manager::execute_default_action(const addon_info& addon, window& wind break; case ADDON_INSTALLED: if(!tracking_info_[addon.id].can_publish) { - utils::string_map symbols{ { "addon", addon.display_title() } }; + utils::string_map symbols{ { "addon", addon.display_title_full() } }; int res = gui2::show_message(_("Uninstall add-on"), VGETTEXT("Do you want to uninstall '$addon|'?", symbols), gui2::dialogs::message::ok_cancel_buttons); @@ -874,8 +883,8 @@ void addon_manager::on_addon_select(window& window) find_widget(parent, "image", false).set_label(info->display_icon()); - find_widget(parent, "title", false).set_label(info->display_title()); - find_widget(parent, "description", false).set_label(info->description); + find_widget(parent, "title", false).set_label(info->display_title_translated_or_original()); + find_widget(parent, "description", false).set_label(info->description_translated()); find_widget(parent, "version", false).set_label(info->version.str()); find_widget(parent, "author", false).set_label(info->author); find_widget(parent, "type", false).set_label(info->display_type()); diff --git a/src/gui/widgets/addon_list.cpp b/src/gui/widgets/addon_list.cpp index 7ca88080790b..4e2602fee492 100644 --- a/src/gui/widgets/addon_list.cpp +++ b/src/gui/widgets/addon_list.cpp @@ -164,7 +164,7 @@ void addon_list::set_addons(const addons_list& addons) item["label"] = addon.display_icon(); data.emplace("icon", item); - item["label"] = addon.display_title(); + item["label"] = addon.display_title_full_shift(); data.emplace("name", item); } else { item["label"] = addon.display_icon() + "~SCALE(72,72)~BLIT(icons/icon-addon-publish.png,8,8)"; @@ -172,7 +172,7 @@ void addon_list::set_addons(const addons_list& addons) const std::string publish_name = formatter() << font::span_color(font::GOOD_COLOR) - << addon.display_title() + << addon.display_title_full_shift() << ""; item["label"] = publish_name; @@ -347,7 +347,7 @@ void addon_list::select_addon(const std::string& id) grid* row = list.get_row_grid(i); const label& name_label = find_widget