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