diff --git a/Editor/locale/CMakeLists.txt b/Editor/locale/CMakeLists.txt new file mode 100644 index 000000000..313d05c15 --- /dev/null +++ b/Editor/locale/CMakeLists.txt @@ -0,0 +1,15 @@ +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 new file mode 100644 index 000000000..2b5a4cf0c --- /dev/null +++ b/Editor/locale/en_GB.yaml @@ -0,0 +1,3 @@ +name: "British English" +en_GB: + color: "Colour" diff --git a/Editor/locale/en_US.yaml b/Editor/locale/en_US.yaml new file mode 100644 index 000000000..3e3871aaf --- /dev/null +++ b/Editor/locale/en_US.yaml @@ -0,0 +1,42 @@ +name: "US English" +en_US: + color: "Color" + branch: "Branch" + commit: "Commit" + release: "Release" + 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" + 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" + version: "Version" \ No newline at end of file diff --git a/Editor/locale/genlocales.cpp b/Editor/locale/genlocales.cpp new file mode 100644 index 000000000..a7238059b --- /dev/null +++ b/Editor/locale/genlocales.cpp @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include "athena/FileReader.hpp" +#include "athena/YAMLDocReader.hpp" + +#define FMT_STRING_ALIAS 1 +#define FMT_ENFORCE_COMPILE_STRING 1 +#include +#include + +int main(int argc, char** argv) { + if (argc < 3) { + fmt::print(FMT_STRING("{} ...\n"), argv[0]); + return 1; + } + + std::ofstream out(argv[1]); + if (!out.is_open()) { + fmt::print(FMT_STRING("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_STRING("Unable to open {} for reading\n"), argv[i]); + return 1; + } + athena::io::YAMLDocReader r; + if (!r.parse(&fr)) { + fmt::print(FMT_STRING("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_STRING("Unable to find 'name' node in {}\n"), argv[i]); + return 1; + } + if (!listNode) { + fmt::print(FMT_STRING("Unable to find list node in {}\n"), argv[i]); + return 1; + } + + if (seenLocales.find(name) == seenLocales.end()) { + seenLocales.insert(name); + fmt::print(enumLocales, FMT_STRING(" {},\n"), name); + fmt::print(declLocales, + FMT_STRING("struct {0} {{ static constexpr auto Name = \"{0}\"sv; static constexpr auto FullName = \"{1}\"sv; }};\n"), + name, fullName); + fmt::print(dos, + FMT_STRING(" case ELocale::{0}:\n" + " return act.template Do<{0}>(std::forward(args)...);\n"), name); + fmt::print(lookups, FMT_STRING("/* {} */\n"), name); + for (const auto& k : listNode->m_mapChildren) { + if (seenKeys.find(k.first) == seenKeys.end()) { + seenKeys.insert(k.first); + fmt::print(keys, FMT_STRING("struct {} {{}};\n"), k.first); + } + fmt::print(lookups, + FMT_STRING("template<> struct Lookup<{}, {}> {{ static constexpr auto Value() {{ return FMT_STRING(\"{}\"); }} }};\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 new file mode 100644 index 000000000..847736741 --- /dev/null +++ b/Editor/locale/ja_JP.yaml @@ -0,0 +1,19 @@ +name: "日本語" +ja_JP: + color: "色" + branch: "分派" + commit: "預ける" + date: "年月日" + new_project: "新しいプロジェクト" + open_project: "プロジェクトを開きます" + extract_game: "ビデオゲームを抽出" + name: "名" + type: "タイプ" + size: "サイズ" + directory: "ディレクトリ" + file: "ファイル" + file_name: "ファイル名" + cancel: "キャンセル" + system_locations: "システムの場所" + recent_projects: "最近使ったプロジェクト" + recent_files: "最近使用したファイル" diff --git a/Editor/locale/locale.cpp b/Editor/locale/locale.cpp new file mode 100644 index 000000000..ff6d0bb34 --- /dev/null +++ b/Editor/locale/locale.cpp @@ -0,0 +1,37 @@ +#include "locale.hpp" +#include +#include +#include + +#undef min +#undef max + +namespace locale { + +std::vector> ListLocales() { + std::vector> ret; + 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; +} + +ELocale LookupLocale(std::string_view name) { + for (ELocale l = ELocale(0); l < ELocale::MAXLocale; l = ELocale(int(l) + 1)) + if (name == GetName(l)) + return l; + return ELocale::Invalid; +} + +ELocale SystemLocaleOrEnglish() { + const char* sysLocale = std::setlocale(LC_ALL, nullptr); + size_t sysLocaleLen = std::strlen(sysLocale); + 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 ELocale::en_US; +} + +} // namespace locale diff --git a/Editor/locale/locale.hpp b/Editor/locale/locale.hpp new file mode 100644 index 000000000..0226aef44 --- /dev/null +++ b/Editor/locale/locale.hpp @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include + +#define FMT_STRING_ALIAS 1 +#define FMT_ENFORCE_COMPILE_STRING 1 +#include + +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(); +ELocale LookupLocale(std::string_view name); +ELocale SystemLocaleOrEnglish(); + +} // namespace locale