From 89aede6e9e676d73e84a0c6e73f7300ab19fc800 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Mon, 25 Nov 2024 06:10:42 -0500 Subject: [PATCH] Add `string::translate()` and `settings::language` for getting translated strings depending on language. --- adapters/standalone.cpp | 1 + include/settings.hpp | 2 ++ include/string.hpp | 5 +++ src/settings.cpp | 7 ++++ src/string.cpp | 74 +++++++++++++++++++++++++++++++++++++++++ translations/en.json | 3 ++ 6 files changed, 92 insertions(+) create mode 100644 translations/en.json diff --git a/adapters/standalone.cpp b/adapters/standalone.cpp index 720b4876..d43103c6 100644 --- a/adapters/standalone.cpp +++ b/adapters/standalone.cpp @@ -179,6 +179,7 @@ int main(int argc, char* argv[]) { exit(1); } } + string::init(); // Check existence of the system res/ directory std::string resDir = asset::system("res"); diff --git a/include/settings.hpp b/include/settings.hpp index a92c2f0e..3092da89 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -27,6 +27,8 @@ extern bool isPlugin; // Persistent state, serialized to settings.json. +/** ISO 639-1 language code for string translations. */ +extern std::string language; /** Launches Rack without loading plugins or the autosave patch. Always set to false when settings are saved. */ extern bool safeMode; /** vcvrack.com user token */ diff --git a/include/string.hpp b/include/string.hpp index 7126b814..991d4efb 100644 --- a/include/string.hpp +++ b/include/string.hpp @@ -131,5 +131,10 @@ struct Version { }; +std::string translate(const std::string& id); +std::string translate(const std::string& id, const std::string& language); +void init(); + + } // namespace string } // namespace rack diff --git a/src/settings.cpp b/src/settings.cpp index 385d4b96..47053c96 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -20,6 +20,7 @@ bool devMode = false; bool headless = false; bool isPlugin = false; +std::string language = "en"; bool safeMode = false; std::string token; bool windowMaximized = false; @@ -126,6 +127,8 @@ void destroy() { json_t* toJson() { json_t* rootJ = json_object(); + json_object_set_new(rootJ, "language", json_string(language.c_str())); + // Always disable safe mode when settings are saved. json_object_set_new(rootJ, "safeMode", json_boolean(false)); @@ -281,6 +284,10 @@ json_t* toJson() { } void fromJson(json_t* rootJ) { + json_t* languageJ = json_object_get(rootJ, "language"); + if (languageJ) + language = json_string_value(languageJ); + json_t* safeModeJ = json_object_get(rootJ, "safeMode"); if (safeModeJ) { // If safe mode is enabled (e.g. by command line flag), don't disable it when loading. diff --git a/src/string.cpp b/src/string.cpp index 128892de..f5ca9161 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -3,6 +3,7 @@ #include #include // for tolower and toupper #include // for transform and equal +#include #include // for dirname and basename #include @@ -11,6 +12,9 @@ #endif #include +#include +#include +#include namespace rack { @@ -322,6 +326,76 @@ bool Version::operator<(const Version& other) { } +/** {language: {id: string}} */ +static std::map> translations; + + +static void loadTranslations() { + translations.clear(); + std::string translationsDir = asset::system("translations"); + + for (std::string filename : system::getEntries(translationsDir)) { + if (system::getExtension(filename) != ".json") + continue; + std::string language = system::getStem(filename); + std::string path = system::join(translationsDir, filename); + + INFO("Loading translation %s from %s", language.c_str(), path.c_str()); + FILE* file = std::fopen(path.c_str(), "r"); + if (!file) { + WARN("Cannot open translation file %s", path.c_str()); + continue; + } + DEFER({std::fclose(file);}); + + json_error_t error; + json_t* rootJ = json_loadf(file, 0, &error); + if (!rootJ) { + WARN("Translation file has invalid JSON at %d:%d %s", error.line, error.column, error.text); + continue; + } + DEFER({json_decref(rootJ);}); + + auto& translation = translations[language]; + + // Load JSON + const char* id; + json_t* strJ; + json_object_foreach(rootJ, id, strJ) { + std::string s(json_string_value(strJ), json_string_length(strJ)); + translation[id] = s; + } + } +} + + +std::string translate(const std::string& id) { + std::string s = translate(id, settings::language); + if (!s.empty()) + return s; + + // English fallback + if (settings::language != "en") { + return translate(id, "en"); + } + return ""; +} + + +std::string translate(const std::string& id, const std::string& language) { + auto it = translations.find(language); + if (it == translations.end()) + return ""; + + const auto& translation = it->second; + return get(translation, id, ""); +} + + +void init() { + loadTranslations(); +} + } // namespace string } // namespace rack diff --git a/translations/en.json b/translations/en.json new file mode 100644 index 00000000..641c8ba2 --- /dev/null +++ b/translations/en.json @@ -0,0 +1,3 @@ +{ + "language": "English" +} \ No newline at end of file