mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-25 17:56:37 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			156 lines
		
	
	
	
		
			5.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
	
		
			5.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "l10n.hpp"
 | |
| 
 | |
| #include <unicode/errorcode.h>
 | |
| 
 | |
| #include <components/debug/debuglog.hpp>
 | |
| #include <components/vfs/manager.hpp>
 | |
| 
 | |
| namespace sol
 | |
| {
 | |
|     template <>
 | |
|     struct is_automagical<LuaUtil::L10nManager::Context> : std::false_type
 | |
|     {
 | |
|     };
 | |
| }
 | |
| 
 | |
| namespace LuaUtil
 | |
| {
 | |
|     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)
 | |
|         {
 | |
|             // Argument values
 | |
|             if (value.is<std::string>())
 | |
|                 args.push_back(icu::Formattable(value.as<std::string>().c_str()));
 | |
|             // Note: While we pass all numbers as doubles, they still seem to be handled appropriately.
 | |
|             // Numbers can be forced to be integers using the argType number and argStyle integer
 | |
|             //     E.g. {var, number, integer}
 | |
|             else if (value.is<double>())
 | |
|                 args.push_back(icu::Formattable(value.as<double>()));
 | |
|             else
 | |
|             {
 | |
|                 Log(Debug::Error) << "Unrecognized argument type for key \"" << key.as<std::string>()
 | |
|                                   << "\" when formatting message \"" << messageId << "\"";
 | |
|             }
 | |
| 
 | |
|             // Argument names
 | |
|             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);
 | |
|     }
 | |
| 
 | |
| }
 |