Fix crashes with ill-formed fmt format strings (fix #2274)

There is a third-party translation (and can happen with our own
translations) that a fmt format string is ill-formed in the .ini file
of the translation (this could happen even if the en.ini file was
manually modified/broken by hand).

This patch includes a refactor of the Strings class so we can:

1) Static check at compile-time about the number of required arguments
   to format a string (no need to call fmt::format() directly with
   arbitrary number of args)
2) If a string is not valid for the fmt library, the runtime exception
   is caught and the default (English) string is returned.
This commit is contained in:
David Capello 2024-06-20 20:14:29 -03:00
parent 3d50a85d14
commit 32a099dcb6
59 changed files with 321 additions and 270 deletions

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A. // Copyright (C) 2019-2024 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
@ -20,7 +20,6 @@
#include "app/pref/preferences.h" #include "app/pref/preferences.h"
#include "app/tx.h" #include "app/tx.h"
#include "doc/palette.h" #include "doc/palette.h"
#include "fmt/format.h"
#ifdef ENABLE_SCRIPTING #ifdef ENABLE_SCRIPTING
#include "app/script/luacpp.h" #include "app/script/luacpp.h"
@ -148,7 +147,7 @@ std::string AddColorCommand::onGetFriendlyName() const
case AddColorSource::Bg: source = Strings::commands_AddColor_Background(); break; case AddColorSource::Bg: source = Strings::commands_AddColor_Background(); break;
case AddColorSource::Color: source = Strings::commands_AddColor_Specific(); break; case AddColorSource::Color: source = Strings::commands_AddColor_Specific(); break;
} }
return fmt::format(getBaseFriendlyName(), source); return Strings::commands_AddColor(source);
} }
Command* CommandFactory::createAddColorCommand() Command* CommandFactory::createAddColorCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020-2022 Igara Studio S.A. // Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2018 David Capello // Copyright (C) 2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -22,7 +22,6 @@
#include "doc/cel.h" #include "doc/cel.h"
#include "doc/cels_range.h" #include "doc/cels_range.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "fmt/format.h"
#include <string> #include <string>
@ -109,9 +108,8 @@ void CelOpacityCommand::onExecute(Context* context)
std::string CelOpacityCommand::onGetFriendlyName() const std::string CelOpacityCommand::onGetFriendlyName() const
{ {
return fmt::format(getBaseFriendlyName(), return Strings::commands_CelOpacity(m_opacity,
m_opacity, int(100.0 * m_opacity / 255.0));
int(100.0 * m_opacity / 255.0));
} }
Command* CommandFactory::createCelOpacityCommand() Command* CommandFactory::createCelOpacityCommand()

View File

@ -27,7 +27,6 @@
#include "doc/image_ref.h" #include "doc/image_ref.h"
#include "doc/primitives.h" #include "doc/primitives.h"
#include "doc/tile.h" #include "doc/tile.h"
#include "fmt/format.h"
#include <algorithm> #include <algorithm>
#include <string> #include <string>
@ -328,10 +327,10 @@ std::string ChangeBrushCommand::onGetFriendlyName() const
case FlipD: change = Strings::commands_ChangeBrush_FlipD(); break; case FlipD: change = Strings::commands_ChangeBrush_FlipD(); break;
case Rotate90CW: change = Strings::commands_ChangeBrush_Rotate90CW(); break; case Rotate90CW: change = Strings::commands_ChangeBrush_Rotate90CW(); break;
case CustomBrush: case CustomBrush:
change = fmt::format(Strings::commands_ChangeBrush_CustomBrush(), m_slot); change = Strings::commands_ChangeBrush_CustomBrush(m_slot);
break; break;
} }
return fmt::format(getBaseFriendlyName(), change); return Strings::commands_ChangeBrush(change);
} }
Command* CommandFactory::createChangeBrushCommand() Command* CommandFactory::createChangeBrushCommand()

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -17,7 +18,6 @@
#include "app/modules/palettes.h" #include "app/modules/palettes.h"
#include "app/ui/color_bar.h" #include "app/ui/color_bar.h"
#include "doc/palette.h" #include "doc/palette.h"
#include "fmt/format.h"
namespace app { namespace app {
@ -120,7 +120,7 @@ std::string ChangeColorCommand::onGetFriendlyName() const
break; break;
} }
return fmt::format(getBaseFriendlyName(), action); return Strings::commands_ChangeColor(action);
} }
Command* CommandFactory::createChangeColorCommand() Command* CommandFactory::createChangeColorCommand()

View File

@ -727,7 +727,7 @@ std::string ChangePixelFormatCommand::onGetFriendlyName() const
else else
conversion = Strings::commands_ChangePixelFormat_MoreOptions(); conversion = Strings::commands_ChangePixelFormat_MoreOptions();
return fmt::format(getBaseFriendlyName(), conversion); return Strings::commands_ChangePixelFormat(conversion);
} }
Command* CommandFactory::createChangePixelFormatCommand() Command* CommandFactory::createChangePixelFormatCommand()

View File

