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) 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".

View File

@ -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<const LangItem*>(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);
}
}

View File

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

View File

@ -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<std::string, LangInfo>;
using Themes = std::map<std::string, ThemeInfo>;
using DitheringMatrices = std::map<std::string, DitheringMatrixInfo>;
@ -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;

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
// 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 <algorithm>
namespace app {
static Strings* singleton = nullptr;
@ -46,20 +49,29 @@ Strings::Strings(Preferences& pref,
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/
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<std::string> 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;
}

View File

@ -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 <set>
#include <string>
#include <unordered_map>
#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<std::string> availableLanguages() const;
std::set<LangInfo> availableLanguages() const;
std::string currentLanguage() const;
void setCurrentLanguage(const std::string& langId);

View File

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

View File

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