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.
This commit is contained in:
Artem226 2020-07-05 23:13:37 +03:00 committed by Iris Morelle
parent 96369589e6
commit 10e75e653c
8 changed files with 212 additions and 19 deletions

View file

@ -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();

View file

@ -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"
+ "<small>(" + display_title() + ")</small>";
}
std::string addon_info::display_icon() const
{
std::string ret = icon;

View file

@ -22,10 +22,67 @@
#include <set>
#include <map>
struct addon_info_translation;
struct addon_info;
class config;
typedef std::map<std::string, addon_info> 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<addon_info_translation> 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;

View file

@ -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)

View file

@ -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.

View file

@ -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);
{

View file

@ -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<drawing>(parent, "image", false).set_label(info->display_icon());
find_widget<styled_widget>(parent, "title", false).set_label(info->display_title());
find_widget<styled_widget>(parent, "description", false).set_label(info->description);
find_widget<styled_widget>(parent, "title", false).set_label(info->display_title_translated_or_original());
find_widget<styled_widget>(parent, "description", false).set_label(info->description_translated());
find_widget<styled_widget>(parent, "version", false).set_label(info->version.str());
find_widget<styled_widget>(parent, "author", false).set_label(info->author);
find_widget<styled_widget>(parent, "type", false).set_label(info->display_type());

View file

@ -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()
<< "</span>";
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<label>(row, "name", false);
if(name_label.get_label().base_str() == info.display_title()) {
if(name_label.get_label().base_str() == info.display_title_full_shift()) {
list.select_row(i);
}
}
@ -369,7 +369,7 @@ void addon_list::finalize_setup()
{
listbox& list = get_listbox();
list.register_translatable_sorting_option(0, [this](const int i) { return addon_vector_[i]->title; });
list.register_translatable_sorting_option(0, [this](const int i) { return addon_vector_[i]->display_title_full(); });
list.register_sorting_option(1, [this](const int i) { return addon_vector_[i]->author; });
list.register_sorting_option(2, [this](const int i) { return addon_vector_[i]->size; });
list.register_sorting_option(3, [this](const int i) { return addon_vector_[i]->downloads; });
@ -402,7 +402,7 @@ void addon_list::select_first_addon()
const addon_info* first_addon = addon_vector_[0];
for(const addon_info* a : addon_vector_) {
if(a->display_title().compare(first_addon->display_title()) < 0) {
if(translation::icompare(a->display_title_full(), first_addon->display_title_full()) < 0) {
first_addon = a;
}
}