@ -48,7 +48,7 @@
#include "export_sprite_sheet.xml.h" #include "export_sprite_sheet.xml.h"
#include <limits> #include <limits>
#include <sstream> #include <string>
namespace app { namespace app {
@ -93,20 +93,19 @@ bool ask_overwrite(const bool askFilename, const std::string& filename,
(askDataname && (askDataname &&
!dataname.empty() && !dataname.empty() &&
base::is_file(dataname))) { base::is_file(dataname))) {
std::stringstream text; std::string text;
if (base::is_file(filename)) if (base::is_file(filename))
text << "<<" << base::get_file_name(filename).c_str(); text += "<<" + base::get_file_name(filename);
if (base::is_file(dataname)) if (base::is_file(dataname))
text << "<<" << base::get_file_name(dataname).c_str(); text += "<<" + base::get_file_name(dataname);
const int ret = const int ret =
OptionalAlert::show( OptionalAlert::show(
Preferences::instance().spriteSheet.showOverwriteFilesAlert, Preferences::instance().spriteSheet.showOverwriteFilesAlert,
1, // Yes is the default option when the alert dialog is disabled 1, // Yes is the default option when the alert dialog is disabled
fmt::format(Strings::alerts_overwrite_files_on_export_sprite_sheet(), Strings::alerts_overwrite_files_on_export_sprite_sheet(text));
text.str()));
if (ret != 1) if (ret != 1)
return false; return false;
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -39,7 +39,6 @@
#include "doc/layer.h" #include "doc/layer.h"
#include "doc/mask.h" #include "doc/mask.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "fmt/format.h"
#include "gfx/size.h" #include "gfx/size.h"
@ -256,7 +255,7 @@ std::string FlipCommand::onGetFriendlyName() const
else else
orientation = Strings::commands_Flip_Vertically(); orientation = Strings::commands_Flip_Vertically();
return fmt::format(getBaseFriendlyName(), content, orientation); return Strings::commands_Flip(content, orientation);
} }
Command* CommandFactory::createFlipCommand() Command* CommandFactory::createFlipCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -31,7 +31,6 @@
#include "base/scoped_value.h" #include "base/scoped_value.h"
#include "base/split_string.h" #include "base/split_string.h"
#include "base/string.h" #include "base/string.h"
#include "fmt/format.h"
#include "ui/alert.h" #include "ui/alert.h"
#include "ui/fit_bounds.h" #include "ui/fit_bounds.h"
#include "ui/graphics.h" #include "ui/graphics.h"
@ -239,9 +238,7 @@ private:
ui::Accelerator accel = m_key->accels()[index]; ui::Accelerator accel = m_key->accels()[index];
if (ui::Alert::show( if (ui::Alert::show(
fmt::format( Strings::alerts_delete_shortcut(accel.toString())) != 1)
Strings::alerts_delete_shortcut(),
accel.toString())) != 1)
return; return;
m_key->disableAccel(accel, KeySource::UserDefined); m_key->disableAccel(accel, KeySource::UserDefined);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020-2022 Igara Studio S.A. // Copyright (C) 2020-2024 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
@ -20,7 +20,6 @@
#include "app/tx.h" #include "app/tx.h"
#include "app/ui/timeline/timeline.h" #include "app/ui/timeline/timeline.h"
#include "doc/layer.h" #include "doc/layer.h"
#include "fmt/format.h"
#include <string> #include <string>
@ -94,9 +93,8 @@ void LayerOpacityCommand::onExecute(Context* context)
std::string LayerOpacityCommand::onGetFriendlyName() const std::string LayerOpacityCommand::onGetFriendlyName() const
{ {
return fmt::format(getBaseFriendlyName(), return Strings::commands_LayerOpacity(m_opacity,
m_opacity, int(100.0 * m_opacity / 255.0));
int(100.0 * m_opacity / 255.0));
} }
Command* CommandFactory::createLayerOpacityCommand() Command* CommandFactory::createLayerOpacityCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -20,7 +20,6 @@
#include "app/util/msk_file.h" #include "app/util/msk_file.h"
#include "doc/mask.h" #include "doc/mask.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/alert.h" #include "ui/alert.h"
namespace app { namespace app {
@ -70,7 +69,7 @@ void LoadMaskCommand::onExecute(Context* context)
std::unique_ptr<Mask> mask(load_msk_file(m_filename.c_str())); std::unique_ptr<Mask> mask(load_msk_file(m_filename.c_str()));
if (!mask) { if (!mask) {
ui::Alert::show(fmt::format(Strings::alerts_error_loading_file(), m_filename)); ui::Alert::show(Strings::alerts_error_loading_file(m_filename));
return; return;
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2023 Igara Studio S.A. // Copyright (C) 2023-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -19,7 +19,6 @@
#include "app/modules/palettes.h" #include "app/modules/palettes.h"
#include "base/fs.h" #include "base/fs.h"
#include "doc/palette.h" #include "doc/palette.h"
#include "fmt/format.h"
#include "ui/alert.h" #include "ui/alert.h"
namespace app { namespace app {
@ -81,7 +80,7 @@ void LoadPaletteCommand::onExecute(Context* context)
std::unique_ptr<doc::Palette> palette(load_palette(filename.c_str())); std::unique_ptr<doc::Palette> palette(load_palette(filename.c_str()));
if (!palette) { if (!palette) {
if (context->isUIAvailable()) if (context->isUIAvailable())
ui::Alert::show(fmt::format(Strings::alerts_error_loading_file(), filename)); ui::Alert::show(Strings::alerts_error_loading_file(filename));
return; return;
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello // Copyright (C) 2015-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -23,7 +23,6 @@
#include "doc/brush_type.h" #include "doc/brush_type.h"
#include "doc/mask.h" #include "doc/mask.h"
#include "filters/neighboring_pixels.h" #include "filters/neighboring_pixels.h"
#include "fmt/format.h"
#include "modify_selection.xml.h" #include "modify_selection.xml.h"
@ -151,11 +150,9 @@ std::string ModifySelectionCommand::onGetFriendlyName() const
{ {
std::string quantity; std::string quantity;
if (m_quantity > 0) if (m_quantity > 0)
quantity = fmt::format(Strings::commands_ModifySelection_Quantity(), m_quantity); quantity = Strings::commands_ModifySelection_Quantity(m_quantity);
return fmt::format(getBaseFriendlyName(), return Strings::commands_ModifySelection(getActionName(), quantity);
getActionName(),
quantity);
} }
std::string ModifySelectionCommand::getActionName() const std::string ModifySelectionCommand::getActionName() const

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -26,7 +26,6 @@
#include "base/convert_to.h" #include "base/convert_to.h"
#include "doc/mask.h" #include "doc/mask.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/view.h" #include "ui/view.h"
namespace app { namespace app {
@ -124,8 +123,8 @@ std::string MoveMaskCommand::onGetFriendlyName() const
case Boundaries: content = Strings::commands_MoveMask_Boundaries(); break; case Boundaries: content = Strings::commands_MoveMask_Boundaries(); break;
case Content: content = Strings::commands_MoveMask_Content(); break; case Content: content = Strings::commands_MoveMask_Content(); break;
} }
return fmt::format(getBaseFriendlyName(), return Strings::commands_MoveMask(content,
content, m_moveThing.getFriendlyString()); m_moveThing.getFriendlyString());
} }
Command* CommandFactory::createMoveMaskCommand() Command* CommandFactory::createMoveMaskCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -285,9 +285,9 @@ void NewFileCommand::onExecute(Context* ctx)
std::string NewFileCommand::onGetFriendlyName() const std::string NewFileCommand::onGetFriendlyName() const
{ {
if (params().fromClipboard()) if (params().fromClipboard())
return fmt::format(Strings::commands_NewFile_FromClipboard()); return Strings::commands_NewFile_FromClipboard();
else else
return fmt::format(Strings::commands_NewFile()); return Strings::commands_NewFile();
} }
Command* CommandFactory::createNewFileCommand() Command* CommandFactory::createNewFileCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -29,7 +29,6 @@
#include "doc/image.h" #include "doc/image.h"
#include "doc/layer.h" #include "doc/layer.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/ui.h" #include "ui/ui.h"
#include <stdexcept> #include <stdexcept>
@ -181,8 +180,7 @@ void NewFrameCommand::onExecute(Context* context)
update_screen_for_document(document); update_screen_for_document(document);
StatusBar::instance()->showTip( StatusBar::instance()->showTip(
1000, fmt::format( 1000, Strings::commands_NewFrame_tooltip(
Strings::commands_NewFrame_tooltip(),
(int)context->activeSite().frame()+1, (int)context->activeSite().frame()+1,
(int)sprite->totalFrames())); (int)sprite->totalFrames()));

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -274,8 +274,7 @@ void NewLayerCommand::onExecute(Context* context)
Layer* layer = nullptr; Layer* layer = nullptr;
{ {
ContextWriter writer(reader); ContextWriter writer(reader);
Tx tx(writer, Tx tx(writer, Strings::commands_NewLayer(layerPrefix()));
fmt::format(Strings::commands_NewLayer(), layerPrefix()));
DocApi api = document->getApi(tx); DocApi api = document->getApi(tx);
bool afterBackground = false; bool afterBackground = false;
@ -513,17 +512,17 @@ std::string NewLayerCommand::onGetFriendlyName() const
{ {
std::string text; std::string text;
if (m_place == Place::BeforeActiveLayer) if (m_place == Place::BeforeActiveLayer)
text = fmt::format(Strings::commands_NewLayer_BeforeActiveLayer(), layerPrefix()); text = Strings::commands_NewLayer_BeforeActiveLayer(layerPrefix());
else else
text = fmt::format(Strings::commands_NewLayer(), layerPrefix()); text = Strings::commands_NewLayer(layerPrefix());
if (params().fromClipboard()) if (params().fromClipboard())
text = fmt::format(Strings::commands_NewLayer_FromClipboard(), text); text = Strings::commands_NewLayer_FromClipboard(text);
if (params().viaCopy()) if (params().viaCopy())
text = fmt::format(Strings::commands_NewLayer_ViaCopy(), text); text = Strings::commands_NewLayer_ViaCopy(text);
if (params().viaCut()) if (params().viaCut())
text = fmt::format(Strings::commands_NewLayer_ViaCut(), text); text = Strings::commands_NewLayer_ViaCut(text);
if (params().ask()) if (params().ask())
text = fmt::format(Strings::commands_NewLayer_WithDialog(), text); text = Strings::commands_NewLayer_WithDialog(text);
return text; return text;
} }

View File

@ -888,9 +888,7 @@ public:
m_pref.save(); m_pref.save();
if (!warnings.empty()) { if (!warnings.empty()) {
ui::Alert::show( ui::Alert::show(Strings::alerts_restart_by_preferences(warnings));
fmt::format(Strings::alerts_restart_by_preferences(),
warnings));
} }
// Probably it's safe to switch this flag in runtime // Probably it's safe to switch this flag in runtime
@ -929,8 +927,7 @@ public:
} }
// Install? // Install?
if (ui::Alert::show( if (ui::Alert::show(Strings::alerts_install_extension(filename)) != 1)
fmt::format(Strings::alerts_install_extension(), filename)) != 1)
return false; return false;
installExtension(filename); installExtension(filename);
@ -1495,8 +1492,7 @@ private:
// Ask if the user want to adjust the Screen/UI Scaling // Ask if the user want to adjust the Screen/UI Scaling
const int result = const int result =
ui::Alert::show( ui::Alert::show(
fmt::format( Strings::alerts_update_screen_ui_scaling_with_theme_values(
Strings::alerts_update_screen_ui_scaling_with_theme_values(),
themeName, themeName,
100 * m_pref.general.screenScale(), 100 * m_pref.general.screenScale(),
100 * (newScreenScale > 0 ? newScreenScale: m_pref.general.screenScale()), 100 * (newScreenScale > 0 ? newScreenScale: m_pref.general.screenScale()),
@ -1587,8 +1583,7 @@ private:
// Uninstall? // Uninstall?
if (ui::Alert::show( if (ui::Alert::show(
fmt::format( Strings::alerts_update_extension(
Strings::alerts_update_extension(),
ext->name(), ext->name(),
(isDowngrade ? Strings::alerts_update_extension_downgrade(): (isDowngrade ? Strings::alerts_update_extension_downgrade():
Strings::alerts_update_extension_upgrade()), Strings::alerts_update_extension_upgrade()),
@ -1664,8 +1659,7 @@ private:
return; return;
if (ui::Alert::show( if (ui::Alert::show(
fmt::format( Strings::alerts_uninstall_extension_warning(
Strings::alerts_uninstall_extension_warning(),
item->text())) != 1) item->text())) != 1)
return; return;

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2023 Igara Studio S.A. // Copyright (C) 2023-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -15,7 +15,6 @@
#include "app/ui/color_bar.h" #include "app/ui/color_bar.h"
#include "base/replace_string.h" #include "base/replace_string.h"
#include "base/trim_string.h" #include "base/trim_string.h"
#include "fmt/format.h"
namespace app { namespace app {
@ -107,7 +106,7 @@ std::string PaletteEditorCommand::onGetFriendlyName() const
popup = Strings::commands_PaletteEditor_FgPopup(); popup = Strings::commands_PaletteEditor_FgPopup();
} }
std::string result = fmt::format(getBaseFriendlyName(), edit, plus, popup); std::string result = Strings::commands_PaletteEditor(edit, plus, popup);
// TODO create a new function to remove duplicate whitespaces // TODO create a new function to remove duplicate whitespaces
base::replace_string(result, " ", " "); base::replace_string(result, " ", " ");
base::replace_string(result, " ", " "); base::replace_string(result, " ", " ");

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020-2021 Igara Studio S.A. // Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -24,7 +24,6 @@
#include "doc/layer_tilemap.h" #include "doc/layer_tilemap.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "doc/tilesets.h" #include "doc/tilesets.h"
#include "fmt/format.h"
#include "ui/alert.h" #include "ui/alert.h"
#include "ui/widget.h" #include "ui/widget.h"
@ -90,7 +89,7 @@ static bool continue_deleting_unused_tilesets(
std::string message; std::string message;
if (tsiToDelete.size() >= 1) if (tsiToDelete.size() >= 1)
message = fmt::format(Strings::alerts_deleting_tilemaps_will_delete_tilesets(), layerNames); message = Strings::alerts_deleting_tilemaps_will_delete_tilesets(layerNames);
return tsiToDelete.empty() || return tsiToDelete.empty() ||
app::OptionalAlert::show( app::OptionalAlert::show(
@ -205,12 +204,14 @@ void RemoveLayerCommand::onExecute(Context* context)
update_screen_for_document(document); update_screen_for_document(document);
StatusBar::instance()->invalidate(); StatusBar::instance()->invalidate();
if (!layerName.empty()) if (!layerName.empty()) {
StatusBar::instance()->showTip( StatusBar::instance()->showTip(
1000, fmt::format(Strings::remove_layer_x_removed(), layerName)); 1000, Strings::remove_layer_x_removed(layerName));
else }
StatusBar::instance()->showTip(1000, else {
Strings::remove_layer_layers_removed()); StatusBar::instance()->showTip(
1000, Strings::remove_layer_layers_removed());
}
} }
#endif #endif
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2017-2018 David Capello // Copyright (C) 2017-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -22,7 +22,6 @@
#include "doc/selected_objects.h" #include "doc/selected_objects.h"
#include "doc/slice.h" #include "doc/slice.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/alert.h" #include "ui/alert.h"
#include "ui/widget.h" #include "ui/widget.h"
@ -121,14 +120,14 @@ void RemoveSliceCommand::onExecute(Context* context)
} }
StatusBar::instance()->invalidate(); StatusBar::instance()->invalidate();
if (!sliceName.empty()) if (!sliceName.empty()) {
StatusBar::instance()->showTip( StatusBar::instance()->showTip(
1000, fmt::format(Strings::remove_slice_x_removed(), sliceName)); 1000, Strings::remove_slice_x_removed(sliceName));
else }
else {
StatusBar::instance()->showTip( StatusBar::instance()->showTip(
1000, 1000, Strings::remove_slice_n_slices_removed(slicesToDelete.size()));
fmt::format(Strings::remove_slice_n_slices_removed(), }
slicesToDelete.size()));
} }
Command* CommandFactory::createRemoveSliceCommand() Command* CommandFactory::createRemoveSliceCommand()

View File

@ -32,7 +32,6 @@
#include "doc/image.h" #include "doc/image.h"
#include "doc/mask.h" #include "doc/mask.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/ui.h" #include "ui/ui.h"
namespace app { namespace app {
@ -261,8 +260,8 @@ std::string RotateCommand::onGetFriendlyName() const
content = Strings::commands_Rotate_Selection(); content = Strings::commands_Rotate_Selection();
else else
content = Strings::commands_Rotate_Sprite(); content = Strings::commands_Rotate_Sprite();
return fmt::format(getBaseFriendlyName(), return Strings::commands_Rotate(content,
content, base::convert_to<std::string>(m_angle)); base::convert_to<std::string>(m_angle));
} }
Command* CommandFactory::createRotateCommand() Command* CommandFactory::createRotateCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -70,7 +70,7 @@ void RunScriptCommand::onExecute(Context* context)
int ret = OptionalAlert::show( int ret = OptionalAlert::show(
Preferences::instance().scripts.showRunScriptAlert, Preferences::instance().scripts.showRunScriptAlert,
1, // Yes is the default option when the alert dialog is disabled 1, // Yes is the default option when the alert dialog is disabled
fmt::format(Strings::alerts_run_script(), m_filename)); Strings::alerts_run_script(m_filename));
if (ret != 1) if (ret != 1)
return; return;
} }
@ -89,11 +89,11 @@ void RunScriptCommand::onExecute(Context* context)
std::string RunScriptCommand::onGetFriendlyName() const std::string RunScriptCommand::onGetFriendlyName() const
{ {
if (m_filename.empty()) if (m_filename.empty())
return getBaseFriendlyName(); return Strings::commands_RunScript();
else
return fmt::format("{0}: {1}", return fmt::format("{0}: {1}",
getBaseFriendlyName(), Strings::commands_RunScript(),
base::get_file_name(m_filename)); base::get_file_name(m_filename));
} }
Command* CommandFactory::createRunScriptCommand() Command* CommandFactory::createRunScriptCommand()

View File

@ -41,7 +41,6 @@
#include "doc/mask.h" #include "doc/mask.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "doc/tag.h" #include "doc/tag.h"
#include "fmt/format.h"
#include "ui/ui.h" #include "ui/ui.h"
#include "undo/undo_state.h" #include "undo/undo_state.h"
@ -269,8 +268,7 @@ void SaveFileBaseCommand::saveDocumentInBackground(
#ifdef ENABLE_UI #ifdef ENABLE_UI
if (context->isUIAvailable() && params().ui()) { if (context->isUIAvailable() && params().ui()) {
StatusBar::instance()->setStatusText( StatusBar::instance()->setStatusText(
2000, fmt::format(Strings::save_file_saved(), 2000, Strings::save_file_saved(base::get_file_name(filename)));
base::get_file_name(filename)));
} }
#endif #endif
} }
@ -428,8 +426,7 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
int ret = OptionalAlert::show( int ret = OptionalAlert::show(
Preferences::instance().exportFile.showOverwriteFilesAlert, Preferences::instance().exportFile.showOverwriteFilesAlert,
1, // Yes is the default option when the alert dialog is disabled 1, // Yes is the default option when the alert dialog is disabled
fmt::format(Strings::alerts_overwrite_files_on_export(), Strings::alerts_overwrite_files_on_export(outputFilename));
outputFilename));
if (ret != 1) if (ret != 1)
goto again; goto again;
} }

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -16,7 +17,6 @@
#include "base/fs.h" #include "base/fs.h"
#include "doc/mask.h" #include "doc/mask.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/alert.h" #include "ui/alert.h"
namespace app { namespace app {
@ -55,7 +55,7 @@ void SaveMaskCommand::onExecute(Context* context)
std::string filename = selFilename.front(); std::string filename = selFilename.front();
if (save_msk_file(document->mask(), filename.c_str()) != 0) if (save_msk_file(document->mask(), filename.c_str()) != 0)
ui::Alert::show(fmt::format(Strings::alerts_error_saving_file(), filename)); ui::Alert::show(Strings::alerts_error_saving_file(filename));
} }
Command* CommandFactory::createSaveMaskCommand() Command* CommandFactory::createSaveMaskCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2021 Igara Studio S.A. // Copyright (C) 2021-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -21,7 +21,6 @@
#include "app/modules/palettes.h" #include "app/modules/palettes.h"
#include "base/fs.h" #include "base/fs.h"
#include "doc/palette.h" #include "doc/palette.h"
#include "fmt/format.h"
#include "ui/alert.h" #include "ui/alert.h"
namespace app { namespace app {
@ -78,8 +77,8 @@ void SavePaletteCommand::onExecute(Context* ctx)
if (!base::has_file_extension(filename, exts)) { if (!base::has_file_extension(filename, exts)) {
if (ctx->isUIAvailable()) { if (ctx->isUIAvailable()) {
ui::Alert::show( ui::Alert::show(
fmt::format(Strings::alerts_file_format_doesnt_support_palette(), Strings::alerts_file_format_doesnt_support_palette(
base::get_file_extension(filename))); base::get_file_extension(filename)));
} }
return; return;
} }
@ -90,7 +89,7 @@ void SavePaletteCommand::onExecute(Context* ctx)
colorSpace = activeDoc->sprite()->colorSpace(); colorSpace = activeDoc->sprite()->colorSpace();
if (!save_palette(filename.c_str(), palette, 16, colorSpace)) // TODO 16 should be configurable if (!save_palette(filename.c_str(), palette, 16, colorSpace)) // TODO 16 should be configurable
ui::Alert::show(fmt::format(Strings::alerts_error_saving_file(), filename)); ui::Alert::show(Strings::alerts_error_saving_file(filename));
if (m_preset == get_default_palette_preset_name()) { if (m_preset == get_default_palette_preset_name()) {
set_default_palette(palette); set_default_palette(palette);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020-2022 Igara Studio S.A. // Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -17,7 +17,6 @@
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "base/convert_to.h" #include "base/convert_to.h"
#include "fmt/format.h"
#include "ui/view.h" #include "ui/view.h"
namespace app { namespace app {
@ -64,8 +63,7 @@ void ScrollCommand::onExecute(Context* context)
std::string ScrollCommand::onGetFriendlyName() const std::string ScrollCommand::onGetFriendlyName() const
{ {
return fmt::format(getBaseFriendlyName(), return Strings::commands_Scroll(m_moveThing.getFriendlyString());
m_moveThing.getFriendlyString());
} }
Command* CommandFactory::createScrollCommand() Command* CommandFactory::createScrollCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2021 Igara Studio SA // Copyright (C) 2021-2024 Igara Studio SA
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -229,7 +229,7 @@ std::string SelectPaletteColorsCommand::onGetFriendlyName() const
case UsedTiles: return Strings::commands_SelectPaletteColors_UsedTiles(); case UsedTiles: return Strings::commands_SelectPaletteColors_UsedTiles();
case UnusedTiles: return Strings::commands_SelectPaletteColors_UnusedTiles(); case UnusedTiles: return Strings::commands_SelectPaletteColors_UnusedTiles();
} }
return getBaseFriendlyName(); return Strings::commands_SelectPaletteColors();
} }
Command* CommandFactory::createSelectPaletteColorsCommand() Command* CommandFactory::createSelectPaletteColorsCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello // Copyright (C) 2015-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -19,7 +19,6 @@
#include "app/tx.h" #include "app/tx.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "doc/mask.h" #include "doc/mask.h"
#include "fmt/format.h"
#include "ui/system.h" #include "ui/system.h"
namespace app { namespace app {
@ -124,7 +123,7 @@ std::string SelectTileCommand::onGetFriendlyName() const
text = Strings::commands_SelectTile_Intersect(); text = Strings::commands_SelectTile_Intersect();
break; break;
default: default:
text = getBaseFriendlyName(); text = Strings::commands_SelectTile();
break; break;
} }
return text; return text;

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2024 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
@ -13,7 +14,6 @@
#include "app/commands/params.h" #include "app/commands/params.h"
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "app/ui/color_bar.h" #include "app/ui/color_bar.h"
#include "fmt/format.h"
namespace app { namespace app {
@ -90,7 +90,7 @@ std::string SetColorSelectorCommand::onGetFriendlyName() const
type = Strings::commands_SetColorSelector_NormalMapWheel(); type = Strings::commands_SetColorSelector_NormalMapWheel();
break; break;
} }
return fmt::format(getBaseFriendlyName() + ": {0}", type); return fmt::format("{0}: {1}", Command::onGetFriendlyName(), type);
} }
Command* CommandFactory::createSetColorSelectorCommand() Command* CommandFactory::createSetColorSelectorCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -16,7 +16,6 @@
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "app/tools/ink_type.h" #include "app/tools/ink_type.h"
#include "app/ui/context_bar.h" #include "app/ui/context_bar.h"
#include "fmt/format.h"
namespace app { namespace app {
@ -72,7 +71,7 @@ std::string SetInkTypeCommand::onGetFriendlyName() const
ink = Strings::inks_shading(); ink = Strings::inks_shading();
break; break;
} }
return fmt::format(getBaseFriendlyName(), ink); return Strings::commands_SetInkType(ink);
} }
Command* CommandFactory::createSetInkTypeCommand() Command* CommandFactory::createSetInkTypeCommand()

View File

@ -35,7 +35,6 @@
#include "doc/sprite.h" #include "doc/sprite.h"
#include "doc/tilesets.h" #include "doc/tilesets.h"
#include "doc/user_data.h" #include "doc/user_data.h"
#include "fmt/format.h"
#include "os/color_space.h" #include "os/color_space.h"
#include "os/system.h" #include "os/system.h"
#include "ui/ui.h" #include "ui/ui.h"
@ -100,7 +99,7 @@ private:
auto sprite = tileset->sprite(); auto sprite = tileset->sprite();
auto tilesetClone = Tileset::MakeCopyCopyingImages(tileset); auto tilesetClone = Tileset::MakeCopyCopyingImages(tileset);
Tx tx(sprite, fmt::format(Strings::commands_TilesetDuplicate())); Tx tx(sprite, Strings::commands_TilesetDuplicate());
tx(new cmd::AddTileset(sprite, tilesetClone)); tx(new cmd::AddTileset(sprite, tilesetClone));
tx.commit(); tx.commit();
@ -119,12 +118,12 @@ private:
} }
if (!tilemapsNames.empty()) { if (!tilemapsNames.empty()) {
tilemapsNames = tilemapsNames.substr(0, tilemapsNames.size()-2); tilemapsNames = tilemapsNames.substr(0, tilemapsNames.size()-2);
ui::Alert::show(fmt::format(Strings::alerts_cannot_delete_used_tileset(), tilemapsNames)); ui::Alert::show(Strings::alerts_cannot_delete_used_tileset(tilemapsNames));
return; return;
} }
auto sprite = tileset->sprite(); auto sprite = tileset->sprite();
Tx tx(sprite, fmt::format(Strings::commands_TilesetDelete())); Tx tx(sprite, Strings::commands_TilesetDelete());
tx(new cmd::RemoveTileset(sprite, tsi)); tx(new cmd::RemoveTileset(sprite, tsi));
tx.commit(); tx.commit();
@ -276,8 +275,8 @@ void SpritePropertiesCommand::onExecute(Context* context)
imgtype_text = Strings::sprite_properties_grayscale(); imgtype_text = Strings::sprite_properties_grayscale();
break; break;
case IMAGE_INDEXED: case IMAGE_INDEXED:
imgtype_text = fmt::format(Strings::sprite_properties_indexed_color(), imgtype_text = Strings::sprite_properties_indexed_color(
sprite->palette(0)->size()); sprite->palette(0)->size());
break; break;
default: default:
ASSERT(false); ASSERT(false);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2021-2022 Igara Studio S.A. // Copyright (C) 2021-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -16,7 +16,6 @@
#include "app/pref/preferences.h" #include "app/pref/preferences.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "base/convert_to.h" #include "base/convert_to.h"
#include "fmt/format.h"
#include "render/zoom.h" #include "render/zoom.h"
#include "ui/manager.h" #include "ui/manager.h"
#include "ui/system.h" #include "ui/system.h"
@ -130,8 +129,7 @@ std::string ZoomCommand::onGetFriendlyName() const
text = Strings::commands_Zoom_Out(); text = Strings::commands_Zoom_Out();
break; break;
case Action::Set: case Action::Set:
text = fmt::format(Strings::commands_Zoom_Set(), text = Strings::commands_Zoom_Set(int(100.0*m_zoom.scale()));
int(100.0*m_zoom.scale()));
break; break;
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -20,18 +20,12 @@ Command::Command(const char* id, CommandFlags flags)
: m_id(id) : m_id(id)
, m_flags(flags) , m_flags(flags)
{ {
generateFriendlyName();
} }
Command::~Command() Command::~Command()
{ {
} }
std::string Command::friendlyName() const
{
return onGetFriendlyName();
}
bool Command::needsParams() const bool Command::needsParams() const
{ {
return onNeedsParams(); return onNeedsParams();
@ -64,15 +58,6 @@ bool Command::isChecked(Context* context)
} }
} }
void Command::generateFriendlyName()
{
std::string strId = "commands." + this->id();
if (auto s = Strings::instance())
m_friendlyName = s->translate(strId.c_str());
else
m_friendlyName = strId;
}
void Command::execute(Context* context) void Command::execute(Context* context)
{ {
onExecute(context); onExecute(context);
@ -110,7 +95,9 @@ void Command::onExecute(Context* context)
std::string Command::onGetFriendlyName() const std::string Command::onGetFriendlyName() const
{ {
return m_friendlyName; if (auto* strings = Strings::instance())
return strings->translate(("commands." + id()).c_str());
return id();
} }
} // namespace app } // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -29,13 +30,12 @@ namespace app {
virtual ~Command(); virtual ~Command();
const std::string& id() const { return m_id; } const std::string& id() const { return m_id; }
std::string friendlyName() const; std::string friendlyName() const { return onGetFriendlyName(); }
bool needsParams() const; bool needsParams() const;
void loadParams(const Params& params); void loadParams(const Params& params);
bool isEnabled(Context* context); bool isEnabled(Context* context);
bool isChecked(Context* context); bool isChecked(Context* context);
void generateFriendlyName();
protected: protected:
virtual bool onNeedsParams() const; virtual bool onNeedsParams() const;
@ -45,16 +45,11 @@ namespace app {
virtual void onExecute(Context* context); virtual void onExecute(Context* context);
virtual std::string onGetFriendlyName() const; virtual std::string onGetFriendlyName() const;
const std::string& getBaseFriendlyName() const {
return m_friendlyName;
}
private: private:
friend class Context; friend class Context;
void execute(Context* context); void execute(Context* context);
std::string m_id; std::string m_id;
std::string m_friendlyName;
CommandFlags m_flags; CommandFlags m_flags;
}; };

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2021-2023 Igara Studio S.A. // Copyright (C) 2021-2024 Igara Studio S.A.
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -303,7 +303,7 @@ std::string ConvertLayerCommand::onGetFriendlyName() const
case ConvertLayerParam::Background: return Strings::commands_ConvertLayer_Background(); break; case ConvertLayerParam::Background: return Strings::commands_ConvertLayer_Background(); break;
case ConvertLayerParam::Layer: return Strings::commands_ConvertLayer_Layer(); break; case ConvertLayerParam::Layer: return Strings::commands_ConvertLayer_Layer(); break;
case ConvertLayerParam::Tilemap: return Strings::commands_ConvertLayer_Tilemap(); break; case ConvertLayerParam::Tilemap: return Strings::commands_ConvertLayer_Tilemap(); break;
default: return getBaseFriendlyName(); default: return Strings::commands_ConvertLayer();
} }
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2017 David Capello // Copyright (C) 2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -16,7 +16,6 @@
#include "app/ui/doc_view.h" #include "app/ui/doc_view.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui_context.h" #include "app/ui_context.h"
#include "fmt/format.h"
#include <algorithm> #include <algorithm>
@ -66,8 +65,7 @@ std::string MoveThing::getFriendlyString() const
case Down: dir = Strings::commands_Move_Down(); break; case Down: dir = Strings::commands_Move_Down(); break;
} }
return fmt::format(Strings::commands_Move_Thing(), return Strings::commands_Move_Thing(quantity, dim, dir);
quantity, dim, dir);
} }
gfx::Point MoveThing::getDelta(Context* context) const gfx::Point MoveThing::getDelta(Context* context) const

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (c) 2023 Igara Studio S.A. // Copyright (c) 2023-2024 Igara Studio S.A.
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -9,9 +9,9 @@
#endif #endif
#include "app/commands/new_params.h" #include "app/commands/new_params.h"
#include "app/ui_context.h" #include "app/i18n/strings.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "fmt/format.h" #include "app/ui_context.h"
namespace app { namespace app {
@ -55,7 +55,7 @@ void SetPlaybackSpeedCommand::onExecute(Context* ctx)
std::string SetPlaybackSpeedCommand::onGetFriendlyName() const std::string SetPlaybackSpeedCommand::onGetFriendlyName() const
{ {
return fmt::format(getBaseFriendlyName(), params().multiplier()); return Strings::commands_SetPlaybackSpeed(params().multiplier());
} }
Command* CommandFactory::createSetPlaybackSpeedCommand() Command* CommandFactory::createSetPlaybackSpeedCommand()

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2022 Igara Studio S.A. // Copyright (C) 2022-2024 Igara Studio S.A.
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -13,7 +13,6 @@
#include "app/commands/new_params.h" #include "app/commands/new_params.h"
#include "app/context.h" #include "app/context.h"
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "fmt/format.h"
namespace app { namespace app {
@ -55,7 +54,7 @@ std::string ShowMenuCommand::onGetFriendlyName() const
name = menuitem->text(); name = menuitem->text();
else else
name = params().menu(); name = params().menu();
return fmt::format(Strings::commands_ShowMenu(), name); return Strings::commands_ShowMenu(name);
} }
MenuItem* ShowMenuCommand::findMenuItem() const MenuItem* ShowMenuCommand::findMenuItem() const

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2020-2024 Igara Studio S.A.
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -13,7 +13,6 @@
#include "app/commands/params.h" #include "app/commands/params.h"
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "app/ui/color_bar.h" #include "app/ui/color_bar.h"
#include "fmt/format.h"
namespace app { namespace app {
@ -52,7 +51,7 @@ protected:
case TilesetMode::Auto: mode = Strings::commands_TilesetMode_Auto(); break; case TilesetMode::Auto: mode = Strings::commands_TilesetMode_Auto(); break;
case TilesetMode::Stack: mode = Strings::commands_TilesetMode_Stack(); break; case TilesetMode::Stack: mode = Strings::commands_TilesetMode_Stack(); break;
} }
return fmt::format(getBaseFriendlyName(), mode); return Strings::commands_TilesetMode(mode);
} }
private: private:

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -704,8 +704,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
// show the alert dialog. // show the alert dialog.
if (fatal) { if (fatal) {
ui::Alert::show( ui::Alert::show(
fmt::format( Strings::alerts_file_format_doesnt_support_error(
Strings::alerts_file_format_doesnt_support_error(),
format->name(), format->name(),
warnings)); warnings));
ret = 1; ret = 1;
@ -714,8 +713,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
ret = OptionalAlert::show( ret = OptionalAlert::show(
Preferences::instance().saveFile.showFileFormatDoesntSupportAlert, Preferences::instance().saveFile.showFileFormatDoesntSupportAlert,
1, // Yes is the default option when the alert dialog is disabled 1, // Yes is the default option when the alert dialog is disabled
fmt::format( Strings::alerts_file_format_doesnt_support_warning(
Strings::alerts_file_format_doesnt_support_warning(),
format->name(), format->name(),
warnings)); warnings));
} }
@ -785,8 +783,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
OptionalAlert::show( OptionalAlert::show(
Preferences::instance().saveFile.showExportAnimationInSequenceAlert, Preferences::instance().saveFile.showExportAnimationInSequenceAlert,
1, 1,
fmt::format( Strings::alerts_export_animation_in_sequence(
Strings::alerts_export_animation_in_sequence(),
int(fop->m_seq.filename_list.size()), int(fop->m_seq.filename_list.size()),
base::get_file_name(fop->m_seq.filename_list[0]), base::get_file_name(fop->m_seq.filename_list[0]),
base::get_file_name(fop->m_seq.filename_list[1]))) != 1) { base::get_file_name(fop->m_seq.filename_list[1]))) != 1) {

View File

@ -17,6 +17,7 @@
#include "app/resource_finder.h" #include "app/resource_finder.h"
#include "app/xml_document.h" #include "app/xml_document.h"
#include "app/xml_exception.h" #include "app/xml_exception.h"
#include "base/debug.h"
#include "base/fs.h" #include "base/fs.h"
#include "cfg/cfg.h" #include "cfg/cfg.h"
@ -116,10 +117,49 @@ void Strings::setCurrentLanguage(const std::string& langId)
LanguageChange(); LanguageChange();
} }
void Strings::logError(const char* id, const char* error) const
{
LOG(ERROR, "I18N: Error in \"%s.ini\" file, string id \"%s\" is \"%s\", threw \"%s\" error\n",
currentLanguage().c_str(), id, translate(id).c_str(), error);
}
// static
std::string Strings::VFormat(const char* id, const fmt::format_args& vargs)
{
Strings* s = Strings::instance();
// First we try to translate the ID string with the current
// translation. If it fails (e.g. a fmt::format_error) it can be
// because an ill-formed string for the fmt library.
//
// In that case then we try to use the regular English string from
// the en.ini (which can fail too if the user modified the en.ini
// file by hand).
try {
return fmt::vformat(s->translate(id), vargs);
}
catch (const std::runtime_error& e) {
s->logError(id, e.what());
// Continue with default translation (English)...
}
try {
return fmt::vformat(s->defaultString(id), vargs);
}
catch (const std::runtime_error& e) {
// This is unexpected, invalid en.ini file
ASSERT(false);
s->logError(id, e.what());
return id;
}
}
void Strings::loadLanguage(const std::string& langId) void Strings::loadLanguage(const std::string& langId)
{ {
m_strings.clear(); m_strings.clear();
loadStringsFromDataDir(kDefLanguage); loadStringsFromDataDir(kDefLanguage);
m_default = m_strings;
if (langId != kDefLanguage) { if (langId != kDefLanguage) {
loadStringsFromDataDir(langId); loadStringsFromDataDir(langId);
loadStringsFromExtension(langId); loadStringsFromExtension(langId);
@ -201,8 +241,15 @@ const std::string& Strings::translate(const char* id) const
auto it = m_strings.find(id); auto it = m_strings.find(id);
if (it != m_strings.end()) if (it != m_strings.end())
return it->second; return it->second;
else return m_strings[id] = id;
return m_strings[id] = id; }
const std::string& Strings::defaultString(const char* id) const
{
auto it = m_default.find(id);
if (it != m_default.end())
return it->second;
return m_default[id] = id;
} }
} // namespace app } // namespace app

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "app/i18n/lang_info.h" #include "app/i18n/lang_info.h"
#include "fmt/core.h"
#include "obs/signal.h" #include "obs/signal.h"
#include "strings.ini.h" #include "strings.ini.h"
@ -32,11 +33,30 @@ namespace app {
static Strings* instance(); static Strings* instance();
const std::string& translate(const char* id) const; const std::string& translate(const char* id) const;
const std::string& defaultString(const char* id) const;
std::set<LangInfo> 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);
void logError(const char* id, const char* error) const;
static const std::string& Translate(const char* id) {
Strings* s = Strings::instance();
return s->translate(id);
}
// Formats a string with the given arguments, if it fails
// (e.g. because the translation contains an invalid formatted
// string) it tries to return the original string from the default
// en.ini file.
template<typename...Args>
static std::string Format(const char* id, Args&&...args) {
return VFormat(id, fmt::make_format_args(args...));
}
static std::string VFormat(const char* id, const fmt::format_args& vargs);
obs::signal<void()> LanguageChange; obs::signal<void()> LanguageChange;
private: private:
@ -50,7 +70,8 @@ namespace app {
Preferences& m_pref; Preferences& m_pref;
Extensions& m_exts; Extensions& m_exts;
mutable std::unordered_map<std::string, std::string> m_strings; mutable std::unordered_map<std::string, std::string> m_default; // Default strings from en.ini
mutable std::unordered_map<std::string, std::string> m_strings; // Strings from current language
}; };
} // namespace app } // namespace app

