Now an extension can contain a language (#124, #1403)

This commit is contained in:
David Capello 2018-03-19 19:37:39 -03:00
parent 0429282967
commit 0e0b83b478
8 changed files with 213 additions and 30 deletions

View File

@ -829,6 +829,7 @@ section_experimental = Experimental
general = General
screen_scaling = Screen Scaling:
ui_scaling = UI Elements Scaling:
language = Language:
gpu_acceleration = GPU acceleration
gpu_acceleration_tooltip = Check this option to enable hardware acceleration
show_menu_bar = Show Aseprite menu bar

View File

@ -28,7 +28,6 @@
<!-- General -->
<vbox id="section_general">
<separator text="@.section_general" horizontal="true" />
<hbox>
<grid columns="2">
<label text="@.screen_scaling" />
<combobox id="screen_scale">
@ -44,8 +43,10 @@
<listitem text="300%" value="3" />
<listitem text="400%" value="4" />
</combobox>
<label text="@.language" />
<combobox id="language" />
</grid>
</hbox>
<check id="gpu_acceleration"
text="@.gpu_acceleration"
tooltip="@.gpu_acceleration_tooltip" />

View File

@ -24,6 +24,7 @@
#include "app/file/file_formats_manager.h"
#include "app/file_system.h"
#include "app/gui_xml.h"
#include "app/i18n/strings.h"
#include "app/ini_file.h"
#include "app/log.h"
#include "app/modules.h"
@ -193,8 +194,10 @@ void App::initialize(const AppOptions& options)
if (isGui()) {
LOG("APP: GUI mode\n");
// Setup the GUI cursor and redraw screen
// Load main language (which might be in an extension)
Strings::instance()->loadCurrentLanguage();
// Setup the GUI cursor and redraw screen
ui::set_use_native_cursors(preferences().cursor.useNativeCursor());
ui::set_mouse_cursor_scale(preferences().cursor.cursorScale());
ui::set_mouse_cursor(kArrowCursor);

View File

@ -41,6 +41,7 @@
namespace app {
static const char* kSectionGeneralId = "section_general";
static const char* kSectionBgId = "section_bg";
static const char* kSectionGridId = "section_grid";
static const char* kSectionThemeId = "section_theme";
@ -375,6 +376,11 @@ public:
onChangeGridScope();
sectionListbox()->selectIndex(m_curSection);
// Refill languages combobox when extensions are enabled/disabled
m_extLanguagesChanges =
App::instance()->extensions().LanguagesChange.connect(
base::Bind<void>(&OptionsWindow::refillLanguages, this));
// Reload themes when extensions are enabled/disabled
m_extThemesChanges =
App::instance()->extensions().ThemesChange.connect(
@ -386,6 +392,10 @@ public:
}
void saveConfig() {
// Update language
Strings::instance()->setCurrentLanguage(
language()->getItemText(language()->getSelectedItemIndex()));
m_pref.general.autoshowTimeline(autotimeline()->isSelected());
m_pref.general.rewindOnStop(rewindOnStop()->isSelected());
m_globPref.timeline.firstFrame(firstFrame()->textInt());
@ -603,8 +613,13 @@ private:
panel()->showChild(findChild(item->getValue().c_str()));
m_curSection = sectionListbox()->getSelectedIndex();
if (item->getValue() == kSectionBgId)
// General section
if (item->getValue() == kSectionGeneralId)
loadLanguages();
// Background section
else if (item->getValue() == kSectionBgId)
onChangeBgScope();
// Grid section
else if (item->getValue() == kSectionGridId)
onChangeGridScope();
// Load themes
@ -744,6 +759,25 @@ private:
}
}
void refillLanguages() {
language()->removeAllItems();
loadLanguages();
}
void loadLanguages() {
// Languages already loaded
if (language()->getItemCount() > 0)
return;
Strings* strings = Strings::instance();
std::string curLang = strings->currentLanguage();
for (const std::string& lang : strings->availableLanguages()) {
int i = language()->addItem(lang);
if (lang == curLang)
language()->setSelectedItemIndex(i);
}
}
void reloadThemes() {
while (themeList()->firstChild())
delete themeList()->lastChild();
@ -1091,6 +1125,7 @@ private:
DocumentPreferences& m_docPref;
DocumentPreferences* m_curPref;
int& m_curSection;
obs::scoped_connection m_extLanguagesChanges;
obs::scoped_connection m_extThemesChanges;
std::string m_restoreThisTheme;
int m_restoreScreenScaling;

View File

@ -220,6 +220,11 @@ Extension::~Extension()
it.second.destroyMatrix();
}
void Extension::addLanguage(const std::string& id, const std::string& path)
{
m_languages[id] = path;
}
void Extension::addTheme(const std::string& id, const std::string& path)
{
m_themes[id] = path;
@ -419,6 +424,19 @@ Extensions::~Extensions()
delete ext;
}
std::string Extensions::languagePath(const std::string& langId)
{
for (auto ext : m_extensions) {
if (!ext->isEnabled()) // Ignore disabled extensions
continue;
auto it = ext->languages().find(langId);
if (it != ext->languages().end())
return it->second;
}
return std::string();
}
std::string Extensions::themePath(const std::string& themeId)
{
for (auto ext : m_extensions) {
@ -630,6 +648,24 @@ Extension* Extensions::loadExtension(const std::string& path,
auto contributes = json["contributes"];
if (contributes.is_object()) {
// Languages
auto languages = contributes["languages"];
if (languages.is_array()) {
for (const auto& lang : languages.array_items()) {
std::string langId = lang["id"].string_value();
std::string langPath = lang["path"].string_value();
// The path must be always relative to the extension
langPath = base::join_path(path, langPath);
LOG("EXT: New language '%s' in '%s'\n",
langId.c_str(),
langPath.c_str());
extension->addLanguage(langId, langPath);
}
}
// Themes
auto themes = contributes["themes"];
if (themes.is_array()) {
@ -695,6 +731,7 @@ Extension* Extensions::loadExtension(const std::string& path,
void Extensions::generateExtensionSignals(Extension* extension)
{
if (extension->hasLanguages()) LanguagesChange(extension);
if (extension->hasThemes()) ThemesChange(extension);
if (extension->hasPalettes()) PalettesChange(extension);
if (extension->hasDitheringMatrices()) DitheringMatricesChange(extension);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2017 David Capello
// Copyright (C) 2017-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -67,9 +67,11 @@ namespace app {
const std::string& version() const { return m_version; }
const std::string& displayName() const { return m_displayName; }
const ExtensionItems& languages() const { return m_languages; }
const ExtensionItems& themes() const { return m_themes; }
const ExtensionItems& palettes() const { return m_palettes; }
void addLanguage(const std::string& id, const std::string& path);
void addTheme(const std::string& id, const std::string& path);
void addPalette(const std::string& id, const std::string& path);
void addDitheringMatrix(const std::string& id,
@ -81,6 +83,7 @@ namespace app {
bool canBeDisabled() const;
bool canBeUninstalled() const;
bool hasLanguages() const { return !m_languages.empty(); }
bool hasThemes() const { return !m_themes.empty(); }
bool hasPalettes() const { return !m_palettes.empty(); }
bool hasDitheringMatrices() const { return !m_ditheringMatrices.empty(); }
@ -92,6 +95,7 @@ namespace app {
bool isCurrentTheme() const;
bool isDefaultTheme() const;
ExtensionItems m_languages;
ExtensionItems m_themes;
ExtensionItems m_palettes;
std::map<std::string, DitheringMatrixInfo> m_ditheringMatrices;
@ -121,6 +125,7 @@ namespace app {
Extension* installCompressedExtension(const std::string& zipFn,
const ExtensionInfo& info);
std::string languagePath(const std::string& langId);
std::string themePath(const std::string& themeId);
std::string palettePath(const std::string& palId);
ExtensionItems palettes() const;
@ -128,6 +133,7 @@ namespace app {
std::vector<Extension::DitheringMatrixInfo> ditheringMatrices();
obs::signal<void(Extension*)> NewExtension;
obs::signal<void(Extension*)> LanguagesChange;
obs::signal<void(Extension*)> ThemesChange;
obs::signal<void(Extension*)> PalettesChange;
obs::signal<void(Extension*)> DitheringMatricesChange;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2016, 2017 David Capello
// Copyright (C) 2016-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -10,8 +10,11 @@
#include "app/i18n/strings.h"
#include "app/app.h"
#include "app/extensions.h"
#include "app/pref/preferences.h"
#include "app/resource_finder.h"
#include "app/ui/main_window.h"
#include "app/xml_document.h"
#include "app/xml_exception.h"
#include "base/fs.h"
@ -19,6 +22,8 @@
namespace app {
static const char* kDefLanguage = "en";
// static
Strings* Strings::instance()
{
@ -30,16 +35,101 @@ Strings* Strings::instance()
Strings::Strings()
{
const std::string lang = Preferences::instance().general.language();
LOG("I18N: Loading strings/%s.ini file\n", lang.c_str());
loadLanguage(kDefLanguage);
}
std::set<std::string> Strings::availableLanguages() const
{
std::set<std::string> result;
// Add languages in data/strings/
ResourceFinder rf;
rf.includeDataDir(("strings/" + lang + ".ini").c_str());
if (!rf.findFirst())
throw base::Exception("strings/" + lang + ".txt was not found");
rf.includeDataDir("strings");
while (rf.next()) {
if (!base::is_directory(rf.filename()))
continue;
for (const auto& fn : base::list_files(rf.filename())) {
const std::string langId = base::get_file_title(fn);
result.insert(langId);
}
}
// Add languages in extensions
for (const auto& ext : App::instance()->extensions()) {
if (ext->isEnabled() &&
ext->hasLanguages()) {
for (const auto& langId : ext->languages())
result.insert(langId.first);
}
}
ASSERT(result.find(kDefLanguage) != result.end);
return result;
}
std::string Strings::currentLanguage() const
{
return Preferences::instance().general.language();
}
void Strings::setCurrentLanguage(const std::string& langId)
{
// Do nothing (same language)
if (currentLanguage() == langId)
return;
Preferences::instance().general.language(langId);
loadLanguage(langId);
// Reload menus
App::instance()->mainWindow()->reloadMenus();
}
// Called when extensions are available
void Strings::loadCurrentLanguage()
{
std::string langId = currentLanguage();
if (langId != kDefLanguage)
loadLanguage(langId);
}
void Strings::loadLanguage(const std::string& langId)
{
m_strings.clear();
loadStringsFromDataDir(kDefLanguage);
if (langId != kDefLanguage) {
loadStringsFromDataDir(langId);
loadStringsFromExtension(langId);
}
}
void Strings::loadStringsFromDataDir(const std::string& langId)
{
// Load the English language file from the Aseprite data directory (so we have the most update list of strings)
LOG("I18N: Loading strings/%s.ini file\n", langId.c_str());
ResourceFinder rf;
rf.includeDataDir(("strings/" + langId + ".ini").c_str());
if (!rf.findFirst()) {
LOG("strings/%s.ini was not found", langId.c_str());
return;
}
loadStringsFromFile(rf.filename());
}
void Strings::loadStringsFromExtension(const std::string& langId)
{
Extensions& exts = App::instance()->extensions();
std::string fn = exts.languagePath(langId);
if (!fn.empty() && base::is_file(fn))
loadStringsFromFile(fn);
}
void Strings::loadStringsFromFile(const std::string& fn)
{
cfg::CfgFile cfg;
cfg.load(rf.filename());
cfg.load(fn);
std::vector<std::string> sections;
std::vector<std::string> keys;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2016-2017 David Capello
// Copyright (C) 2016-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -8,9 +8,9 @@
#define APP_I18N_STRINGS_INCLUDED
#pragma once
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "strings.ini.h"
@ -23,9 +23,19 @@ namespace app {
const std::string& translate(const char* id) const;
void loadCurrentLanguage();
std::set<std::string> availableLanguages() const;
std::string currentLanguage() const;
void setCurrentLanguage(const std::string& langId);
private:
Strings();
void loadLanguage(const std::string& langId);
void loadStringsFromDataDir(const std::string& langId);
void loadStringsFromExtension(const std::string& langId);
void loadStringsFromFile(const std::string& fn);
mutable std::unordered_map<std::string, std::string> m_strings;
};