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:
parent
96369589e6
commit
10e75e653c
8 changed files with 212 additions and 19 deletions
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue