mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-30 08:15:37 +00:00
Separate l10n manager from lua
This commit is contained in:
parent
de83a41de6
commit
3697c9266b
15 changed files with 224 additions and 215 deletions
|
@ -34,6 +34,8 @@
|
|||
|
||||
#include <components/version/version.hpp>
|
||||
|
||||
#include <components/l10n/manager.hpp>
|
||||
|
||||
#include <components/misc/frameratelimiter.hpp>
|
||||
|
||||
#include <components/sceneutil/color.hpp>
|
||||
|
@ -391,6 +393,7 @@ OMW::Engine::~Engine()
|
|||
mStateManager = nullptr;
|
||||
mLuaWorker = nullptr;
|
||||
mLuaManager = nullptr;
|
||||
mL10nManager = nullptr;
|
||||
|
||||
mScriptContext = nullptr;
|
||||
|
||||
|
@ -682,6 +685,10 @@ void OMW::Engine::prepareEngine()
|
|||
|
||||
mViewer->addEventHandler(mScreenCaptureHandler);
|
||||
|
||||
mL10nManager = std::make_unique<l10n::Manager>(mVFS.get());
|
||||
mL10nManager->setPreferredLocales(Settings::Manager::getStringArray("preferred locales", "General"));
|
||||
mEnvironment.setL10nManager(*mL10nManager);
|
||||
|
||||
mLuaManager = std::make_unique<MWLua::LuaManager>(mVFS.get(), mResDir / "lua_libs");
|
||||
mEnvironment.setLuaManager(*mLuaManager);
|
||||
|
||||
|
@ -767,7 +774,6 @@ void OMW::Engine::prepareEngine()
|
|||
mEnvironment.setWorld(*mWorld);
|
||||
|
||||
mWindowManager->setStore(mWorld->getStore());
|
||||
mLuaManager->initL10n();
|
||||
mWindowManager->initUI();
|
||||
|
||||
// Load translation data
|
||||
|
|
|
@ -111,6 +111,11 @@ namespace MWDialogue
|
|||
class Journal;
|
||||
}
|
||||
|
||||
namespace l10n
|
||||
{
|
||||
class Manager;
|
||||
}
|
||||
|
||||
struct SDL_Window;
|
||||
|
||||
namespace OMW
|
||||
|
@ -134,6 +139,7 @@ namespace OMW
|
|||
std::unique_ptr<MWState::StateManager> mStateManager;
|
||||
std::unique_ptr<MWLua::LuaManager> mLuaManager;
|
||||
std::unique_ptr<MWLua::Worker> mLuaWorker;
|
||||
std::unique_ptr<l10n::Manager> mL10nManager;
|
||||
MWBase::Environment mEnvironment;
|
||||
ToUTF8::FromType mEncoding;
|
||||
std::unique_ptr<ToUTF8::Utf8Encoder> mEncoder;
|
||||
|
|
|
@ -10,6 +10,11 @@ namespace Resource
|
|||
class ResourceSystem;
|
||||
}
|
||||
|
||||
namespace l10n
|
||||
{
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace MWBase
|
||||
{
|
||||
class World;
|
||||
|
@ -42,6 +47,7 @@ namespace MWBase
|
|||
StateManager* mStateManager = nullptr;
|
||||
LuaManager* mLuaManager = nullptr;
|
||||
Resource::ResourceSystem* mResourceSystem = nullptr;
|
||||
l10n::Manager* mL10nManager = nullptr;
|
||||
float mFrameRateLimit = 0;
|
||||
float mFrameDuration = 0;
|
||||
|
||||
|
@ -76,6 +82,8 @@ namespace MWBase
|
|||
|
||||
void setResourceSystem(Resource::ResourceSystem& value) { mResourceSystem = &value; }
|
||||
|
||||
void setL10nManager(l10n::Manager& value) { mL10nManager = &value; }
|
||||
|
||||
Misc::NotNullPtr<World> getWorld() const { return mWorld; }
|
||||
|
||||
Misc::NotNullPtr<SoundManager> getSoundManager() const { return mSoundManager; }
|
||||
|
@ -98,6 +106,8 @@ namespace MWBase
|
|||
|
||||
Misc::NotNullPtr<Resource::ResourceSystem> getResourceSystem() const { return mResourceSystem; }
|
||||
|
||||
Misc::NotNullPtr<l10n::Manager> getL10nManager() const { return mL10nManager; }
|
||||
|
||||
float getFrameRateLimit() const { return mFrameRateLimit; }
|
||||
|
||||
void setFrameRateLimit(float value) { mFrameRateLimit = value; }
|
||||
|
|
|
@ -34,7 +34,6 @@ namespace MWBase
|
|||
public:
|
||||
virtual ~LuaManager() = default;
|
||||
|
||||
virtual std::string translate(const std::string& contextName, const std::string& key) = 0;
|
||||
virtual void newGameStarted() = 0;
|
||||
virtual void gameLoaded() = 0;
|
||||
virtual void registerObject(const MWWorld::Ptr& ptr) = 0;
|
||||
|
|
|
@ -49,10 +49,11 @@
|
|||
#include <components/misc/frameratelimiter.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
|
||||
#include <components/l10n/manager.hpp>
|
||||
|
||||
#include <components/lua_ui/util.hpp>
|
||||
|
||||
#include "../mwbase/inputmanager.hpp"
|
||||
#include "../mwbase/luamanager.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/statemanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
@ -1080,13 +1081,12 @@ namespace MWGui
|
|||
std::vector<std::string> split;
|
||||
Misc::StringUtils::split(tag, split, ":");
|
||||
|
||||
// TODO: LocalizationManager should not be a part of lua
|
||||
const auto& luaManager = MWBase::Environment::get().getLuaManager();
|
||||
l10n::Manager& l10nManager = *MWBase::Environment::get().getL10nManager();
|
||||
|
||||
// If a key has a "Context:KeyName" format, use YAML to translate data
|
||||
if (split.size() == 2 && luaManager != nullptr)
|
||||
if (split.size() == 2)
|
||||
{
|
||||
_result = luaManager->translate(split[0], split[1]);
|
||||
_result = l10nManager.getContext(split[0])->formatMessage(split[1], {}, {});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace LuaUtil
|
|||
{
|
||||
class LuaState;
|
||||
class UserdataSerializer;
|
||||
class L10nManager;
|
||||
}
|
||||
|
||||
namespace MWLua
|
||||
|
@ -21,7 +20,6 @@ namespace MWLua
|
|||
LuaManager* mLuaManager;
|
||||
LuaUtil::LuaState* mLua;
|
||||
LuaUtil::UserdataSerializer* mSerializer;
|
||||
LuaUtil::L10nManager* mL10n;
|
||||
WorldView* mWorldView;
|
||||
LocalEventQueue* mLocalEventQueue;
|
||||
GlobalEventQueue* mGlobalEventQueue;
|
||||
|
|
|
@ -58,12 +58,7 @@ namespace MWLua
|
|||
{ std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) });
|
||||
};
|
||||
addTimeBindings(api, context, false);
|
||||
api["l10n"] = [l10n = context.mL10n](const std::string& context, const sol::object& fallbackLocale) {
|
||||
if (fallbackLocale == sol::nil)
|
||||
return l10n->getContext(context);
|
||||
else
|
||||
return l10n->getContext(context, fallbackLocale.as<std::string>());
|
||||
};
|
||||
api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager());
|
||||
const MWWorld::Store<ESM::GameSetting>* gmst
|
||||
= &MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
api["getGMST"] = [lua = context.mLua, gmst](const std::string& setting) -> sol::object {
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include <components/l10n/manager.hpp>
|
||||
|
||||
#include <components/lua/utilpackage.hpp>
|
||||
|
||||
#include <components/lua_ui/util.hpp>
|
||||
|
@ -38,7 +40,6 @@ namespace MWLua
|
|||
LuaManager::LuaManager(const VFS::Manager* vfs, const std::filesystem::path& libsDir)
|
||||
: mLua(vfs, &mConfiguration)
|
||||
, mUiResourceManager(vfs)
|
||||
, mL10n(vfs, &mLua)
|
||||
{
|
||||
Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion();
|
||||
mLua.addInternalLibSearchPath(libsDir);
|
||||
|
@ -60,19 +61,12 @@ namespace MWLua
|
|||
mGlobalScripts.setAutoStartConf(mConfiguration.getGlobalConf());
|
||||
}
|
||||
|
||||
void LuaManager::initL10n()
|
||||
{
|
||||
mL10n.init();
|
||||
mL10n.setPreferredLocales(Settings::Manager::getStringArray("preferred locales", "General"));
|
||||
}
|
||||
|
||||
void LuaManager::init()
|
||||
{
|
||||
Context context;
|
||||
context.mIsGlobal = true;
|
||||
context.mLuaManager = this;
|
||||
context.mLua = &mLua;
|
||||
context.mL10n = &mL10n;
|
||||
context.mWorldView = &mWorldView;
|
||||
context.mLocalEventQueue = &mLocalEvents;
|
||||
context.mGlobalEventQueue = &mGlobalEvents;
|
||||
|
@ -109,11 +103,6 @@ namespace MWLua
|
|||
mInitialized = true;
|
||||
}
|
||||
|
||||
std::string LuaManager::translate(const std::string& contextName, const std::string& key)
|
||||
{
|
||||
return mL10n.translate(contextName, key);
|
||||
}
|
||||
|
||||
void LuaManager::loadPermanentStorage(const std::filesystem::path& userConfigPath)
|
||||
{
|
||||
const auto globalPath = userConfigPath / "global_storage.bin";
|
||||
|
@ -516,9 +505,9 @@ namespace MWLua
|
|||
|
||||
LuaUi::clearUserInterface();
|
||||
MWBase::Environment::get().getWindowManager()->setConsoleMode("");
|
||||
MWBase::Environment::get().getL10nManager()->dropCache();
|
||||
mUiResourceManager.clear();
|
||||
mLua.dropScriptCache();
|
||||
mL10n.clear();
|
||||
initConfiguration();
|
||||
|
||||
{ // Reload global scripts
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <components/lua/l10n.hpp>
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/lua/storage.hpp>
|
||||
|
||||
|
@ -29,9 +28,6 @@ namespace MWLua
|
|||
public:
|
||||
LuaManager(const VFS::Manager* vfs, const std::filesystem::path& libsDir);
|
||||
|
||||
// Called by engine.cpp before UI setup.
|
||||
void initL10n();
|
||||
|
||||
// Called by engine.cpp when the environment is fully initialized.
|
||||
void init();
|
||||
|
||||
|
@ -42,8 +38,6 @@ namespace MWLua
|
|||
// thread (in parallel with osg Cull). Can not use scene graph.
|
||||
void update();
|
||||
|
||||
std::string translate(const std::string& contextName, const std::string& key) override;
|
||||
|
||||
// Called by engine.cpp from the main thread. Can use scene graph.
|
||||
void synchronizedUpdate();
|
||||
|
||||
|
@ -140,7 +134,6 @@ namespace MWLua
|
|||
LuaUtil::ScriptsConfiguration mConfiguration;
|
||||
LuaUtil::LuaState mLua;
|
||||
LuaUi::ResourceManager mUiResourceManager;
|
||||
LuaUtil::L10nManager mL10n;
|
||||
sol::table mNearbyPackage;
|
||||
sol::table mUserInterfacePackage;
|
||||
sol::table mCameraPackage;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <components/files/fixedpath.hpp>
|
||||
|
||||
#include <components/l10n/manager.hpp>
|
||||
#include <components/lua/l10n.hpp>
|
||||
#include <components/lua/luastate.hpp>
|
||||
|
||||
|
@ -84,20 +85,21 @@ you_have_arrows: "Arrows count: {count}"
|
|||
internal::CaptureStdout();
|
||||
LuaUtil::LuaState lua{ mVFS.get(), &mCfg };
|
||||
sol::state& l = lua.sol();
|
||||
LuaUtil::L10nManager l10n(mVFS.get(), &lua);
|
||||
l10n.init();
|
||||
l10n.setPreferredLocales({ "de", "en" });
|
||||
l10n::Manager l10nManager(mVFS.get());
|
||||
l10nManager.setPreferredLocales({ "de", "en" });
|
||||
EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: de en\n");
|
||||
|
||||
l["l10n"] = LuaUtil::initL10nLoader(l, &l10nManager);
|
||||
|
||||
internal::CaptureStdout();
|
||||
l["t1"] = l10n.getContext("Test1");
|
||||
l.safe_script("t1 = l10n('Test1')");
|
||||
EXPECT_THAT(internal::GetCapturedStdout(),
|
||||
"Fallback locale: en\n"
|
||||
"Language file \"l10n/Test1/de.yaml\" is enabled\n"
|
||||
"Language file \"l10n/Test1/en.yaml\" is enabled\n");
|
||||
|
||||
internal::CaptureStdout();
|
||||
l["t2"] = l10n.getContext("Test2");
|
||||
l.safe_script("t2 = l10n('Test2')");
|
||||
{
|
||||
std::string output = internal::GetCapturedStdout();
|
||||
EXPECT_THAT(output, HasSubstr("Language file \"l10n/Test2/en.yaml\" is enabled"));
|
||||
|
@ -111,7 +113,7 @@ you_have_arrows: "Arrows count: {count}"
|
|||
EXPECT_EQ(get<std::string>(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3");
|
||||
|
||||
internal::CaptureStdout();
|
||||
l10n.setPreferredLocales({ "en", "de" });
|
||||
l10nManager.setPreferredLocales({ "en", "de" });
|
||||
EXPECT_THAT(internal::GetCapturedStdout(),
|
||||
"Preferred locales: en de\n"
|
||||
"Language file \"l10n/Test1/en.yaml\" is enabled\n"
|
||||
|
@ -140,7 +142,7 @@ you_have_arrows: "Arrows count: {count}"
|
|||
EXPECT_EQ(get<std::string>(l, "t1('{unknown_arg}')"), "{unknown_arg}");
|
||||
EXPECT_EQ(get<std::string>(l, "t1('{num, integer}', {num=1})"), "{num, integer}");
|
||||
// Doesn't give a valid currency symbol with `en`. Not that openmw is designed for real world currency.
|
||||
l10n.setPreferredLocales({ "en-US", "de" });
|
||||
l10nManager.setPreferredLocales({ "en-US", "de" });
|
||||
EXPECT_EQ(get<std::string>(l, "t1('currency', {money=10000.10})"), "You have $10,000.10");
|
||||
// Note: Not defined in English localisation file, so we fall back to the German before falling back to the key
|
||||
EXPECT_EQ(get<std::string>(l, "t1('Hello {name}!', {name='World'})"), "Hallo World!");
|
||||
|
@ -149,7 +151,7 @@ you_have_arrows: "Arrows count: {count}"
|
|||
|
||||
// Test that locales with variants and country codes fall back to more generic locales
|
||||
internal::CaptureStdout();
|
||||
l10n.setPreferredLocales({ "en-GB-oed", "de" });
|
||||
l10nManager.setPreferredLocales({ "en-GB-oed", "de" });
|
||||
EXPECT_THAT(internal::GetCapturedStdout(),
|
||||
"Preferred locales: en_GB_OED de\n"
|
||||
"Language file \"l10n/Test1/en.yaml\" is enabled\n"
|
||||
|
@ -158,8 +160,8 @@ you_have_arrows: "Arrows count: {count}"
|
|||
EXPECT_EQ(get<std::string>(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3");
|
||||
|
||||
// Test setting fallback language
|
||||
l["t3"] = l10n.getContext("Test3", "de");
|
||||
l10n.setPreferredLocales({ "en" });
|
||||
l.safe_script("t3 = l10n('Test3', 'de')");
|
||||
l10nManager.setPreferredLocales({ "en" });
|
||||
EXPECT_EQ(get<std::string>(l, "t3('Hello {name}!', {name='World'})"), "Hallo World!");
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ add_component_dir (lua
|
|||
)
|
||||
|
||||
add_component_dir (l10n
|
||||
messagebundles
|
||||
messagebundles manager
|
||||
)
|
||||
|
||||
add_component_dir (settings
|
||||
|
|
95
components/l10n/manager.cpp
Normal file
95
components/l10n/manager.cpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
#include "manager.hpp"
|
||||
|
||||
#include <unicode/errorcode.h>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
namespace l10n
|
||||
{
|
||||
|
||||
void Manager::setPreferredLocales(const std::vector<std::string>& langs)
|
||||
{
|
||||
mPreferredLocales.clear();
|
||||
for (const auto& lang : langs)
|
||||
mPreferredLocales.push_back(icu::Locale(lang.c_str()));
|
||||
{
|
||||
Log msg(Debug::Info);
|
||||
msg << "Preferred locales:";
|
||||
for (const icu::Locale& l : mPreferredLocales)
|
||||
msg << " " << l.getName();
|
||||
}
|
||||
for (auto& [key, context] : mCache)
|
||||
updateContext(key.first, *context);
|
||||
}
|
||||
|
||||
void Manager::readLangData(const std::string& name, MessageBundles& ctx, const icu::Locale& lang)
|
||||
{
|
||||
std::string path = "l10n/";
|
||||
path.append(name);
|
||||
path.append("/");
|
||||
path.append(lang.getName());
|
||||
path.append(".yaml");
|
||||
if (!mVFS->exists(path))
|
||||
return;
|
||||
|
||||
ctx.load(*mVFS->get(path), lang, path);
|
||||
}
|
||||
|
||||
void Manager::updateContext(const std::string& name, MessageBundles& ctx)
|
||||
{
|
||||
icu::Locale fallbackLocale = ctx.getFallbackLocale();
|
||||
ctx.setPreferredLocales(mPreferredLocales);
|
||||
int localeCount = 0;
|
||||
bool fallbackLocaleInPreferred = false;
|
||||
for (const icu::Locale& loc : ctx.getPreferredLocales())
|
||||
{
|
||||
if (!ctx.isLoaded(loc))
|
||||
readLangData(name, ctx, loc);
|
||||
if (ctx.isLoaded(loc))
|
||||
{
|
||||
localeCount++;
|
||||
Log(Debug::Verbose) << "Language file \"l10n/" << name << "/" << loc.getName() << ".yaml\" is enabled";
|
||||
if (loc == ctx.getFallbackLocale())
|
||||
fallbackLocaleInPreferred = true;
|
||||
}
|
||||
}
|
||||
if (!ctx.isLoaded(ctx.getFallbackLocale()))
|
||||
readLangData(name, ctx, ctx.getFallbackLocale());
|
||||
if (ctx.isLoaded(ctx.getFallbackLocale()) && !fallbackLocaleInPreferred)
|
||||
Log(Debug::Verbose) << "Fallback language file \"l10n/" << name << "/" << ctx.getFallbackLocale().getName()
|
||||
<< ".yaml\" is enabled";
|
||||
|
||||
if (localeCount == 0)
|
||||
{
|
||||
Log(Debug::Warning) << "No language files for the preferred languages found in \"l10n/" << name << "\"";
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<const MessageBundles> Manager::getContext(
|
||||
const std::string& contextName, const std::string& fallbackLocaleName)
|
||||
{
|
||||
std::pair<std::string, std::string> key(contextName, fallbackLocaleName);
|
||||
auto it = mCache.find(key);
|
||||
if (it != mCache.end())
|
||||
return it->second;
|
||||
auto allowedChar = [](char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_';
|
||||
};
|
||||
bool valid = !contextName.empty();
|
||||
for (char c : contextName)
|
||||
valid = valid && allowedChar(c);
|
||||
if (!valid)
|
||||
throw std::runtime_error(std::string("Invalid l10n context name: ") + contextName);
|
||||
icu::Locale fallbackLocale(fallbackLocaleName.c_str());
|
||||
std::shared_ptr<MessageBundles> ctx = std::make_shared<MessageBundles>(mPreferredLocales, fallbackLocale);
|
||||
{
|
||||
Log msg(Debug::Verbose);
|
||||
msg << "Fallback locale: " << fallbackLocale.getName();
|
||||
}
|
||||
updateContext(contextName, *ctx);
|
||||
mCache.emplace(key, ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
}
|
40
components/l10n/manager.hpp
Normal file
40
components/l10n/manager.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#ifndef COMPONENTS_L10N_MANAGER_H
|
||||
#define COMPONENTS_L10N_MANAGER_H
|
||||
|
||||
#include <components/l10n/messagebundles.hpp>
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace l10n
|
||||
{
|
||||
|
||||
class Manager
|
||||
{
|
||||
public:
|
||||
Manager(const VFS::Manager* vfs)
|
||||
: mVFS(vfs)
|
||||
{
|
||||
}
|
||||
|
||||
void dropCache() { mCache.clear(); }
|
||||
void setPreferredLocales(const std::vector<std::string>& locales);
|
||||
const std::vector<icu::Locale>& getPreferredLocales() const { return mPreferredLocales; }
|
||||
|
||||
std::shared_ptr<const MessageBundles> getContext(
|
||||
const std::string& contextName, const std::string& fallbackLocale = "en");
|
||||
|
||||
private:
|
||||
void readLangData(const std::string& name, MessageBundles& ctx, const icu::Locale& lang);
|
||||
void updateContext(const std::string& name, MessageBundles& ctx);
|
||||
|
||||
const VFS::Manager* mVFS;
|
||||
std::vector<icu::Locale> mPreferredLocales;
|
||||
std::map<std::pair<std::string, std::string>, std::shared_ptr<MessageBundles>> mCache;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // COMPONENTS_L10N_MANAGER_H
|
|
@ -1,65 +1,18 @@
|
|||
#include "l10n.hpp"
|
||||
|
||||
#include <unicode/errorcode.h>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
#include <components/l10n/manager.hpp>
|
||||
|
||||
namespace sol
|
||||
namespace
|
||||
{
|
||||
template <>
|
||||
struct is_automagical<LuaUtil::L10nManager::Context> : std::false_type
|
||||
struct L10nContext
|
||||
{
|
||||
std::shared_ptr<const l10n::MessageBundles> mData;
|
||||
};
|
||||
}
|
||||
|
||||
namespace LuaUtil
|
||||
{
|
||||
void L10nManager::init()
|
||||
void getICUArgs(std::string_view messageId, const sol::table& table, std::vector<icu::UnicodeString>& argNames,
|
||||
std::vector<icu::Formattable>& args)
|
||||
{
|
||||
sol::usertype<Context> ctx = mLua->sol().new_usertype<Context>("L10nContext");
|
||||
ctx[sol::meta_function::call] = &Context::translate;
|
||||
}
|
||||
|
||||
std::string L10nManager::translate(const std::string& contextName, const std::string& key)
|
||||
{
|
||||
Context& ctx = getContext(contextName).as<Context>();
|
||||
return ctx.translate(key, sol::nil);
|
||||
}
|
||||
|
||||
void L10nManager::setPreferredLocales(const std::vector<std::string>& langs)
|
||||
{
|
||||
mPreferredLocales.clear();
|
||||
for (const auto& lang : langs)
|
||||
mPreferredLocales.push_back(icu::Locale(lang.c_str()));
|
||||
{
|
||||
Log msg(Debug::Info);
|
||||
msg << "Preferred locales:";
|
||||
for (const icu::Locale& l : mPreferredLocales)
|
||||
msg << " " << l.getName();
|
||||
}
|
||||
for (auto& [_, context] : mContexts)
|
||||
context.updateLang(this);
|
||||
}
|
||||
|
||||
void L10nManager::Context::readLangData(L10nManager* manager, const icu::Locale& lang)
|
||||
{
|
||||
std::string path = "l10n/";
|
||||
path.append(mName);
|
||||
path.append("/");
|
||||
path.append(lang.getName());
|
||||
path.append(".yaml");
|
||||
if (!manager->mVFS->exists(path))
|
||||
return;
|
||||
|
||||
mMessageBundles->load(*manager->mVFS->get(path), lang, path);
|
||||
}
|
||||
|
||||
std::pair<std::vector<icu::Formattable>, std::vector<icu::UnicodeString>> getICUArgs(
|
||||
std::string_view messageId, const sol::table& table)
|
||||
{
|
||||
std::vector<icu::Formattable> args;
|
||||
std::vector<icu::UnicodeString> argNames;
|
||||
for (auto& [key, value] : table)
|
||||
{
|
||||
// Argument values
|
||||
|
@ -80,77 +33,37 @@ namespace LuaUtil
|
|||
const auto str = key.as<std::string>();
|
||||
argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(str.data(), str.size())));
|
||||
}
|
||||
return std::make_pair(args, argNames);
|
||||
}
|
||||
|
||||
std::string L10nManager::Context::translate(std::string_view key, const sol::object& data)
|
||||
{
|
||||
std::vector<icu::Formattable> args;
|
||||
std::vector<icu::UnicodeString> argNames;
|
||||
|
||||
if (data.is<sol::table>())
|
||||
{
|
||||
sol::table dataTable = data.as<sol::table>();
|
||||
auto argData = getICUArgs(key, dataTable);
|
||||
args = argData.first;
|
||||
argNames = argData.second;
|
||||
}
|
||||
|
||||
return mMessageBundles->formatMessage(key, argNames, args);
|
||||
}
|
||||
|
||||
void L10nManager::Context::updateLang(L10nManager* manager)
|
||||
{
|
||||
icu::Locale fallbackLocale = mMessageBundles->getFallbackLocale();
|
||||
mMessageBundles->setPreferredLocales(manager->mPreferredLocales);
|
||||
int localeCount = 0;
|
||||
bool fallbackLocaleInPreferred = false;
|
||||
for (const icu::Locale& loc : mMessageBundles->getPreferredLocales())
|
||||
{
|
||||
if (!mMessageBundles->isLoaded(loc))
|
||||
readLangData(manager, loc);
|
||||
if (mMessageBundles->isLoaded(loc))
|
||||
{
|
||||
localeCount++;
|
||||
Log(Debug::Verbose) << "Language file \"l10n/" << mName << "/" << loc.getName() << ".yaml\" is enabled";
|
||||
if (loc == fallbackLocale)
|
||||
fallbackLocaleInPreferred = true;
|
||||
}
|
||||
}
|
||||
if (!mMessageBundles->isLoaded(fallbackLocale))
|
||||
readLangData(manager, fallbackLocale);
|
||||
if (mMessageBundles->isLoaded(fallbackLocale) && !fallbackLocaleInPreferred)
|
||||
Log(Debug::Verbose) << "Fallback language file \"l10n/" << mName << "/" << fallbackLocale.getName()
|
||||
<< ".yaml\" is enabled";
|
||||
|
||||
if (localeCount == 0)
|
||||
{
|
||||
Log(Debug::Warning) << "No language files for the preferred languages found in \"l10n/" << mName << "\"";
|
||||
}
|
||||
}
|
||||
|
||||
sol::object L10nManager::getContext(const std::string& contextName, const std::string& fallbackLocaleName)
|
||||
{
|
||||
auto it = mContexts.find(contextName);
|
||||
if (it != mContexts.end())
|
||||
return sol::make_object(mLua->sol(), it->second);
|
||||
auto allowedChar = [](char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_';
|
||||
};
|
||||
bool valid = !contextName.empty();
|
||||
for (char c : contextName)
|
||||
valid = valid && allowedChar(c);
|
||||
if (!valid)
|
||||
throw std::runtime_error(std::string("Invalid l10n context name: ") + contextName);
|
||||
icu::Locale fallbackLocale(fallbackLocaleName.c_str());
|
||||
Context ctx{ contextName, std::make_shared<l10n::MessageBundles>(mPreferredLocales, fallbackLocale) };
|
||||
{
|
||||
Log msg(Debug::Verbose);
|
||||
msg << "Fallback locale: " << fallbackLocale.getName();
|
||||
}
|
||||
ctx.updateLang(this);
|
||||
mContexts.emplace(contextName, ctx);
|
||||
return sol::make_object(mLua->sol(), ctx);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace sol
|
||||
{
|
||||
template <>
|
||||
struct is_automagical<L10nContext> : std::false_type
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
namespace LuaUtil
|
||||
{
|
||||
sol::function initL10nLoader(sol::state& lua, l10n::Manager* manager)
|
||||
{
|
||||
sol::usertype<L10nContext> ctxDef = lua.new_usertype<L10nContext>("L10nContext");
|
||||
ctxDef[sol::meta_function::call]
|
||||
= [](const L10nContext& ctx, std::string_view key, sol::optional<sol::table> args) {
|
||||
std::vector<icu::Formattable> argValues;
|
||||
std::vector<icu::UnicodeString> argNames;
|
||||
if (args)
|
||||
getICUArgs(key, *args, argNames, argValues);
|
||||
return ctx.mData->formatMessage(key, argNames, argValues);
|
||||
};
|
||||
|
||||
return sol::make_object(
|
||||
lua, [manager](const std::string& contextName, sol::optional<std::string> fallbackLocale) {
|
||||
if (fallbackLocale)
|
||||
return L10nContext{ manager->getContext(contextName, *fallbackLocale) };
|
||||
else
|
||||
return L10nContext{ manager->getContext(contextName) };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,53 +1,16 @@
|
|||
#ifndef COMPONENTS_LUA_I18N_H
|
||||
#define COMPONENTS_LUA_I18N_H
|
||||
#ifndef COMPONENTS_LUA_L10N_H
|
||||
#define COMPONENTS_LUA_L10N_H
|
||||
|
||||
#include "luastate.hpp"
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
#include <components/l10n/messagebundles.hpp>
|
||||
|
||||
namespace VFS
|
||||
namespace l10n
|
||||
{
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace LuaUtil
|
||||
{
|
||||
|
||||
class L10nManager
|
||||
{
|
||||
public:
|
||||
L10nManager(const VFS::Manager* vfs, LuaState* lua)
|
||||
: mVFS(vfs)
|
||||
, mLua(lua)
|
||||
{
|
||||
}
|
||||
void init();
|
||||
void clear() { mContexts.clear(); }
|
||||
|
||||
void setPreferredLocales(const std::vector<std::string>& locales);
|
||||
const std::vector<icu::Locale>& getPreferredLocales() const { return mPreferredLocales; }
|
||||
|
||||
sol::object getContext(const std::string& contextName, const std::string& fallbackLocale = "en");
|
||||
std::string translate(const std::string& contextName, const std::string& key);
|
||||
|
||||
private:
|
||||
struct Context
|
||||
{
|
||||
const std::string mName;
|
||||
// Must be a shared pointer so that sol::make_object copies the pointer, not the data structure.
|
||||
std::shared_ptr<l10n::MessageBundles> mMessageBundles;
|
||||
|
||||
void updateLang(L10nManager* manager);
|
||||
void readLangData(L10nManager* manager, const icu::Locale& lang);
|
||||
std::string translate(std::string_view key, const sol::object& data);
|
||||
};
|
||||
|
||||
const VFS::Manager* mVFS;
|
||||
LuaState* mLua;
|
||||
std::vector<icu::Locale> mPreferredLocales;
|
||||
std::map<std::string, Context> mContexts;
|
||||
};
|
||||
|
||||
sol::function initL10nLoader(sol::state& lua, l10n::Manager* manager);
|
||||
}
|
||||
|
||||
#endif // COMPONENTS_LUA_I18N_H
|
||||
#endif // COMPONENTS_LUA_L10N_H
|
||||
|
|
Loading…
Reference in a new issue