#include "messagebundles.hpp" #include #include #include #include #include namespace l10n { MessageBundles::MessageBundles(const std::vector& preferredLocales, icu::Locale& fallbackLocale) : mFallbackLocale(fallbackLocale) { setPreferredLocales(preferredLocales); } void MessageBundles::setPreferredLocales(const std::vector& preferredLocales) { mPreferredLocales.clear(); mPreferredLocaleStrings.clear(); for (const icu::Locale& loc : preferredLocales) { mPreferredLocales.push_back(loc); mPreferredLocaleStrings.emplace_back(loc.getName()); // Try without variant or country if they are specified, starting with the most specific if (strcmp(loc.getVariant(), "") != 0) { icu::Locale withoutVariant(loc.getLanguage(), loc.getCountry()); mPreferredLocales.push_back(withoutVariant); mPreferredLocaleStrings.emplace_back(withoutVariant.getName()); } if (strcmp(loc.getCountry(), "") != 0) { icu::Locale withoutCountry(loc.getLanguage()); mPreferredLocales.push_back(withoutCountry); mPreferredLocaleStrings.emplace_back(withoutCountry.getName()); } } } std::string getErrorText(const UParseError& parseError) { icu::UnicodeString preContext(parseError.preContext), postContext(parseError.postContext); std::string parseErrorString; preContext.toUTF8String(parseErrorString); postContext.toUTF8String(parseErrorString); return parseErrorString; } static bool checkSuccess( const icu::ErrorCode& status, const std::string& message, const UParseError parseError = UParseError()) { if (status.isFailure()) { std::string errorText = getErrorText(parseError); if (!errorText.empty()) { Log(Debug::Error) << message << ": " << status.errorName() << " in \"" << errorText << "\""; } else { Log(Debug::Error) << message << ": " << status.errorName(); } } return status.isSuccess(); } void MessageBundles::load(std::istream& input, const icu::Locale& lang) { YAML::Node data = YAML::Load(input); std::string localeName = lang.getName(); const icu::Locale& langOrEn = localeName == "gmst" ? icu::Locale::getEnglish() : lang; for (const auto& it : data) { const auto key = it.first.as(); const auto value = it.second.as(); icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(icu::StringPiece(value.data(), static_cast(value.size()))); icu::ErrorCode status; UParseError parseError; icu::MessageFormat message(pattern, langOrEn, parseError, status); if (checkSuccess(status, std::string("Failed to create message ") + key + " for locale " + lang.getName(), parseError)) { mBundles[localeName].insert(std::make_pair(key, message)); } } } const icu::MessageFormat* MessageBundles::findMessage(std::string_view key, const std::string& localeName) const { auto iter = mBundles.find(localeName); if (iter != mBundles.end()) { auto message = iter->second.find(key.data()); if (message != iter->second.end()) { return &(message->second); } } return nullptr; } std::string MessageBundles::formatMessage( std::string_view key, const std::map& args) const { std::vector argNames; std::vector argValues; for (auto& [k, v] : args) { argNames.push_back( icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), static_cast(k.size())))); argValues.push_back(v); } return formatMessage(key, argNames, argValues); } static std::string loadGmst( const std::function gmstLoader, const icu::MessageFormat* message) { icu::UnicodeString gmstNameUnicode; std::string gmstName; icu::ErrorCode success; message->format(nullptr, nullptr, 0, gmstNameUnicode, success); gmstNameUnicode.toUTF8String(gmstName); if (gmstLoader) return gmstLoader(gmstName); else return "GMST:" + gmstName; } std::string MessageBundles::formatMessage(std::string_view key, const std::vector& argNames, const std::vector& args) const { icu::UnicodeString result; std::string resultString; icu::ErrorCode success; const icu::MessageFormat* message = nullptr; for (auto& loc : mPreferredLocaleStrings) { message = findMessage(key, loc); if (message) { if (loc == "gmst") return loadGmst(mGmstLoader, message); break; } } // If no requested locales included the message, try the fallback locale if (!message) message = findMessage(key, mFallbackLocale.getName()); if (message) { if (!args.empty() && !argNames.empty()) message->format(argNames.data(), args.data(), static_cast(args.size()), result, success); else message->format(nullptr, nullptr, static_cast(args.size()), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); result.toUTF8String(resultString); return resultString; } icu::Locale defaultLocale(nullptr); if (!mPreferredLocales.empty()) { defaultLocale = mPreferredLocales[0]; } UParseError parseError; icu::MessageFormat defaultMessage( icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), static_cast(key.size()))), defaultLocale, parseError, success); if (!checkSuccess(success, std::string("Failed to create message ") + key.data(), parseError)) // If we can't parse the key as a pattern, just return the key return std::string(key); if (!args.empty() && !argNames.empty()) defaultMessage.format( argNames.data(), args.data(), static_cast(args.size()), result, success); else defaultMessage.format(nullptr, nullptr, static_cast(args.size()), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); result.toUTF8String(resultString); return resultString; } }