From 32a099dcb6f6a24cbcada83e0d309b0abc468080 Mon Sep 17 00:00:00 2001 From: David Capello Date: Thu, 20 Jun 2024 20:14:29 -0300 Subject: [PATCH] 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. --- src/app/commands/cmd_add_color.cpp | 5 +- src/app/commands/cmd_cel_opacity.cpp | 8 +- src/app/commands/cmd_change_brush.cpp | 5 +- src/app/commands/cmd_change_color.cpp | 4 +- src/app/commands/cmd_change_pixel_format.cpp | 2 +- src/app/commands/cmd_export_sprite_sheet.cpp | 11 ++- src/app/commands/cmd_flip.cpp | 5 +- src/app/commands/cmd_keyboard_shortcuts.cpp | 7 +- src/app/commands/cmd_layer_opacity.cpp | 8 +- src/app/commands/cmd_load_mask.cpp | 5 +- src/app/commands/cmd_load_palette.cpp | 5 +- src/app/commands/cmd_modify_selection.cpp | 9 +- src/app/commands/cmd_move_mask.cpp | 7 +- src/app/commands/cmd_new_file.cpp | 6 +- src/app/commands/cmd_new_frame.cpp | 6 +- src/app/commands/cmd_new_layer.cpp | 17 ++-- src/app/commands/cmd_options.cpp | 16 ++-- src/app/commands/cmd_palette_editor.cpp | 5 +- src/app/commands/cmd_remove_layer.cpp | 17 ++-- src/app/commands/cmd_remove_slice.cpp | 15 ++-- src/app/commands/cmd_rotate.cpp | 5 +- src/app/commands/cmd_run_script.cpp | 14 +-- src/app/commands/cmd_save_file.cpp | 7 +- src/app/commands/cmd_save_mask.cpp | 4 +- src/app/commands/cmd_save_palette.cpp | 9 +- src/app/commands/cmd_scroll.cpp | 6 +- src/app/commands/cmd_select_palette.cpp | 4 +- src/app/commands/cmd_select_tile.cpp | 5 +- src/app/commands/cmd_set_color_selector.cpp | 4 +- src/app/commands/cmd_set_ink_type.cpp | 5 +- src/app/commands/cmd_sprite_properties.cpp | 11 ++- src/app/commands/cmd_zoom.cpp | 6 +- src/app/commands/command.cpp | 21 +---- src/app/commands/command.h | 9 +- src/app/commands/convert_layer.cpp | 4 +- src/app/commands/move_thing.cpp | 6 +- src/app/commands/set_playback_speed.cpp | 8 +- src/app/commands/show_menu.cpp | 5 +- src/app/commands/tileset_mode.cpp | 5 +- src/app/file/file.cpp | 11 +-- src/app/i18n/strings.cpp | 51 ++++++++++- src/app/i18n/strings.h | 23 ++++- src/app/i18n/xml_translator.cpp | 4 +- src/app/job.cpp | 4 +- src/app/launcher.cpp | 8 +- src/app/ui/button_set.cpp | 1 - src/app/ui/context_bar.cpp | 16 ++-- src/app/ui/data_recovery_view.cpp | 14 ++- src/app/ui/doc_view.cpp | 3 +- src/app/ui/editor/standby_state.cpp | 14 ++- src/app/ui/editor/tool_loop_impl.cpp | 7 +- src/app/ui/export_file_window.cpp | 9 +- src/app/ui/file_selector.cpp | 10 +-- src/app/ui/home_view.cpp | 3 +- src/app/ui/main_window.cpp | 11 +-- src/app/ui/toolbar.cpp | 5 +- src/app/util/layer_utils.cpp | 5 +- src/gen/strings_class.cpp | 89 +++++++++++++++++++- third_party/fmt | 2 +- 59 files changed, 321 insertions(+), 270 deletions(-) diff --git a/src/app/commands/cmd_add_color.cpp b/src/app/commands/cmd_add_color.cpp index 7ff2e19ec..da7572b27 100644 --- a/src/app/commands/cmd_add_color.cpp +++ b/src/app/commands/cmd_add_color.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2016-2018 David Capello // // This program is distributed under the terms of @@ -20,7 +20,6 @@ #include "app/pref/preferences.h" #include "app/tx.h" #include "doc/palette.h" -#include "fmt/format.h" #ifdef ENABLE_SCRIPTING #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::Color: source = Strings::commands_AddColor_Specific(); break; } - return fmt::format(getBaseFriendlyName(), source); + return Strings::commands_AddColor(source); } Command* CommandFactory::createAddColorCommand() diff --git a/src/app/commands/cmd_cel_opacity.cpp b/src/app/commands/cmd_cel_opacity.cpp index 112c453fd..7389787a1 100644 --- a/src/app/commands/cmd_cel_opacity.cpp +++ b/src/app/commands/cmd_cel_opacity.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020-2022 Igara Studio S.A. +// Copyright (C) 2020-2024 Igara Studio S.A. // Copyright (C) 2018 David Capello // // This program is distributed under the terms of @@ -22,7 +22,6 @@ #include "doc/cel.h" #include "doc/cels_range.h" #include "doc/sprite.h" -#include "fmt/format.h" #include @@ -109,9 +108,8 @@ void CelOpacityCommand::onExecute(Context* context) std::string CelOpacityCommand::onGetFriendlyName() const { - return fmt::format(getBaseFriendlyName(), - m_opacity, - int(100.0 * m_opacity / 255.0)); + return Strings::commands_CelOpacity(m_opacity, + int(100.0 * m_opacity / 255.0)); } Command* CommandFactory::createCelOpacityCommand() diff --git a/src/app/commands/cmd_change_brush.cpp b/src/app/commands/cmd_change_brush.cpp index 5430292bd..93aeae7db 100644 --- a/src/app/commands/cmd_change_brush.cpp +++ b/src/app/commands/cmd_change_brush.cpp @@ -27,7 +27,6 @@ #include "doc/image_ref.h" #include "doc/primitives.h" #include "doc/tile.h" -#include "fmt/format.h" #include #include @@ -328,10 +327,10 @@ std::string ChangeBrushCommand::onGetFriendlyName() const case FlipD: change = Strings::commands_ChangeBrush_FlipD(); break; case Rotate90CW: change = Strings::commands_ChangeBrush_Rotate90CW(); break; case CustomBrush: - change = fmt::format(Strings::commands_ChangeBrush_CustomBrush(), m_slot); + change = Strings::commands_ChangeBrush_CustomBrush(m_slot); break; } - return fmt::format(getBaseFriendlyName(), change); + return Strings::commands_ChangeBrush(change); } Command* CommandFactory::createChangeBrushCommand() diff --git a/src/app/commands/cmd_change_color.cpp b/src/app/commands/cmd_change_color.cpp index 076bc836b..894af8f56 100644 --- a/src/app/commands/cmd_change_color.cpp +++ b/src/app/commands/cmd_change_color.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -17,7 +18,6 @@ #include "app/modules/palettes.h" #include "app/ui/color_bar.h" #include "doc/palette.h" -#include "fmt/format.h" namespace app { @@ -120,7 +120,7 @@ std::string ChangeColorCommand::onGetFriendlyName() const break; } - return fmt::format(getBaseFriendlyName(), action); + return Strings::commands_ChangeColor(action); } Command* CommandFactory::createChangeColorCommand() diff --git a/src/app/commands/cmd_change_pixel_format.cpp b/src/app/commands/cmd_change_pixel_format.cpp index 3a30d653a..500013497 100644 --- a/src/app/commands/cmd_change_pixel_format.cpp +++ b/src/app/commands/cmd_change_pixel_format.cpp @@ -727,7 +727,7 @@ std::string ChangePixelFormatCommand::onGetFriendlyName() const else conversion = Strings::commands_ChangePixelFormat_MoreOptions(); - return fmt::format(getBaseFriendlyName(), conversion); + return Strings::commands_ChangePixelFormat(conversion); } Command* CommandFactory::createChangePixelFormatCommand() diff --git a/src/app/commands/cmd_export_sprite_sheet.cpp b/src/app/commands/cmd_export_sprite_sheet.cpp index 25d508a97..37f716718 100644 --- a/src/app/commands/cmd_export_sprite_sheet.cpp +++ b/src/app/commands/cmd_export_sprite_sheet.cpp @@ -48,7 +48,7 @@ #include "export_sprite_sheet.xml.h" #include -#include +#include namespace app { @@ -93,20 +93,19 @@ bool ask_overwrite(const bool askFilename, const std::string& filename, (askDataname && !dataname.empty() && base::is_file(dataname))) { - std::stringstream text; + std::string text; if (base::is_file(filename)) - text << "<<" << base::get_file_name(filename).c_str(); + text += "<<" + base::get_file_name(filename); if (base::is_file(dataname)) - text << "<<" << base::get_file_name(dataname).c_str(); + text += "<<" + base::get_file_name(dataname); const int ret = OptionalAlert::show( Preferences::instance().spriteSheet.showOverwriteFilesAlert, 1, // Yes is the default option when the alert dialog is disabled - fmt::format(Strings::alerts_overwrite_files_on_export_sprite_sheet(), - text.str())); + Strings::alerts_overwrite_files_on_export_sprite_sheet(text)); if (ret != 1) return false; } diff --git a/src/app/commands/cmd_flip.cpp b/src/app/commands/cmd_flip.cpp index 24878ffa6..14c58ea10 100644 --- a/src/app/commands/cmd_flip.cpp +++ b/src/app/commands/cmd_flip.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2022 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -39,7 +39,6 @@ #include "doc/layer.h" #include "doc/mask.h" #include "doc/sprite.h" -#include "fmt/format.h" #include "gfx/size.h" @@ -256,7 +255,7 @@ std::string FlipCommand::onGetFriendlyName() const else orientation = Strings::commands_Flip_Vertically(); - return fmt::format(getBaseFriendlyName(), content, orientation); + return Strings::commands_Flip(content, orientation); } Command* CommandFactory::createFlipCommand() diff --git a/src/app/commands/cmd_keyboard_shortcuts.cpp b/src/app/commands/cmd_keyboard_shortcuts.cpp index 679c76e71..5f12092f7 100644 --- a/src/app/commands/cmd_keyboard_shortcuts.cpp +++ b/src/app/commands/cmd_keyboard_shortcuts.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2022 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -31,7 +31,6 @@ #include "base/scoped_value.h" #include "base/split_string.h" #include "base/string.h" -#include "fmt/format.h" #include "ui/alert.h" #include "ui/fit_bounds.h" #include "ui/graphics.h" @@ -239,9 +238,7 @@ private: ui::Accelerator accel = m_key->accels()[index]; if (ui::Alert::show( - fmt::format( - Strings::alerts_delete_shortcut(), - accel.toString())) != 1) + Strings::alerts_delete_shortcut(accel.toString())) != 1) return; m_key->disableAccel(accel, KeySource::UserDefined); diff --git a/src/app/commands/cmd_layer_opacity.cpp b/src/app/commands/cmd_layer_opacity.cpp index d4b0b67f5..ca5ce2e9d 100644 --- a/src/app/commands/cmd_layer_opacity.cpp +++ b/src/app/commands/cmd_layer_opacity.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020-2022 Igara Studio S.A. +// Copyright (C) 2020-2024 Igara Studio S.A. // Copyright (C) 2016-2018 David Capello // // This program is distributed under the terms of @@ -20,7 +20,6 @@ #include "app/tx.h" #include "app/ui/timeline/timeline.h" #include "doc/layer.h" -#include "fmt/format.h" #include @@ -94,9 +93,8 @@ void LayerOpacityCommand::onExecute(Context* context) std::string LayerOpacityCommand::onGetFriendlyName() const { - return fmt::format(getBaseFriendlyName(), - m_opacity, - int(100.0 * m_opacity / 255.0)); + return Strings::commands_LayerOpacity(m_opacity, + int(100.0 * m_opacity / 255.0)); } Command* CommandFactory::createLayerOpacityCommand() diff --git a/src/app/commands/cmd_load_mask.cpp b/src/app/commands/cmd_load_mask.cpp index c34d570bf..93ff7e7c2 100644 --- a/src/app/commands/cmd_load_mask.cpp +++ b/src/app/commands/cmd_load_mask.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -20,7 +20,6 @@ #include "app/util/msk_file.h" #include "doc/mask.h" #include "doc/sprite.h" -#include "fmt/format.h" #include "ui/alert.h" namespace app { @@ -70,7 +69,7 @@ void LoadMaskCommand::onExecute(Context* context) std::unique_ptr mask(load_msk_file(m_filename.c_str())); 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; } diff --git a/src/app/commands/cmd_load_palette.cpp b/src/app/commands/cmd_load_palette.cpp index cb6136e08..d71ccc1c4 100644 --- a/src/app/commands/cmd_load_palette.cpp +++ b/src/app/commands/cmd_load_palette.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2023 Igara Studio S.A. +// Copyright (C) 2023-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -19,7 +19,6 @@ #include "app/modules/palettes.h" #include "base/fs.h" #include "doc/palette.h" -#include "fmt/format.h" #include "ui/alert.h" namespace app { @@ -81,7 +80,7 @@ void LoadPaletteCommand::onExecute(Context* context) std::unique_ptr palette(load_palette(filename.c_str())); if (!palette) { if (context->isUIAvailable()) - ui::Alert::show(fmt::format(Strings::alerts_error_loading_file(), filename)); + ui::Alert::show(Strings::alerts_error_loading_file(filename)); return; } diff --git a/src/app/commands/cmd_modify_selection.cpp b/src/app/commands/cmd_modify_selection.cpp index ae5a4a536..5cc911d1c 100644 --- a/src/app/commands/cmd_modify_selection.cpp +++ b/src/app/commands/cmd_modify_selection.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2022 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2015-2018 David Capello // // This program is distributed under the terms of @@ -23,7 +23,6 @@ #include "doc/brush_type.h" #include "doc/mask.h" #include "filters/neighboring_pixels.h" -#include "fmt/format.h" #include "modify_selection.xml.h" @@ -151,11 +150,9 @@ std::string ModifySelectionCommand::onGetFriendlyName() const { std::string quantity; if (m_quantity > 0) - quantity = fmt::format(Strings::commands_ModifySelection_Quantity(), m_quantity); + quantity = Strings::commands_ModifySelection_Quantity(m_quantity); - return fmt::format(getBaseFriendlyName(), - getActionName(), - quantity); + return Strings::commands_ModifySelection(getActionName(), quantity); } std::string ModifySelectionCommand::getActionName() const diff --git a/src/app/commands/cmd_move_mask.cpp b/src/app/commands/cmd_move_mask.cpp index a19e531e1..7d26f5d13 100644 --- a/src/app/commands/cmd_move_mask.cpp +++ b/src/app/commands/cmd_move_mask.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2022 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -26,7 +26,6 @@ #include "base/convert_to.h" #include "doc/mask.h" #include "doc/sprite.h" -#include "fmt/format.h" #include "ui/view.h" namespace app { @@ -124,8 +123,8 @@ std::string MoveMaskCommand::onGetFriendlyName() const case Boundaries: content = Strings::commands_MoveMask_Boundaries(); break; case Content: content = Strings::commands_MoveMask_Content(); break; } - return fmt::format(getBaseFriendlyName(), - content, m_moveThing.getFriendlyString()); + return Strings::commands_MoveMask(content, + m_moveThing.getFriendlyString()); } Command* CommandFactory::createMoveMaskCommand() diff --git a/src/app/commands/cmd_new_file.cpp b/src/app/commands/cmd_new_file.cpp index 02b7fa269..e09391455 100644 --- a/src/app/commands/cmd_new_file.cpp +++ b/src/app/commands/cmd_new_file.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2022 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -285,9 +285,9 @@ void NewFileCommand::onExecute(Context* ctx) std::string NewFileCommand::onGetFriendlyName() const { if (params().fromClipboard()) - return fmt::format(Strings::commands_NewFile_FromClipboard()); + return Strings::commands_NewFile_FromClipboard(); else - return fmt::format(Strings::commands_NewFile()); + return Strings::commands_NewFile(); } Command* CommandFactory::createNewFileCommand() diff --git a/src/app/commands/cmd_new_frame.cpp b/src/app/commands/cmd_new_frame.cpp index b60574e79..2d1d9a2c2 100644 --- a/src/app/commands/cmd_new_frame.cpp +++ b/src/app/commands/cmd_new_frame.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2023 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -29,7 +29,6 @@ #include "doc/image.h" #include "doc/layer.h" #include "doc/sprite.h" -#include "fmt/format.h" #include "ui/ui.h" #include @@ -181,8 +180,7 @@ void NewFrameCommand::onExecute(Context* context) update_screen_for_document(document); StatusBar::instance()->showTip( - 1000, fmt::format( - Strings::commands_NewFrame_tooltip(), + 1000, Strings::commands_NewFrame_tooltip( (int)context->activeSite().frame()+1, (int)sprite->totalFrames())); diff --git a/src/app/commands/cmd_new_layer.cpp b/src/app/commands/cmd_new_layer.cpp index 2c9e1df6d..b39e4cc44 100644 --- a/src/app/commands/cmd_new_layer.cpp +++ b/src/app/commands/cmd_new_layer.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2023 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -274,8 +274,7 @@ void NewLayerCommand::onExecute(Context* context) Layer* layer = nullptr; { ContextWriter writer(reader); - Tx tx(writer, - fmt::format(Strings::commands_NewLayer(), layerPrefix())); + Tx tx(writer, Strings::commands_NewLayer(layerPrefix())); DocApi api = document->getApi(tx); bool afterBackground = false; @@ -513,17 +512,17 @@ std::string NewLayerCommand::onGetFriendlyName() const { std::string text; if (m_place == Place::BeforeActiveLayer) - text = fmt::format(Strings::commands_NewLayer_BeforeActiveLayer(), layerPrefix()); + text = Strings::commands_NewLayer_BeforeActiveLayer(layerPrefix()); else - text = fmt::format(Strings::commands_NewLayer(), layerPrefix()); + text = Strings::commands_NewLayer(layerPrefix()); if (params().fromClipboard()) - text = fmt::format(Strings::commands_NewLayer_FromClipboard(), text); + text = Strings::commands_NewLayer_FromClipboard(text); if (params().viaCopy()) - text = fmt::format(Strings::commands_NewLayer_ViaCopy(), text); + text = Strings::commands_NewLayer_ViaCopy(text); if (params().viaCut()) - text = fmt::format(Strings::commands_NewLayer_ViaCut(), text); + text = Strings::commands_NewLayer_ViaCut(text); if (params().ask()) - text = fmt::format(Strings::commands_NewLayer_WithDialog(), text); + text = Strings::commands_NewLayer_WithDialog(text); return text; } diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp index e3d982962..b5ca06826 100644 --- a/src/app/commands/cmd_options.cpp +++ b/src/app/commands/cmd_options.cpp @@ -888,9 +888,7 @@ public: m_pref.save(); if (!warnings.empty()) { - ui::Alert::show( - fmt::format(Strings::alerts_restart_by_preferences(), - warnings)); + ui::Alert::show(Strings::alerts_restart_by_preferences(warnings)); } // Probably it's safe to switch this flag in runtime @@ -929,8 +927,7 @@ public: } // Install? - if (ui::Alert::show( - fmt::format(Strings::alerts_install_extension(), filename)) != 1) + if (ui::Alert::show(Strings::alerts_install_extension(filename)) != 1) return false; installExtension(filename); @@ -1495,8 +1492,7 @@ private: // Ask if the user want to adjust the Screen/UI Scaling const int result = ui::Alert::show( - fmt::format( - Strings::alerts_update_screen_ui_scaling_with_theme_values(), + Strings::alerts_update_screen_ui_scaling_with_theme_values( themeName, 100 * m_pref.general.screenScale(), 100 * (newScreenScale > 0 ? newScreenScale: m_pref.general.screenScale()), @@ -1587,8 +1583,7 @@ private: // Uninstall? if (ui::Alert::show( - fmt::format( - Strings::alerts_update_extension(), + Strings::alerts_update_extension( ext->name(), (isDowngrade ? Strings::alerts_update_extension_downgrade(): Strings::alerts_update_extension_upgrade()), @@ -1664,8 +1659,7 @@ private: return; if (ui::Alert::show( - fmt::format( - Strings::alerts_uninstall_extension_warning(), + Strings::alerts_uninstall_extension_warning( item->text())) != 1) return; diff --git a/src/app/commands/cmd_palette_editor.cpp b/src/app/commands/cmd_palette_editor.cpp index 146c9db42..aca99fe1c 100644 --- a/src/app/commands/cmd_palette_editor.cpp +++ b/src/app/commands/cmd_palette_editor.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2023 Igara Studio S.A. +// Copyright (C) 2023-2024 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -15,7 +15,6 @@ #include "app/ui/color_bar.h" #include "base/replace_string.h" #include "base/trim_string.h" -#include "fmt/format.h" namespace app { @@ -107,7 +106,7 @@ std::string PaletteEditorCommand::onGetFriendlyName() const 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 base::replace_string(result, " ", " "); base::replace_string(result, " ", " "); diff --git a/src/app/commands/cmd_remove_layer.cpp b/src/app/commands/cmd_remove_layer.cpp index 54000058b..8a95637ba 100644 --- a/src/app/commands/cmd_remove_layer.cpp +++ b/src/app/commands/cmd_remove_layer.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020-2021 Igara Studio S.A. +// Copyright (C) 2020-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -24,7 +24,6 @@ #include "doc/layer_tilemap.h" #include "doc/sprite.h" #include "doc/tilesets.h" -#include "fmt/format.h" #include "ui/alert.h" #include "ui/widget.h" @@ -90,7 +89,7 @@ static bool continue_deleting_unused_tilesets( std::string message; 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() || app::OptionalAlert::show( @@ -205,12 +204,14 @@ void RemoveLayerCommand::onExecute(Context* context) update_screen_for_document(document); StatusBar::instance()->invalidate(); - if (!layerName.empty()) + if (!layerName.empty()) { StatusBar::instance()->showTip( - 1000, fmt::format(Strings::remove_layer_x_removed(), layerName)); - else - StatusBar::instance()->showTip(1000, - Strings::remove_layer_layers_removed()); + 1000, Strings::remove_layer_x_removed(layerName)); + } + else { + StatusBar::instance()->showTip( + 1000, Strings::remove_layer_layers_removed()); + } } #endif } diff --git a/src/app/commands/cmd_remove_slice.cpp b/src/app/commands/cmd_remove_slice.cpp index de5d8f9aa..aaab00cee 100644 --- a/src/app/commands/cmd_remove_slice.cpp +++ b/src/app/commands/cmd_remove_slice.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2017-2018 David Capello // // This program is distributed under the terms of @@ -22,7 +22,6 @@ #include "doc/selected_objects.h" #include "doc/slice.h" #include "doc/sprite.h" -#include "fmt/format.h" #include "ui/alert.h" #include "ui/widget.h" @@ -121,14 +120,14 @@ void RemoveSliceCommand::onExecute(Context* context) } StatusBar::instance()->invalidate(); - if (!sliceName.empty()) + if (!sliceName.empty()) { StatusBar::instance()->showTip( - 1000, fmt::format(Strings::remove_slice_x_removed(), sliceName)); - else + 1000, Strings::remove_slice_x_removed(sliceName)); + } + else { StatusBar::instance()->showTip( - 1000, - fmt::format(Strings::remove_slice_n_slices_removed(), - slicesToDelete.size())); + 1000, Strings::remove_slice_n_slices_removed(slicesToDelete.size())); + } } Command* CommandFactory::createRemoveSliceCommand() diff --git a/src/app/commands/cmd_rotate.cpp b/src/app/commands/cmd_rotate.cpp index 25da56f9b..fbf67f545 100644 --- a/src/app/commands/cmd_rotate.cpp +++ b/src/app/commands/cmd_rotate.cpp @@ -32,7 +32,6 @@ #include "doc/image.h" #include "doc/mask.h" #include "doc/sprite.h" -#include "fmt/format.h" #include "ui/ui.h" namespace app { @@ -261,8 +260,8 @@ std::string RotateCommand::onGetFriendlyName() const content = Strings::commands_Rotate_Selection(); else content = Strings::commands_Rotate_Sprite(); - return fmt::format(getBaseFriendlyName(), - content, base::convert_to(m_angle)); + return Strings::commands_Rotate(content, + base::convert_to(m_angle)); } Command* CommandFactory::createRotateCommand() diff --git a/src/app/commands/cmd_run_script.cpp b/src/app/commands/cmd_run_script.cpp index 68f187033..4c7b1e1ec 100644 --- a/src/app/commands/cmd_run_script.cpp +++ b/src/app/commands/cmd_run_script.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2023 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -70,7 +70,7 @@ void RunScriptCommand::onExecute(Context* context) int ret = OptionalAlert::show( Preferences::instance().scripts.showRunScriptAlert, 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) return; } @@ -89,11 +89,11 @@ void RunScriptCommand::onExecute(Context* context) std::string RunScriptCommand::onGetFriendlyName() const { if (m_filename.empty()) - return getBaseFriendlyName(); - else - return fmt::format("{0}: {1}", - getBaseFriendlyName(), - base::get_file_name(m_filename)); + return Strings::commands_RunScript(); + + return fmt::format("{0}: {1}", + Strings::commands_RunScript(), + base::get_file_name(m_filename)); } Command* CommandFactory::createRunScriptCommand() diff --git a/src/app/commands/cmd_save_file.cpp b/src/app/commands/cmd_save_file.cpp index 4f00aba55..b54fe446d 100644 --- a/src/app/commands/cmd_save_file.cpp +++ b/src/app/commands/cmd_save_file.cpp @@ -41,7 +41,6 @@ #include "doc/mask.h" #include "doc/sprite.h" #include "doc/tag.h" -#include "fmt/format.h" #include "ui/ui.h" #include "undo/undo_state.h" @@ -269,8 +268,7 @@ void SaveFileBaseCommand::saveDocumentInBackground( #ifdef ENABLE_UI if (context->isUIAvailable() && params().ui()) { StatusBar::instance()->setStatusText( - 2000, fmt::format(Strings::save_file_saved(), - base::get_file_name(filename))); + 2000, Strings::save_file_saved(base::get_file_name(filename))); } #endif } @@ -428,8 +426,7 @@ void SaveFileCopyAsCommand::onExecute(Context* context) int ret = OptionalAlert::show( Preferences::instance().exportFile.showOverwriteFilesAlert, 1, // Yes is the default option when the alert dialog is disabled - fmt::format(Strings::alerts_overwrite_files_on_export(), - outputFilename)); + Strings::alerts_overwrite_files_on_export(outputFilename)); if (ret != 1) goto again; } diff --git a/src/app/commands/cmd_save_mask.cpp b/src/app/commands/cmd_save_mask.cpp index 4e4c78eb2..885b45e6e 100644 --- a/src/app/commands/cmd_save_mask.cpp +++ b/src/app/commands/cmd_save_mask.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -16,7 +17,6 @@ #include "base/fs.h" #include "doc/mask.h" #include "doc/sprite.h" -#include "fmt/format.h" #include "ui/alert.h" namespace app { @@ -55,7 +55,7 @@ void SaveMaskCommand::onExecute(Context* context) std::string filename = selFilename.front(); 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() diff --git a/src/app/commands/cmd_save_palette.cpp b/src/app/commands/cmd_save_palette.cpp index e52ce22cf..dc8055aef 100644 --- a/src/app/commands/cmd_save_palette.cpp +++ b/src/app/commands/cmd_save_palette.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2021 Igara Studio S.A. +// Copyright (C) 2021-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -21,7 +21,6 @@ #include "app/modules/palettes.h" #include "base/fs.h" #include "doc/palette.h" -#include "fmt/format.h" #include "ui/alert.h" namespace app { @@ -78,8 +77,8 @@ void SavePaletteCommand::onExecute(Context* ctx) if (!base::has_file_extension(filename, exts)) { if (ctx->isUIAvailable()) { ui::Alert::show( - fmt::format(Strings::alerts_file_format_doesnt_support_palette(), - base::get_file_extension(filename))); + Strings::alerts_file_format_doesnt_support_palette( + base::get_file_extension(filename))); } return; } @@ -90,7 +89,7 @@ void SavePaletteCommand::onExecute(Context* ctx) colorSpace = activeDoc->sprite()->colorSpace(); 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()) { set_default_palette(palette); diff --git a/src/app/commands/cmd_scroll.cpp b/src/app/commands/cmd_scroll.cpp index 0a3b4f796..ebff1eb9e 100644 --- a/src/app/commands/cmd_scroll.cpp +++ b/src/app/commands/cmd_scroll.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020-2022 Igara Studio S.A. +// Copyright (C) 2020-2024 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -17,7 +17,6 @@ #include "app/i18n/strings.h" #include "app/ui/editor/editor.h" #include "base/convert_to.h" -#include "fmt/format.h" #include "ui/view.h" namespace app { @@ -64,8 +63,7 @@ void ScrollCommand::onExecute(Context* context) std::string ScrollCommand::onGetFriendlyName() const { - return fmt::format(getBaseFriendlyName(), - m_moveThing.getFriendlyString()); + return Strings::commands_Scroll(m_moveThing.getFriendlyString()); } Command* CommandFactory::createScrollCommand() diff --git a/src/app/commands/cmd_select_palette.cpp b/src/app/commands/cmd_select_palette.cpp index c707d0454..c743c10be 100644 --- a/src/app/commands/cmd_select_palette.cpp +++ b/src/app/commands/cmd_select_palette.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2021 Igara Studio SA +// Copyright (C) 2021-2024 Igara Studio SA // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -229,7 +229,7 @@ std::string SelectPaletteColorsCommand::onGetFriendlyName() const case UsedTiles: return Strings::commands_SelectPaletteColors_UsedTiles(); case UnusedTiles: return Strings::commands_SelectPaletteColors_UnusedTiles(); } - return getBaseFriendlyName(); + return Strings::commands_SelectPaletteColors(); } Command* CommandFactory::createSelectPaletteColorsCommand() diff --git a/src/app/commands/cmd_select_tile.cpp b/src/app/commands/cmd_select_tile.cpp index 0bfeb30a9..bde47da9b 100644 --- a/src/app/commands/cmd_select_tile.cpp +++ b/src/app/commands/cmd_select_tile.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2022 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2015-2018 David Capello // // This program is distributed under the terms of @@ -19,7 +19,6 @@ #include "app/tx.h" #include "app/ui/editor/editor.h" #include "doc/mask.h" -#include "fmt/format.h" #include "ui/system.h" namespace app { @@ -124,7 +123,7 @@ std::string SelectTileCommand::onGetFriendlyName() const text = Strings::commands_SelectTile_Intersect(); break; default: - text = getBaseFriendlyName(); + text = Strings::commands_SelectTile(); break; } return text; diff --git a/src/app/commands/cmd_set_color_selector.cpp b/src/app/commands/cmd_set_color_selector.cpp index a506d77b4..a1d353c90 100644 --- a/src/app/commands/cmd_set_color_selector.cpp +++ b/src/app/commands/cmd_set_color_selector.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2016-2018 David Capello // // This program is distributed under the terms of @@ -13,7 +14,6 @@ #include "app/commands/params.h" #include "app/i18n/strings.h" #include "app/ui/color_bar.h" -#include "fmt/format.h" namespace app { @@ -90,7 +90,7 @@ std::string SetColorSelectorCommand::onGetFriendlyName() const type = Strings::commands_SetColorSelector_NormalMapWheel(); break; } - return fmt::format(getBaseFriendlyName() + ": {0}", type); + return fmt::format("{0}: {1}", Command::onGetFriendlyName(), type); } Command* CommandFactory::createSetColorSelectorCommand() diff --git a/src/app/commands/cmd_set_ink_type.cpp b/src/app/commands/cmd_set_ink_type.cpp index 4655fed98..b60e073ff 100644 --- a/src/app/commands/cmd_set_ink_type.cpp +++ b/src/app/commands/cmd_set_ink_type.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2024 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -16,7 +16,6 @@ #include "app/i18n/strings.h" #include "app/tools/ink_type.h" #include "app/ui/context_bar.h" -#include "fmt/format.h" namespace app { @@ -72,7 +71,7 @@ std::string SetInkTypeCommand::onGetFriendlyName() const ink = Strings::inks_shading(); break; } - return fmt::format(getBaseFriendlyName(), ink); + return Strings::commands_SetInkType(ink); } Command* CommandFactory::createSetInkTypeCommand() diff --git a/src/app/commands/cmd_sprite_properties.cpp b/src/app/commands/cmd_sprite_properties.cpp index a601b6fb4..5d34ea9b2 100644 --- a/src/app/commands/cmd_sprite_properties.cpp +++ b/src/app/commands/cmd_sprite_properties.cpp @@ -35,7 +35,6 @@ #include "doc/sprite.h" #include "doc/tilesets.h" #include "doc/user_data.h" -#include "fmt/format.h" #include "os/color_space.h" #include "os/system.h" #include "ui/ui.h" @@ -100,7 +99,7 @@ private: auto sprite = tileset->sprite(); 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.commit(); @@ -119,12 +118,12 @@ private: } if (!tilemapsNames.empty()) { 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; } 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.commit(); @@ -276,8 +275,8 @@ void SpritePropertiesCommand::onExecute(Context* context) imgtype_text = Strings::sprite_properties_grayscale(); break; case IMAGE_INDEXED: - imgtype_text = fmt::format(Strings::sprite_properties_indexed_color(), - sprite->palette(0)->size()); + imgtype_text = Strings::sprite_properties_indexed_color( + sprite->palette(0)->size()); break; default: ASSERT(false); diff --git a/src/app/commands/cmd_zoom.cpp b/src/app/commands/cmd_zoom.cpp index fefbca729..84b304104 100644 --- a/src/app/commands/cmd_zoom.cpp +++ b/src/app/commands/cmd_zoom.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2021-2022 Igara Studio S.A. +// Copyright (C) 2021-2024 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -16,7 +16,6 @@ #include "app/pref/preferences.h" #include "app/ui/editor/editor.h" #include "base/convert_to.h" -#include "fmt/format.h" #include "render/zoom.h" #include "ui/manager.h" #include "ui/system.h" @@ -130,8 +129,7 @@ std::string ZoomCommand::onGetFriendlyName() const text = Strings::commands_Zoom_Out(); break; case Action::Set: - text = fmt::format(Strings::commands_Zoom_Set(), - int(100.0*m_zoom.scale())); + text = Strings::commands_Zoom_Set(int(100.0*m_zoom.scale())); break; } diff --git a/src/app/commands/command.cpp b/src/app/commands/command.cpp index 64b5563b6..320c7b4ad 100644 --- a/src/app/commands/command.cpp +++ b/src/app/commands/command.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2024 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -20,18 +20,12 @@ Command::Command(const char* id, CommandFlags flags) : m_id(id) , m_flags(flags) { - generateFriendlyName(); } Command::~Command() { } -std::string Command::friendlyName() const -{ - return onGetFriendlyName(); -} - bool Command::needsParams() const { 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) { onExecute(context); @@ -110,7 +95,9 @@ void Command::onExecute(Context* context) std::string Command::onGetFriendlyName() const { - return m_friendlyName; + if (auto* strings = Strings::instance()) + return strings->translate(("commands." + id()).c_str()); + return id(); } } // namespace app diff --git a/src/app/commands/command.h b/src/app/commands/command.h index f3da3ac58..d67e986be 100644 --- a/src/app/commands/command.h +++ b/src/app/commands/command.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -29,13 +30,12 @@ namespace app { virtual ~Command(); const std::string& id() const { return m_id; } - std::string friendlyName() const; + std::string friendlyName() const { return onGetFriendlyName(); } bool needsParams() const; void loadParams(const Params& params); bool isEnabled(Context* context); bool isChecked(Context* context); - void generateFriendlyName(); protected: virtual bool onNeedsParams() const; @@ -45,16 +45,11 @@ namespace app { virtual void onExecute(Context* context); virtual std::string onGetFriendlyName() const; - const std::string& getBaseFriendlyName() const { - return m_friendlyName; - } - private: friend class Context; void execute(Context* context); std::string m_id; - std::string m_friendlyName; CommandFlags m_flags; }; diff --git a/src/app/commands/convert_layer.cpp b/src/app/commands/convert_layer.cpp index 3a6281483..738839fe2 100644 --- a/src/app/commands/convert_layer.cpp +++ b/src/app/commands/convert_layer.cpp @@ -1,5 +1,5 @@ // 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 // 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::Layer: return Strings::commands_ConvertLayer_Layer(); break; case ConvertLayerParam::Tilemap: return Strings::commands_ConvertLayer_Tilemap(); break; - default: return getBaseFriendlyName(); + default: return Strings::commands_ConvertLayer(); } } diff --git a/src/app/commands/move_thing.cpp b/src/app/commands/move_thing.cpp index f3224eb70..290779c79 100644 --- a/src/app/commands/move_thing.cpp +++ b/src/app/commands/move_thing.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2017 David Capello // // This program is distributed under the terms of @@ -16,7 +16,6 @@ #include "app/ui/doc_view.h" #include "app/ui/editor/editor.h" #include "app/ui_context.h" -#include "fmt/format.h" #include @@ -66,8 +65,7 @@ std::string MoveThing::getFriendlyString() const case Down: dir = Strings::commands_Move_Down(); break; } - return fmt::format(Strings::commands_Move_Thing(), - quantity, dim, dir); + return Strings::commands_Move_Thing(quantity, dim, dir); } gfx::Point MoveThing::getDelta(Context* context) const diff --git a/src/app/commands/set_playback_speed.cpp b/src/app/commands/set_playback_speed.cpp index 2a67f66fb..9e89bb8ce 100644 --- a/src/app/commands/set_playback_speed.cpp +++ b/src/app/commands/set_playback_speed.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (c) 2023 Igara Studio S.A. +// Copyright (c) 2023-2024 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -9,9 +9,9 @@ #endif #include "app/commands/new_params.h" -#include "app/ui_context.h" +#include "app/i18n/strings.h" #include "app/ui/editor/editor.h" -#include "fmt/format.h" +#include "app/ui_context.h" namespace app { @@ -55,7 +55,7 @@ void SetPlaybackSpeedCommand::onExecute(Context* ctx) std::string SetPlaybackSpeedCommand::onGetFriendlyName() const { - return fmt::format(getBaseFriendlyName(), params().multiplier()); + return Strings::commands_SetPlaybackSpeed(params().multiplier()); } Command* CommandFactory::createSetPlaybackSpeedCommand() diff --git a/src/app/commands/show_menu.cpp b/src/app/commands/show_menu.cpp index b53784d5e..654929318 100644 --- a/src/app/commands/show_menu.cpp +++ b/src/app/commands/show_menu.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2022 Igara Studio S.A. +// Copyright (C) 2022-2024 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -13,7 +13,6 @@ #include "app/commands/new_params.h" #include "app/context.h" #include "app/i18n/strings.h" -#include "fmt/format.h" namespace app { @@ -55,7 +54,7 @@ std::string ShowMenuCommand::onGetFriendlyName() const name = menuitem->text(); else name = params().menu(); - return fmt::format(Strings::commands_ShowMenu(), name); + return Strings::commands_ShowMenu(name); } MenuItem* ShowMenuCommand::findMenuItem() const diff --git a/src/app/commands/tileset_mode.cpp b/src/app/commands/tileset_mode.cpp index 548cd0295..309eb3698 100644 --- a/src/app/commands/tileset_mode.cpp +++ b/src/app/commands/tileset_mode.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2024 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -13,7 +13,6 @@ #include "app/commands/params.h" #include "app/i18n/strings.h" #include "app/ui/color_bar.h" -#include "fmt/format.h" namespace app { @@ -52,7 +51,7 @@ protected: case TilesetMode::Auto: mode = Strings::commands_TilesetMode_Auto(); break; case TilesetMode::Stack: mode = Strings::commands_TilesetMode_Stack(); break; } - return fmt::format(getBaseFriendlyName(), mode); + return Strings::commands_TilesetMode(mode); } private: diff --git a/src/app/file/file.cpp b/src/app/file/file.cpp index 2f5243b5a..1a8c4c46c 100644 --- a/src/app/file/file.cpp +++ b/src/app/file/file.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2023 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -704,8 +704,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context, // show the alert dialog. if (fatal) { ui::Alert::show( - fmt::format( - Strings::alerts_file_format_doesnt_support_error(), + Strings::alerts_file_format_doesnt_support_error( format->name(), warnings)); ret = 1; @@ -714,8 +713,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context, ret = OptionalAlert::show( Preferences::instance().saveFile.showFileFormatDoesntSupportAlert, 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(), warnings)); } @@ -785,8 +783,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context, OptionalAlert::show( Preferences::instance().saveFile.showExportAnimationInSequenceAlert, 1, - fmt::format( - Strings::alerts_export_animation_in_sequence(), + Strings::alerts_export_animation_in_sequence( 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[1]))) != 1) { diff --git a/src/app/i18n/strings.cpp b/src/app/i18n/strings.cpp index 1f065c066..31820f8f6 100644 --- a/src/app/i18n/strings.cpp +++ b/src/app/i18n/strings.cpp @@ -17,6 +17,7 @@ #include "app/resource_finder.h" #include "app/xml_document.h" #include "app/xml_exception.h" +#include "base/debug.h" #include "base/fs.h" #include "cfg/cfg.h" @@ -116,10 +117,49 @@ void Strings::setCurrentLanguage(const std::string& langId) 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) { m_strings.clear(); loadStringsFromDataDir(kDefLanguage); + m_default = m_strings; + if (langId != kDefLanguage) { loadStringsFromDataDir(langId); loadStringsFromExtension(langId); @@ -201,8 +241,15 @@ const std::string& Strings::translate(const char* id) const auto it = m_strings.find(id); if (it != m_strings.end()) 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 diff --git a/src/app/i18n/strings.h b/src/app/i18n/strings.h index e115f16d5..a5cef7b33 100644 --- a/src/app/i18n/strings.h +++ b/src/app/i18n/strings.h @@ -10,6 +10,7 @@ #pragma once #include "app/i18n/lang_info.h" +#include "fmt/core.h" #include "obs/signal.h" #include "strings.ini.h" @@ -32,11 +33,30 @@ namespace app { static Strings* instance(); const std::string& translate(const char* id) const; + const std::string& defaultString(const char* id) const; std::set availableLanguages() const; std::string currentLanguage() const; 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 + 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 LanguageChange; private: @@ -50,7 +70,8 @@ namespace app { Preferences& m_pref; Extensions& m_exts; - mutable std::unordered_map m_strings; + mutable std::unordered_map m_default; // Default strings from en.ini + mutable std::unordered_map m_strings; // Strings from current language }; } // namespace app diff --git a/src/app/i18n/xml_translator.cpp b/src/app/i18n/xml_translator.cpp index 21ff28f9f..40207deb4 100644 --- a/src/app/i18n/xml_translator.cpp +++ b/src/app/i18n/xml_translator.cpp @@ -26,9 +26,9 @@ std::string XmlTranslator::operator()(const XMLElement* elem, return std::string(); else if (value[0] == '@') { // Translate string if (value[1] == '.') - return Strings::instance()->translate((m_stringIdPrefix + (value+1)).c_str()); + return Strings::Translate((m_stringIdPrefix + (value+1)).c_str()); else - return Strings::instance()->translate(value+1); + return Strings::Translate(value+1); } else if (value[0] == '!') // Raw string return std::string(value+1); diff --git a/src/app/job.cpp b/src/app/job.cpp index f6221ad01..0803641a4 100644 --- a/src/app/job.cpp +++ b/src/app/job.cpp @@ -15,7 +15,6 @@ #include "app/console.h" #include "app/context.h" #include "app/i18n/strings.h" -#include "fmt/format.h" #include "ui/alert.h" #include "ui/widget.h" #include "ui/window.h" @@ -41,8 +40,7 @@ Job::Job(const std::string& jobName, m_canceled_flag = false; if (showProgress && App::instance()->isGui()) { - m_alert_window = ui::Alert::create( - fmt::format(Strings::alerts_job_working(), jobName)); + m_alert_window = ui::Alert::create(Strings::alerts_job_working(jobName)); m_alert_window->addProgress(); m_timer = std::make_unique(kMonitoringPeriod, m_alert_window.get()); diff --git a/src/app/launcher.cpp b/src/app/launcher.cpp index 8ca6a75fb..e38cab896 100644 --- a/src/app/launcher.cpp +++ b/src/app/launcher.cpp @@ -1,5 +1,6 @@ // 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 // the End-User License Agreement for Aseprite. @@ -13,7 +14,6 @@ #include "app/i18n/strings.h" #include "base/exception.h" #include "base/launcher.h" -#include "fmt/format.h" #include "ui/alert.h" namespace app { @@ -27,13 +27,13 @@ void open_url(const std::string& url) void open_file(const std::string& 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) { 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 diff --git a/src/app/ui/button_set.cpp b/src/app/ui/button_set.cpp index 3b0a2096d..32fc0ca73 100644 --- a/src/app/ui/button_set.cpp +++ b/src/app/ui/button_set.cpp @@ -13,7 +13,6 @@ #include "app/modules/gui.h" #include "app/ui/skin/skin_theme.h" -#include "fmt/format.h" #include "gfx/color.h" #include "os/surface.h" #include "ui/box.h" diff --git a/src/app/ui/context_bar.cpp b/src/app/ui/context_bar.cpp index a15ee240f..3a594e97d 100644 --- a/src/app/ui/context_bar.cpp +++ b/src/app/ui/context_bar.cpp @@ -1436,30 +1436,24 @@ protected: class ContextBar::EyedropperField : public HBox { public: EyedropperField() { - const auto combined = Strings::context_bar_eyedropper_combined(); - m_channel.addItem(fmt::format( - combined, + m_channel.addItem(Strings::context_bar_eyedropper_combined( Strings::context_bar_eyedropper_color(), Strings::context_bar_eyedropper_alpha())); m_channel.addItem(Strings::context_bar_eyedropper_color()); m_channel.addItem(Strings::context_bar_eyedropper_alpha()); - m_channel.addItem(fmt::format( - combined, + m_channel.addItem(Strings::context_bar_eyedropper_combined( Strings::context_bar_eyedropper_rgb(), Strings::context_bar_eyedropper_alpha())); m_channel.addItem(Strings::context_bar_eyedropper_rgb()); - m_channel.addItem(fmt::format( - combined, + m_channel.addItem(Strings::context_bar_eyedropper_combined( Strings::context_bar_eyedropper_hsv(), Strings::context_bar_eyedropper_alpha())); m_channel.addItem(Strings::context_bar_eyedropper_hsv()); - m_channel.addItem(fmt::format( - combined, + m_channel.addItem(Strings::context_bar_eyedropper_combined( Strings::context_bar_eyedropper_hsl(), Strings::context_bar_eyedropper_alpha())); m_channel.addItem(Strings::context_bar_eyedropper_hsl()); - m_channel.addItem(fmt::format( - combined, + m_channel.addItem(Strings::context_bar_eyedropper_combined( Strings::context_bar_eyedropper_gray(), Strings::context_bar_eyedropper_alpha())); m_channel.addItem(Strings::context_bar_eyedropper_gray()); diff --git a/src/app/ui/data_recovery_view.cpp b/src/app/ui/data_recovery_view.cpp index c3609a12e..56dd4c8f3 100644 --- a/src/app/ui/data_recovery_view.cpp +++ b/src/app/ui/data_recovery_view.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2022 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -30,7 +30,6 @@ #include "app/ui/workspace.h" #include "app/ui_context.h" #include "base/fs.h" -#include "fmt/format.h" #include "ui/alert.h" #include "ui/button.h" #include "ui/entry.h" @@ -346,9 +345,7 @@ void DataRecoveryView::fillListWith(const bool crashes) std::string title = session->name(); if (session->version() != get_app_version()) - title = - fmt::format(Strings::recover_files_incompatible(), - title, session->version()); + title = Strings::recover_files_incompatible(title, session->version()); auto sep = new SeparatorInView(title, HORIZONTAL); sep->InitTheme.connect( @@ -492,8 +489,7 @@ void DataRecoveryView::onDelete() return; // Delete one backup - if (Alert::show( - fmt::format(Strings::alerts_delete_selected_backups(), + if (Alert::show(Strings::alerts_delete_selected_backups( int(items.size()))) != 1) return; // Cancel @@ -531,11 +527,11 @@ void DataRecoveryView::onChangeSelection() m_openButton.setEnabled(count > 0); if (count < 2) { m_openButton.mainButton()->setText( - fmt::format(Strings::recover_files_recover_sprite(), count)); + Strings::recover_files_recover_sprite()); } else { m_openButton.mainButton()->setText( - fmt::format(Strings::recover_files_recover_n_sprites(), count)); + Strings::recover_files_recover_n_sprites(count)); } } diff --git a/src/app/ui/doc_view.cpp b/src/app/ui/doc_view.cpp index fcf95d3fb..7c27fe9ed 100644 --- a/src/app/ui/doc_view.cpp +++ b/src/app/ui/doc_view.cpp @@ -317,8 +317,7 @@ bool DocView::onCloseView(Workspace* workspace, bool quitting) while (m_document->isModified()) { // ask what want to do the user with the changes in the sprite int ret = Alert::show( - fmt::format( - Strings::alerts_save_sprite_changes(), + Strings::alerts_save_sprite_changes( m_document->name(), (quitting ? Strings::alerts_save_sprite_changes_quitting(): Strings::alerts_save_sprite_changes_closing()))); diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index 003e75db7..9dc69064f 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2023 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -187,13 +187,11 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) } else if (!layer->isVisibleHierarchy()) { StatusBar::instance()->showTip( - 1000, - fmt::format(Strings::statusbar_tips_layer_x_is_hidden(), - layer->name())); + 1000, Strings::statusbar_tips_layer_x_is_hidden(layer->name())); } else if (!layer->isMovable() || !layer->isEditableHierarchy()) { StatusBar::instance()->showTip( - 1000, fmt::format(Strings::statusbar_tips_layer_locked(), layer->name())); + 1000, Strings::statusbar_tips_layer_locked(layer->name())); } else { MovingCelCollect collect(editor, layer); @@ -698,7 +696,7 @@ bool StandbyState::checkStartDrawingStraightLine(Editor* editor, if (drawingState) { // Disable stabilizer so that it does not affect the line preview drawingState->disableMouseStabilizer(); - + drawingState->sendMovementToToolLoop( tools::Pointer( pointer ? pointer->point(): editor->screenToEditor(editor->mousePosInDisplay()), @@ -760,9 +758,7 @@ void StandbyState::transformSelection(Editor* editor, MouseMessage* msg, HandleT Layer* layer = editor->layer(); if (layer && layer->isReference()) { StatusBar::instance()->showTip( - 1000, - fmt::format(Strings::statusbar_tips_non_transformable_reference_layer(), - layer->name())); + 1000, Strings::statusbar_tips_non_transformable_reference_layer(layer->name())); return; } diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp index deec878ee..a554b54a7 100644 --- a/src/app/ui/editor/tool_loop_impl.cpp +++ b/src/app/ui/editor/tool_loop_impl.cpp @@ -869,9 +869,7 @@ tools::ToolLoop* create_tool_loop( if (toolPref.floodfill.referTo() == app::gen::FillReferTo::ACTIVE_LAYER) { StatusBar::instance()->showTip( - 1000, - fmt::format(Strings::statusbar_tips_layer_x_is_hidden(), - layer->name())); + 1000, Strings::statusbar_tips_layer_x_is_hidden(layer->name())); return nullptr; } } @@ -883,8 +881,7 @@ tools::ToolLoop* create_tool_loop( else if (layer->isReference()) { StatusBar::instance()->showTip( 1000, - fmt::format(Strings::statusbar_tips_unmodifiable_reference_layer(), - layer->name())); + Strings::statusbar_tips_unmodifiable_reference_layer(layer->name())); return nullptr; } } diff --git a/src/app/ui/export_file_window.cpp b/src/app/ui/export_file_window.cpp index b17ec4c3b..a1c362469 100644 --- a/src/app/ui/export_file_window.cpp +++ b/src/app/ui/export_file_window.cpp @@ -256,9 +256,10 @@ void ExportFileWindow::updateAdjustResizeButton() if (adjustResize()->isVisible() != newState) { adjustResize()->setVisible(newState); - if (newState) - adjustResize()->setText(fmt::format(Strings::export_file_adjust_resize(), - 100 * m_preferredResize)); + if (newState) { + adjustResize()->setText( + Strings::export_file_adjust_resize(100 * m_preferredResize)); + } adjustResize()->parent()->layout(); } } @@ -286,7 +287,7 @@ void ExportFileWindow::onOK() } else { ui::Alert::show( - fmt::format(Strings::alerts_unknown_output_file_format_error(), ext)); + Strings::alerts_unknown_output_file_format_error(ext)); return; } } diff --git a/src/app/ui/file_selector.cpp b/src/app/ui/file_selector.cpp index e6053c650..4c2251ae3 100644 --- a/src/app/ui/file_selector.cpp +++ b/src/app/ui/file_selector.cpp @@ -28,7 +28,6 @@ #include "base/fs.h" #include "base/paths.h" #include "base/string.h" -#include "fmt/format.h" #include "ui/ui.h" #include "new_folder_window.xml.h" @@ -638,9 +637,8 @@ again: const char* invalid_chars = ": * ? \" < > |"; ui::Alert::show( - fmt::format( - Strings::alerts_invalid_chars_in_filename(), - invalid_chars)); + Strings::alerts_invalid_chars_in_filename( + invalid_chars)); // show the window again setVisible(true); @@ -659,9 +657,7 @@ again: if (m_type == FileSelectorType::Save && base::is_file(buf)) { 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) { setVisible(true); goto again; diff --git a/src/app/ui/home_view.cpp b/src/app/ui/home_view.cpp index d61828437..2d72d8a09 100644 --- a/src/app/ui/home_view.cpp +++ b/src/app/ui/home_view.cpp @@ -303,8 +303,7 @@ void HomeView::onUpToDate() void HomeView::onNewUpdate(const std::string& url, const std::string& version) { checkUpdate()->setText( - fmt::format(Strings::home_view_new_version_available(), - get_app_name(), version)); + Strings::home_view_new_version_available(get_app_name(), version)); #ifdef ENABLE_DRM DRM_INVALID { checkUpdate()->setUrl(url); diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp index 81252dec6..f0cef3c1f 100644 --- a/src/app/ui/main_window.cpp +++ b/src/app/ui/main_window.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2023 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -217,15 +217,6 @@ MainWindow::~MainWindow() void MainWindow::onLanguageChange() { - auto commands = Commands::instance(); - std::vector commandIDs; - commands->getAllIds(commandIDs); - - for (const auto& commandID : commandIDs) { - Command* command = commands->byId(commandID.c_str()); - command->generateFriendlyName(); - } - m_menuBar->reload(); layout(); invalidate(); diff --git a/src/app/ui/toolbar.cpp b/src/app/ui/toolbar.cpp index 68b29e08e..c6a04f017 100644 --- a/src/app/ui/toolbar.cpp +++ b/src/app/ui/toolbar.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2022 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // 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); if (key && !key->accels().empty()) { tooltip += "\n\n"; - tooltip += fmt::format(Strings::tools_shortcut(), - key->accels().front().toString()); + tooltip += Strings::tools_shortcut(key->accels().front().toString()); } } else if (group_index == PreviewVisibilityIndex) { diff --git a/src/app/util/layer_utils.cpp b/src/app/util/layer_utils.cpp index 874ba819f..8482bbbb9 100644 --- a/src/app/util/layer_utils.cpp +++ b/src/app/util/layer_utils.cpp @@ -56,8 +56,7 @@ bool layer_is_locked(Editor* editor) #ifdef ENABLE_UI if (statusBar) { statusBar->showTip( - 1000, fmt::format(Strings::statusbar_tips_layer_x_is_hidden(), - layer->name())); + 1000, Strings::statusbar_tips_layer_x_is_hidden(layer->name())); } #endif return true; @@ -67,7 +66,7 @@ bool layer_is_locked(Editor* editor) #ifdef ENABLE_UI if (statusBar) { statusBar->showTip( - 1000, fmt::format(Strings::statusbar_tips_layer_locked(), layer->name())); + 1000, Strings::statusbar_tips_layer_locked(layer->name())); } #endif return true; diff --git a/src/gen/strings_class.cpp b/src/gen/strings_class.cpp index 43401c6db..1cf02c91f 100644 --- a/src/gen/strings_class.cpp +++ b/src/gen/strings_class.cpp @@ -1,4 +1,5 @@ // Aseprite Code Generator +// Copyright (c) 2024 Igara Studio S.A. // Copyright (c) 2016-2017 David Capello // // This file is released under the terms of the MIT license. @@ -11,6 +12,8 @@ #include "base/string.h" #include "cfg/cfg.h" +#include +#include #include static std::string to_cpp(std::string stringId) @@ -19,6 +22,33 @@ static std::string to_cpp(std::string stringId) return stringId; } +static size_t count_fmt_args(const std::string& format) +{ + size_t n = 0; + for (size_t i=0; i(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) { cfg::CfgFile cfg; @@ -38,7 +68,9 @@ void gen_strings_class(const std::string& inputFn) << "\n" << " template\n" << " class Strings {\n" - << " public:\n"; + << " public:\n" + << " class ID {\n" + << " public:\n"; std::vector sections; std::vector keys; @@ -49,10 +81,61 @@ void gen_strings_class(const std::string& inputFn) std::string textId = section; 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); - 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); } diff --git a/third_party/fmt b/third_party/fmt index a0b8a92e3..67c0c0c09 160000 --- a/third_party/fmt +++ b/third_party/fmt @@ -1 +1 @@ -Subproject commit a0b8a92e3d1532361c2f7feb63babc5c18d00ef2 +Subproject commit 67c0c0c09cf74d407d71a29c194761981614df3e