diff --git a/Editor/locale/CMakeLists.txt b/Editor/locale/CMakeLists.txt index e82d8d9f3..313d05c15 100644 --- a/Editor/locale/CMakeLists.txt +++ b/Editor/locale/CMakeLists.txt @@ -1,9 +1,15 @@ -bintoc(en_US.cpp en_US.yaml L_en_US) -bintoc(en_GB.cpp en_GB.yaml L_en_GB) -bintoc(ja_JP.cpp ja_JP.yaml L_ja_JP) -add_library(UrdeLocales - en_US.yaml en_US.cpp - en_GB.yaml en_GB.cpp - ja_JP.yaml ja_JP.cpp - locale.hpp locale.cpp) -target_link_libraries(UrdeLocales specter) +add_executable(genlocales genlocales.cpp) +target_link_libraries(genlocales fmt athena-core) + +set(LOCALES_IN en_US.yaml en_GB.yaml ja_JP.yaml) +set(LOCALES_OUT ${CMAKE_CURRENT_BINARY_DIR}/locales-inl.hpp) +add_custom_command(OUTPUT ${LOCALES_OUT} COMMAND $ + ARGS ${LOCALES_OUT} ${LOCALES_IN} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS genlocales ${LOCALES_IN}) + +add_library(UrdeLocales ${LOCALES_OUT} locale.hpp locale.cpp) +target_link_libraries(UrdeLocales fmt) +target_include_directories(UrdeLocales PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +target_link_libraries(specter PUBLIC UrdeLocales) diff --git a/Editor/locale/en_GB.yaml b/Editor/locale/en_GB.yaml index 595f29f28..2b5a4cf0c 100644 --- a/Editor/locale/en_GB.yaml +++ b/Editor/locale/en_GB.yaml @@ -1,18 +1,3 @@ +name: "British English" en_GB: color: "Colour" - branch: "Branch" - commit: "Commit" - date: "Date" - new_project: "New Project" - open_project: "Open Project" - extract_game: "Extract Game" - name: "Name" - type: "Type" - size: "Size" - directory: "Directory" - file: "File" - file_name: "File Name" - cancel: "Cancel" - system_locations: "System Locations" - recent_projects: "Recent Projects" - recent_files: "Recent Files" diff --git a/Editor/locale/en_US.yaml b/Editor/locale/en_US.yaml index a66066a8d..509e1789e 100644 --- a/Editor/locale/en_US.yaml +++ b/Editor/locale/en_US.yaml @@ -1,7 +1,9 @@ +name: "US English" en_US: color: "Color" branch: "Branch" commit: "Commit" + release: "Release" date: "Date" new_project: "New Project" open_project: "Open Project" @@ -16,3 +18,24 @@ en_US: system_locations: "System Locations" recent_projects: "Recent Projects" recent_files: "Recent Files" + scroll_left: "Scroll Left" + scroll_right: "Scroll Right" + ok: "OK" + cancel: "Cancel" + boundary_action: "Boundary Action" + split: "Split" + join: "Join" + hecl_project: "HECL Project" + no_access_as_dir: "Unable to access '{}' as directory" + file_field_empty: "Unable to save empty file" + overwrite_confirm: "Overwrite '{}'?" + directory_field_empty: "Unable to make empty-named directory" + no_overwrite_file: "Unable to make directory over file" + no_overwrite_project: "Unable to make project within existing project" + no_access_as_file: "Unable to access '{}' as file" + space_types: "Space Types" + resource_browser: "Resource Browser" + effect_editor: "Effect Editor" + model_viewer: "Model Viewer" + information_center: "Information Center" + game_mode: "Game Mode" \ No newline at end of file diff --git a/Editor/locale/genlocales.cpp b/Editor/locale/genlocales.cpp new file mode 100644 index 000000000..235fc9ebc --- /dev/null +++ b/Editor/locale/genlocales.cpp @@ -0,0 +1,116 @@ +#include +#include +#include +#include +#include "athena/FileReader.hpp" +#include "athena/YAMLDocReader.hpp" + +#define FMT_STRING_ALIAS 1 +#define FMT_ENFORCE_COMPILE_STRING 1 +#define FMT_USE_GRISU 0 +#include +#include + +int main(int argc, char** argv) { + if (argc < 3) { + fmt::print(fmt("{} ...\n"), argv[0]); + return 1; + } + + std::ofstream out(argv[1]); + if (!out.is_open()) { + fmt::print(fmt("Unable to open {} for writing\n"), argv[1]); + return 1; + } + + std::unordered_set seenLocales; + std::stringstream enumLocales; + std::stringstream declLocales; + std::unordered_set seenKeys; + std::stringstream keys; + std::stringstream lookups; + std::stringstream dos; + + for (int i = 2; i < argc; ++i) { + athena::io::FileReader fr(argv[i]); + if (!fr.isOpen()) { + fmt::print(fmt("Unable to open {} for reading\n"), argv[i]); + return 1; + } + athena::io::YAMLDocReader r; + if (!r.parse(&fr)) { + fmt::print(fmt("Unable to parse {}\n"), argv[i]); + return 1; + } + + std::string name; + std::string fullName; + athena::io::YAMLNode* listNode = nullptr; + for (const auto& c : r.getRootNode()->m_mapChildren) { + if (c.first == "name") { + fullName = c.second->m_scalarString; + } else { + name = c.first; + listNode = c.second.get(); + } + } + if (fullName.empty()) { + fmt::print(fmt("Unable to find 'name' node in {}\n"), argv[i]); + return 1; + } + if (!listNode) { + fmt::print(fmt("Unable to find list node in {}\n"), argv[i]); + return 1; + } + + if (seenLocales.find(name) == seenLocales.end()) { + seenLocales.insert(name); + fmt::print(enumLocales, fmt(" {},\n"), name); + fmt::print(declLocales, + fmt("struct {0} {{ static constexpr auto Name = \"{0}\"sv; static constexpr auto FullName = \"{1}\"sv; }};\n"), + name, fullName); + fmt::print(dos, + fmt(" case ELocale::{0}:\n" + " return act.template Do<{0}>(std::forward(args)...);\n"), name); + fmt::print(lookups, fmt("/* {} */\n"), name); + for (const auto& k : listNode->m_mapChildren) { + if (seenKeys.find(k.first) == seenKeys.end()) { + seenKeys.insert(k.first); + fmt::print(keys, fmt("struct {} {{}};\n"), k.first); + } + fmt::print(lookups, + fmt("template<> struct Lookup<{}, {}> {{ static constexpr auto Value() {{ return fmt(\"{}\"); }} }};\n"), + name, k.first, k.second->m_scalarString); + } + } + lookups << '\n'; + } + + out << "/* Locales */\n" + "enum class ELocale {\n" + " Invalid = -1,\n"; + out << enumLocales.str(); + out << " MAXLocale\n" + "};\n"; + out << declLocales.str(); + out << "\n" + "using DefaultLocale = en_US;\n" + "template struct Lookup {\n" + " static_assert(!std::is_same_v, \"The default locale must translate all keys\");\n" + " static constexpr auto Value() { return Lookup::Value(); }\n" + "};\n" + "\n" + "/* Keys */\n"; + out << keys.str(); + out << "\n"; + out << lookups.str(); + out << "template \n" + "constexpr auto Do(ELocale l, Action act, Args&&... args) {\n" + " switch (l) {\n" + " default:\n"; + out << dos.str(); + out << " }\n" + "}\n"; + + return 0; +} diff --git a/Editor/locale/ja_JP.yaml b/Editor/locale/ja_JP.yaml index 144468948..847736741 100644 --- a/Editor/locale/ja_JP.yaml +++ b/Editor/locale/ja_JP.yaml @@ -1,3 +1,4 @@ +name: "日本語" ja_JP: color: "色" branch: "分派" diff --git a/Editor/locale/locale.cpp b/Editor/locale/locale.cpp index 7c8e5273e..8aa21ebf3 100644 --- a/Editor/locale/locale.cpp +++ b/Editor/locale/locale.cpp @@ -6,54 +6,32 @@ #undef min #undef max -extern "C" const uint8_t L_en_US[]; -extern "C" size_t L_en_US_SZ; - -extern "C" const uint8_t L_en_GB[]; -extern "C" size_t L_en_GB_SZ; - -extern "C" const uint8_t L_ja_JP[]; -extern "C" size_t L_ja_JP_SZ; - -namespace urde { - -using namespace std::literals; - -static const specter::Locale Locales[] = {{"en_US"sv, "US English"sv, L_en_US, L_en_US_SZ}, - {"en_GB"sv, "British English"sv, L_en_GB, L_en_GB_SZ}, - {"ja_JP"sv, "Japanese"sv, L_ja_JP, L_ja_JP_SZ}}; +namespace locale { std::vector> ListLocales() { - constexpr size_t localeCount = std::extent::value; std::vector> ret; - ret.reserve(localeCount); - for (size_t i = 0; i < localeCount; ++i) { - const specter::Locale& l = Locales[i]; - ret.emplace_back(l.name(), l.fullName()); - } + ret.reserve(std::size_t(ELocale::MAXLocale)); + for (ELocale l = ELocale(0); l < ELocale::MAXLocale; l = ELocale(int(l) + 1)) + ret.emplace_back(GetName(l), GetFullName(l)); return ret; } -const specter::Locale* LookupLocale(std::string_view name) { - constexpr size_t localeCount = std::extent::value; - for (size_t i = 0; i < localeCount; ++i) { - const specter::Locale& l = Locales[i]; - if (!name.compare(l.name())) - return &l; - } - return nullptr; +ELocale LookupLocale(std::string_view name) { + for (ELocale l = ELocale(0); l < ELocale::MAXLocale; l = ELocale(int(l) + 1)) + if (!name.compare(GetName(l))) + return l; + return ELocale::Invalid; } -const specter::Locale* SystemLocaleOrEnglish() { +ELocale SystemLocaleOrEnglish() { const char* sysLocale = std::setlocale(LC_ALL, nullptr); size_t sysLocaleLen = std::strlen(sysLocale); - constexpr size_t localeCount = std::extent::value; - for (size_t i = 0; i < localeCount; ++i) { - const specter::Locale& l = Locales[i]; - if (!l.name().compare(0, std::min(l.name().size(), sysLocaleLen), sysLocale)) - return &l; + for (ELocale l = ELocale(0); l < ELocale::MAXLocale; l = ELocale(int(l) + 1)) { + auto name = GetName(l); + if (!name.compare(0, std::min(name.size(), sysLocaleLen), sysLocale)) + return l; } - return Locales; + return ELocale::en_US; } -} // namespace urde +} // namespace locale diff --git a/Editor/locale/locale.hpp b/Editor/locale/locale.hpp index f924e9b14..4e152a485 100644 --- a/Editor/locale/locale.hpp +++ b/Editor/locale/locale.hpp @@ -1,11 +1,45 @@ #pragma once +#include +#include +#include -#include +#define FMT_STRING_ALIAS 1 +#define FMT_ENFORCE_COMPILE_STRING 1 +#define FMT_USE_GRISU 0 +#include -namespace urde { +namespace locale { +using namespace std::literals; +#include + +struct DoGetName { + template + constexpr auto Do() { return L::Name; } +}; +constexpr auto GetName(ELocale l) { + return Do(l, DoGetName()); +} + +struct DoGetFullName { + template + constexpr auto Do() { return L::FullName; } +}; +constexpr auto GetFullName(ELocale l) { + return Do(l, DoGetFullName()); +} + +template +struct DoTranslate { + template + constexpr auto Do(Args&&... args) { return fmt::format(Lookup::Value(), std::forward(args)...); } +}; +template +constexpr auto Translate(ELocale l, Args&&... args) { + return Do(l, DoTranslate(), std::forward(args)...); +} std::vector> ListLocales(); -const specter::Locale* LookupLocale(std::string_view name); -const specter::Locale* SystemLocaleOrEnglish(); +ELocale LookupLocale(std::string_view name); +ELocale SystemLocaleOrEnglish(); -} // namespace urde +} // namespace locale