mirror of
https://github.com/AxioDL/metaforce.git
synced 2025-07-03 01:55:52 +00:00
Compile-time locale refactor
This commit is contained in:
parent
91824e14ea
commit
16851c5869
@ -1,9 +1,15 @@
|
|||||||
bintoc(en_US.cpp en_US.yaml L_en_US)
|
add_executable(genlocales genlocales.cpp)
|
||||||
bintoc(en_GB.cpp en_GB.yaml L_en_GB)
|
target_link_libraries(genlocales fmt athena-core)
|
||||||
bintoc(ja_JP.cpp ja_JP.yaml L_ja_JP)
|
|
||||||
add_library(UrdeLocales
|
set(LOCALES_IN en_US.yaml en_GB.yaml ja_JP.yaml)
|
||||||
en_US.yaml en_US.cpp
|
set(LOCALES_OUT ${CMAKE_CURRENT_BINARY_DIR}/locales-inl.hpp)
|
||||||
en_GB.yaml en_GB.cpp
|
add_custom_command(OUTPUT ${LOCALES_OUT} COMMAND $<TARGET_FILE:genlocales>
|
||||||
ja_JP.yaml ja_JP.cpp
|
ARGS ${LOCALES_OUT} ${LOCALES_IN}
|
||||||
locale.hpp locale.cpp)
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
target_link_libraries(UrdeLocales specter)
|
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)
|
||||||
|
@ -1,18 +1,3 @@
|
|||||||
|
name: "British English"
|
||||||
en_GB:
|
en_GB:
|
||||||
color: "Colour"
|
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"
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
name: "US English"
|
||||||
en_US:
|
en_US:
|
||||||
color: "Color"
|
color: "Color"
|
||||||
branch: "Branch"
|
branch: "Branch"
|
||||||
commit: "Commit"
|
commit: "Commit"
|
||||||
|
release: "Release"
|
||||||
date: "Date"
|
date: "Date"
|
||||||
new_project: "New Project"
|
new_project: "New Project"
|
||||||
open_project: "Open Project"
|
open_project: "Open Project"
|
||||||
@ -16,3 +18,24 @@ en_US:
|
|||||||
system_locations: "System Locations"
|
system_locations: "System Locations"
|
||||||
recent_projects: "Recent Projects"
|
recent_projects: "Recent Projects"
|
||||||
recent_files: "Recent Files"
|
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"
|
116
Editor/locale/genlocales.cpp
Normal file
116
Editor/locale/genlocales.cpp
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <unordered_set>
|
||||||
|
#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 <fmt/format.h>
|
||||||
|
#include <fmt/ostream.h>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if (argc < 3) {
|
||||||
|
fmt::print(fmt("{} <out-header> <in-yamls>...\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<std::string> seenLocales;
|
||||||
|
std::stringstream enumLocales;
|
||||||
|
std::stringstream declLocales;
|
||||||
|
std::unordered_set<std::string> 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>(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<typename L, typename K> struct Lookup {\n"
|
||||||
|
" static_assert(!std::is_same_v<L, DefaultLocale>, \"The default locale must translate all keys\");\n"
|
||||||
|
" static constexpr auto Value() { return Lookup<DefaultLocale, K>::Value(); }\n"
|
||||||
|
"};\n"
|
||||||
|
"\n"
|
||||||
|
"/* Keys */\n";
|
||||||
|
out << keys.str();
|
||||||
|
out << "\n";
|
||||||
|
out << lookups.str();
|
||||||
|
out << "template <typename Action, typename... Args>\n"
|
||||||
|
"constexpr auto Do(ELocale l, Action act, Args&&... args) {\n"
|
||||||
|
" switch (l) {\n"
|
||||||
|
" default:\n";
|
||||||
|
out << dos.str();
|
||||||
|
out << " }\n"
|
||||||
|
"}\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
name: "日本語"
|
||||||
ja_JP:
|
ja_JP:
|
||||||
color: "色"
|
color: "色"
|
||||||
branch: "分派"
|
branch: "分派"
|
||||||
|
@ -6,54 +6,32 @@
|
|||||||
#undef min
|
#undef min
|
||||||
#undef max
|
#undef max
|
||||||
|
|
||||||
extern "C" const uint8_t L_en_US[];
|
namespace locale {
|
||||||
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}};
|
|
||||||
|
|
||||||
std::vector<std::pair<std::string_view, std::string_view>> ListLocales() {
|
std::vector<std::pair<std::string_view, std::string_view>> ListLocales() {
|
||||||
constexpr size_t localeCount = std::extent<decltype(Locales)>::value;
|
|
||||||
std::vector<std::pair<std::string_view, std::string_view>> ret;
|
std::vector<std::pair<std::string_view, std::string_view>> ret;
|
||||||
ret.reserve(localeCount);
|
ret.reserve(std::size_t(ELocale::MAXLocale));
|
||||||
for (size_t i = 0; i < localeCount; ++i) {
|
for (ELocale l = ELocale(0); l < ELocale::MAXLocale; l = ELocale(int(l) + 1))
|
||||||
const specter::Locale& l = Locales[i];
|
ret.emplace_back(GetName(l), GetFullName(l));
|
||||||
ret.emplace_back(l.name(), l.fullName());
|
|
||||||
}
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
const specter::Locale* LookupLocale(std::string_view name) {
|
ELocale LookupLocale(std::string_view name) {
|
||||||
constexpr size_t localeCount = std::extent<decltype(Locales)>::value;
|
for (ELocale l = ELocale(0); l < ELocale::MAXLocale; l = ELocale(int(l) + 1))
|
||||||
for (size_t i = 0; i < localeCount; ++i) {
|
if (!name.compare(GetName(l)))
|
||||||
const specter::Locale& l = Locales[i];
|
return l;
|
||||||
if (!name.compare(l.name()))
|
return ELocale::Invalid;
|
||||||
return &l;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const specter::Locale* SystemLocaleOrEnglish() {
|
ELocale SystemLocaleOrEnglish() {
|
||||||
const char* sysLocale = std::setlocale(LC_ALL, nullptr);
|
const char* sysLocale = std::setlocale(LC_ALL, nullptr);
|
||||||
size_t sysLocaleLen = std::strlen(sysLocale);
|
size_t sysLocaleLen = std::strlen(sysLocale);
|
||||||
constexpr size_t localeCount = std::extent<decltype(Locales)>::value;
|
for (ELocale l = ELocale(0); l < ELocale::MAXLocale; l = ELocale(int(l) + 1)) {
|
||||||
for (size_t i = 0; i < localeCount; ++i) {
|
auto name = GetName(l);
|
||||||
const specter::Locale& l = Locales[i];
|
if (!name.compare(0, std::min(name.size(), sysLocaleLen), sysLocale))
|
||||||
if (!l.name().compare(0, std::min(l.name().size(), sysLocaleLen), sysLocale))
|
return l;
|
||||||
return &l;
|
|
||||||
}
|
}
|
||||||
return Locales;
|
return ELocale::en_US;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace urde
|
} // namespace locale
|
||||||
|
@ -1,11 +1,45 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <type_traits>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <specter/Translator.hpp>
|
#define FMT_STRING_ALIAS 1
|
||||||
|
#define FMT_ENFORCE_COMPILE_STRING 1
|
||||||
|
#define FMT_USE_GRISU 0
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
namespace urde {
|
namespace locale {
|
||||||
|
using namespace std::literals;
|
||||||
|
#include <locales-inl.hpp>
|
||||||
|
|
||||||
|
struct DoGetName {
|
||||||
|
template <typename L>
|
||||||
|
constexpr auto Do() { return L::Name; }
|
||||||
|
};
|
||||||
|
constexpr auto GetName(ELocale l) {
|
||||||
|
return Do(l, DoGetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DoGetFullName {
|
||||||
|
template <typename L>
|
||||||
|
constexpr auto Do() { return L::FullName; }
|
||||||
|
};
|
||||||
|
constexpr auto GetFullName(ELocale l) {
|
||||||
|
return Do(l, DoGetFullName());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Key>
|
||||||
|
struct DoTranslate {
|
||||||
|
template <typename L, typename... Args>
|
||||||
|
constexpr auto Do(Args&&... args) { return fmt::format(Lookup<L, Key>::Value(), std::forward<Args>(args)...); }
|
||||||
|
};
|
||||||
|
template <typename Key, typename... Args>
|
||||||
|
constexpr auto Translate(ELocale l, Args&&... args) {
|
||||||
|
return Do(l, DoTranslate<Key>(), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::pair<std::string_view, std::string_view>> ListLocales();
|
std::vector<std::pair<std::string_view, std::string_view>> ListLocales();
|
||||||
const specter::Locale* LookupLocale(std::string_view name);
|
ELocale LookupLocale(std::string_view name);
|
||||||
const specter::Locale* SystemLocaleOrEnglish();
|
ELocale SystemLocaleOrEnglish();
|
||||||
|
|
||||||
} // namespace urde
|
} // namespace locale
|
||||||
|
Loading…
x
Reference in New Issue
Block a user