From 8f7daa9bd100345d7e23639604c9a3a50ce6448b Mon Sep 17 00:00:00 2001 From: rtk0c Date: Fri, 11 Jun 2021 13:35:35 -0700 Subject: Convert runtime-loaded l10n to string literals chosen at compile time --- core/CMakeLists.txt | 12 +- core/locale/en_US.json | 3 - core/locale/zh_CN.json | 75 -------- core/src/Entrypoint/main.cpp | 11 -- core/src/Locale/zh_CN.h | 83 +++++++++ core/src/Model/Assets.cpp | 37 ++-- core/src/Model/Template/Template_Main.cpp | 24 +-- core/src/Model/Workflow/Workflow_Main.cpp | 18 +- core/src/UI/Localization.cpp | 3 - core/src/UI/Localization.hpp | 126 ------------- core/src/UI/UI_DatabaseView.cpp | 46 ++--- core/src/UI/UI_Items.cpp | 62 +++--- core/src/UI/UI_MainWindow.cpp | 65 +++---- core/src/UI/UI_Settings.cpp | 2 - core/src/UI/UI_Templates.cpp | 20 +- core/src/UI/UI_Workflows.cpp | 28 ++- core/src/UI/fwd.hpp | 3 - core/src/Utils/I18n.cpp | 300 ------------------------------ core/src/Utils/I18n.hpp | 93 +-------- core/src/Utils/Macros.hpp | 5 + core/src/Utils/fwd.hpp | 9 - 21 files changed, 243 insertions(+), 782 deletions(-) delete mode 100644 core/locale/en_US.json delete mode 100644 core/locale/zh_CN.json create mode 100644 core/src/Locale/zh_CN.h delete mode 100644 core/src/UI/Localization.cpp delete mode 100644 core/src/UI/Localization.hpp delete mode 100644 core/src/Utils/I18n.cpp (limited to 'core') diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 64a31d7..d3d7496 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -4,6 +4,8 @@ option(BUILD_CORE_WITH_UNITY_BUILD OFF) option(BUILD_CORE_MAIN "Whether to compile the main executable or not." ON) option(BUILD_CORE_TESTS "Whether to compile the tests executable or not." OFF) +set(TARGET_LOCALE "en_US" CACHE STRING "Locale to build.") + option(BUILD_CORE_WITH_OPENGL2_BACKEND ON) option(BUILD_CORE_WITH_OPENGL3_BACKEND ON) option(BUILD_CORE_WITH_VULKAN_BACKEND ON) @@ -70,7 +72,6 @@ add_source_group(MODEL_WORKFLOW_VALUES_MODULE_SOURCES ) set(UI_MODULE_SOURCES - src/UI/Localization.cpp src/UI/UI_DatabaseView.cpp src/UI/UI_Items.cpp src/UI/UI_MainWindow.cpp @@ -81,7 +82,6 @@ set(UI_MODULE_SOURCES ) add_source_group(UTILS_MODULE_SOURCES - src/Utils/I18n.cpp src/Utils/Sigslot.cpp src/Utils/StandardDirectories.cpp src/Utils/Time.cpp @@ -136,6 +136,14 @@ function(add_executable_variant TARGET_NAME) PLATFORM_LINUX=$ ) + if(NOT TARGET_LOCALE STREQUAL "en_US") + target_compile_definitions(${TARGET_NAME} + PRIVATE + TARGET_LOCALE=${TARGET_LOCALE} + TARGET_LOCALE_FILE="Locale/${TARGET_LOCALE}.h" + ) + endif() + if(WIN32) message("CpltCore: - building with DirectX11 backend ${BUILD_CORE_WITH_DX11_BACKEND}") message("CpltCore: - building with DirectX12 backend ${BUILD_CORE_WITH_DX12_BACKEND}") diff --git a/core/locale/en_US.json b/core/locale/en_US.json deleted file mode 100644 index b6c9c38..0000000 --- a/core/locale/en_US.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "$localized_name": "English - United States" -} \ No newline at end of file diff --git a/core/locale/zh_CN.json b/core/locale/zh_CN.json deleted file mode 100644 index b095256..0000000 --- a/core/locale/zh_CN.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "$localized_name": "中文 - 中国", - "Generic.Error": "错误", - "Generic.Add": "\uf067 新建", - "Generic.Edit": "\uf044 编辑", - "Generic.Delete": "\uf1f8 删除", - "Generic.Rename": "\uf246 重命名", - "Generic.Disconnect": "\uf127 断开连接", - "Generic.Open": "打开", - "Generic.Close": "\uf00d 关闭", - "Generic.Confirm": "确定", - "Generic.Cancel": "取消", - "Generic.Name": "名称", - "Generic.Type": "类型", - "Generic.Add.DialogTitle": "新建向导", - "Generic.Delete.DialogTitle": "确认删除", - "Generic.Rename.DialogTitle": "确认重命名", - "Generic.EmptyNameError": "名称不能为空", - "Generic.DuplicateNameError": "名称已被占用", - "MainWindow.Tab.Settings": "\uf013 设置", - "MainWindow.Tab.Project": "\uf15b 项目", - "MainWindow.Tab.DatabaseView": "\uf1c0 数据", - "MainWindow.Tab.Items": "\uf466 物品", - "MainWindow.Tab.Workflows": "\uf70e 工作流", - "MainWindow.Tab.Templates": "\uf0ce 模板", - "Project.New": "新建项目...", - "Project.New.DialogTitle": "新建项目向导", - "Project.New.Name": "项目名称", - "Project.New.Path": "项目路径", - "Project.New.Path.DialogTitle": "项目路径", - "Project.New.EmptyNameError": "项目名不能为空", - "Project.New.InvalidPathError": "无效路径", - "Project.Open": "打开项目...", - "Project.Open.DialogTitle": "打开项目", - "Project.Recents": "最近使用", - "Project.Recents.Clear": "清空", - "Project.Recents.NonePresent": "(暂无最近使用的项目)", - "Project.Recents.Open.Tooltip": "打开该项目", - "Project.Recents.Delete.Tooltip": "将该项目从最近使用列表中删除,项目本身将不受影响。", - "Project.InvalidProjectFormat": "无效的项目文件", - "ActiveProject.OpenInFilesystem": "\uf07b 在文件系统中打开", - "ActiveProject.Info.Name": "项目名称:", - "ActiveProject.Info.Path": "项目路径:", - "Database.SalesView.TabName": "销售", - "Database.SalesView.Edit.DialogTitle": "编辑销售记录", - "Database.PurchasesView.TabName": "采购", - "Database.PurchasesView.Edit.DialogTitle": "编辑采购记录", - "Database.Column.Items": "项目", - "Database.Column.Customer": "客户", - "Database.Column.Factory": "工厂", - "Database.Column.Deadline": "交货期限", - "Database.Column.OrderTime": "下单时间", - "Database.Column.CompletionTime": "交货时间", - "Database.Column.ShipmentTime": "发货时间", - "Database.Column.ArrivalTime": "实际到达时间", - "Database.Message.NoOrderSelected": "选择任意一个订单以查看与其相关的批次", - "Database.Message.NotDelivered": "N/A", - "Item.Add.DialogTitle": "新建物品项", - "Item.Edit.DialogTitle": "编辑物品项", - "Item.Delete.DialogTitle": "删除物品项", - "Item.Delete.DialogMessage": "确定删除该物品项吗?", - "Item.CategoryName.Product": "产品", - "Item.CategoryName.Factory": "工厂", - "Item.CategoryName.Customer": "客户", - "Item.Column.Name": "名称", - "Item.Column.Description": "描述", - "Item.Column.Email": "邮箱", - "Item.Column.Stock": "库存", - "Item.Column.Price": "价格", - "Asset.Open": "打开资源...", - "Asset.Open.DialogTitle": "打开资源", - "Asset.Manage": "管理资源...", - "Asset.Manage.DialogTitle": "管理资源", - "Template.InvalidTypeError": "无效的模板类型", -} diff --git a/core/src/Entrypoint/main.cpp b/core/src/Entrypoint/main.cpp index d6f2cdf..3cd3862 100644 --- a/core/src/Entrypoint/main.cpp +++ b/core/src/Entrypoint/main.cpp @@ -1,6 +1,5 @@ #include "Entrypoint/Backend.hpp" #include "Model/GlobalStates.hpp" -#include "UI/Localization.hpp" #include "UI/UI.hpp" #include "Utils/I18n.hpp" #include "Utils/ScopeGuard.hpp" @@ -140,16 +139,6 @@ int main(int argc, char* argv[]) io.Fonts->AddFontFromFileTTF("fonts/FontAwesome5-Solid.otf", 14, &config, iconRanges); } - // Initialize localization utilities - { - I18n::OnLanguageChange.Connect([]() { LocaleStrings::Instance = std::make_unique(); }); - // Do i18n initialization after linking reload signals, so that when SetLanguage() is called, the locale strings will be initialized (without us writing the code another time outside the slot) - I18n::Init(); - I18n::SetLanguage("zh_CN"); - // All of our usage are cached in XxxTranslation objects, no need to keep key -> entry mappings anymore - I18n::Unload(); - } - auto dataDirOption = parser.get("--global-data-directory"); if (dataDirOption == "default") { GlobalStates::Init(); diff --git a/core/src/Locale/zh_CN.h b/core/src/Locale/zh_CN.h new file mode 100644 index 0000000..478c627 --- /dev/null +++ b/core/src/Locale/zh_CN.h @@ -0,0 +1,83 @@ +#pragma once + +#define L10N_ERROR "错误" +#define L10N_ADD "新建" +#define L10N_EDIT "编辑" +#define L10N_DELETE "删除" +#define L10N_RENAME "重命名" +#define L10N_DISCONNECT "断开连接" +#define L10N_OPEN "打开" +#define L10N_CLOSE "关闭" +#define L10N_CONFIRM "确定" +#define L10N_CANCEL "取消" +#define L10N_NAME "名称" +#define L10N_TYPE "类型" + +#define L10N_INVALID_PATH_ERROR "无效路径" +#define L10N_EMPTY_NAME_ERROR "名称不能为空" +#define L10N_DUPLICATE_NAME_ERROR "名称已被占用" + +#define L10N_MAIN_TAB_SETTINGS "设置" +#define L10N_MAIN_WINDOW_TAB_PROJECT "项目" +#define L10N_MAIN_WINDOW_TAB_DATABASE_VIEW "数据" +#define L10N_MAIN_WINDOW_TAB_ITEMS "物品" +#define L10N_MAIN_WINDOW_TAB_WORKFLOWS "工作流" +#define L10N_MAIN_WINDOW_TAB_TEMPLATES "模板" + +#define L10N_PROJECT_NEW "新建项目..." +#define L10N_PROJECT_NEW_DIALOG_TITLE "新建项目向导" +#define L10N_PROJECT_NAME "项目名称" +#define L10N_PROJECT_PATH "项目路径" +#define L10N_PROJECT_NEW_PATH_DIALOG_TITLE "项目路径" +#define L10N_PROJECT_OPEN "打开项目..." +#define L10N_PROJECT_OPEN_DIALOG_TITLE "打开项目" +#define L10N_PROJECT_RECENTS "最近使用" +#define L10N_PROJECT_RECENTS_CLEAR "清空" +#define L10N_PROJECT_RECENTS_NONE_PRESENT "(暂无最近使用的项目)" +#define L10N_PROJECT_RECENTS_OPEN_TOOLTIP "打开该项目" +#define L10N_PROJECT_RECENTS_DELETE_TOOLTIP "将该项目从最近使用列表中删除,项目本身将不受影响。" +#define L10N_PROJECT_INVALID_PROJECT_FORMAT "无效的项目文件" + +#define L10N_PROJECT_OPEN_IN_FILESYSTEM "在文件系统中打开" + +#define L10N_DATABASE_SALES_VIEW_TAB_NAME "销售" +#define L10N_DATABASE_SALES_VIEW_EDIT_DIALOG_TITLE "编辑销售记录" +#define L10N_DATABASE_PURCHASES_VIEW_TAB_NAME "采购" +#define L10N_DATABASE_PURCHASES_VIEW_EDIT_DIALOG_TITLE "编辑采购记录" +#define L10N_DATABASE_COLUMN_ITEMS "项目" +#define L10N_DATABASE_COLUMN_CUSTOMER "客户" +#define L10N_DATABASE_COLUMN_FACTORY "工厂" +/// 销售订单的交货期限 +#define L10N_DATABASE_COLUMN_DEADLINE "交货期限" +/// 采购订单的下单时间 +#define L10N_DATABASE_COLUMN_ORDER_TIME "下单时间" +/// 所有订单的“完成”时间。对于销售来说是实际交货时间,对于采购来说是收货时间。 +#define L10N_DATABASE_COLUMN_COMPLETION_TIME "交货时间" +/// 运输批次的发货时间,适用于采购和销售批次。 +#define L10N_DATABASE_COLUMN_SHIPMENT_TIME "发货时间" +/// 运输批次的收获时间,适用于采购和销售批次。 +#define L10N_DATABASE_COLUMN_ARRIVAL_TIME "实际到达时间" +#define L10N_DATABASE_MESSAGE_NO_ORDER_SELECTED "选择任意一个订单以查看与其相关的批次" +#define L10N_DATABASE_MESSAGE_NOT_DELIVERED "N/A" + +#define L10N_ITEM_ADD_DIALOG_TITLE "新建物品项" +#define L10N_ITEM_EDIT_DIALOG_TITLE "编辑物品项" +#define L10N_ITEM_DELETE_DIALOG_TITLE "删除物品项" +#define L10N_ITEM_DELETE_DIALOG_MESSAGE "确定删除该物品项吗?" +#define L10N_ITEM_CATEGORY_PRODUCT "产品" +#define L10N_ITEM_CATEGORY_FACTORY "工厂" +#define L10N_ITEM_CATEGORY_CUSTOMER "客户" +#define L10N_ITEM_COLUMN_NAME "名称" +#define L10N_ITEM_COLUMN_DESCRIPTION "描述" +#define L10N_ITEM_COLUMN_EMAIL "邮箱" +#define L10N_ITEM_COLUMN_STOCK "库存" +#define L10N_ITEM_COLUMN_PRICE "价格" + +#define L10N_ASSET_OPEN "打开资源..." +#define L10N_ASSET_OPEN_DIALOG_TITLE "打开资源" +#define L10N_ASSET_MANAGE "管理资源..." +#define L10N_ASSET_MANAGE_DIALOG_TITLE "管理资源" +#define L10N_ADD_ASSET_DIALOG_TITLE "新建资源向导" +#define L10N_DELETE_ASSET_DIALOG_TITLE "确认删除资源" +#define L10N_RENAME_ASSET_DIALOG_TITLE "重命名资源" +#define L10N_TEMPLATE_INVALID_TYPE_ERROR "无效的模板类型" diff --git a/core/src/Model/Assets.cpp b/core/src/Model/Assets.cpp index ac3335a..64e1f22 100644 --- a/core/src/Model/Assets.cpp +++ b/core/src/Model/Assets.cpp @@ -1,11 +1,12 @@ #include "Assets.hpp" -#include "UI/Localization.hpp" #include "UI/UI.hpp" +#include "Utils/I18n.hpp" #include #include #include +#include #include #include #include @@ -34,10 +35,10 @@ public: switch (NewNameError) { case NameSelectionError::None: break; case NameSelectionError::Duplicated: - ImGui::ErrorMessage("Duplicate template name"); + ImGui::ErrorMessage(I18N_TEXT("Duplicate name", L10N_DUPLICATE_NAME_ERROR)); break; case NameSelectionError::Empty: - ImGui::ErrorMessage("Template name cannot be empty"); + ImGui::ErrorMessage(I18N_TEXT("Name cannot be empty", L10N_EMPTY_NAME_ERROR)); break; } } @@ -189,28 +190,26 @@ void AssetList::DisplayDetailsList(ListState& state) void AssetList::DisplayControls(ListState& state) { auto& ps = mPrivate->PopupPrivateState; - auto ls = LocaleStrings::Instance.get(); - bool openedDummy = true; - if (ImGui::Button(ls->Add.Get())) { - ImGui::OpenPopup(ls->AddDialogTitle.Get()); + if (ImGui::Button(ICON_FA_PLUS " " I18N_TEXT("Add", L10N_ADD))) { + ImGui::OpenPopup(I18N_TEXT("Add asset wizard", L10N_ADD_ASSET_DIALOG_TITLE)); } - if (ImGui::BeginPopupModal(ls->AddDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::BeginPopupModal(I18N_TEXT("Add asset wizard", L10N_ADD_ASSET_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { DisplayAssetCreator(state); ImGui::EndPopup(); } ImGui::SameLine(); - if (ImGui::Button(ls->Rename.Get(), state.SelectedAsset == nullptr)) { - ImGui::OpenPopup(ls->RenameDialogTitle.Get()); + if (ImGui::Button(ICON_FA_I_CURSOR " " I18N_TEXT("Rename", L10N_RENAME), state.SelectedAsset == nullptr)) { + ImGui::OpenPopup(I18N_TEXT("Rename asset wizard", L10N_RENAME_ASSET_DIALOG_TITLE)); } - if (ImGui::BeginPopupModal(ls->RenameDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - if (ImGui::InputText("New name", &ps.NewName)) { + if (ImGui::BeginPopupModal(I18N_TEXT("Rename asset wizard", L10N_RENAME_ASSET_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::InputText(I18N_TEXT("Name", L10N_NAME), &ps.NewName)) { ps.Validate(*this); } - if (ImGui::Button(ls->Confirm.Get(), ps.HasErrors())) { + if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM), ps.HasErrors())) { ImGui::CloseCurrentPopup(); auto movedAsset = Rename(state.SelectedAsset->Name, ps.NewName); @@ -220,7 +219,7 @@ void AssetList::DisplayControls(ListState& state) ps = {}; } ImGui::SameLine(); - if (ImGui::Button(ls->Cancel.Get())) { + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { ImGui::CloseCurrentPopup(); } @@ -230,11 +229,11 @@ void AssetList::DisplayControls(ListState& state) } ImGui::SameLine(); - if (ImGui::Button(ls->Delete.Get(), state.SelectedAsset == nullptr)) { - ImGui::OpenPopup(ls->DeleteDialogTitle.Get()); + if (ImGui::Button(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE), state.SelectedAsset == nullptr)) { + ImGui::OpenPopup(I18N_TEXT("Delete asset", L10N_DELETE_ASSET_DIALOG_TITLE)); } - if (ImGui::BeginPopupModal(ls->DeleteDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - if (ImGui::Button(ls->Confirm.Get())) { + if (ImGui::BeginPopupModal(I18N_TEXT("Delete asset", L10N_DELETE_ASSET_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM))) { ImGui::CloseCurrentPopup(); auto& assetName = state.SelectedAsset->Name; @@ -243,7 +242,7 @@ void AssetList::DisplayControls(ListState& state) state.SelectedAsset = nullptr; } ImGui::SameLine(); - if (ImGui::Button(ls->Cancel.Get())) { + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); diff --git a/core/src/Model/Template/Template_Main.cpp b/core/src/Model/Template/Template_Main.cpp index 7d1b755..7dd5f87 100644 --- a/core/src/Model/Template/Template_Main.cpp +++ b/core/src/Model/Template/Template_Main.cpp @@ -2,7 +2,7 @@ #include "Model/GlobalStates.hpp" #include "Model/Project.hpp" -#include "UI/Localization.hpp" +#include "Utils/I18n.hpp" #include "UI/UI.hpp" #include "Utils/UUID.hpp" @@ -91,8 +91,6 @@ Template* TemplateAssetList::LoadImpl(const SavedAsset& asset) const void TemplateAssetList::DisplayAssetCreator(ListState& state) { - auto ls = LocaleStrings::Instance.get(); - auto ValidateNewName = [&]() -> void { if (mACNewName.empty()) { mACNewNameError = NameSelectionError::Empty; @@ -111,17 +109,17 @@ void TemplateAssetList::DisplayAssetCreator(ListState& state) switch (mACNewNameError) { case NameSelectionError::None: break; case NameSelectionError::Duplicated: - ImGui::ErrorMessage(ls->DuplicateNameError.Get()); + ImGui::ErrorMessage(I18N_TEXT("Duplicate name", L10N_DUPLICATE_NAME_ERROR)); break; case NameSelectionError::Empty: - ImGui::ErrorMessage(ls->EmptyNameError.Get()); + ImGui::ErrorMessage(I18N_TEXT("Name cannot be empty", L10N_EMPTY_NAME_ERROR)); break; } }; auto ShowNewKindErrors = [&]() -> void { if (mACNewKind == Template::InvalidKind) { - ImGui::ErrorMessage(ls->InvalidTemplateTypeError.Get()); + ImGui::ErrorMessage(I18N_TEXT("Invalid template type", L10N_TEMPLATE_INVALID_TYPE_ERROR)); } }; @@ -136,11 +134,11 @@ void TemplateAssetList::DisplayAssetCreator(ListState& state) ValidateNewName(); }; - if (ImGui::InputText(ls->Name.Get(), &mACNewName)) { + if (ImGui::InputText(I18N_TEXT("Name", L10N_NAME), &mACNewName)) { ValidateNewName(); } - if (ImGui::BeginCombo(ls->Type.Get(), Template::FormatKind(mACNewKind))) { + if (ImGui::BeginCombo(I18N_TEXT("Type", L10N_TYPE), Template::FormatKind(mACNewKind))) { for (int i = 0; i < Template::KindCount; ++i) { auto kind = static_cast(i); if (ImGui::Selectable(Template::FormatKind(kind), mACNewKind == kind)) { @@ -153,7 +151,7 @@ void TemplateAssetList::DisplayAssetCreator(ListState& state) ShowNewNameErrors(); ShowNewKindErrors(); - if (ImGui::Button(ls->Confirm.Get(), !IsInputValid())) { + if (ImGui::Button(I18N_TEXT("OK", L10N_CONFIRM), !IsInputValid())) { ImGui::CloseCurrentPopup(); Create(SavedAsset{ @@ -163,19 +161,17 @@ void TemplateAssetList::DisplayAssetCreator(ListState& state) ResetState(); } ImGui::SameLine(); - if (ImGui::Button(ls->Cancel.Get())) { + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { ImGui::CloseCurrentPopup(); } } void TemplateAssetList::SetupDetailsTable(const char* tableId) const { - auto ls = LocaleStrings::Instance.get(); - ImGui::BeginTable(tableId, 2, ImGuiTableFlags_Borders); - ImGui::TableSetupColumn(ls->Name.Get()); - ImGui::TableSetupColumn(ls->Type.Get()); + ImGui::TableSetupColumn(I18N_TEXT("Name", L10N_NAME)); + ImGui::TableSetupColumn(I18N_TEXT("Type", L10N_TYPE)); ImGui::TableHeadersRow(); } diff --git a/core/src/Model/Workflow/Workflow_Main.cpp b/core/src/Model/Workflow/Workflow_Main.cpp index a01ab7b..adf944e 100644 --- a/core/src/Model/Workflow/Workflow_Main.cpp +++ b/core/src/Model/Workflow/Workflow_Main.cpp @@ -2,9 +2,9 @@ #include "Model/GlobalStates.hpp" #include "Model/Project.hpp" -#include "UI/Localization.hpp" #include "UI/UI.hpp" #include "Utils/UUID.hpp" +#include "Utils/I18n.hpp" #include #include @@ -804,8 +804,6 @@ Workflow* WorkflowAssetList::LoadImpl(const SavedAsset& asset) const void WorkflowAssetList::DisplayAssetCreator(ListState& state) { - auto ls = LocaleStrings::Instance.get(); - auto ValidateNewName = [&]() -> void { if (mACNewName.empty()) { mACNewNameError = NameSelectionError::Empty; @@ -824,10 +822,10 @@ void WorkflowAssetList::DisplayAssetCreator(ListState& state) switch (mACNewNameError) { case NameSelectionError::None: break; case NameSelectionError::Duplicated: - ImGui::ErrorMessage(ls->DuplicateNameError.Get()); + ImGui::ErrorMessage(I18N_TEXT("Duplicate name", L10N_DUPLICATE_NAME_ERROR)); break; case NameSelectionError::Empty: - ImGui::ErrorMessage(ls->EmptyNameError.Get()); + ImGui::ErrorMessage(I18N_TEXT("Name cannot be empty", L10N_EMPTY_NAME_ERROR)); break; } }; @@ -841,13 +839,13 @@ void WorkflowAssetList::DisplayAssetCreator(ListState& state) ValidateNewName(); }; - if (ImGui::InputText(ls->Name.Get(), &mACNewName)) { + if (ImGui::InputText(I18N_TEXT("Name", L10N_NAME), &mACNewName)) { ValidateNewName(); } ShowNewNameErrors(); - if (ImGui::Button(ls->Confirm.Get(), !IsInputValid())) { + if (ImGui::Button(I18N_TEXT("OK", L10N_CONFIRM), !IsInputValid())) { ImGui::CloseCurrentPopup(); Create(SavedAsset{ @@ -856,18 +854,16 @@ void WorkflowAssetList::DisplayAssetCreator(ListState& state) ResetState(); } ImGui::SameLine(); - if (ImGui::Button(ls->Cancel.Get())) { + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { ImGui::CloseCurrentPopup(); } } void WorkflowAssetList::SetupDetailsTable(const char* tableId) const { - auto ls = LocaleStrings::Instance.get(); - ImGui::BeginTable(tableId, 1, ImGuiTableFlags_Borders); - ImGui::TableSetupColumn(ls->Name.Get()); + ImGui::TableSetupColumn(I18N_TEXT("Name", L10N_NAME)); ImGui::TableHeadersRow(); } diff --git a/core/src/UI/Localization.cpp b/core/src/UI/Localization.cpp deleted file mode 100644 index 220df6f..0000000 --- a/core/src/UI/Localization.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "Localization.hpp" - -std::unique_ptr LocaleStrings::Instance{}; diff --git a/core/src/UI/Localization.hpp b/core/src/UI/Localization.hpp deleted file mode 100644 index 3474053..0000000 --- a/core/src/UI/Localization.hpp +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -#include "Utils/I18n.hpp" - -#include -#include - -using namespace std::literals::string_view_literals; - -class LocaleStrings -{ -public: - static std::unique_ptr Instance; - -public: - /* Generic */ - - BasicTranslation Error{ "Generic.Error"sv }; - BasicTranslation Add{ "Generic.Add"sv }; - BasicTranslation Edit{ "Generic.Edit"sv }; - BasicTranslation Delete{ "Generic.Delete"sv }; - BasicTranslation Rename{ "Generic.Rename"sv }; - BasicTranslation Disconnect{ "Generic.Disconnect"sv }; - BasicTranslation Open{ "Generic.Open"sv }; - BasicTranslation Close{ "Generic.Close"sv }; - BasicTranslation Confirm{ "Generic.Confirm"sv }; - BasicTranslation Cancel{ "Generic.Cancel"sv }; - BasicTranslation Name{ "Generic.Name"sv }; - BasicTranslation Type{ "Generic.Type"sv }; - - BasicTranslation AddDialogTitle{ "Generic.Add.DialogTitle"sv }; - BasicTranslation DeleteDialogTitle{ "Generic.Delete.DialogTitle"sv }; - BasicTranslation RenameDialogTitle{ "Generic.Rename.DialogTitle"sv }; - - BasicTranslation EmptyNameError{ "Generic.EmptyNameError"sv }; - BasicTranslation DuplicateNameError{ "Generic.DuplicateNameError"sv }; - - /* Main window */ - - BasicTranslation SettingsTab{ "MainWindow.Tab.Settings"sv }; - BasicTranslation ProjectTab{ "MainWindow.Tab.Project"sv }; - BasicTranslation DatabaseViewTab{ "MainWindow.Tab.DatabaseView"sv }; - BasicTranslation ItemsTab{ "MainWindow.Tab.Items"sv }; - BasicTranslation WorkflowsTab{ "MainWindow.Tab.Workflows"sv }; - BasicTranslation TemplatesTab{ "MainWindow.Tab.Templates"sv }; - - /* Project tab */ - - BasicTranslation NewProject{ "Project.New"sv }; - BasicTranslation NewProjectDialogTitle{ "Project.New.DialogTitle"sv }; - BasicTranslation NewProjectNameHint{ "Project.New.Name"sv }; - BasicTranslation NewProjectPathHint{ "Project.New.Path"sv }; - BasicTranslation NewProjectPathDialogTitle{ "Project.New.Path.DialogTitle"sv }; - BasicTranslation NewProjectEmptyNameError{ "Project.New.EmptyNameError"sv }; - BasicTranslation NewProjectInvalidPathError{ "Project.New.InvalidPathError"sv }; - - BasicTranslation OpenProject{ "Project.Open"sv }; - BasicTranslation OpenProjectDialogTitle{ "Project.Open.DialogTitle"sv }; - - BasicTranslation RecentProjects{ "Project.Recents"sv }; - BasicTranslation ClearRecentProjects{ "Project.Recents.Clear"sv }; - BasicTranslation NoRecentProjectsMessage{ "Project.Recents.NonePresent"sv }; - BasicTranslation OpenRecentProjectTooltip{ "Project.Recents.Open.Tooltip"sv }; - BasicTranslation DeleteRecentProjectTooltip{ "Project.Recents.Delete.Tooltip"sv }; - - BasicTranslation InvalidProjectFormat{ "Project.InvalidProjectFormat"sv }; - - BasicTranslation OpenActiveProjectInFileSystem{ "ActiveProject.OpenInFilesystem"sv }; - BasicTranslation ActiveProjectName{ "ActiveProject.Info.Name"sv }; - BasicTranslation ActiveProjectPath{ "ActiveProject.Info.Path"sv }; - - /* Database view tab */ - - BasicTranslation SalesViewTab{ "Database.SalesView.TabName"sv }; - BasicTranslation EditSaleEntryDialogTitle{ "Database.SalesView.Edit.DialogTitle"sv }; - - BasicTranslation PurchasesViewTab{ "Database.PurchasesView.TabName"sv }; - BasicTranslation EditPurchaseEntryDialogTitle{ "Database.PurchasesView.Edit.DialogTitle"sv }; - - BasicTranslation DatabaseItemsColumn{ "Database.Column.Items"sv }; - BasicTranslation DatabaseCustomerColumn{ "Database.Column.Customer"sv }; - BasicTranslation DatabaseFactoryColumn{ "Database.Column.Factory"sv }; - /// 销售订单的交货期限 - BasicTranslation DatabaseDeadlineColumn{ "Database.Column.Deadline"sv }; - /// 采购订单的下单时间 - BasicTranslation DatabaseOrderTimeColumn{ "Database.Column.OrderTime"sv }; - /// 所有订单的“完成”时间。对于销售来说是实际交货时间,对于采购来说是收货时间。 - BasicTranslation DatabaseCompletionTimeColumn{ "Database.Column.CompletionTime"sv }; - /// (运输)批次的发货时间,适用于采购和销售批次。 - BasicTranslation DatabaseShipmentTimeColumn{ "Database.Column.ShipmentTime"sv }; - /// (运输)批次的收获时间,适用于采购和销售批次。 - BasicTranslation DatabaseArrivalTimeColumn{ "Database.Column.ArrivalTime"sv }; - - BasicTranslation SelectOrderToShowAssociatedDeliveries{ "Database.Message.NoOrderSelected"sv }; - BasicTranslation NotDelivered{ "Database.Message.NotDelivered"sv }; - - /* Items tab */ - - BasicTranslation AddItemDialogTitle{ "Item.Add.DialogTitle"sv }; - BasicTranslation EditItemDialogTitle{ "Item.Edit.DialogTitle"sv }; - BasicTranslation DeleteItemDialogTitle{ "Item.Delete.DialogTitle"sv }; - BasicTranslation DeleteItemDialogMessage{ "Item.Delete.DialogMessage"sv }; - - BasicTranslation ProductCategoryName{ "Item.CategoryName.Product"sv }; - BasicTranslation FactoryCategoryName{ "Item.CategoryName.Factory"sv }; - BasicTranslation CustomerCategoryName{ "Item.CategoryName.Customer"sv }; - - BasicTranslation ItemNameColumn{ "Item.Column.Name"sv }; - BasicTranslation ItemDescriptionColumn{ "Item.Column.Description"sv }; - BasicTranslation ItemEmailColumn{ "Item.Column.Email"sv }; - BasicTranslation ItemStockColumn{ "Item.Column.Stock"sv }; - BasicTranslation ItemPriceColumn{ "Item.Column.Price"sv }; - - /* Assets */ - - BasicTranslation OpenAsset{ "Asset.Open"sv }; - BasicTranslation OpenAssetDialogTitle{ "Asset.Open.DialogTitle"sv }; - BasicTranslation ManageAssets{ "Asset.Manage"sv }; - BasicTranslation ManageAssetsDialogTitle{ "Asset.Manage.DialogTitle"sv }; - - /* Workflow tab */ - - /* Templates tab */ - - BasicTranslation InvalidTemplateTypeError{ "Template.InvalidTypeError"sv }; -}; diff --git a/core/src/UI/UI_DatabaseView.cpp b/core/src/UI/UI_DatabaseView.cpp index bd0efa5..caf81d8 100644 --- a/core/src/UI/UI_DatabaseView.cpp +++ b/core/src/UI/UI_DatabaseView.cpp @@ -3,7 +3,7 @@ #include "Model/Filter.hpp" #include "Model/GlobalStates.hpp" #include "Model/Project.hpp" -#include "UI/Localization.hpp" +#include "Utils/I18n.hpp" #include "Utils/ScopeGuard.hpp" #include "Utils/Time.hpp" @@ -210,7 +210,6 @@ public: void Display() { bool dummy = true; - auto ls = LocaleStrings::Instance.get(); if (ImGui::Button(ICON_FA_ARROW_LEFT, mCurrentPageNumber == 0)) { SetPage(mCurrentPageNumber - 1); @@ -226,7 +225,7 @@ public: } ImGui::SameLine(); - if (ImGui::Button(ls->Edit.Get(), mSelectRow == -1)) { + if (ImGui::Button(ICON_FA_EDIT " " I18N_TEXT("Edit", L10N_EDIT), mSelectRow == -1)) { ImGui::OpenPopup(mEditDialogTitle); } if (ImGui::BeginPopupModal(mEditDialogTitle, &dummy, ImGuiWindowFlags_AlwaysAutoResize)) { @@ -237,12 +236,12 @@ public: } ImGui::SameLine(); - if (ImGui::Button(ls->Add.Get())) { + if (ImGui::Button(ICON_FA_PLUS " " I18N_TEXT("Add", L10N_ADD))) { // TODO } ImGui::SameLine(); - if (ImGui::Button(ls->Delete.Get(), mSelectRow == -1)) { + if (ImGui::Button(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE), mSelectRow == -1)) { // TODO } @@ -252,7 +251,7 @@ public: ImGui::NextColumn(); if (mSelectRow == -1) { - ImGui::TextWrapped("%s", ls->SelectOrderToShowAssociatedDeliveries.Get()); + ImGui::TextWrapped("%s", I18N_TEXT("Select an entry to show associated deliveries", L10N_DATABASE_MESSAGE_NO_ORDER_SELECTED)); } else { DisplayDeliveriesTable(); } @@ -284,15 +283,14 @@ private: void DisplayMainTable() { - auto ls = LocaleStrings::Instance.get(); if (ImGui::BeginTable("DataTable", kColumnCount, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX)) { - if constexpr (kHasCustomer) ImGui::TableSetupColumn(ls->DatabaseCustomerColumn.Get()); - if constexpr (kHasDeadline) ImGui::TableSetupColumn(ls->DatabaseDeadlineColumn.Get()); - if constexpr (kHasFactory) ImGui::TableSetupColumn(ls->DatabaseFactoryColumn.Get()); - if constexpr (kHasOrderTime) ImGui::TableSetupColumn(ls->DatabaseOrderTimeColumn.Get()); - if constexpr (kHasCompletionTime) ImGui::TableSetupColumn(ls->DatabaseCompletionTimeColumn.Get()); - if constexpr (kHasItems) ImGui::TableSetupColumn(ls->DatabaseItemsColumn.Get()); + if constexpr (kHasCustomer) ImGui::TableSetupColumn(I18N_TEXT("Customer", L10N_DATABASE_COLUMN_CUSTOMER)); + if constexpr (kHasDeadline) ImGui::TableSetupColumn(I18N_TEXT("Deadline", L10N_DATABASE_COLUMN_DEADLINE)); + if constexpr (kHasFactory) ImGui::TableSetupColumn(I18N_TEXT("Factory", L10N_DATABASE_COLUMN_FACTORY)); + if constexpr (kHasOrderTime) ImGui::TableSetupColumn(I18N_TEXT("Order time", L10N_DATABASE_COLUMN_ORDER_TIME)); + if constexpr (kHasCompletionTime) ImGui::TableSetupColumn(I18N_TEXT("Completion time", L10N_DATABASE_COLUMN_COMPLETION_TIME)); + if constexpr (kHasItems) ImGui::TableSetupColumn(I18N_TEXT("Items", L10N_DATABASE_COLUMN_ITEMS)); ImGui::TableHeadersRow(); if (mActiveFilter) { @@ -327,8 +325,6 @@ private: void DisplayEntry(T& entry, int rowIdx, int entryIdx) { - auto ls = LocaleStrings::Instance.get(); - ImGui::PushID(rowIdx); ImGui::TableNextRow(); @@ -359,7 +355,7 @@ private: if constexpr (kHasCompletionTime) { ImGui::TableNextColumn(); if (entry.DeliveryTime.empty()) { - ImGui::TextUnformatted(ls->NotDelivered.Get()); + ImGui::TextUnformatted(I18N_TEXT("Not delivered", L10N_DATABASE_MESSAGE_NOT_DELIVERED)); } else { ImGui::TextUnformatted(entry.DeliveryTime.c_str()); } @@ -383,12 +379,11 @@ private: void DisplayDeliveriesTable() { - auto ls = LocaleStrings::Instance.get(); if (ImGui::BeginTable("DeliveriesTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX)) { - ImGui::TableSetupColumn(ls->DatabaseShipmentTimeColumn.Get()); - ImGui::TableSetupColumn(ls->DatabaseArrivalTimeColumn.Get()); - ImGui::TableSetupColumn(ls->DatabaseItemsColumn.Get()); + ImGui::TableSetupColumn(I18N_TEXT("Shipment time", L10N_DATABASE_COLUMN_SHIPMENT_TIME)); + ImGui::TableSetupColumn(I18N_TEXT("Arrival time", L10N_DATABASE_COLUMN_ARRIVAL_TIME)); + ImGui::TableSetupColumn(I18N_TEXT("Items", L10N_DATABASE_COLUMN_ITEMS)); ImGui::TableHeadersRow(); auto& entry = (*mCurrentPage)[mSelectRow]; @@ -601,8 +596,7 @@ class SalesTableView : public GenericTableView public: SalesTableView() { - auto ls = LocaleStrings::Instance.get(); - mEditDialogTitle = ls->EditSaleEntryDialogTitle.Get(); + mEditDialogTitle = I18N_TEXT("Edit sales entry", L10N_DATABASE_SALES_VIEW_EDIT_DIALOG_TITLE); } #pragma clang diagnostic push @@ -626,8 +620,7 @@ class PurchasesTableView : public GenericTableView public: PurchasesTableView() { - auto ls = LocaleStrings::Instance.get(); - mEditDialogTitle = ls->EditPurchaseEntryDialogTitle.Get(); + mEditDialogTitle = I18N_TEXT("Edit purchase entry", L10N_DATABASE_PURCHASES_VIEW_EDIT_DIALOG_TITLE); } #pragma clang diagnostic push @@ -649,7 +642,6 @@ public: void UI::DatabaseViewTab() { - auto ls = LocaleStrings::Instance.get(); auto& gs = GlobalStates::GetInstance(); static Project* currentProject = nullptr; @@ -663,11 +655,11 @@ void UI::DatabaseViewTab() } if (ImGui::BeginTabBar("DatabaseViewTabs")) { - if (ImGui::BeginTabItem(ls->SalesViewTab.Get())) { + if (ImGui::BeginTabItem(I18N_TEXT("Sales", L10N_DATABASE_SALES_VIEW_TAB_NAME))) { sales.Display(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem(ls->PurchasesViewTab.Get())) { + if (ImGui::BeginTabItem(I18N_TEXT("Purchases", L10N_DATABASE_PURCHASES_VIEW_TAB_NAME))) { purchases.Display(); ImGui::EndTabItem(); } diff --git a/core/src/UI/UI_Items.cpp b/core/src/UI/UI_Items.cpp index 6887f23..a557eb4 100644 --- a/core/src/UI/UI_Items.cpp +++ b/core/src/UI/UI_Items.cpp @@ -2,8 +2,9 @@ #include "Model/GlobalStates.hpp" #include "Model/Project.hpp" -#include "UI/Localization.hpp" +#include "Utils/I18n.hpp" +#include #include #include @@ -30,7 +31,6 @@ ActionResult ItemEditor(ItemList& list, T* item) t.GetEmail(); }; - auto ls = LocaleStrings::Instance.get(); auto& gs = GlobalStates::GetInstance(); static bool duplicateName = false; @@ -50,23 +50,23 @@ ActionResult ItemEditor(ItemList& list, T* item) description = {}; }; - if (ImGui::InputText(ls->ItemNameColumn.Get(), &name)) { + if (ImGui::InputText(I18N_TEXT("Name", L10N_ITEM_COLUMN_NAME), &name)) { duplicateName = name != item->GetName() && list.Find(name) != nullptr; } - if constexpr (kHasDescription) ImGui::InputText(ls->ItemDescriptionColumn.Get(), &description); - if constexpr (kHasEmail) ImGui::InputText(ls->ItemEmailColumn.Get(), &email); + if constexpr (kHasDescription) ImGui::InputText(I18N_TEXT("Description", L10N_ITEM_COLUMN_DESCRIPTION), &description); + if constexpr (kHasEmail) ImGui::InputText(I18N_TEXT("Email", L10N_ITEM_COLUMN_EMAIL), &email); if (name.empty()) { - ImGui::ErrorMessage("%s", ls->EmptyNameError.Get()); + ImGui::ErrorMessage(I18N_TEXT("Name cannot be empty", L10N_EMPTY_NAME_ERROR)); } if (duplicateName) { - ImGui::ErrorMessage("%s", ls->DuplicateNameError.Get()); + ImGui::ErrorMessage(I18N_TEXT("Duplicate name", L10N_DUPLICATE_NAME_ERROR)); } // Return Value auto rv = ActionResult::Pending; - if (ImGui::Button(ls->Confirm.Get(), name.empty() || duplicateName)) { + if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM), name.empty() || duplicateName)) { if (item->GetName() != name) { item->SetName(std::move(name)); } @@ -85,7 +85,7 @@ ActionResult ItemEditor(ItemList& list, T* item) } ImGui::SameLine(); - if (ImGui::Button(ls->Cancel.Get())) { + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { ImGui::CloseCurrentPopup(); ClearStates(); rv = ActionResult::Canceled; @@ -115,16 +115,15 @@ void ItemListEntries(ItemList& list, int& selectedIdx) }; constexpr int kColumns = 1 /* Name column */ + kHasDescription + kHasEmail + kHasStock + kHasPrice; - auto ls = LocaleStrings::Instance.get(); auto& gs = GlobalStates::GetInstance(); if (ImGui::BeginTable("", kColumns, ImGuiTableFlags_Borders)) { - ImGui::TableSetupColumn(ls->ItemNameColumn.Get()); - if constexpr (kHasDescription) ImGui::TableSetupColumn(ls->ItemDescriptionColumn.Get()); - if constexpr (kHasEmail) ImGui::TableSetupColumn(ls->ItemEmailColumn.Get()); - if constexpr (kHasStock) ImGui::TableSetupColumn(ls->ItemStockColumn.Get()); - if constexpr (kHasPrice) ImGui::TableSetupColumn(ls->ItemPriceColumn.Get()); + ImGui::TableSetupColumn(I18N_TEXT("Name", L10N_ITEM_COLUMN_NAME)); + if constexpr (kHasDescription) ImGui::TableSetupColumn(I18N_TEXT("Description", L10N_ITEM_COLUMN_DESCRIPTION)); + if constexpr (kHasEmail) ImGui::TableSetupColumn(I18N_TEXT("Email", L10N_ITEM_COLUMN_EMAIL)); + if constexpr (kHasStock) ImGui::TableSetupColumn(I18N_TEXT("Stock", L10N_ITEM_COLUMN_STOCK)); + if constexpr (kHasPrice) ImGui::TableSetupColumn(I18N_TEXT("Price", L10N_ITEM_COLUMN_PRICE)); ImGui::TableHeadersRow(); size_t idx = 0; @@ -170,19 +169,17 @@ void ItemListEntries(ItemList& list, int& selectedIdx) template void ItemListEditor(ItemList& list) { - auto ls = LocaleStrings::Instance.get(); - bool opened = true; static int selectedIdx = -1; static T* editingItem = nullptr; - if (ImGui::Button(ls->Add.Get())) { + if (ImGui::Button(ICON_FA_PLUS " " I18N_TEXT("Add", L10N_ADD))) { ImGui::SetNextWindowCentered(); - ImGui::OpenPopup(ls->AddItemDialogTitle.Get()); + ImGui::OpenPopup(I18N_TEXT("Add item", L10N_ITEM_ADD_DIALOG_TITLE)); editingItem = &list.Insert(""); } - if (ImGui::BeginPopupModal(ls->AddItemDialogTitle.Get(), &opened, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::BeginPopupModal(I18N_TEXT("Add item", L10N_ITEM_ADD_DIALOG_TITLE), &opened, ImGuiWindowFlags_AlwaysAutoResize)) { switch (ItemEditor(list, editingItem)) { case ActionResult::Confirmed: editingItem = nullptr; @@ -198,13 +195,13 @@ void ItemListEditor(ItemList& list) } ImGui::SameLine(); - if (ImGui::Button(ls->Edit.Get(), selectedIdx == -1)) { + if (ImGui::Button(ICON_FA_EDIT " " I18N_TEXT("Edit", L10N_EDIT), selectedIdx == -1)) { ImGui::SetNextWindowCentered(); - ImGui::OpenPopup(ls->EditItemDialogTitle.Get()); + ImGui::OpenPopup(I18N_TEXT("Edit item", L10N_ITEM_EDIT_DIALOG_TITLE)); editingItem = list.Find(selectedIdx); } - if (ImGui::BeginPopupModal(ls->EditItemDialogTitle.Get(), &opened, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::BeginPopupModal(I18N_TEXT("Edit item", L10N_ITEM_EDIT_DIALOG_TITLE), &opened, ImGuiWindowFlags_AlwaysAutoResize)) { if (ItemEditor(list, editingItem) != ActionResult::Pending) { editingItem = nullptr; } @@ -212,21 +209,21 @@ void ItemListEditor(ItemList& list) } ImGui::SameLine(); - if (ImGui::Button(ls->Delete.Get(), selectedIdx == -1)) { + if (ImGui::Button(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE), selectedIdx == -1)) { ImGui::SetNextWindowCentered(); - ImGui::OpenPopup(ls->DeleteItemDialogTitle.Get()); + ImGui::OpenPopup(I18N_TEXT("Delete item", L10N_ITEM_DELETE_DIALOG_TITLE)); list.Remove(selectedIdx); } - if (ImGui::BeginPopupModal(ls->DeleteItemDialogTitle.Get(), &opened, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::TextUnformatted(ls->DeleteItemDialogMessage.Get()); + if (ImGui::BeginPopupModal(I18N_TEXT("Delete item", L10N_ITEM_DELETE_DIALOG_TITLE), &opened, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::TextUnformatted(I18N_TEXT("Are you sure you want to delete this item?", L10N_ITEM_DELETE_DIALOG_MESSAGE)); - if (ImGui::Button(ls->Confirm.Get())) { + if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM))) { ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - if (ImGui::Button(ls->Cancel.Get())) { + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { ImGui::CloseCurrentPopup(); } @@ -239,19 +236,18 @@ void ItemListEditor(ItemList& list) void UI::ItemsTab() { - auto ls = LocaleStrings::Instance.get(); auto& gs = GlobalStates::GetInstance(); if (ImGui::BeginTabBar("ItemViewTabs")) { - if (ImGui::BeginTabItem(ls->ProductCategoryName.Get())) { + if (ImGui::BeginTabItem(I18N_TEXT("Products", L10N_ITEM_CATEGORY_PRODUCT))) { ItemListEditor(gs.GetCurrentProject()->Products); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem(ls->FactoryCategoryName.Get())) { + if (ImGui::BeginTabItem(I18N_TEXT("Factories", L10N_ITEM_CATEGORY_FACTORY))) { ItemListEditor(gs.GetCurrentProject()->Factories); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem(ls->CustomerCategoryName.Get())) { + if (ImGui::BeginTabItem(I18N_TEXT("Customers", L10N_ITEM_CATEGORY_CUSTOMER))) { ItemListEditor(gs.GetCurrentProject()->Customers); ImGui::EndTabItem(); } diff --git a/core/src/UI/UI_MainWindow.cpp b/core/src/UI/UI_MainWindow.cpp index 8131508..ce6e2e0 100644 --- a/core/src/UI/UI_MainWindow.cpp +++ b/core/src/UI/UI_MainWindow.cpp @@ -2,7 +2,7 @@ #include "Model/GlobalStates.hpp" #include "Model/Project.hpp" -#include "UI/Localization.hpp" +#include "Utils/I18n.hpp" #include #include @@ -16,25 +16,23 @@ namespace fs = std::filesystem; namespace { void ProjectTab_Normal() { - auto ls = LocaleStrings::Instance.get(); auto& gs = GlobalStates::GetInstance(); - if (ImGui::Button(ls->Close.Get())) { + if (ImGui::Button(ICON_FA_TIMES " " I18N_TEXT("Close", L10N_CLOSE))) { gs.SetCurrentProject(nullptr); return; } ImGui::SameLine(); - if (ImGui::Button(ls->OpenActiveProjectInFileSystem.Get())) { + if (ImGui::Button(ICON_FA_FOLDER " " I18N_TEXT("Open in filesystem", L10N_PROJECT_OPEN_IN_FILESYSTEM))) { // TODO } - ImGui::Text("%s%s", ls->ActiveProjectName.Get(), gs.GetCurrentProject()->GetName().c_str()); - ImGui::Text("%s%s", ls->ActiveProjectPath.Get(), gs.GetCurrentProject()->GetPathString().c_str()); + ImGui::Text("%s %s", I18N_TEXT("Project name", L10N_PROJECT_NAME), gs.GetCurrentProject()->GetName().c_str()); + ImGui::Text("%s %s", I18N_TEXT("Project path", L10N_PROJECT_PATH), gs.GetCurrentProject()->GetPathString().c_str()); } void ProjectTab_NoProject() { - auto ls = LocaleStrings::Instance.get(); auto& gs = GlobalStates::GetInstance(); bool openedDummy = true; @@ -54,37 +52,37 @@ void ProjectTab_NoProject() } }; - if (ImGui::Button(ls->NewProject.Get())) { + if (ImGui::Button(I18N_TEXT("Create project....", L10N_PROJECT_NEW))) { ImGui::SetNextWindowCentered(); - ImGui::OpenPopup(ls->NewProjectDialogTitle.Get()); + ImGui::OpenPopup(I18N_TEXT("Create project wizard", L10N_PROJECT_NEW_DIALOG_TITLE)); } // Make it so that the modal dialog has a close button - if (ImGui::BeginPopupModal(ls->NewProjectDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::InputTextWithHint("##ProjectName", ls->NewProjectNameHint.Get(), &projectName); + if (ImGui::BeginPopupModal(I18N_TEXT("Create project wizard", L10N_PROJECT_NEW_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::InputTextWithHint("##ProjectName", I18N_TEXT("Project name", L10N_PROJECT_NAME), &projectName); - if (ImGui::InputTextWithHint("##ProjectPath", ls->NewProjectPathHint.Get(), &dirName)) { + if (ImGui::InputTextWithHint("##ProjectPath", I18N_TEXT("Project path", L10N_PROJECT_PATH), &dirName)) { // Changed, validate value TrySelectPath(fs::path(dirName)); } ImGui::SameLine(); if (ImGui::Button("...")) { - auto selection = pfd::select_folder(ls->NewProjectPathDialogTitle.Get()).result(); + auto selection = pfd::select_folder(I18N_TEXT("Project path", L10N_PROJECT_NEW_PATH_DIALOG_TITLE)).result(); if (!selection.empty()) { TrySelectPath(fs::path(selection)); } } if (projectName.empty()) { - ImGui::ErrorMessage("%s", ls->NewProjectEmptyNameError.Get()); + ImGui::ErrorMessage("%s", I18N_TEXT("Name cannot be empty", L10N_EMPTY_NAME_ERROR)); } if (!dirNameIsValid) { - ImGui::ErrorMessage("%s", ls->NewProjectInvalidPathError.Get()); + ImGui::ErrorMessage("%s", I18N_TEXT("Invalid path", L10N_INVALID_PATH_ERROR)); } ImGui::Spacing(); - if (ImGui::Button(ls->Confirm.Get(), !dirNameIsValid || projectName.empty())) { + if (ImGui::Button(I18N_TEXT("Confirm", L10N_CONFIRM), !dirNameIsValid || projectName.empty())) { ImGui::CloseCurrentPopup(); gs.SetCurrentProject(std::make_unique(std::move(dirPath), std::move(projectName))); @@ -97,7 +95,7 @@ void ProjectTab_NoProject() } ImGui::SameLine(); - if (ImGui::Button(ls->Cancel.Get())) { + if (ImGui::Button(I18N_TEXT("Cancel", L10N_CANCEL))) { ImGui::CloseCurrentPopup(); } @@ -105,8 +103,8 @@ void ProjectTab_NoProject() } ImGui::SameLine(); - if (ImGui::Button(ls->OpenProject.Get())) { - auto selection = pfd::open_file(ls->OpenProjectDialogTitle.Get()).result(); + if (ImGui::Button(I18N_TEXT("Open project...", L10N_PROJECT_OPEN))) { + auto selection = pfd::open_file(I18N_TEXT("Open project", L10N_PROJECT_OPEN_DIALOG_TITLE)).result(); if (!selection.empty()) { fs::path path(selection[0]); @@ -124,10 +122,10 @@ void ProjectTab_NoProject() // Recent projects ImGui::Separator(); - ImGui::TextUnformatted(ls->RecentProjects.Get()); + ImGui::TextUnformatted(I18N_TEXT("Recent projects", L10N_PROJECT_RECENTS)); ImGui::SameLine(); - if (ImGui::Button(ls->ClearRecentProjects.Get())) { + if (ImGui::Button(I18N_TEXT("Clear", L10N_PROJECT_RECENTS_CLEAR))) { gs.ClearRecentProjects(); } @@ -136,7 +134,7 @@ void ProjectTab_NoProject() size_t toRemoveIdx = rp.size(); if (rp.empty()) { - ImGui::TextUnformatted(ls->NoRecentProjectsMessage.Get()); + ImGui::TextUnformatted(I18N_TEXT("No recent projects", L10N_PROJECT_RECENTS_NONE_PRESENT)); } else { for (auto it = rp.rbegin(); it != rp.rend(); ++it) { auto& [path, recent] = *it; @@ -155,7 +153,7 @@ void ProjectTab_NoProject() } } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s", ls->OpenRecentProjectTooltip.Get()); + ImGui::SetTooltip(I18N_TEXT("Open this project", L10N_PROJECT_RECENTS_OPEN_TOOLTIP)); } ImGui::SameLine(); @@ -163,7 +161,7 @@ void ProjectTab_NoProject() toRemoveIdx = idx; } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s", ls->DeleteRecentProjectTooltip.Get()); + ImGui::SetTooltip(I18N_TEXT("Delete this project from the Recent Projects list, the project itself will not be affected", L10N_PROJECT_RECENTS_DELETE_TOOLTIP)); } ImGui::PopID(); @@ -176,10 +174,10 @@ void ProjectTab_NoProject() if (openErrorDialog) { ImGui::SetNextWindowCentered(); - ImGui::OpenPopup(ls->Error.Get()); + ImGui::OpenPopup(I18N_TEXT("Error", L10N_ERROR)); } - if (ImGui::BeginPopupModal(ls->Error.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::ErrorMessage("%s", ls->InvalidProjectFormat.Get()); + if (ImGui::BeginPopupModal(I18N_TEXT("Error", L10N_ERROR), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::ErrorMessage("%s", I18N_TEXT("Invalid project file", L10N_PROJECT_INVALID_PROJECT_FORMAT)); ImGui::EndPopup(); } } @@ -187,7 +185,6 @@ void ProjectTab_NoProject() void UI::MainWindow() { - auto ls = LocaleStrings::Instance.get(); auto& gs = GlobalStates::GetInstance(); auto windowSize = ImGui::GetMainViewport()->Size; @@ -195,12 +192,12 @@ void UI::MainWindow() ImGui::SetNextWindowPos({ 0, 0 }); ImGui::Begin("##MainWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); if (ImGui::BeginTabBar("MainWindowTabs")) { - if (ImGui::BeginTabItem(ls->SettingsTab.Get())) { + if (ImGui::BeginTabItem(ICON_FA_COGS " " I18N_TEXT("Settings", L10N_MAIN_TAB_SETTINGS))) { UI::SettingsTab(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem(ls->ProjectTab.Get(), nullptr)) { + if (ImGui::BeginTabItem(ICON_FA_FILE " " I18N_TEXT("Project", L10N_MAIN_WINDOW_TAB_PROJECT), nullptr)) { if (gs.HasCurrentProject()) { ProjectTab_Normal(); } else { @@ -213,22 +210,22 @@ void UI::MainWindow() goto endTab; } - if (ImGui::BeginTabItem(ls->DatabaseViewTab.Get())) { + if (ImGui::BeginTabItem(ICON_FA_DATABASE " " I18N_TEXT("Data", L10N_MAIN_WINDOW_TAB_DATABASE_VIEW))) { UI::DatabaseViewTab(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem(ls->ItemsTab.Get())) { + if (ImGui::BeginTabItem(ICON_FA_BOX " " I18N_TEXT("Items", L10N_MAIN_WINDOW_TAB_ITEMS))) { UI::ItemsTab(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem(ls->WorkflowsTab.Get())) { + if (ImGui::BeginTabItem(ICON_FA_SCROLL " " I18N_TEXT("Workflows", L10N_MAIN_WINDOW_TAB_WORKFLOWS))) { UI::WorkflowsTab(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem(ls->TemplatesTab.Get())) { + if (ImGui::BeginTabItem(ICON_FA_TABLE " " I18N_TEXT("Templates", L10N_MAIN_WINDOW_TAB_TEMPLATES))) { UI::TemplatesTab(); ImGui::EndTabItem(); } diff --git a/core/src/UI/UI_Settings.cpp b/core/src/UI/UI_Settings.cpp index da935c6..107b94c 100644 --- a/core/src/UI/UI_Settings.cpp +++ b/core/src/UI/UI_Settings.cpp @@ -1,7 +1,5 @@ #include "UI/UI.hpp" -#include "UI/Localization.hpp" - #include void UI::SettingsTab() diff --git a/core/src/UI/UI_Templates.cpp b/core/src/UI/UI_Templates.cpp index b3a1e05..e08510a 100644 --- a/core/src/UI/UI_Templates.cpp +++ b/core/src/UI/UI_Templates.cpp @@ -4,8 +4,9 @@ #include "Model/Project.hpp" #include "Model/Template/TableTemplate.hpp" #include "Model/Template/Template.hpp" -#include "UI/Localization.hpp" +#include "Utils/I18n.hpp" +#include #include #include #include @@ -134,7 +135,6 @@ std::unique_ptr TemplateUI::CreateByKind(Template::Kind kind) void UI::TemplatesTab() { - auto ls = LocaleStrings::Instance.get(); auto& project = *GlobalStates::GetInstance().GetCurrentProject(); static std::unique_ptr openTemplate; @@ -142,17 +142,17 @@ void UI::TemplatesTab() bool openedDummy = true; // Toolbar item: close - if (ImGui::Button(ls->Close.Get(), openTemplate == nullptr)) { + if (ImGui::Button(ICON_FA_TIMES " " I18N_TEXT("Close", L10N_CLOSE), openTemplate == nullptr)) { openTemplate = nullptr; } // Toolbar item: open... ImGui::SameLine(); - if (ImGui::Button(ls->OpenAsset.Get())) { - ImGui::OpenPopup(ls->OpenAssetDialogTitle.Get()); + if (ImGui::Button(I18N_TEXT("Open asset...", L10N_ASSET_OPEN))) { + ImGui::OpenPopup(I18N_TEXT("Open asset", L10N_ASSET_OPEN_DIALOG_TITLE)); } - if (ImGui::BeginPopupModal(ls->OpenAssetDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - if (ImGui::Button(ls->Open.Get(), state.SelectedAsset == nullptr)) { + if (ImGui::BeginPopupModal(I18N_TEXT("Open asset", L10N_ASSET_OPEN_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::Button(I18N_TEXT("Open", L10N_OPEN), state.SelectedAsset == nullptr)) { auto kind = static_cast(state.SelectedAsset->Payload); openTemplate = TemplateUI::CreateByKind(kind); } @@ -165,10 +165,10 @@ void UI::TemplatesTab() // Toolbar item: manage... ImGui::SameLine(); - if (ImGui::Button(ls->ManageAssets.Get())) { - ImGui::OpenPopup(ls->ManageAssetsDialogTitle.Get()); + if (ImGui::Button(I18N_TEXT("Manage assets...", L10N_ASSET_MANAGE))) { + ImGui::OpenPopup(I18N_TEXT("Manage assets", L10N_ASSET_MANAGE_DIALOG_TITLE)); } - if (ImGui::BeginPopupModal(ls->ManageAssetsDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::BeginPopupModal(I18N_TEXT("Manage assets", L10N_ASSET_MANAGE_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { project.Templates.DisplayControls(state); project.Templates.DisplayDetailsList(state); ImGui::EndPopup(); diff --git a/core/src/UI/UI_Workflows.cpp b/core/src/UI/UI_Workflows.cpp index 3230cf9..4535f08 100644 --- a/core/src/UI/UI_Workflows.cpp +++ b/core/src/UI/UI_Workflows.cpp @@ -7,9 +7,10 @@ #include "Model/Workflow/Nodes/TextNodes.hpp" #include "Model/Workflow/Nodes/UserInputNodes.hpp" #include "Model/Workflow/Workflow.hpp" -#include "UI/Localization.hpp" +#include "Utils/I18n.hpp" #include "Utils/Macros.hpp" +#include #include #include #include @@ -162,8 +163,6 @@ public: void Draw() { - auto ls = LocaleStrings::Instance.get(); - ImNodes::SetCurrentEditor(mContext); ImNodes::Begin(""); @@ -278,7 +277,7 @@ public: auto& node = *mWorkflow->GetNodeByNodeId(mContextMenuNodeId); node.DrawDebugInfo(); - if (ImGui::MenuItem(ls->Delete.Get())) { + if (ImGui::MenuItem(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE))) { ImNodes::DeleteNode(mContextMenuNodeId); } @@ -293,7 +292,7 @@ public: node->DrawInputPinDebugInfo(pinId); } - if (ImGui::MenuItem(ls->Disconnect.Get())) { + if (ImGui::MenuItem(ICON_FA_UNLINK " " I18N_TEXT("Disconnect", L10N_DISCONNECT))) { if (isOutput) { auto& pin = node->GetOutputPin(pinId); if (pin.IsConnected()) { @@ -318,7 +317,7 @@ public: auto& conn = *mWorkflow->GetConnectionByLinkId(mContextMenuLinkId); conn.DrawDebugInfo(); - if (ImGui::MenuItem(ls->Delete.Get())) { + if (ImGui::MenuItem(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE))) { ImNodes::DeleteLink(mContextMenuLinkId); } @@ -362,7 +361,6 @@ public: void UI::WorkflowsTab() { - auto ls = LocaleStrings::Instance.get(); auto& project = *GlobalStates::GetInstance().GetCurrentProject(); static std::unique_ptr openWorkflow; @@ -370,17 +368,17 @@ void UI::WorkflowsTab() bool openedDummy = true; // Toolbar item: close - if (ImGui::Button(ls->Close.Get(), openWorkflow == nullptr)) { + if (ImGui::Button(ICON_FA_TIMES " " I18N_TEXT("Close", L10N_CLOSE), openWorkflow == nullptr)) { openWorkflow = nullptr; } // Toolbar item: open... ImGui::SameLine(); - if (ImGui::Button(ls->OpenAsset.Get())) { - ImGui::OpenPopup(ls->OpenAssetDialogTitle.Get()); + if (ImGui::Button((I18N_TEXT("Open asset...", L10N_ASSET_OPEN)))) { + ImGui::OpenPopup(I18N_TEXT("Open asset", L10N_ASSET_OPEN_DIALOG_TITLE)); } - if (ImGui::BeginPopupModal(ls->OpenAssetDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { - if (ImGui::Button(ls->Open.Get(), state.SelectedAsset == nullptr)) { + if (ImGui::BeginPopupModal(I18N_TEXT("Open asset", L10N_ASSET_OPEN_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::Button(I18N_TEXT("Open", L10N_OPEN), state.SelectedAsset == nullptr)) { auto workflow = project.Workflows.Load(*state.SelectedAsset); openWorkflow = std::make_unique(std::move(workflow)); } @@ -393,10 +391,10 @@ void UI::WorkflowsTab() // Toolbar item: manage... ImGui::SameLine(); - if (ImGui::Button(ls->ManageAssets.Get())) { - ImGui::OpenPopup(ls->ManageAssetsDialogTitle.Get()); + if (ImGui::Button(I18N_TEXT("Manage assets...", L10N_ASSET_MANAGE))) { + ImGui::OpenPopup(I18N_TEXT("Manage assets", L10N_ASSET_MANAGE_DIALOG_TITLE)); } - if (ImGui::BeginPopupModal(ls->ManageAssetsDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::BeginPopupModal(I18N_TEXT("Manage assets", L10N_ASSET_MANAGE_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { project.Workflows.DisplayControls(state); project.Workflows.DisplayDetailsList(state); ImGui::EndPopup(); diff --git a/core/src/UI/fwd.hpp b/core/src/UI/fwd.hpp index 9b88370..756e567 100644 --- a/core/src/UI/fwd.hpp +++ b/core/src/UI/fwd.hpp @@ -1,8 +1,5 @@ #pragma once -// Localization.hpp -class LocaleStrings; - // UI.hpp namespace ImGui { enum class IconType; diff --git a/core/src/Utils/I18n.cpp b/core/src/Utils/I18n.cpp deleted file mode 100644 index e5131cc..0000000 --- a/core/src/Utils/I18n.cpp +++ /dev/null @@ -1,300 +0,0 @@ -#include "I18n.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; -using namespace std::literals::string_view_literals; - -namespace { - -struct LanguageInfo -{ - std::string CodeName; - std::string LocaleName; - fs::path File; -}; - -class I18nState -{ -public: - static I18nState& Get() - { - static I18nState instance; - return instance; - } - -public: - tsl::array_map LocaleInfos; - tsl::array_map CurrentEntries; - LanguageInfo* CurrentLanguage = nullptr; - bool Unloaded = false; - - void Unload() - { - Unloaded = true; - CurrentEntries = {}; - } - - void EnsureLoaded() - { - if (Unloaded) { - Unloaded = false; - Reload(); - } - } - - void Reload() - { - if (!CurrentLanguage) return; - - std::ifstream ifs(CurrentLanguage->File); - Json::Value root; - ifs >> root; - - for (auto name : root.getMemberNames()) { - if (name == "$localized_name") { - continue; - } - - auto& value = root[name]; - if (value.isString()) { - CurrentEntries.insert(name, value.asCString()); - } - } - } -}; - -std::string FindLocalizedName(const fs::path& localeFile) -{ - std::ifstream ifs(localeFile); - if (!ifs) { - throw std::runtime_error("Failed to open locale file."); - } - - Json::Value root; - ifs >> root; - if (auto& name = root["$localized_name"]; name.isString()) { - return std::string(name.asCString()); - } else { - throw std::runtime_error("Failed to find $localized_name in language file."); - } -} - -} // namespace - -void I18n::Init() -{ - auto& state = I18nState::Get(); - - auto dir = fs::current_path() / "locale"; - if (!fs::exists(dir)) { - throw std::runtime_error("Failed to find locale directory."); - } - - for (auto& elm : fs::directory_iterator{ dir }) { - if (!elm.is_regular_file()) continue; - - auto& path = elm.path(); - auto codeName = path.stem().string(); - - state.LocaleInfos.emplace( - codeName, - LanguageInfo{ - .CodeName = codeName, - .LocaleName = FindLocalizedName(path), - .File = path, - }); - } -} - -void I18n::Shutdown() -{ - auto& state = I18nState::Get(); - state.LocaleInfos.clear(); - state.CurrentEntries.clear(); - state.CurrentLanguage = nullptr; - state.Unloaded = false; -} - -void I18n::Unload() -{ - auto& state = I18nState::Get(); - state.Unload(); - OnUnload(); -} - -std::string_view I18n::GetLanguage() -{ - auto& state = I18nState::Get(); - return state.CurrentLanguage->CodeName; -} - -bool I18n::SetLanguage(std::string_view lang) -{ - auto& state = I18nState::Get(); - if (state.CurrentLanguage && - state.CurrentLanguage->CodeName == lang) - { - return false; - } - - if (auto iter = state.LocaleInfos.find(lang); iter != state.LocaleInfos.end()) { - state.CurrentLanguage = &iter.value(); - state.Reload(); - } - - OnLanguageChange(); - return true; -} - -std::optional I18n::Lookup(std::string_view key) -{ - auto& state = I18nState::Get(); - state.EnsureLoaded(); - - auto iter = state.CurrentEntries.find(key); - if (iter != state.CurrentEntries.end()) { - return iter.value(); - } else { - return std::nullopt; - } -} - -std::string_view I18n::LookupUnwrap(std::string_view key) -{ - auto o = Lookup(key); - if (!o) { - std::string msg; - msg.append("Unable to find locale for '"); - msg.append(key); - msg.append("'."); - throw std::runtime_error(std::move(msg)); - }; - return o.value(); -} - -std::string_view I18n::LookupLanguage(std::string_view lang) -{ - auto& state = I18nState::Get(); - auto iter = state.LocaleInfos.find(lang); - if (iter != state.LocaleInfos.end()) { - return iter.value().LocaleName; - } else { - return ""sv; - } -} - -BasicTranslation::BasicTranslation(std::string_view key) - : mContent{ I18n::LookupUnwrap(key) } -{ -} - -const std::string& BasicTranslation::GetString() const -{ - return mContent; -} - -const char* BasicTranslation::Get() const -{ - return mContent.c_str(); -} - -FormattedTranslation::FormattedTranslation(std::string_view key) -{ - auto src = I18n::LookupUnwrap(key); - - mMinimumResultLen = 0; - - bool escape = false; - bool matchingCloseBrace = false; - std::string buf; - for (char c : src) { - switch (c) { - case '\\': { - // Disallow double (or more) escaping - if (escape) { - buf += '\\'; - escape = false; - break; - } - - escape = true; - } break; - - case '{': { - // Escaping an opening brace cause the whole "argument" (if any) gets parsed as a part of the previous literal - if (escape) { - buf += '{'; - escape = false; - break; - } - - // Generate literal - mMinimumResultLen += buf.size(); - mParsedElements.push_back(Element{ std::move(buf) }); // Should also clear buf - - matchingCloseBrace = true; - } break; - case '}': { - if (escape) { - throw std::runtime_error("Cannot escape '}', put \\ before the '{' if intended to escape braces."); - } - - // If there is no pairing '{', simply treat this as a normal character - // (escaping for closing braces) - if (!matchingCloseBrace) { - buf += '}'; - break; - } - - // Generate argument - if (buf.empty()) { - // No index given, default to use current argument's index - auto currArgIdx = (int)mNumArguments; - mParsedElements.push_back(Element{ currArgIdx }); - } else { - // Use provided index - int argIdx = std::stoi(buf); - mParsedElements.push_back(Element{ argIdx }); - buf.clear(); - } - } break; - - default: { - if (escape) { - throw std::runtime_error("Cannot escape normal character '" + std::to_string(c) + "'."); - } - - buf += c; - } break; - } - } -} - -std::string FormattedTranslation::Format(std::span args) -{ - if (args.size() != mNumArguments) { - throw std::runtime_error("Invalid number of arguments for FormattedTranslation::Format, expected " + std::to_string(mNumArguments) + " but found " + std::to_string(args.size()) + "."); - } - - std::string result; - result.reserve(mMinimumResultLen); - - for (auto& elm : mParsedElements) { - if (auto literal = std::get_if(&elm)) { - result.append(*literal); - } - if (auto idx = std::get_if(&elm)) { - result.append(args[*idx]); - } - } - - return result; -} diff --git a/core/src/Utils/I18n.hpp b/core/src/Utils/I18n.hpp index 6285d60..e9eaac9 100644 --- a/core/src/Utils/I18n.hpp +++ b/core/src/Utils/I18n.hpp @@ -1,87 +1,10 @@ #pragma once -#include "Utils/Sigslot.hpp" -#include "Utils/fwd.hpp" - -#include -#include -#include -#include -#include -#include -#include - -class I18n -{ -public: - static inline Signal<> OnLanguageChange{}; - static inline Signal<> OnUnload{}; - - static void Init(); - static void Shutdown(); - - /// Discard in-memory mapping from key to locale entries. - /// When any of the entry accessors are invoked, unloaded entries will be reloaded. - static void Unload(); - - static std::string_view GetLanguage(); - static bool SetLanguage(std::string_view lang); - - /* Entry accessors */ - /// Find the localized entry with the given key, return \c std::nullopt if does not exist. Reloads locale entries if they are currently unloaded. - static std::optional Lookup(std::string_view key); - /// Find the localized entry with the given key, throw an exception if does not exist. EnsureLoaded locale entries if they are currently unloaded. - static std::string_view LookupUnwrap(std::string_view key); - - /// Query the localized name of a locale, e.g. "en_US" -> "English - United States". - /// If the queried locale does not exist, an empty string will be returned (existing locales can never have an empty localized name). - static std::string_view LookupLanguage(std::string_view lang); -}; - -struct StringArgument -{ - std::string Value; -}; - -struct IntArgument -{ - int Value; -}; - -struct FloatArgument -{ - double Value; -}; - -class BasicTranslation -{ -private: - std::string mContent; - -public: - BasicTranslation(std::string_view key); - const std::string& GetString() const; - const char* Get() const; -}; - -class FormattedTranslation -{ -public: - using Element = std::variant; - using Argument = std::string; - -private: - std::vector mParsedElements; - size_t mNumArguments; - size_t mMinimumResultLen; - -public: - FormattedTranslation(std::string_view key); - std::string Format(std::span args); -}; - -class NumericTranslation -{ -public: - // TODO -}; +#include "Utils/Macros.hpp" + +#if !defined(TARGET_LOCALE) +# define I18N_TEXT(defaultText, name) defaultText +#else +# include TARGET_LOCALE_FILE +# define I18N_TEXT(defaultText, name) name +#endif diff --git a/core/src/Utils/Macros.hpp b/core/src/Utils/Macros.hpp index 68b93fb..6958ed1 100644 --- a/core/src/Utils/Macros.hpp +++ b/core/src/Utils/Macros.hpp @@ -1,7 +1,12 @@ #pragma once +#define STRINGIFY_IMPL(text) #text +#define STRINGIFY(text) STRINGIFY_IMPL(text) + #define CONCAT_IMPL(a, b) a##b #define CONCAT(a, b) CONCAT_IMPL(a, b) +#define CONCAT_3(a, b, c) CONCAT(a, CONCAT(b, c)) +#define CONCAT_4(a, b, c, d) CONCAT(CONCAT(a, b), CONCAT(c, d)) #define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__) #define UNIQUE_NAME_LINE(prefix) CONCAT(prefix, __LINE__) diff --git a/core/src/Utils/fwd.hpp b/core/src/Utils/fwd.hpp index 74e642d..5d1fe45 100644 --- a/core/src/Utils/fwd.hpp +++ b/core/src/Utils/fwd.hpp @@ -4,15 +4,6 @@ class RgbaColor; class HsvColor; -// I18n.hpp -class I18n; -struct StringArgument; -struct IntArgument; -struct FloatArgument; -class BasicTranslation; -class FormattedTranslation; -class NumericTranslation; - // Sigslot.hpp class SignalStub; template -- cgit v1.2.3-70-g09d2