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).
This commit is contained in:
David Capello 2023-07-12 12:25:30 -03:00
parent 35e64ad2f3
commit 00b75a76a8
9 changed files with 127 additions and 35 deletions

View File

@ -2,6 +2,13 @@
# Copyright (C) 2018-2023 Igara Studio S.A. # Copyright (C) 2018-2023 Igara Studio S.A.
# Copyright (C) 2016-2018 David Capello # 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] [advanced_mode]
title = Warning - Important title = Warning - Important
description = You are going to enter in "Advanced Mode". description = You are going to enter in "Advanced Mode".

View File

@ -164,6 +164,19 @@ class OptionsWindow : public app::gen::Options {
std::string m_name; 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 { class ExtensionItem : public ListItem {
public: public:
ExtensionItem(Extension* extension) ExtensionItem(Extension* extension)
@ -641,8 +654,9 @@ public:
#endif #endif
// Update language // Update language
Strings::instance()->setCurrentLanguage( if (auto item = dynamic_cast<const LangItem*>(language()->getSelectedItem())) {
language()->getItemText(language()->getSelectedItemIndex())); Strings::instance()->setCurrentLanguage(item->langId());
}
m_globPref.timeline.firstFrame(firstFrame()->textInt()); m_globPref.timeline.firstFrame(firstFrame()->textInt());
m_pref.general.showFullPath(showFullPath()->isSelected()); m_pref.general.showFullPath(showFullPath()->isSelected());
@ -1285,11 +1299,12 @@ private:
if (language()->getItemCount() > 0) if (language()->getItemCount() > 0)
return; return;
// Select current language by lang ID
Strings* strings = Strings::instance(); Strings* strings = Strings::instance();
std::string curLang = strings->currentLanguage(); std::string curLang = strings->currentLanguage();
for (const std::string& lang : strings->availableLanguages()) { for (const LangInfo& lang : strings->availableLanguages()) {
int i = language()->addItem(lang); int i = language()->addItem(new LangItem(lang));
if (lang == curLang) if (lang.id == curLang)
language()->setSelectedItemIndex(i); language()->setSelectedItemIndex(i);
} }
} }

View File

@ -271,13 +271,17 @@ void Extension::addKeys(const std::string& id, const std::string& path)
updateCategory(Category::Keys); 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); 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); m_themes[id] = ThemeInfo(path, variant);
updateCategory(Category::Themes); updateCategory(Category::Themes);
@ -871,7 +875,7 @@ std::string Extensions::languagePath(const std::string& langId)
auto it = ext->languages().find(langId); auto it = ext->languages().find(langId);
if (it != ext->languages().end()) if (it != ext->languages().end())
return it->second; return it->second.path;
} }
return std::string(); return std::string();
} }
@ -1147,15 +1151,19 @@ Extension* Extensions::loadExtension(const std::string& path,
for (const auto& lang : languages.array_items()) { for (const auto& lang : languages.array_items()) {
std::string langId = lang["id"].string_value(); std::string langId = lang["id"].string_value();
std::string langPath = lang["path"].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 // The path must be always relative to the extension
langPath = base::join_path(path, langPath); 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(), langId.c_str(),
langPath.c_str()); langPath.c_str(),
langDisplayName.c_str());
extension->addLanguage(langId, langPath); extension->addLanguage(langId,
langPath,
langDisplayName);
} }
} }

View File