View File

@ -26,9 +26,9 @@ std::string XmlTranslator::operator()(const XMLElement* elem,
return std::string(); return std::string();
else if (value[0] == '@') { // Translate string else if (value[0] == '@') { // Translate string
if (value[1] == '.') if (value[1] == '.')
return Strings::instance()->translate((m_stringIdPrefix + (value+1)).c_str()); return Strings::Translate((m_stringIdPrefix + (value+1)).c_str());
else else
return Strings::instance()->translate(value+1); return Strings::Translate(value+1);
} }
else if (value[0] == '!') // Raw string else if (value[0] == '!') // Raw string
return std::string(value+1); return std::string(value+1);

View File

@ -15,7 +15,6 @@
#include "app/console.h" #include "app/console.h"
#include "app/context.h" #include "app/context.h"
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "fmt/format.h"
#include "ui/alert.h" #include "ui/alert.h"
#include "ui/widget.h" #include "ui/widget.h"
#include "ui/window.h" #include "ui/window.h"
@ -41,8 +40,7 @@ Job::Job(const std::string& jobName,
m_canceled_flag = false; m_canceled_flag = false;
if (showProgress && App::instance()->isGui()) { if (showProgress && App::instance()->isGui()) {
m_alert_window = ui::Alert::create( m_alert_window = ui::Alert::create(Strings::alerts_job_working(jobName));
fmt::format(Strings::alerts_job_working(), jobName));
m_alert_window->addProgress(); m_alert_window->addProgress();
m_timer = std::make_unique<ui::Timer>(kMonitoringPeriod, m_alert_window.get()); m_timer = std::make_unique<ui::Timer>(kMonitoringPeriod, m_alert_window.get());

View File

@ -1,5 +1,6 @@
// Aseprite // Aseprite
// Copyright (C) 2001-2015, 2017 David Capello // Copyright (C) 2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -13,7 +14,6 @@
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "base/exception.h" #include "base/exception.h"
#include "base/launcher.h" #include "base/launcher.h"
#include "fmt/format.h"
#include "ui/alert.h" #include "ui/alert.h"
namespace app { namespace app {
@ -27,13 +27,13 @@ void open_url(const std::string& url)
void open_file(const std::string& file) void open_file(const std::string& file)
{ {
if (!base::launcher::open_file(file)) if (!base::launcher::open_file(file))
ui::Alert::show(fmt::format(Strings::alerts_cannot_open_file(), file)); ui::Alert::show(Strings::alerts_cannot_open_file(file));
} }
void open_folder(const std::string& file) void open_folder(const std::string& file)
{ {
if (!base::launcher::open_folder(file)) if (!base::launcher::open_folder(file))
ui::Alert::show(fmt::format(Strings::alerts_cannot_open_folder(), file)); ui::Alert::show(Strings::alerts_cannot_open_folder(file));
} }
} // namespace launcher } // namespace launcher

View File

@ -13,7 +13,6 @@
#include "app/modules/gui.h" #include "app/modules/gui.h"
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
#include "fmt/format.h"
#include "gfx/color.h" #include "gfx/color.h"
#include "os/surface.h" #include "os/surface.h"
#include "ui/box.h" #include "ui/box.h"

View File

@ -1436,30 +1436,24 @@ protected:
class ContextBar::EyedropperField : public HBox { class ContextBar::EyedropperField : public HBox {
public: public:
EyedropperField() { EyedropperField() {
const auto combined = Strings::context_bar_eyedropper_combined(); m_channel.addItem(Strings::context_bar_eyedropper_combined(
m_channel.addItem(fmt::format(
combined,
Strings::context_bar_eyedropper_color(), Strings::context_bar_eyedropper_color(),
Strings::context_bar_eyedropper_alpha())); Strings::context_bar_eyedropper_alpha()));
m_channel.addItem(Strings::context_bar_eyedropper_color()); m_channel.addItem(Strings::context_bar_eyedropper_color());
m_channel.addItem(Strings::context_bar_eyedropper_alpha()); m_channel.addItem(Strings::context_bar_eyedropper_alpha());
m_channel.addItem(fmt::format( m_channel.addItem(Strings::context_bar_eyedropper_combined(
combined,
Strings::context_bar_eyedropper_rgb(), Strings::context_bar_eyedropper_rgb(),
Strings::context_bar_eyedropper_alpha())); Strings::context_bar_eyedropper_alpha()));
m_channel.addItem(Strings::context_bar_eyedropper_rgb()); m_channel.addItem(Strings::context_bar_eyedropper_rgb());
m_channel.addItem(fmt::format( m_channel.addItem(Strings::context_bar_eyedropper_combined(
combined,
Strings::context_bar_eyedropper_hsv(), Strings::context_bar_eyedropper_hsv(),
Strings::context_bar_eyedropper_alpha())); Strings::context_bar_eyedropper_alpha()));
m_channel.addItem(Strings::context_bar_eyedropper_hsv()); m_channel.addItem(Strings::context_bar_eyedropper_hsv());
m_channel.addItem(fmt::format( m_channel.addItem(Strings::context_bar_eyedropper_combined(
combined,
Strings::context_bar_eyedropper_hsl(), Strings::context_bar_eyedropper_hsl(),
Strings::context_bar_eyedropper_alpha())); Strings::context_bar_eyedropper_alpha()));
m_channel.addItem(Strings::context_bar_eyedropper_hsl()); m_channel.addItem(Strings::context_bar_eyedropper_hsl());
m_channel.addItem(fmt::format( m_channel.addItem(Strings::context_bar_eyedropper_combined(
combined,
Strings::context_bar_eyedropper_gray(), Strings::context_bar_eyedropper_gray(),
Strings::context_bar_eyedropper_alpha())); Strings::context_bar_eyedropper_alpha()));
m_channel.addItem(Strings::context_bar_eyedropper_gray()); m_channel.addItem(Strings::context_bar_eyedropper_gray());

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -30,7 +30,6 @@
#include "app/ui/workspace.h" #include "app/ui/workspace.h"
#include "app/ui_context.h" #include "app/ui_context.h"
#include "base/fs.h" #include "base/fs.h"
#include "fmt/format.h"
#include "ui/alert.h" #include "ui/alert.h"
#include "ui/button.h" #include "ui/button.h"
#include "ui/entry.h" #include "ui/entry.h"
@ -346,9 +345,7 @@ void DataRecoveryView::fillListWith(const bool crashes)
std::string title = session->name(); std::string title = session->name();
if (session->version() != get_app_version()) if (session->version() != get_app_version())
title = title = Strings::recover_files_incompatible(title, session->version());
fmt::format(Strings::recover_files_incompatible(),
title, session->version());
auto sep = new SeparatorInView(title, HORIZONTAL); auto sep = new SeparatorInView(title, HORIZONTAL);
sep->InitTheme.connect( sep->InitTheme.connect(
@ -492,8 +489,7 @@ void DataRecoveryView::onDelete()
return; return;
// Delete one backup // Delete one backup
if (Alert::show( if (Alert::show(Strings::alerts_delete_selected_backups(
fmt::format(Strings::alerts_delete_selected_backups(),
int(items.size()))) != 1) int(items.size()))) != 1)
return; // Cancel return; // Cancel
@ -531,11 +527,11 @@ void DataRecoveryView::onChangeSelection()
m_openButton.setEnabled(count > 0); m_openButton.setEnabled(count > 0);
if (count < 2) { if (count < 2) {
m_openButton.mainButton()->setText( m_openButton.mainButton()->setText(
fmt::format(Strings::recover_files_recover_sprite(), count)); Strings::recover_files_recover_sprite());
} }
else { else {
m_openButton.mainButton()->setText( m_openButton.mainButton()->setText(
fmt::format(Strings::recover_files_recover_n_sprites(), count)); Strings::recover_files_recover_n_sprites(count));
} }
} }

View File

@ -317,8 +317,7 @@ bool DocView::onCloseView(Workspace* workspace, bool quitting)
while (m_document->isModified()) { while (m_document->isModified()) {
// ask what want to do the user with the changes in the sprite // ask what want to do the user with the changes in the sprite
int ret = Alert::show( int ret = Alert::show(
fmt::format( Strings::alerts_save_sprite_changes(
Strings::alerts_save_sprite_changes(),
m_document->name(), m_document->name(),
(quitting ? Strings::alerts_save_sprite_changes_quitting(): (quitting ? Strings::alerts_save_sprite_changes_quitting():
Strings::alerts_save_sprite_changes_closing()))); Strings::alerts_save_sprite_changes_closing())));

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -187,13 +187,11 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
} }
else if (!layer->isVisibleHierarchy()) { else if (!layer->isVisibleHierarchy()) {
StatusBar::instance()->showTip( StatusBar::instance()->showTip(
1000, 1000, Strings::statusbar_tips_layer_x_is_hidden(layer->name()));
fmt::format(Strings::statusbar_tips_layer_x_is_hidden(),
layer->name()));
} }
else if (!layer->isMovable() || !layer->isEditableHierarchy()) { else if (!layer->isMovable() || !layer->isEditableHierarchy()) {
StatusBar::instance()->showTip( StatusBar::instance()->showTip(
1000, fmt::format(Strings::statusbar_tips_layer_locked(), layer->name())); 1000, Strings::statusbar_tips_layer_locked(layer->name()));
} }
else { else {
MovingCelCollect collect(editor, layer); MovingCelCollect collect(editor, layer);
@ -698,7 +696,7 @@ bool StandbyState::checkStartDrawingStraightLine(Editor* editor,
if (drawingState) { if (drawingState) {
// Disable stabilizer so that it does not affect the line preview // Disable stabilizer so that it does not affect the line preview
drawingState->disableMouseStabilizer(); drawingState->disableMouseStabilizer();
drawingState->sendMovementToToolLoop( drawingState->sendMovementToToolLoop(
tools::Pointer( tools::Pointer(
pointer ? pointer->point(): editor->screenToEditor(editor->mousePosInDisplay()), pointer ? pointer->point(): editor->screenToEditor(editor->mousePosInDisplay()),
@ -760,9 +758,7 @@ void StandbyState::transformSelection(Editor* editor, MouseMessage* msg, HandleT
Layer* layer = editor->layer(); Layer* layer = editor->layer();
if (layer && layer->isReference()) { if (layer && layer->isReference()) {
StatusBar::instance()->showTip( StatusBar::instance()->showTip(
1000, 1000, Strings::statusbar_tips_non_transformable_reference_layer(layer->name()));
fmt::format(Strings::statusbar_tips_non_transformable_reference_layer(),
layer->name()));
return; return;
} }

View File

@ -869,9 +869,7 @@ tools::ToolLoop* create_tool_loop(
if (toolPref.floodfill.referTo() == if (toolPref.floodfill.referTo() ==
app::gen::FillReferTo::ACTIVE_LAYER) { app::gen::FillReferTo::ACTIVE_LAYER) {
StatusBar::instance()->showTip( StatusBar::instance()->showTip(
1000, 1000, Strings::statusbar_tips_layer_x_is_hidden(layer->name()));
fmt::format(Strings::statusbar_tips_layer_x_is_hidden(),
layer->name()));
return nullptr; return nullptr;
} }
} }
@ -883,8 +881,7 @@ tools::ToolLoop* create_tool_loop(
else if (layer->isReference()) { else if (layer->isReference()) {
StatusBar::instance()->showTip( StatusBar::instance()->showTip(
1000, 1000,
fmt::format(Strings::statusbar_tips_unmodifiable_reference_layer(), Strings::statusbar_tips_unmodifiable_reference_layer(layer->name()));
layer->name()));
return nullptr; return nullptr;
} }
} }

View File

@ -256,9 +256,10 @@ void ExportFileWindow::updateAdjustResizeButton()
if (adjustResize()->isVisible() != newState) { if (adjustResize()->isVisible() != newState) {
adjustResize()->setVisible(newState); adjustResize()->setVisible(newState);
if (newState) if (newState) {
adjustResize()->setText(fmt::format(Strings::export_file_adjust_resize(), adjustResize()->setText(
100 * m_preferredResize)); Strings::export_file_adjust_resize(100 * m_preferredResize));
}
adjustResize()->parent()->layout(); adjustResize()->parent()->layout();
} }
} }
@ -286,7 +287,7 @@ void ExportFileWindow::onOK()
} }
else { else {
ui::Alert::show( ui::Alert::show(
fmt::format(Strings::alerts_unknown_output_file_format_error(), ext)); Strings::alerts_unknown_output_file_format_error(ext));
return; return;
} }
} }

View File

@ -28,7 +28,6 @@
#include "base/fs.h" #include "base/fs.h"
#include "base/paths.h" #include "base/paths.h"
#include "base/string.h" #include "base/string.h"
#include "fmt/format.h"
#include "ui/ui.h" #include "ui/ui.h"
#include "new_folder_window.xml.h" #include "new_folder_window.xml.h"
@ -638,9 +637,8 @@ again:
const char* invalid_chars = ": * ? \" < > |"; const char* invalid_chars = ": * ? \" < > |";
ui::Alert::show( ui::Alert::show(
fmt::format( Strings::alerts_invalid_chars_in_filename(
Strings::alerts_invalid_chars_in_filename(), invalid_chars));
invalid_chars));
// show the window again // show the window again
setVisible(true); setVisible(true);
@ -659,9 +657,7 @@ again:
if (m_type == FileSelectorType::Save && base::is_file(buf)) { if (m_type == FileSelectorType::Save && base::is_file(buf)) {
int ret = Alert::show( int ret = Alert::show(
fmt::format( Strings::alerts_overwrite_existent_file(base::get_file_name(buf)));
Strings::alerts_overwrite_existent_file(),
base::get_file_name(buf)));
if (ret == 2) { if (ret == 2) {
setVisible(true); setVisible(true);
goto again; goto again;

View File

@ -303,8 +303,7 @@ void HomeView::onUpToDate()
void HomeView::onNewUpdate(const std::string& url, const std::string& version) void HomeView::onNewUpdate(const std::string& url, const std::string& version)
{ {
checkUpdate()->setText( checkUpdate()->setText(
fmt::format(Strings::home_view_new_version_available(), Strings::home_view_new_version_available(get_app_name(), version));
get_app_name(), version));
#ifdef ENABLE_DRM #ifdef ENABLE_DRM
DRM_INVALID { DRM_INVALID {
checkUpdate()->setUrl(url); checkUpdate()->setUrl(url);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -217,15 +217,6 @@ MainWindow::~MainWindow()
void MainWindow::onLanguageChange() void MainWindow::onLanguageChange()
{ {
auto commands = Commands::instance();
std::vector<std::string> commandIDs;
commands->getAllIds(commandIDs);
for (const auto& commandID : commandIDs) {
Command* command = commands->byId(commandID.c_str());
command->generateFriendlyName();
}
m_menuBar->reload(); m_menuBar->reload();
layout(); layout();
invalidate(); invalidate();

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -518,8 +518,7 @@ void ToolBar::openTipWindow(int group_index, Tool* tool)
KeyPtr key = KeyboardShortcuts::instance()->tool(tool); KeyPtr key = KeyboardShortcuts::instance()->tool(tool);
if (key && !key->accels().empty()) { if (key && !key->accels().empty()) {
tooltip += "\n\n"; tooltip += "\n\n";
tooltip += fmt::format(Strings::tools_shortcut(), tooltip += Strings::tools_shortcut(key->accels().front().toString());
key->accels().front().toString());
} }
} }
else if (group_index == PreviewVisibilityIndex) { else if (group_index == PreviewVisibilityIndex) {

View File

@ -56,8 +56,7 @@ bool layer_is_locked(Editor* editor)
#ifdef ENABLE_UI #ifdef ENABLE_UI
if (statusBar) { if (statusBar) {
statusBar->showTip( statusBar->showTip(
1000, fmt::format(Strings::statusbar_tips_layer_x_is_hidden(), 1000, Strings::statusbar_tips_layer_x_is_hidden(layer->name()));
layer->name()));
} }
#endif #endif
return true; return true;
@ -67,7 +66,7 @@ bool layer_is_locked(Editor* editor)
#ifdef ENABLE_UI #ifdef ENABLE_UI
if (statusBar) { if (statusBar) {
statusBar->showTip( statusBar->showTip(
1000, fmt::format(Strings::statusbar_tips_layer_locked(), layer->name())); 1000, Strings::statusbar_tips_layer_locked(layer->name()));
} }
#endif #endif
return true; return true;

View File

@ -1,4 +1,5 @@
// Aseprite Code Generator // Aseprite Code Generator
// Copyright (c) 2024 Igara Studio S.A.
// Copyright (c) 2016-2017 David Capello // Copyright (c) 2016-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.
@ -11,6 +12,8 @@
#include "base/string.h" #include "base/string.h"
#include "cfg/cfg.h" #include "cfg/cfg.h"
#include <algorithm>
#include <cctype>
#include <iostream> #include <iostream>
static std::string to_cpp(std::string stringId) static std::string to_cpp(std::string stringId)
@ -19,6 +22,33 @@ static std::string to_cpp(std::string stringId)
return stringId; return stringId;
} }
static size_t count_fmt_args(const std::string& format)
{
size_t n = 0;
for (size_t i=0; i<format.size(); ++i) {
if (format[i] == '{') {
if (format[i+1] == '}') {
++n;
}
else if (std::isdigit(format[i+1]) &&
format[i+2] == '}') {
n = std::max<size_t>(n, (format[i+1]-'0')+1);
}
}
}
return n;
}
// Strings with raw "{something}" that are not a fmt format string.
//
// TODO This is a super hacky way to filter out these strings, find
// another way.
static bool force_simple_string(const std::string& stringId)
{
return (stringId == "data_filename_format_tooltip" ||
stringId == "data_tagname_format_tooltip");
}
void gen_strings_class(const std::string& inputFn) void gen_strings_class(const std::string& inputFn)
{ {
cfg::CfgFile cfg; cfg::CfgFile cfg;
@ -38,7 +68,9 @@ void gen_strings_class(const std::string& inputFn)
<< "\n" << "\n"
<< " template<typename T>\n" << " template<typename T>\n"
<< " class Strings {\n" << " class Strings {\n"
<< " public:\n"; << " public:\n"
<< " class ID {\n"
<< " public:\n";
std::vector<std::string> sections; std::vector<std::string> sections;
std::vector<std::string> keys; std::vector<std::string> keys;
@ -49,10 +81,61 @@ void gen_strings_class(const std::string& inputFn)
std::string textId = section; std::string textId = section;
textId.push_back('.'); textId.push_back('.');
for (auto key : keys) { for (const auto& key : keys) {
textId.append(key);
std::cout << " static constexpr const char* " << to_cpp(textId) << " = \"" << textId << "\";\n";
textId.erase(section.size()+1);
}
}
std::cout
<< " };\n"
<< "\n";
for (const auto& section : sections) {
keys.clear();
cfg.getAllKeys(section.c_str(), keys);
std::string textId = section;
textId.push_back('.');
for (const auto& key : keys) {
textId.append(key); textId.append(key);
std::cout << " static const std::string& " << to_cpp(textId) << "() { return T::instance()->translate(\"" << textId << "\"); }\n"; std::string value = cfg.getValue(section.c_str(), key.c_str(), "");
std::string cppId = to_cpp(textId);
size_t nargs = count_fmt_args(value);
// Create just a function to get the translated string (it
// doesn't have arguments).
if (nargs == 0 || force_simple_string(cppId)) {
std::cout << " static const std::string& " << cppId << "() { return T::Translate(ID::" << cppId << "); }\n";
}
// Create a function to format the translated string with a
// specific number of arguments (the good part is that we can
// check the number of required arguments at compile-time).
else {
std::cout << " template<";
for (int i=1; i<=nargs; ++i) {
std::cout << "typename T" << i;
if (i < nargs)
std::cout << ", ";
}
std::cout << ">\n";
std::cout << " static std::string " << cppId << "(";
for (int i=1; i<=nargs; ++i) {
std::cout << "T" << i << "&& arg" << i;
if (i < nargs)
std::cout << ", ";
}
std::cout << ") { return T::Format(ID::" << cppId << ", ";
for (int i=1; i<=nargs; ++i) {
std::cout << "arg" << i;
if (i < nargs)
std::cout << ", ";
}
std::cout << "); }\n";
}
textId.erase(section.size()+1); textId.erase(section.size()+1);
} }

2
third_party/fmt vendored

@ -1 +1 @@
Subproject commit a0b8a92e3d1532361c2f7feb63babc5c18d00ef2 Subproject commit 67c0c0c09cf74d407d71a29c194761981614df3e