From 00b75a76a8e087c7377fb97b2a55b15b5cd29ed8 Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 12 Jul 2023 12:25:30 -0300 Subject: [PATCH] Add displayName property for language extensions (fix #3964) The default language (en.ini) has a new "display_name" property, but probably we should remove it and transform the English language in an extension (just as the default Aseprite theme). --- data/strings/en.ini | 7 +++++++ src/app/commands/cmd_options.cpp | 25 +++++++++++++++++----- src/app/extensions.cpp | 22 ++++++++++++------- src/app/extensions.h | 14 +++++++++---- src/app/i18n/lang_info.h | 36 ++++++++++++++++++++++++++++++++ src/app/i18n/strings.cpp | 33 ++++++++++++++++++++++------- src/app/i18n/strings.h | 11 +++++----- src/cfg/cfg.cpp | 10 +++++---- src/cfg/cfg.h | 4 ++-- 9 files changed, 127 insertions(+), 35 deletions(-) create mode 100644 src/app/i18n/lang_info.h diff --git a/data/strings/en.ini b/data/strings/en.ini index 24ffcec98..2147cb17c 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -2,6 +2,13 @@ # Copyright (C) 2018-2023 Igara Studio S.A. # Copyright (C) 2016-2018 David Capello +# Don't translate this string, in extensions you have to use the +# "displayName" property of your "languages" item, see: +# +# https://aseprite.org/docs/extensions/languages/ +# +display_name = English + [advanced_mode] title = Warning - Important description = You are going to enter in "Advanced Mode". diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp index f0fb0d341..06b05cd60 100644 --- a/src/app/commands/cmd_options.cpp +++ b/src/app/commands/cmd_options.cpp @@ -164,6 +164,19 @@ class OptionsWindow : public app::gen::Options { std::string m_name; }; + class LangItem : public ListItem { + public: + LangItem(const LangInfo& langInfo) + : ListItem(langInfo.displayName) + , m_langInfo(langInfo) { + } + const std::string& langId() const { + return m_langInfo.id; + } + private: + LangInfo m_langInfo; + }; + class ExtensionItem : public ListItem { public: ExtensionItem(Extension* extension) @@ -641,8 +654,9 @@ public: #endif // Update language - Strings::instance()->setCurrentLanguage( - language()->getItemText(language()->getSelectedItemIndex())); + if (auto item = dynamic_cast(language()->getSelectedItem())) { + Strings::instance()->setCurrentLanguage(item->langId()); + } m_globPref.timeline.firstFrame(firstFrame()->textInt()); m_pref.general.showFullPath(showFullPath()->isSelected()); @@ -1285,11 +1299,12 @@ private: if (language()->getItemCount() > 0) return; + // Select current language by lang ID Strings* strings = Strings::instance(); std::string curLang = strings->currentLanguage(); - for (const std::string& lang : strings->availableLanguages()) { - int i = language()->addItem(lang); - if (lang == curLang) + for (const LangInfo& lang : strings->availableLanguages()) { + int i = language()->addItem(new LangItem(lang)); + if (lang.id == curLang) language()->setSelectedItemIndex(i); } } diff --git a/src/app/extensions.cpp b/src/app/extensions.cpp index c85e850cf..768a9435e 100644 --- a/src/app/extensions.cpp +++ b/src/app/extensions.cpp @@ -271,13 +271,17 @@ void Extension::addKeys(const std::string& id, const std::string& path) updateCategory(Category::Keys); } -void Extension::addLanguage(const std::string& id, const std::string& path) +void Extension::addLanguage(const std::string& id, + const std::string& path, + const std::string& displayName) { - m_languages[id] = path; + m_languages[id] = LangInfo(id, path, displayName); updateCategory(Category::Languages); } -void Extension::addTheme(const std::string& id, const std::string& path, const std::string& variant) +void Extension::addTheme(const std::string& id, + const std::string& path, + const std::string& variant) { m_themes[id] = ThemeInfo(path, variant); updateCategory(Category::Themes); @@ -871,7 +875,7 @@ std::string Extensions::languagePath(const std::string& langId) auto it = ext->languages().find(langId); if (it != ext->languages().end()) - return it->second; + return it->second.path; } return std::string(); } @@ -1147,15 +1151,19 @@ Extension* Extensions::loadExtension(const std::string& path, for (const auto& lang : languages.array_items()) { std::string langId = lang["id"].string_value(); std::string langPath = lang["path"].string_value(); + std::string langDisplayName = lang["displayName"].string_value(); // The path must be always relative to the extension langPath = base::join_path(path, langPath); - LOG("EXT: New language id=%s path=%s\n", + LOG("EXT: New language id=%s path=%s displayName=%s\n", langId.c_str(), - langPath.c_str()); + langPath.c_str(), + langDisplayName.c_str()); - extension->addLanguage(langId, langPath); + extension->addLanguage(langId, + langPath, + langDisplayName); } } diff --git a/src/app/extensions.h b/src/app/extensions.h index e1ddb8755..072ecfbd6 100644 --- a/src/app/extensions.h +++ b/src/app/extensions.h @@ -9,6 +9,7 @@ #define APP_EXTENSIONS_H_INCLUDED #pragma once +#include "app/i18n/lang_info.h" #include "obs/signal.h" #include "render/dithering_matrix.h" @@ -80,6 +81,7 @@ namespace app { , variant(variant) { } }; + using Languages = std::map; using Themes = std::map; using DitheringMatrices = std::map; @@ -101,13 +103,17 @@ namespace app { const Category category() const { return m_category; } const ExtensionItems& keys() const { return m_keys; } - const ExtensionItems& languages() const { return m_languages; } + const Languages& languages() const { return m_languages; } const Themes& themes() const { return m_themes; } const ExtensionItems& palettes() const { return m_palettes; } void addKeys(const std::string& id, const std::string& path); - void addLanguage(const std::string& id, const std::string& path); - void addTheme(const std::string& id, const std::string& path, const std::string& variant); + void addLanguage(const std::string& id, + const std::string& path, + const std::string& displayName); + void addTheme(const std::string& id, + const std::string& path, + const std::string& variant); void addPalette(const std::string& id, const std::string& path); void addDitheringMatrix(const std::string& id, const std::string& path, @@ -152,7 +158,7 @@ namespace app { #endif ExtensionItems m_keys; - ExtensionItems m_languages; + Languages m_languages; Themes m_themes; ExtensionItems m_palettes; DitheringMatrices m_ditheringMatrices; diff --git a/src/app/i18n/lang_info.h b/src/app/i18n/lang_info.h new file mode 100644 index 000000000..5a9b9d9ec --- /dev/null +++ b/src/app/i18n/lang_info.h @@ -0,0 +1,36 @@ +// Aseprite +// Copyright (C) 2023 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_I18N_LANG_INFO_INCLUDED +#define APP_I18N_LANG_INFO_INCLUDED +#pragma once + +#include + +namespace app { + +struct LangInfo { + std::string id; + std::string path; + std::string displayName; + + LangInfo() = default; + LangInfo(const std::string& id, + const std::string& path, + const std::string& displayName) + : id(id) + , path(path) + , displayName(displayName.empty() ? id: displayName) { + } + + bool operator<(const LangInfo& other) const { + return id < other.id; + } +}; + +} // namespace app + +#endif diff --git a/src/app/i18n/strings.cpp b/src/app/i18n/strings.cpp index a179f274f..46633ed4b 100644 --- a/src/app/i18n/strings.cpp +++ b/src/app/i18n/strings.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2023 Igara Studio S.A. // Copyright (C) 2016-2018 David Capello // // This program is distributed under the terms of @@ -19,6 +20,8 @@ #include "base/fs.h" #include "cfg/cfg.h" +#include + namespace app { static Strings* singleton = nullptr; @@ -46,20 +49,29 @@ Strings::Strings(Preferences& pref, loadLanguage(currentLanguage()); } -std::set Strings::availableLanguages() const +std::set Strings::availableLanguages() const { - std::set result; + std::set result; // Add languages in data/strings/ ResourceFinder rf; rf.includeDataDir("strings"); while (rf.next()) { - if (!base::is_directory(rf.filename())) + const std::string stringsPath = rf.filename(); + if (!base::is_directory(stringsPath)) continue; - for (const auto& fn : base::list_files(rf.filename())) { + for (const auto& fn : base::list_files(stringsPath)) { const std::string langId = base::get_file_title(fn); - result.insert(langId); + std::string path = base::join_path(stringsPath, fn); + std::string displayName = langId; + + // Load display name + cfg::CfgFile cfg; + if (cfg.load(path)) + displayName = cfg.getValue("", "display_name", displayName.c_str()); + + result.insert(LangInfo(langId, path, displayName)); } } @@ -67,12 +79,17 @@ std::set Strings::availableLanguages() const for (const auto& ext : m_exts) { if (ext->isEnabled() && ext->hasLanguages()) { - for (const auto& langId : ext->languages()) - result.insert(langId.first); + for (const auto& lang : ext->languages()) + result.insert(lang.second); } } - ASSERT(result.find(kDefLanguage) != result.end()); + // Check that the default language exists. + ASSERT(std::find_if(result.begin(), result.end(), + [](const LangInfo& li){ + return li.id == kDefLanguage; + }) != result.end()); + return result; } diff --git a/src/app/i18n/strings.h b/src/app/i18n/strings.h index c180c9a9f..4ff627192 100644 --- a/src/app/i18n/strings.h +++ b/src/app/i18n/strings.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2023 Igara Studio S.A. // Copyright (C) 2016-2018 David Capello // // This program is distributed under the terms of @@ -8,14 +9,14 @@ #define APP_I18N_STRINGS_INCLUDED #pragma once +#include "app/i18n/lang_info.h" +#include "obs/signal.h" +#include "strings.ini.h" + #include #include #include -#include "obs/signal.h" - -#include "strings.ini.h" - namespace app { class Preferences; @@ -30,7 +31,7 @@ namespace app { const std::string& translate(const char* id) const; - std::set availableLanguages() const; + std::set availableLanguages() const; std::string currentLanguage() const; void setCurrentLanguage(const std::string& langId); diff --git a/src/cfg/cfg.cpp b/src/cfg/cfg.cpp index 8865cd14b..9d1214e2b 100644 --- a/src/cfg/cfg.cpp +++ b/src/cfg/cfg.cpp @@ -1,5 +1,5 @@ // Aseprite Config Library -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2023 Igara Studio S.A. // Copyright (C) 2014-2017 David Capello // // This file is released under the terms of the MIT license. @@ -88,7 +88,7 @@ public: m_ini.Delete(section, nullptr, true); } - void load(const std::string& filename) { + bool load(const std::string& filename) { m_filename = filename; base::FileHandle file(base::open_file(m_filename, "rb")); @@ -98,8 +98,10 @@ public: if (err != SI_OK) { LOG(ERROR, "CFG: Error %d loading configuration from %s\n", (int)err, m_filename.c_str()); + return false; } } + return true; } void save() { @@ -193,9 +195,9 @@ void CfgFile::deleteSection(const char* section) m_impl->deleteSection(section); } -void CfgFile::load(const std::string& filename) +bool CfgFile::load(const std::string& filename) { - m_impl->load(filename); + return m_impl->load(filename); } void CfgFile::save() diff --git a/src/cfg/cfg.h b/src/cfg/cfg.h index fef98afd0..974d7a95b 100644 --- a/src/cfg/cfg.h +++ b/src/cfg/cfg.h @@ -1,5 +1,5 @@ // Aseprite Config Library -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2023 Igara Studio S.A. // Copyright (C) 2014-2016 David Capello // // This file is released under the terms of the MIT license. @@ -37,7 +37,7 @@ namespace cfg { void deleteValue(const char* section, const char* name); void deleteSection(const char* section); - void load(const std::string& filename); + bool load(const std::string& filename); void save(); private: