LCOV - code coverage report
Current view: top level - source/i18n - L10n.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 282 0.4 %
Date: 2023-01-19 00:18:29 Functions: 2 42 4.8 %

          Line data    Source code
       1             : /* Copyright (C) 2023 Wildfire Games.
       2             :  *
       3             :  * Permission is hereby granted, free of charge, to any person obtaining
       4             :  * a copy of this software and associated documentation files (the
       5             :  * "Software"), to deal in the Software without restriction, including
       6             :  * without limitation the rights to use, copy, modify, merge, publish,
       7             :  * distribute, sublicense, and/or sell copies of the Software, and to
       8             :  * permit persons to whom the Software is furnished to do so, subject to
       9             :  * the following conditions:
      10             :  *
      11             :  * The above copyright notice and this permission notice shall be included
      12             :  * in all copies or substantial portions of the Software.
      13             :  *
      14             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
      15             :  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
      16             :  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
      17             :  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
      18             :  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
      19             :  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
      20             :  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
      21             :  */
      22             : 
      23             : #include "precompiled.h"
      24             : 
      25             : #include "i18n/L10n.h"
      26             : 
      27             : #include "gui/GUIManager.h"
      28             : #include "lib/external_libraries/tinygettext.h"
      29             : #include "lib/file/file_system.h"
      30             : #include "lib/utf8.h"
      31             : #include "ps/CLogger.h"
      32             : #include "ps/ConfigDB.h"
      33             : #include "ps/Filesystem.h"
      34             : #include "ps/GameSetup/GameSetup.h"
      35             : 
      36             : #include <boost/algorithm/string.hpp>
      37             : #include <boost/concept_check.hpp>
      38             : #include <sstream>
      39             : #include <string>
      40             : 
      41             : namespace
      42             : {
      43             : /**
      44             :  * Determines the list of locales that the game supports.
      45             :  *
      46             :  * LoadListOfAvailableLocales() checks the locale codes of the translation
      47             :  * files in the 'l10n' folder of the virtual filesystem. If it finds a
      48             :  * translation file prefixed with a locale code followed by a dot, it
      49             :  * determines that the game supports that locale.
      50             :  */
      51           0 : std::vector<icu::Locale> LoadListOfAvailableLocales()
      52             : {
      53             :     // US is always available.
      54           0 :     std::vector<icu::Locale> availableLocales{icu::Locale::getUS()};
      55             : 
      56           0 :     VfsPaths filenames;
      57           0 :     if (vfs::GetPathnames(g_VFS, L"l10n/", L"*.po", filenames) < 0)
      58           0 :         return availableLocales;
      59             : 
      60           0 :     availableLocales.reserve(filenames.size());
      61             : 
      62           0 :     for (const VfsPath& path : filenames)
      63             :     {
      64             :         // Note: PO files follow this naming convention: "l10n/<locale code>.<mod name>.po". For example: "l10n/gl.public.po".
      65           0 :         const std::string filename = utf8_from_wstring(path.string()).substr(strlen("l10n/"));
      66           0 :         const size_t lengthToFirstDot = filename.find('.');
      67           0 :         const std::string localeCode = filename.substr(0, lengthToFirstDot);
      68           0 :         icu::Locale locale(icu::Locale::createCanonical(localeCode.c_str()));
      69           0 :         const auto it = std::find(availableLocales.begin(), availableLocales.end(), locale);
      70           0 :         if (it != availableLocales.end())
      71           0 :             continue;
      72             : 
      73           0 :         availableLocales.push_back(std::move(locale));
      74             :     }
      75           0 :     availableLocales.shrink_to_fit();
      76             : 
      77           0 :     return availableLocales;
      78             : }
      79             : 
      80           0 : Status ReloadChangedFileCB(void* param, const VfsPath& path)
      81             : {
      82           0 :     return static_cast<L10n*>(param)->ReloadChangedFile(path);
      83             : }
      84             : 
      85             : /**
      86             :  * Loads the specified content of a PO file into the specified dictionary.
      87             :  *
      88             :  * Used by LoadDictionaryForCurrentLocale() to add entries to the game
      89             :  * translations @link dictionary.
      90             :  *
      91             :  * @param poContent Content of a PO file as a string.
      92             :  * @param dictionary Dictionary where the entries from the PO file should be
      93             :  *        stored.
      94             :  */
      95           0 : void ReadPoIntoDictionary(const std::string& poContent, tinygettext::Dictionary* dictionary)
      96             : {
      97             :     try
      98             :     {
      99           0 :         std::istringstream inputStream(poContent);
     100           0 :         tinygettext::POParser::parse("virtual PO file", inputStream, *dictionary);
     101             :     }
     102           0 :     catch (std::exception& e)
     103             :     {
     104           0 :         LOGERROR("[Localization] Exception while reading virtual PO file: %s", e.what());
     105             :     }
     106           0 : }
     107             : 
     108             : /**
     109             :  * Creates an ICU date formatted with the specified settings.
     110             :  *
     111             :  * @param type Whether formatted dates must show both the date and the time,
     112             :  *        only the date or only the time.
     113             :  * @param style ICU style to format dates by default.
     114             :  * @param locale Locale that the date formatter should use to parse strings.
     115             :  *        It has no relevance for date formatting, only matters for date
     116             :  *        parsing.
     117             :  * @return ICU date formatter.
     118             :  */
     119           0 : std::unique_ptr<icu::DateFormat> CreateDateTimeInstance(const L10n::DateTimeType& type, const icu::DateFormat::EStyle& style, const icu::Locale& locale)
     120             : {
     121           0 :     switch (type)
     122             :     {
     123           0 :     case L10n::Date:
     124             :         return std::unique_ptr<icu::DateFormat>{
     125           0 :             icu::SimpleDateFormat::createDateInstance(style, locale)};
     126             : 
     127           0 :     case L10n::Time:
     128             :         return std::unique_ptr<icu::DateFormat>{
     129           0 :             icu::SimpleDateFormat::createTimeInstance(style, locale)};
     130             : 
     131           0 :     case L10n::DateTime: FALLTHROUGH;
     132             :     default:
     133             :         return std::unique_ptr<icu::DateFormat>{
     134           0 :             icu::SimpleDateFormat::createDateTimeInstance(style, style, locale)};
     135             :     }
     136             : }
     137             : 
     138             : } // anonymous namespace
     139             : 
     140           0 : L10n::L10n()
     141           0 :     : m_Dictionary(std::make_unique<tinygettext::Dictionary>())
     142             : {
     143             :     // Determine whether or not to print tinygettext messages to the standard
     144             :     // error output, which it tinygettext's default behavior, but not ours.
     145           0 :     bool tinygettext_debug = false;
     146           0 :     CFG_GET_VAL("tinygettext.debug", tinygettext_debug);
     147           0 :     if (!tinygettext_debug)
     148             :     {
     149           0 :         tinygettext::Log::log_info_callback = 0;
     150           0 :         tinygettext::Log::log_warning_callback = 0;
     151           0 :         tinygettext::Log::log_error_callback = 0;
     152             :     }
     153             : 
     154           0 :     m_AvailableLocales = LoadListOfAvailableLocales();
     155             : 
     156           0 :     ReevaluateCurrentLocaleAndReload();
     157             : 
     158             :     // Handle hotloading
     159           0 :     RegisterFileReloadFunc(ReloadChangedFileCB, this);
     160           0 : }
     161             : 
     162           0 : L10n::~L10n()
     163             : {
     164           0 :     UnregisterFileReloadFunc(ReloadChangedFileCB, this);
     165           0 : }
     166             : 
     167           0 : const icu::Locale& L10n::GetCurrentLocale() const
     168             : {
     169           0 :     return m_CurrentLocale;
     170             : }
     171             : 
     172           0 : bool L10n::SaveLocale(const std::string& localeCode) const
     173             : {
     174           0 :     if (localeCode == "long" && InDevelopmentCopy())
     175             :     {
     176           0 :         g_ConfigDB.SetValueString(CFG_USER, "locale", "long");
     177           0 :         return true;
     178             :     }
     179           0 :     return SaveLocale(icu::Locale(icu::Locale::createCanonical(localeCode.c_str())));
     180             : }
     181             : 
     182           0 : bool L10n::SaveLocale(const icu::Locale& locale) const
     183             : {
     184           0 :     if (!ValidateLocale(locale))
     185           0 :         return false;
     186             : 
     187           0 :     g_ConfigDB.SetValueString(CFG_USER, "locale", locale.getName());
     188           0 :     return g_ConfigDB.WriteValueToFile(CFG_USER, "locale", locale.getName());
     189             : }
     190             : 
     191           0 : bool L10n::ValidateLocale(const std::string& localeCode) const
     192             : {
     193           0 :     return ValidateLocale(icu::Locale::createCanonical(localeCode.c_str()));
     194             : }
     195             : 
     196             : // Returns true if both of these conditions are true:
     197             : //  1. ICU has resources for that locale (which also ensures it's a valid locale string)
     198             : //  2. Either a dictionary for language_country or for language is available.
     199           0 : bool L10n::ValidateLocale(const icu::Locale& locale) const
     200             : {
     201           0 :     if (locale.isBogus())
     202           0 :         return false;
     203             : 
     204           0 :     return !GetFallbackToAvailableDictLocale(locale).empty();
     205             : }
     206             : 
     207           0 : std::vector<std::wstring> L10n::GetDictionariesForLocale(const std::string& locale) const
     208             : {
     209           0 :     std::vector<std::wstring> ret;
     210           0 :     VfsPaths filenames;
     211             : 
     212           0 :     std::wstring dictName = GetFallbackToAvailableDictLocale(icu::Locale::createCanonical(locale.c_str()));
     213           0 :     vfs::GetPathnames(g_VFS, L"l10n/", dictName.append(L".*.po").c_str(), filenames);
     214             : 
     215           0 :     for (const VfsPath& path : filenames)
     216           0 :         ret.push_back(path.Filename().string());
     217             : 
     218           0 :     return ret;
     219             : }
     220             : 
     221           0 : std::wstring L10n::GetFallbackToAvailableDictLocale(const std::string& locale) const
     222             : {
     223           0 :     return GetFallbackToAvailableDictLocale(icu::Locale::createCanonical(locale.c_str()));
     224             : }
     225             : 
     226           0 : std::wstring L10n::GetFallbackToAvailableDictLocale(const icu::Locale& locale) const
     227             : {
     228           0 :     std::wstringstream stream;
     229             : 
     230           0 :     const auto checkLangAndCountry = [&locale](const icu::Locale& l)
     231           0 :     {
     232           0 :         return strcmp(locale.getLanguage(), l.getLanguage()) == 0
     233           0 :             && strcmp(locale.getCountry(), l.getCountry()) == 0;
     234           0 :     };
     235             : 
     236           0 :     if (strcmp(locale.getCountry(), "") != 0
     237           0 :         && std::find_if(m_AvailableLocales.begin(), m_AvailableLocales.end(), checkLangAndCountry) !=
     238           0 :         m_AvailableLocales.end())
     239             :     {
     240           0 :         stream << locale.getLanguage() << L"_" << locale.getCountry();
     241           0 :         return stream.str();
     242             :     }
     243             : 
     244           0 :     const auto checkLang = [&locale](const icu::Locale& l)
     245           0 :     {
     246           0 :         return strcmp(locale.getLanguage(), l.getLanguage()) == 0;
     247           0 :     };
     248             : 
     249           0 :     if (std::find_if(m_AvailableLocales.begin(), m_AvailableLocales.end(), checkLang) !=
     250           0 :         m_AvailableLocales.end())
     251             :     {
     252           0 :         stream << locale.getLanguage();
     253           0 :         return stream.str();
     254             :     }
     255             : 
     256           0 :     return L"";
     257             : }
     258             : 
     259           0 : std::string L10n::GetDictionaryLocale(const std::string& configLocaleString) const
     260             : {
     261           0 :     icu::Locale out;
     262           0 :     GetDictionaryLocale(configLocaleString, out);
     263           0 :     return out.getName();
     264             : }
     265             : 
     266             : // First, try to get a valid locale from the config, then check if the system locale can be used and otherwise fall back to en_US.
     267           0 : void L10n::GetDictionaryLocale(const std::string& configLocaleString, icu::Locale& outLocale) const
     268             : {
     269           0 :     if (!configLocaleString.empty())
     270             :     {
     271           0 :         icu::Locale configLocale = icu::Locale::createCanonical(configLocaleString.c_str());
     272           0 :         if (ValidateLocale(configLocale))
     273             :         {
     274           0 :             outLocale = configLocale;
     275           0 :             return;
     276             :         }
     277             :         else
     278           0 :             LOGWARNING("The configured locale is not valid or no translations are available. Falling back to another locale.");
     279             :     }
     280             : 
     281           0 :     icu::Locale systemLocale = icu::Locale::getDefault();
     282           0 :     if (ValidateLocale(systemLocale))
     283           0 :         outLocale = systemLocale;
     284             :     else
     285           0 :         outLocale = icu::Locale::getUS();
     286             : }
     287             : 
     288             : // Try to find the best dictionary locale based on user configuration and system locale, set the currentLocale and reload the dictionary.
     289           0 : void L10n::ReevaluateCurrentLocaleAndReload()
     290             : {
     291           0 :     std::string locale;
     292           0 :     CFG_GET_VAL("locale", locale);
     293             : 
     294           0 :     if (locale == "long")
     295             :     {
     296             :         // Set ICU to en_US to have a valid language for displaying dates
     297           0 :         m_CurrentLocale = icu::Locale::getUS();
     298           0 :         m_CurrentLocaleIsOriginalGameLocale = false;
     299           0 :         m_UseLongStrings = true;
     300             :     }
     301             :     else
     302             :     {
     303           0 :         GetDictionaryLocale(locale, m_CurrentLocale);
     304           0 :         m_CurrentLocaleIsOriginalGameLocale = (m_CurrentLocale == icu::Locale::getUS()) == 1;
     305           0 :         m_UseLongStrings = false;
     306             :     }
     307           0 :     LoadDictionaryForCurrentLocale();
     308           0 : }
     309             : 
     310             : // Get all locales supported by ICU.
     311           0 : std::vector<std::string> L10n::GetAllLocales() const
     312             : {
     313           0 :     std::vector<std::string> ret;
     314             :     int32_t count;
     315           0 :     const icu::Locale* icuSupportedLocales = icu::Locale::getAvailableLocales(count);
     316           0 :     for (int i=0; i<count; ++i)
     317           0 :         ret.push_back(icuSupportedLocales[i].getName());
     318           0 :     return ret;
     319             : }
     320             : 
     321             : 
     322           0 : bool L10n::UseLongStrings() const
     323             : {
     324           0 :     return m_UseLongStrings;
     325             : };
     326             : 
     327           0 : std::vector<std::string> L10n::GetSupportedLocaleBaseNames() const
     328             : {
     329           0 :     std::vector<std::string> supportedLocaleCodes;
     330           0 :     for (const icu::Locale& locale : m_AvailableLocales)
     331             :     {
     332           0 :         if (!InDevelopmentCopy() && strcmp(locale.getBaseName(), "long") == 0)
     333           0 :             continue;
     334           0 :         supportedLocaleCodes.push_back(locale.getBaseName());
     335             :     }
     336           0 :     return supportedLocaleCodes;
     337             : }
     338             : 
     339           0 : std::vector<std::wstring> L10n::GetSupportedLocaleDisplayNames() const
     340             : {
     341           0 :     std::vector<std::wstring> supportedLocaleDisplayNames;
     342           0 :     for (const icu::Locale& locale : m_AvailableLocales)
     343             :     {
     344           0 :         if (strcmp(locale.getBaseName(), "long") == 0)
     345             :         {
     346           0 :             if (InDevelopmentCopy())
     347           0 :                 supportedLocaleDisplayNames.push_back(wstring_from_utf8(Translate("Long strings")));
     348           0 :             continue;
     349             :         }
     350             : 
     351           0 :         icu::UnicodeString utf16LocaleDisplayName;
     352           0 :         locale.getDisplayName(locale, utf16LocaleDisplayName);
     353             :         char localeDisplayName[512];
     354           0 :         icu::CheckedArrayByteSink sink(localeDisplayName, ARRAY_SIZE(localeDisplayName));
     355           0 :         utf16LocaleDisplayName.toUTF8(sink);
     356           0 :         ENSURE(!sink.Overflowed());
     357             : 
     358           0 :         supportedLocaleDisplayNames.push_back(wstring_from_utf8(std::string(localeDisplayName, sink.NumberOfBytesWritten())));
     359             :     }
     360           0 :     return supportedLocaleDisplayNames;
     361             : }
     362             : 
     363           0 : std::string L10n::GetCurrentLocaleString() const
     364             : {
     365           0 :     return m_CurrentLocale.getName();
     366             : }
     367             : 
     368           0 : std::string L10n::GetLocaleLanguage(const std::string& locale) const
     369             : {
     370           0 :     icu::Locale loc = icu::Locale::createCanonical(locale.c_str());
     371           0 :     return loc.getLanguage();
     372             : }
     373             : 
     374           0 : std::string L10n::GetLocaleBaseName(const std::string& locale) const
     375             : {
     376           0 :     icu::Locale loc = icu::Locale::createCanonical(locale.c_str());
     377           0 :     return loc.getBaseName();
     378             : }
     379             : 
     380           0 : std::string L10n::GetLocaleCountry(const std::string& locale) const
     381             : {
     382           0 :     icu::Locale loc = icu::Locale::createCanonical(locale.c_str());
     383           0 :     return loc.getCountry();
     384             : }
     385             : 
     386           0 : std::string L10n::GetLocaleScript(const std::string& locale) const
     387             : {
     388           0 :     icu::Locale loc = icu::Locale::createCanonical(locale.c_str());
     389           0 :     return loc.getScript();
     390             : }
     391             : 
     392           0 : std::string L10n::Translate(const std::string& sourceString) const
     393             : {
     394           0 :     if (!m_CurrentLocaleIsOriginalGameLocale)
     395           0 :         return m_Dictionary->translate(sourceString);
     396             : 
     397           0 :     return sourceString;
     398             : }
     399             : 
     400           0 : std::string L10n::TranslateWithContext(const std::string& context, const std::string& sourceString) const
     401             : {
     402           0 :     if (!m_CurrentLocaleIsOriginalGameLocale)
     403           0 :         return m_Dictionary->translate_ctxt(context, sourceString);
     404             : 
     405           0 :     return sourceString;
     406             : }
     407             : 
     408           0 : std::string L10n::TranslatePlural(const std::string& singularSourceString, const std::string& pluralSourceString, int number) const
     409             : {
     410           0 :     if (!m_CurrentLocaleIsOriginalGameLocale)
     411           0 :         return m_Dictionary->translate_plural(singularSourceString, pluralSourceString, number);
     412             : 
     413           0 :     if (number == 1)
     414           0 :         return singularSourceString;
     415             : 
     416           0 :     return pluralSourceString;
     417             : }
     418             : 
     419           0 : std::string L10n::TranslatePluralWithContext(const std::string& context, const std::string& singularSourceString, const std::string& pluralSourceString, int number) const
     420             : {
     421           0 :     if (!m_CurrentLocaleIsOriginalGameLocale)
     422           0 :         return m_Dictionary->translate_ctxt_plural(context, singularSourceString, pluralSourceString, number);
     423             : 
     424           0 :     if (number == 1)
     425           0 :         return singularSourceString;
     426             : 
     427           0 :     return pluralSourceString;
     428             : }
     429             : 
     430           0 : std::string L10n::TranslateLines(const std::string& sourceString) const
     431             : {
     432           0 :     std::string targetString;
     433           0 :     std::stringstream stringOfLines(sourceString);
     434           0 :     std::string line;
     435             : 
     436           0 :     while (std::getline(stringOfLines, line))
     437             :     {
     438           0 :         if (!line.empty())
     439           0 :             targetString.append(Translate(line));
     440           0 :         targetString.append("\n");
     441             :     }
     442             : 
     443           0 :     return targetString;
     444             : }
     445             : 
     446           0 : UDate L10n::ParseDateTime(const std::string& dateTimeString, const std::string& dateTimeFormat, const icu::Locale& locale) const
     447             : {
     448           0 :     UErrorCode success = U_ZERO_ERROR;
     449           0 :     icu::UnicodeString utf16DateTimeString = icu::UnicodeString::fromUTF8(dateTimeString.c_str());
     450           0 :     icu::UnicodeString utf16DateTimeFormat = icu::UnicodeString::fromUTF8(dateTimeFormat.c_str());
     451             : 
     452           0 :     const icu::SimpleDateFormat dateFormatter{utf16DateTimeFormat, locale, success};
     453           0 :     return dateFormatter.parse(utf16DateTimeString, success);
     454             : }
     455             : 
     456           0 : std::string L10n::LocalizeDateTime(const UDate dateTime, const DateTimeType& type, const icu::DateFormat::EStyle& style) const
     457             : {
     458           0 :     icu::UnicodeString utf16Date;
     459             : 
     460             :     const std::unique_ptr<const icu::DateFormat> dateFormatter{
     461           0 :         CreateDateTimeInstance(type, style, m_CurrentLocale)};
     462           0 :     dateFormatter->format(dateTime, utf16Date);
     463             :     char utf8Date[512];
     464           0 :     icu::CheckedArrayByteSink sink(utf8Date, ARRAY_SIZE(utf8Date));
     465           0 :     utf16Date.toUTF8(sink);
     466           0 :     ENSURE(!sink.Overflowed());
     467             : 
     468           0 :     return std::string(utf8Date, sink.NumberOfBytesWritten());
     469             : }
     470             : 
     471           0 : std::string L10n::FormatMillisecondsIntoDateString(const UDate milliseconds, const std::string& formatString, bool useLocalTimezone) const
     472             : {
     473           0 :     UErrorCode status = U_ZERO_ERROR;
     474           0 :     icu::UnicodeString dateString;
     475           0 :     std::string resultString;
     476             : 
     477           0 :     icu::UnicodeString unicodeFormat = icu::UnicodeString::fromUTF8(formatString.c_str());
     478           0 :     icu::SimpleDateFormat dateFormat{unicodeFormat, status};
     479           0 :     if (U_FAILURE(status))
     480           0 :         LOGERROR("Error creating SimpleDateFormat: %s", u_errorName(status));
     481             : 
     482           0 :     status = U_ZERO_ERROR;
     483             :     std::unique_ptr<icu::Calendar> calendar{
     484             :         useLocalTimezone ?
     485           0 :             icu::Calendar::createInstance(m_CurrentLocale, status) :
     486           0 :             icu::Calendar::createInstance(*icu::TimeZone::getGMT(), m_CurrentLocale, status)};
     487             : 
     488           0 :     if (U_FAILURE(status))
     489           0 :         LOGERROR("Error creating calendar: %s", u_errorName(status));
     490             : 
     491           0 :     dateFormat.adoptCalendar(calendar.release());
     492           0 :     dateFormat.format(milliseconds, dateString);
     493             : 
     494           0 :     dateString.toUTF8String(resultString);
     495           0 :     return resultString;
     496             : }
     497             : 
     498           0 : std::string L10n::FormatDecimalNumberIntoString(double number) const
     499             : {
     500           0 :     UErrorCode success = U_ZERO_ERROR;
     501           0 :     icu::UnicodeString utf16Number;
     502             :     std::unique_ptr<icu::NumberFormat> numberFormatter{
     503           0 :         icu::NumberFormat::createInstance(m_CurrentLocale, UNUM_DECIMAL, success)};
     504           0 :     numberFormatter->format(number, utf16Number);
     505             :     char utf8Number[512];
     506           0 :     icu::CheckedArrayByteSink sink(utf8Number, ARRAY_SIZE(utf8Number));
     507           0 :     utf16Number.toUTF8(sink);
     508           0 :     ENSURE(!sink.Overflowed());
     509             : 
     510           0 :     return std::string(utf8Number, sink.NumberOfBytesWritten());
     511             : }
     512             : 
     513           0 : VfsPath L10n::LocalizePath(const VfsPath& sourcePath) const
     514             : {
     515           0 :     VfsPath localizedPath = sourcePath.Parent() / L"l10n" /
     516           0 :         wstring_from_utf8(m_CurrentLocale.getLanguage()) / sourcePath.Filename();
     517           0 :     if (!VfsFileExists(localizedPath))
     518           0 :         return sourcePath;
     519             : 
     520           0 :     return localizedPath;
     521             : }
     522             : 
     523           0 : Status L10n::ReloadChangedFile(const VfsPath& path)
     524             : {
     525           0 :     if (!boost::algorithm::starts_with(path.string(), L"l10n/"))
     526           0 :         return INFO::OK;
     527             : 
     528           0 :     if (path.Extension() != L".po")
     529           0 :         return INFO::OK;
     530             : 
     531             :     // If the file was deleted, ignore it
     532           0 :     if (!VfsFileExists(path))
     533           0 :         return INFO::OK;
     534             : 
     535           0 :     std::wstring dictName = GetFallbackToAvailableDictLocale(m_CurrentLocale);
     536           0 :     if (m_UseLongStrings)
     537           0 :         dictName = L"long";
     538           0 :     if (dictName.empty())
     539           0 :         return INFO::OK;
     540             : 
     541             :     // Only the currently used language is loaded, so ignore all others
     542           0 :     if (path.string().rfind(dictName) == std::string::npos)
     543           0 :         return INFO::OK;
     544             : 
     545           0 :     LOGMESSAGE("Hotloading translations from '%s'", path.string8());
     546             : 
     547           0 :     CVFSFile file;
     548           0 :     if (file.Load(g_VFS, path) != PSRETURN_OK)
     549             :     {
     550           0 :         LOGERROR("Failed to read translations from '%s'", path.string8());
     551           0 :         return ERR::FAIL;
     552             :     }
     553             : 
     554           0 :     std::string content = file.DecodeUTF8();
     555           0 :     ReadPoIntoDictionary(content, m_Dictionary.get());
     556             : 
     557           0 :     if (g_GUI)
     558           0 :         g_GUI->ReloadAllPages();
     559             : 
     560           0 :     return INFO::OK;
     561             : }
     562             : 
     563           0 : void L10n::LoadDictionaryForCurrentLocale()
     564             : {
     565           0 :     m_Dictionary = std::make_unique<tinygettext::Dictionary>();
     566           0 :     VfsPaths filenames;
     567             : 
     568           0 :     if (m_UseLongStrings)
     569             :     {
     570           0 :         if (vfs::GetPathnames(g_VFS, L"l10n/", L"long.*.po", filenames) < 0)
     571           0 :             return;
     572             :     }
     573             :     else
     574             :     {
     575           0 :         std::wstring dictName = GetFallbackToAvailableDictLocale(m_CurrentLocale);
     576           0 :         if (vfs::GetPathnames(g_VFS, L"l10n/", dictName.append(L".*.po").c_str(), filenames) < 0)
     577             :         {
     578           0 :             LOGERROR("No files for the dictionary found, but at this point the input should already be validated!");
     579           0 :             return;
     580             :         }
     581             :     }
     582             : 
     583           0 :     for (const VfsPath& path : filenames)
     584             :     {
     585           0 :         CVFSFile file;
     586           0 :         file.Load(g_VFS, path);
     587           0 :         std::string content = file.DecodeUTF8();
     588           0 :         ReadPoIntoDictionary(content, m_Dictionary.get());
     589             :     }
     590           3 : }

Generated by: LCOV version 1.13