@ -9,6 +9,7 @@
#define APP_EXTENSIONS_H_INCLUDED #define APP_EXTENSIONS_H_INCLUDED
#pragma once #pragma once
#include "app/i18n/lang_info.h"
#include "obs/signal.h" #include "obs/signal.h"
#include "render/dithering_matrix.h" #include "render/dithering_matrix.h"
@ -80,6 +81,7 @@ namespace app {
, variant(variant) { } , variant(variant) { }
}; };
using Languages = std::map<std::string, LangInfo>;
using Themes = std::map<std::string, ThemeInfo>; using Themes = std::map<std::string, ThemeInfo>;
using DitheringMatrices = std::map<std::string, DitheringMatrixInfo>; using DitheringMatrices = std::map<std::string, DitheringMatrixInfo>;
@ -101,13 +103,17 @@ namespace app {
const Category category() const { return m_category; } const Category category() const { return m_category; }
const ExtensionItems& keys() const { return m_keys; } 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 Themes& themes() const { return m_themes; }
const ExtensionItems& palettes() const { return m_palettes; } const ExtensionItems& palettes() const { return m_palettes; }
void addKeys(const std::string& id, const std::string& path); void addKeys(const std::string& id, const std::string& path);
void addLanguage(const std::string& id, const std::string& path); void addLanguage(const std::string& id,
void addTheme(const std::string& id, const std::string& path, const std::string& variant); 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 addPalette(const std::string& id, const std::string& path);
void addDitheringMatrix(const std::string& id, void addDitheringMatrix(const std::string& id,
const std::string& path, const std::string& path,
@ -152,7 +158,7 @@ namespace app {
#endif #endif
ExtensionItems m_keys; ExtensionItems m_keys;
ExtensionItems m_languages; Languages m_languages;
Themes m_themes; Themes m_themes;
ExtensionItems m_palettes; ExtensionItems m_palettes;
DitheringMatrices m_ditheringMatrices; DitheringMatrices m_ditheringMatrices;

36
src/app/i18n/lang_info.h Normal file
View File

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

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2023 Igara Studio S.A.
// Copyright (C) 2016-2018 David Capello // Copyright (C) 2016-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -19,6 +20,8 @@
#include "base/fs.h" #include "base/fs.h"
#include "cfg/cfg.h" #include "cfg/cfg.h"
#include <algorithm>
namespace app { namespace app {
static Strings* singleton = nullptr; static Strings* singleton = nullptr;
@ -46,20 +49,29 @@ Strings::Strings(Preferences& pref,
loadLanguage(currentLanguage()); loadLanguage(currentLanguage());
} }
std::set<std::string> Strings::availableLanguages() const std::set<LangInfo> Strings::availableLanguages() const
{ {
std::set<std::string> result; std::set<LangInfo> result;
// Add languages in data/strings/ // Add languages in data/strings/
ResourceFinder rf; ResourceFinder rf;
rf.includeDataDir("strings"); rf.includeDataDir("strings");
while (rf.next()) { while (rf.next()) {
if (!base::is_directory(rf.filename())) const std::string stringsPath = rf.filename();
if (!base::is_directory(stringsPath))
continue; 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); 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<std::string> Strings::availableLanguages() const
for (const auto& ext : m_exts) { for (const auto& ext : m_exts) {
if (ext->isEnabled() && if (ext->isEnabled() &&
ext->hasLanguages()) { ext->hasLanguages()) {
for (const auto& langId : ext->languages()) for (const auto& lang : ext->languages())
result.insert(langId.first); 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; return result;
} }

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2023 Igara Studio S.A.
// Copyright (C) 2016-2018 David Capello // Copyright (C) 2016-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -8,14 +9,14 @@
#define APP_I18N_STRINGS_INCLUDED #define APP_I18N_STRINGS_INCLUDED
#pragma once #pragma once
#include "app/i18n/lang_info.h"
#include "obs/signal.h"
#include "strings.ini.h"
#include <set> #include <set>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include "obs/signal.h"
#include "strings.ini.h"
namespace app { namespace app {
class Preferences; class Preferences;
@ -30,7 +31,7 @@ namespace app {
const std::string& translate(const char* id) const; const std::string& translate(const char* id) const;
std::set<std::string> availableLanguages() const; std::set<LangInfo> availableLanguages() const;
std::string currentLanguage() const; std::string currentLanguage() const;
void setCurrentLanguage(const std::string& langId); void setCurrentLanguage(const std::string& langId);

View File

@ -1,5 +1,5 @@
// Aseprite Config Library // 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 // Copyright (C) 2014-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -88,7 +88,7 @@ public:
m_ini.Delete(section, nullptr, true); m_ini.Delete(section, nullptr, true);
} }
void load(const std::string& filename) { bool load(const std::string& filename) {
m_filename = filename; m_filename = filename;
base::FileHandle file(base::open_file(m_filename, "rb")); base::FileHandle file(base::open_file(m_filename, "rb"));
@ -98,8 +98,10 @@ public:
if (err != SI_OK) { if (err != SI_OK) {
LOG(ERROR, "CFG: Error %d loading configuration from %s\n", LOG(ERROR, "CFG: Error %d loading configuration from %s\n",
(int)err, m_filename.c_str()); (int)err, m_filename.c_str());
return false;
} }
} }
return true;
} }
void save() { void save() {
@ -193,9 +195,9 @@ void CfgFile::deleteSection(const char* section)
m_impl->deleteSection(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() void CfgFile::save()

View File

@ -1,5 +1,5 @@
// Aseprite Config Library // Aseprite Config Library
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2014-2016 David Capello // Copyright (C) 2014-2016 David Capello
// //
// This file is released under the terms of the MIT license. // 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 deleteValue(const char* section, const char* name);
void deleteSection(const char* section); void deleteSection(const char* section);
void load(const std::string& filename); bool load(const std::string& filename);
void save(); void save();
private: private: