Index: ps/trunk/source/i18n/L10n.cpp =================================================================== --- ps/trunk/source/i18n/L10n.cpp (revision 25041) +++ ps/trunk/source/i18n/L10n.cpp (revision 25042) @@ -1,569 +1,580 @@ /* Copyright (C) 2021 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "precompiled.h" #include "i18n/L10n.h" #include "gui/GUIManager.h" #include "lib/external_libraries/tinygettext.h" #include "lib/file/file_system.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/GameSetup/GameSetup.h" #include #include #include #include namespace { Status ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } /** * Loads the specified content of a PO file into the specified dictionary. * * Used by LoadDictionaryForCurrentLocale() to add entries to the game * translations @link dictionary. * * @param poContent Content of a PO file as a string. * @param dictionary Dictionary where the entries from the PO file should be * stored. */ void ReadPoIntoDictionary(const std::string& poContent, tinygettext::Dictionary* dictionary) { try { std::istringstream inputStream(poContent); tinygettext::POParser::parse("virtual PO file", inputStream, *dictionary); } catch (std::exception& e) { LOGERROR("[Localization] Exception while reading virtual PO file: %s", e.what()); } } +/** + * Creates an ICU date formatted with the specified settings. + * + * @param type Whether formatted dates must show both the date and the time, + * only the date or only the time. + * @param style ICU style to format dates by default. + * @param locale Locale that the date formatter should use to parse strings. + * It has no relevance for date formatting, only matters for date + * parsing. + * @return ICU date formatter. + */ +icu::DateFormat* CreateDateTimeInstance(const L10n::DateTimeType& type, const icu::DateFormat::EStyle& style, const icu::Locale& locale) +{ + switch (type) + { + case L10n::Date: + return icu::SimpleDateFormat::createDateInstance(style, locale); + + case L10n::Time: + return icu::SimpleDateFormat::createTimeInstance(style, locale); + + case L10n::DateTime: + default: + return icu::SimpleDateFormat::createDateTimeInstance(style, style, locale); + } +} + } // anonymous namespace void L10n::DictionaryDeleter::operator()(tinygettext::Dictionary* dictionary) { delete dictionary; } L10n::L10n() : m_Dictionary(new tinygettext::Dictionary()), currentLocaleIsOriginalGameLocale(false), useLongStrings(false) { // Determine whether or not to print tinygettext messages to the standard // error output, which it tinygettext's default behavior, but not ours. bool tinygettext_debug = false; CFG_GET_VAL("tinygettext.debug", tinygettext_debug); if (!tinygettext_debug) { tinygettext::Log::log_info_callback = 0; tinygettext::Log::log_warning_callback = 0; tinygettext::Log::log_error_callback = 0; } LoadListOfAvailableLocales(); ReevaluateCurrentLocaleAndReload(); // Handle hotloading RegisterFileReloadFunc(ReloadChangedFileCB, this); } L10n::~L10n() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); } icu::Locale L10n::GetCurrentLocale() const { return currentLocale; } bool L10n::SaveLocale(const std::string& localeCode) const { if (localeCode == "long" && InDevelopmentCopy()) { g_ConfigDB.SetValueString(CFG_USER, "locale", "long"); return true; } return SaveLocale(icu::Locale(icu::Locale::createCanonical(localeCode.c_str()))); } bool L10n::SaveLocale(const icu::Locale& locale) const { if (!ValidateLocale(locale)) return false; g_ConfigDB.SetValueString(CFG_USER, "locale", locale.getName()); return g_ConfigDB.WriteValueToFile(CFG_USER, "locale", locale.getName()); } bool L10n::ValidateLocale(const std::string& localeCode) const { return ValidateLocale(icu::Locale::createCanonical(localeCode.c_str())); } // Returns true if both of these conditions are true: // 1. ICU has resources for that locale (which also ensures it's a valid locale string) // 2. Either a dictionary for language_country or for language is available. bool L10n::ValidateLocale(const icu::Locale& locale) const { if (locale.isBogus()) return false; return !GetFallbackToAvailableDictLocale(locale).empty(); } std::vector L10n::GetDictionariesForLocale(const std::string& locale) const { std::vector ret; VfsPaths filenames; std::wstring dictName = GetFallbackToAvailableDictLocale(icu::Locale::createCanonical(locale.c_str())); vfs::GetPathnames(g_VFS, L"l10n/", dictName.append(L".*.po").c_str(), filenames); for (const VfsPath& path : filenames) ret.push_back(path.Filename().string()); return ret; } std::wstring L10n::GetFallbackToAvailableDictLocale(const std::string& locale) const { return GetFallbackToAvailableDictLocale(icu::Locale::createCanonical(locale.c_str())); } std::wstring L10n::GetFallbackToAvailableDictLocale(const icu::Locale& locale) const { std::wstringstream stream; auto checkLangAndCountry = [&locale](const std::unique_ptr& l) { return strcmp(locale.getLanguage(), l->getLanguage()) == 0 && strcmp(locale.getCountry(), l->getCountry()) == 0; }; if (strcmp(locale.getCountry(), "") != 0 && std::find_if(availableLocales.begin(), availableLocales.end(), checkLangAndCountry) != availableLocales.end()) { stream << locale.getLanguage() << L"_" << locale.getCountry(); return stream.str(); } auto checkLang = [&locale](const std::unique_ptr& l) { return strcmp(locale.getLanguage(), l->getLanguage()) == 0; }; if (std::find_if(availableLocales.begin(), availableLocales.end(), checkLang) != availableLocales.end()) { stream << locale.getLanguage(); return stream.str(); } return L""; } std::string L10n::GetDictionaryLocale(const std::string& configLocaleString) const { icu::Locale out; GetDictionaryLocale(configLocaleString, out); return out.getName(); } // 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. void L10n::GetDictionaryLocale(const std::string& configLocaleString, icu::Locale& outLocale) const { if (!configLocaleString.empty()) { icu::Locale configLocale = icu::Locale::createCanonical(configLocaleString.c_str()); if (ValidateLocale(configLocale)) { outLocale = configLocale; return; } else LOGWARNING("The configured locale is not valid or no translations are available. Falling back to another locale."); } icu::Locale systemLocale = icu::Locale::getDefault(); if (ValidateLocale(systemLocale)) outLocale = systemLocale; else outLocale = icu::Locale::getUS(); } // Try to find the best dictionary locale based on user configuration and system locale, set the currentLocale and reload the dictionary. void L10n::ReevaluateCurrentLocaleAndReload() { std::string locale; CFG_GET_VAL("locale", locale); if (locale == "long") { // Set ICU to en_US to have a valid language for displaying dates currentLocale = icu::Locale::getUS(); currentLocaleIsOriginalGameLocale = false; useLongStrings = true; } else { GetDictionaryLocale(locale, currentLocale); currentLocaleIsOriginalGameLocale = (currentLocale == icu::Locale::getUS()) == 1; useLongStrings = false; } LoadDictionaryForCurrentLocale(); } // Get all locales supported by ICU. std::vector L10n::GetAllLocales() const { std::vector ret; int32_t count; const icu::Locale* icuSupportedLocales = icu::Locale::getAvailableLocales(count); for (int i=0; i L10n::GetSupportedLocaleBaseNames() const { std::vector supportedLocaleCodes; for (const std::unique_ptr& locale : availableLocales) { if (!InDevelopmentCopy() && strcmp(locale->getBaseName(), "long") == 0) continue; supportedLocaleCodes.push_back(locale->getBaseName()); } return supportedLocaleCodes; } std::vector L10n::GetSupportedLocaleDisplayNames() const { std::vector supportedLocaleDisplayNames; for (const std::unique_ptr& locale : availableLocales) { if (strcmp(locale->getBaseName(), "long") == 0) { if (InDevelopmentCopy()) supportedLocaleDisplayNames.push_back(wstring_from_utf8(Translate("Long strings"))); continue; } icu::UnicodeString utf16LocaleDisplayName; locale->getDisplayName(*locale, utf16LocaleDisplayName); char localeDisplayName[512]; icu::CheckedArrayByteSink sink(localeDisplayName, ARRAY_SIZE(localeDisplayName)); utf16LocaleDisplayName.toUTF8(sink); ENSURE(!sink.Overflowed()); supportedLocaleDisplayNames.push_back(wstring_from_utf8(std::string(localeDisplayName, sink.NumberOfBytesWritten()))); } return supportedLocaleDisplayNames; } std::string L10n::GetCurrentLocaleString() const { return currentLocale.getName(); } std::string L10n::GetLocaleLanguage(const std::string& locale) const { icu::Locale loc = icu::Locale::createCanonical(locale.c_str()); return loc.getLanguage(); } std::string L10n::GetLocaleBaseName(const std::string& locale) const { icu::Locale loc = icu::Locale::createCanonical(locale.c_str()); return loc.getBaseName(); } std::string L10n::GetLocaleCountry(const std::string& locale) const { icu::Locale loc = icu::Locale::createCanonical(locale.c_str()); return loc.getCountry(); } std::string L10n::GetLocaleScript(const std::string& locale) const { icu::Locale loc = icu::Locale::createCanonical(locale.c_str()); return loc.getScript(); } std::string L10n::Translate(const std::string& sourceString) const { if (!currentLocaleIsOriginalGameLocale) return m_Dictionary->translate(sourceString); return sourceString; } std::string L10n::TranslateWithContext(const std::string& context, const std::string& sourceString) const { if (!currentLocaleIsOriginalGameLocale) return m_Dictionary->translate_ctxt(context, sourceString); return sourceString; } std::string L10n::TranslatePlural(const std::string& singularSourceString, const std::string& pluralSourceString, int number) const { if (!currentLocaleIsOriginalGameLocale) return m_Dictionary->translate_plural(singularSourceString, pluralSourceString, number); if (number == 1) return singularSourceString; return pluralSourceString; } std::string L10n::TranslatePluralWithContext(const std::string& context, const std::string& singularSourceString, const std::string& pluralSourceString, int number) const { if (!currentLocaleIsOriginalGameLocale) return m_Dictionary->translate_ctxt_plural(context, singularSourceString, pluralSourceString, number); if (number == 1) return singularSourceString; return pluralSourceString; } std::string L10n::TranslateLines(const std::string& sourceString) const { std::string targetString; std::stringstream stringOfLines(sourceString); std::string line; while (std::getline(stringOfLines, line)) { if (!line.empty()) targetString.append(Translate(line)); targetString.append("\n"); } return targetString; } UDate L10n::ParseDateTime(const std::string& dateTimeString, const std::string& dateTimeFormat, const icu::Locale& locale) const { UErrorCode success = U_ZERO_ERROR; icu::UnicodeString utf16DateTimeString = icu::UnicodeString::fromUTF8(dateTimeString.c_str()); icu::UnicodeString utf16DateTimeFormat = icu::UnicodeString::fromUTF8(dateTimeFormat.c_str()); icu::DateFormat* dateFormatter = new icu::SimpleDateFormat(utf16DateTimeFormat, locale, success); UDate date = dateFormatter->parse(utf16DateTimeString, success); delete dateFormatter; return date; } std::string L10n::LocalizeDateTime(const UDate dateTime, const DateTimeType& type, const icu::DateFormat::EStyle& style) const { icu::UnicodeString utf16Date; icu::DateFormat* dateFormatter = CreateDateTimeInstance(type, style, currentLocale); dateFormatter->format(dateTime, utf16Date); char utf8Date[512]; icu::CheckedArrayByteSink sink(utf8Date, ARRAY_SIZE(utf8Date)); utf16Date.toUTF8(sink); ENSURE(!sink.Overflowed()); delete dateFormatter; return std::string(utf8Date, sink.NumberOfBytesWritten()); } std::string L10n::FormatMillisecondsIntoDateString(const UDate milliseconds, const std::string& formatString, bool useLocalTimezone) const { UErrorCode status = U_ZERO_ERROR; icu::UnicodeString dateString; std::string resultString; icu::UnicodeString unicodeFormat = icu::UnicodeString::fromUTF8(formatString.c_str()); icu::SimpleDateFormat* dateFormat = new icu::SimpleDateFormat(unicodeFormat, status); if (U_FAILURE(status)) LOGERROR("Error creating SimpleDateFormat: %s", u_errorName(status)); status = U_ZERO_ERROR; icu::Calendar* calendar = useLocalTimezone ? icu::Calendar::createInstance(currentLocale, status) : icu::Calendar::createInstance(*icu::TimeZone::getGMT(), currentLocale, status); if (U_FAILURE(status)) LOGERROR("Error creating calendar: %s", u_errorName(status)); dateFormat->adoptCalendar(calendar); dateFormat->format(milliseconds, dateString); delete dateFormat; dateString.toUTF8String(resultString); return resultString; } std::string L10n::FormatDecimalNumberIntoString(double number) const { UErrorCode success = U_ZERO_ERROR; icu::UnicodeString utf16Number; icu::NumberFormat* numberFormatter = icu::NumberFormat::createInstance(currentLocale, UNUM_DECIMAL, success); numberFormatter->format(number, utf16Number); char utf8Number[512]; icu::CheckedArrayByteSink sink(utf8Number, ARRAY_SIZE(utf8Number)); utf16Number.toUTF8(sink); ENSURE(!sink.Overflowed()); return std::string(utf8Number, sink.NumberOfBytesWritten()); } VfsPath L10n::LocalizePath(const VfsPath& sourcePath) const { VfsPath localizedPath = sourcePath.Parent() / L"l10n" / wstring_from_utf8(currentLocale.getLanguage()) / sourcePath.Filename(); if (!VfsFileExists(localizedPath)) return sourcePath; return localizedPath; } Status L10n::ReloadChangedFile(const VfsPath& path) { if (!boost::algorithm::starts_with(path.string(), L"l10n/")) return INFO::OK; if (path.Extension() != L".po") return INFO::OK; // If the file was deleted, ignore it if (!VfsFileExists(path)) return INFO::OK; std::wstring dictName = GetFallbackToAvailableDictLocale(currentLocale); if (useLongStrings) dictName = L"long"; if (dictName.empty()) return INFO::OK; // Only the currently used language is loaded, so ignore all others if (path.string().rfind(dictName) == std::string::npos) return INFO::OK; LOGMESSAGE("Hotloading translations from '%s'", path.string8()); CVFSFile file; if (file.Load(g_VFS, path) != PSRETURN_OK) { LOGERROR("Failed to read translations from '%s'", path.string8()); return ERR::FAIL; } std::string content = file.DecodeUTF8(); ReadPoIntoDictionary(content, m_Dictionary.get()); if (g_GUI) g_GUI->ReloadAllPages(); return INFO::OK; } void L10n::LoadDictionaryForCurrentLocale() { m_Dictionary.reset(new tinygettext::Dictionary()); VfsPaths filenames; if (useLongStrings) { if (vfs::GetPathnames(g_VFS, L"l10n/", L"long.*.po", filenames) < 0) return; } else { std::wstring dictName = GetFallbackToAvailableDictLocale(currentLocale); if (vfs::GetPathnames(g_VFS, L"l10n/", dictName.append(L".*.po").c_str(), filenames) < 0) { LOGERROR("No files for the dictionary found, but at this point the input should already be validated!"); return; } } for (const VfsPath& path : filenames) { CVFSFile file; file.Load(g_VFS, path); std::string content = file.DecodeUTF8(); ReadPoIntoDictionary(content, m_Dictionary.get()); } } void L10n::LoadListOfAvailableLocales() { availableLocales.clear(); // US is always available. availableLocales.emplace_back(new icu::Locale(icu::Locale::getUS())); VfsPaths filenames; if (vfs::GetPathnames(g_VFS, L"l10n/", L"*.po", filenames) < 0) return; for (const VfsPath& path : filenames) { // Note: PO files follow this naming convention: "l10n/..po". For example: "l10n/gl.public.po". std::string filename = utf8_from_wstring(path.string()).substr(strlen("l10n/")); size_t lengthToFirstDot = filename.find('.'); std::string localeCode = filename.substr(0, lengthToFirstDot); std::unique_ptr locale = std::make_unique(icu::Locale::createCanonical(localeCode.c_str())); auto it = std::find_if(availableLocales.begin(), availableLocales.end(), [&locale](const std::unique_ptr& l) { return *locale == *l; }); if (it != availableLocales.end()) continue; availableLocales.push_back(std::move(locale)); } } - -icu::DateFormat* L10n::CreateDateTimeInstance(const L10n::DateTimeType& type, const icu::DateFormat::EStyle& style, const icu::Locale& locale) const -{ - switch(type) - { - case Date: - return icu::SimpleDateFormat::createDateInstance(style, locale); - - case Time: - return icu::SimpleDateFormat::createTimeInstance(style, locale); - - case DateTime: - default: - return icu::SimpleDateFormat::createDateTimeInstance(style, style, locale); - } -} Index: ps/trunk/source/i18n/L10n.h =================================================================== --- ps/trunk/source/i18n/L10n.h (revision 25041) +++ ps/trunk/source/i18n/L10n.h (revision 25042) @@ -1,581 +1,569 @@ /* Copyright (C) 2021 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef INCLUDED_L10N #define INCLUDED_L10N #include "lib/code_annotation.h" #include "lib/external_libraries/icu.h" #include "lib/file/vfs/vfs_path.h" #include "ps/Singleton.h" #include #include #include namespace tinygettext { - class Dictionary; +class Dictionary; } #define g_L10n L10n::GetSingleton() /** * %Singleton for internationalization and localization. * * @sa http://trac.wildfiregames.com/wiki/Internationalization_and_Localization */ class L10n : public Singleton { public: /** * Creates an instance of L10n. * * L10n is a singleton. Use Instance() instead of creating you own instances * of L10n. */ L10n(); /** - * Handles the descruction of L10n. + * Handles the destruction of L10n. * * Never destroy the L10n singleton manually. It must run as long as the * game runs, and it is destroyed automatically when you quit the game. */ ~L10n(); /** * Types of dates. * * @sa LocalizeDateTime() */ - enum DateTimeType { + enum DateTimeType + { DateTime, ///< Both date and time. Date, ///< Only date. Time ///< Only time. }; /** * Returns the current locale. * * @sa GetCurrentLocaleString() * @sa GetSupportedLocaleBaseNames() * @sa GetAllLocales() * @sa ReevaluateCurrentLocaleAndReload() */ icu::Locale GetCurrentLocale() const; /** * Returns the code of the current locale. * * A locale code is a string such as "de" or "pt_BR". * * @sa GetCurrentLocale() * @sa GetSupportedLocaleBaseNames() * @sa GetAllLocales() * @sa ReevaluateCurrentLocaleAndReload() */ std::string GetCurrentLocaleString() const; /** * Returns a vector of locale codes supported by ICU. * * A locale code is a string such as "de" or "pt_BR". * * @return Vector of supported locale codes. * * @sa GetSupportedLocaleBaseNames() * @sa GetCurrentLocale() * * @sa http://www.icu-project.org/apiref/icu4c/classicu_1_1Locale.html#a073d70df8c9c8d119c0d42d70de24137 */ std::vector GetAllLocales() const; /** * Saves the specified locale in the game configuration file. * * The next time that the game starts, the game uses the locale in the * configuration file if there are translation files available for it. * * SaveLocale() checks the validity of the specified locale with * ValidateLocale(). If the specified locale is not valid, SaveLocale() * returns @c false and does not save the locale to the configuration file. * * @param localeCode Locale to save to the configuration file. * @return Whether the specified locale is valid (@c true) or not * (@c false). */ bool SaveLocale(const std::string& localeCode) const; ///@overload SaveLocale bool SaveLocale(const icu::Locale& locale) const; /** * Returns an array of supported locale codes sorted alphabetically. * * A locale code is a string such as "de" or "pt_BR". * * If yours is a development copy (the 'config/dev.cfg' file is found in the * virtual filesystem), the output array may include the special "long" * locale code. * * @return Array of supported locale codes. * * @sa GetSupportedLocaleDisplayNames() * @sa GetAllLocales() * @sa GetCurrentLocale() * * @sa http://trac.wildfiregames.com/wiki/Implementation_of_Internationalization_and_Localization#LongStringsLocale */ std::vector GetSupportedLocaleBaseNames() const; /** * Returns an array of supported locale names sorted alphabetically by * locale code. * * A locale code is a string such as "de" or "pt_BR". * * If yours is a development copy (the 'config/dev.cfg' file is found in the * virtual filesystem), the output array may include the special "Long * Strings" locale name. * * @return Array of supported locale codes. * * @sa GetSupportedLocaleBaseNames() * * @sa http://trac.wildfiregames.com/wiki/Implementation_of_Internationalization_and_Localization#LongStringsLocale */ std::vector GetSupportedLocaleDisplayNames() const; /** * Returns the ISO-639 language code of the specified locale code. * * For example, if you specify the 'en_US' locate, it returns 'en'. * * @param locale Locale code. * @return Language code. * * @sa http://www.icu-project.org/apiref/icu4c/classicu_1_1Locale.html#af36d821adced72a870d921ebadd0ca93 */ std::string GetLocaleLanguage(const std::string& locale) const; /** * Returns the programmatic code of the entire locale without keywords. * * @param locale Locale code. * @return Locale code without keywords. * * @sa http://www.icu-project.org/apiref/icu4c/classicu_1_1Locale.html#a4c1acbbdf95dc15599db5f322fa4c4d0 */ std::string GetLocaleBaseName(const std::string& locale) const; /** * Returns the ISO-3166 country code of the specified locale code. * * For example, if you specify the 'en_US' locate, it returns 'US'. * * @param locale Locale code. * @return Country code. * * @sa http://www.icu-project.org/apiref/icu4c/classicu_1_1Locale.html#ae3f1fc415c00d4f0ab33288ceadccbf9 */ std::string GetLocaleCountry(const std::string& locale) const; /** * Returns the ISO-15924 abbreviation script code of the specified locale code. * * @param locale Locale code. * @return Script code. * * @sa http://www.icu-project.org/apiref/icu4c/classicu_1_1Locale.html#a5e0145a339d30794178a1412dcc55abe */ std::string GetLocaleScript(const std::string& locale) const; /** * Returns @c true if the current locale is the special "Long Strings" * locale. It returns @c false otherwise. * * @return Whether the current locale is the special "Long Strings" * (@c true) or not (@c false). */ bool UseLongStrings() const; /** * Returns an array of paths to files in the virtual filesystem that provide * translations for the specified locale code. * * @param locale Locale code. * @return Array of paths to files in the virtual filesystem that provide * translations for @p locale. */ std::vector GetDictionariesForLocale(const std::string& locale) const; std::wstring GetFallbackToAvailableDictLocale(const icu::Locale& locale) const; std::wstring GetFallbackToAvailableDictLocale(const std::string& locale) const; /** * Returns the code of the recommended locale for the current user that the * game supports. * * "That the game supports" means both that a translation file is available * for that locale and that the locale code is either supported by ICU or * the special "long" locale code. * * The mechanism to select a recommended locale follows this logic: * 1. First see if the game supports the specified locale,\n * @p configLocale. * 2. Otherwise, check the system locale and see if the game supports\n * that locale. * 3. Else, return the default locale, 'en_US'. * * @param configLocaleString Locale to check for support first. Pass an * empty string to check the system locale directly. * @return Code of a locale that the game supports. * * @sa http://trac.wildfiregames.com/wiki/Implementation_of_Internationalization_and_Localization#LongStringsLocale */ std::string GetDictionaryLocale(const std::string& configLocaleString) const; /** * Saves an instance of the recommended locale for the current user that the * game supports in the specified variable. * * "That the game supports" means both that a translation file is available * for that locale and that the locale code is either supported by ICU or * the special "long" locale code. * * The mechanism to select a recommended locale follows this logic: * 1. First see if the game supports the specified locale,\n * @p configLocale. * 2. Otherwise, check the system locale and see if the game supports\n * that locale. * 3. Else, return the default locale, 'en_US'. * * @param configLocaleString Locale to check for support first. Pass an * empty string to check the system locale directly. * @param outLocale The recommended locale. * * @sa http://trac.wildfiregames.com/wiki/Implementation_of_Internationalization_and_Localization#LongStringsLocale */ void GetDictionaryLocale(const std::string& configLocaleString, icu::Locale& outLocale) const; /** * Determines the best, supported locale for the current user, makes it the * current game locale and reloads the translations dictionary with * translations for that locale. * * To determine the best locale, ReevaluateCurrentLocaleAndReload(): * 1. Checks the user game configuration. * 2. If the locale is not defined there, it checks the system locale. * 3. If none of those locales are supported by the game, the default\n * locale, 'en_US', is used. * * @sa GetCurrentLocale() */ void ReevaluateCurrentLocaleAndReload(); /** * Returns @c true if the locale is supported by both ICU and the game. It * returns @c false otherwise. * * It returns @c true if both of these conditions are true: * 1. ICU has resources for that locale (which also ensures it's a valid\n * locale string). * 2. Either a dictionary for language_country or for language is\n * available. * * @param locale Locale to check. * @return Whether @p locale is supported by both ICU and the game (@c true) * or not (@c false). */ bool ValidateLocale(const icu::Locale& locale) const; ///@overload ValidateLocale bool ValidateLocale(const std::string& localeCode) const; /** * Returns the translation of the specified string to the * @link L10n::GetCurrentLocale() current locale@endlink. * * @param sourceString String to translate to the current locale. * @return Translation of @p sourceString to the current locale, or * @p sourceString if there is no translation available. */ std::string Translate(const std::string& sourceString) const; /** * Returns the translation of the specified string to the * @link L10n::GetCurrentLocale() current locale@endlink in the specified * context. * * @param context Context where the string is used. See * http://www.gnu.org/software/gettext/manual/html_node/Contexts.html * @param sourceString String to translate to the current locale. * @return Translation of @p sourceString to the current locale in the * specified @p context, or @p sourceString if there is no * translation available. */ std::string TranslateWithContext(const std::string& context, const std::string& sourceString) const; /** * Returns the translation of the specified string to the * @link L10n::GetCurrentLocale() current locale@endlink based on the * specified number. * * @param singularSourceString String to translate to the current locale, * in English' singular form. * @param pluralSourceString String to translate to the current locale, in * English' plural form. * @param number Number that determines the required form of the translation * (or the English string if no translation is available). * @return Translation of the source string to the current locale for the * specified @p number, or either @p singularSourceString (if * @p number is 1) or @p pluralSourceString (if @p number is not 1) * if there is no translation available. */ std::string TranslatePlural(const std::string& singularSourceString, const std::string& pluralSourceString, int number) const; /** * Returns the translation of the specified string to the * @link L10n::GetCurrentLocale() current locale@endlink in the specified * context, based on the specified number. * * @param context Context where the string is used. See * http://www.gnu.org/software/gettext/manual/html_node/Contexts.html * @param singularSourceString String to translate to the current locale, * in English' singular form. * @param pluralSourceString String to translate to the current locale, in * English' plural form. * @param number Number that determines the required form of the translation * (or the English string if no translation is available). * * @return Translation of the source string to the current locale in the * specified @p context and for the specified @p number, or either * @p singularSourceString (if @p number is 1) or * @p pluralSourceString (if @p number is not 1) if there is no * translation available. */ std::string TranslatePluralWithContext(const std::string& context, const std::string& singularSourceString, const std::string& pluralSourceString, int number) const; /** * Translates a text line by line to the * @link L10n::GetCurrentLocale() current locale@endlink. * * TranslateLines() is helpful when you need to translate a plain text file * after you load it. * * @param sourceString Text to translate to the current locale. * @return Line by line translation of @p sourceString to the current * locale. Some of the lines in the returned text may be in English * because there was not translation available for them. */ std::string TranslateLines(const std::string& sourceString) const; /** * Parses the date in the input string using the specified date format, and * returns the parsed date as a UNIX timestamp in milliseconds (not * seconds). * * @param dateTimeString String containing the date to parse. * @param dateTimeFormat Date format string to parse the input date, defined * using ICU date formatting symbols. * @param locale Locale to use when parsing the input date. * @return Specified date as a UNIX timestamp in milliseconds (not seconds). * * @sa GetCurrentLocale() * * @sa http://en.wikipedia.org/wiki/Unix_time * @sa https://sites.google.com/site/icuprojectuserguide/formatparse/datetime?pli=1#TOC-Date-Field-Symbol-Table */ UDate ParseDateTime(const std::string& dateTimeString, const std::string& dateTimeFormat, const icu::Locale& locale) const; /** * Returns the specified date using the specified date format. * * @param dateTime Date specified as a UNIX timestamp in milliseconds * (not seconds). * @param type Whether the formatted date must show both the date and the * time, only the date or only the time. * @param style ICU style for the formatted date. * @return String containing the specified date with the specified date * format. * * @sa http://en.wikipedia.org/wiki/Unix_time * @sa http://icu-project.org/apiref/icu4c521/classicu_1_1DateFormat.html */ std::string LocalizeDateTime(const UDate dateTime, const DateTimeType& type, const icu::DateFormat::EStyle& style) const; /** * Returns the specified date using the specified date format. * * @param milliseconds Date specified as a UNIX timestamp in milliseconds * (not seconds). * @param formatString Date format string defined using ICU date formatting * symbols. Usually, you internationalize the format string and * get it translated before you pass it to * FormatMillisecondsIntoDateString(). * @param useLocalTimezone Boolean useful for durations * @return String containing the specified date with the specified date * format. * * @sa http://en.wikipedia.org/wiki/Unix_time * @sa https://sites.google.com/site/icuprojectuserguide/formatparse/datetime?pli=1#TOC-Date-Field-Symbol-Table */ std::string FormatMillisecondsIntoDateString(const UDate milliseconds, const std::string& formatString, bool useLocalTimezone) const; /** * Returns the specified floating-point number as a string, with the number * formatted as a decimal number using the * @link L10n::GetCurrentLocale() current locale@endlink. * * @param number Number to format. * @return Decimal number formatted using the current locale. */ std::string FormatDecimalNumberIntoString(double number) const; /** * Returns the localized version of the specified path if there is one for * the @link L10n::GetCurrentLocale() current locale@endlink. * * If there is no localized version of the specified path, it returns the * specified path. * * For example, if the code of the current locale is 'de_DE', LocalizePath() * splits the input path into folder path and file name, and checks whether * the '/l10n/de/' file exists. If it does, it returns that * path. Otherwise, it returns the input path, verbatim. * * This function is used for file localization (for example, image * localization). * * @param sourcePath %Path to localize. * @return Localized path if it exists, @c sourcePath otherwise. * * @sa http://trac.wildfiregames.com/wiki/Localization#LocalizingImages */ VfsPath LocalizePath(const VfsPath& sourcePath) const; /** * Loads @p path into the dictionary if it is a translation file of the * @link L10n::GetCurrentLocale() current locale@endlink. */ Status ReloadChangedFile(const VfsPath& path); private: struct DictionaryDeleter { void operator()(tinygettext::Dictionary* dictionary); }; /** * Dictionary that contains the translations for the * @link L10n::GetCurrentLocale() current locale@endlink and the matching * English strings, including contexts and plural forms. * * @sa LoadDictionaryForCurrentLocale() */ std::unique_ptr m_Dictionary; /** * Locale that the game is currently using. * * To get the current locale, use its getter: GetCurrentLocale(). You can * also use GetCurrentLocaleString() to get the locale code of the current * locale. * * To change the value of this variable: * 1. Save a new locale to the game configuration file with SaveLocale(). * 2. Reload the translation dictionary with\n * ReevaluateCurrentLocaleAndReload(). */ icu::Locale currentLocale; /** * Vector with the locales that the game supports. * * The list of available locales is calculated when the game starts. Call * LoadListOfAvailableLocales() to refresh the list. * * @sa GetSupportedLocaleBaseNames() * @sa GetSupportedLocaleDisplayNames() */ std::vector> availableLocales; /** * Whether the game is using the default game locale (@c true), 'en_US', or * not (@c false). * * This variable is used in the L10n implementation for performance reasons. * Many localization steps can be skipped when this variable is @c true. */ bool currentLocaleIsOriginalGameLocale; /** * Whether the game is using the special game locale with the longest * strings of each translation (@c true) or not (@c false). * * @sa http://trac.wildfiregames.com/wiki/Implementation_of_Internationalization_and_Localization#LongStringsLocale */ bool useLongStrings; /** * Loads the translation files for the * @link L10n::GetCurrentLocale() current locale@endlink. * * This method loads every file in the 'l10n' folder of the game virtual * filesystem that is prefixed with the code of the current locale followed * by a dot. * * For example, if the code of the current locale code is 'de', * LoadDictionaryForCurrentLocale() loads the 'l10n/de.engine.po' and * 'l10n/de.public.po' translation files. * * @sa dictionary * @sa ReadPoIntoDictionary() */ void LoadDictionaryForCurrentLocale(); /** * Determines the list of locales that the game supports. * * LoadListOfAvailableLocales() checks the locale codes of the translation * files in the 'l10n' folder of the virtual filesystem. If it finds a * translation file prefixed with a locale code followed by a dot, it * determines that the game supports that locale. * * @sa availableLocales */ void LoadListOfAvailableLocales(); - - /** - * Creates an ICU date formatted with the specified settings. - * - * @param type Whether formatted dates must show both the date and the time, - * only the date or only the time. - * @param style ICU style to format dates by default. - * @param locale Locale that the date formatter should use to parse strings. - * It has no relevance for date formatting, only matters for date - * parsing. - * @return ICU date formatter. - */ - icu::DateFormat* CreateDateTimeInstance(const DateTimeType& type, const icu::DateFormat::EStyle& style, const icu::Locale& locale) const; }; #endif // INCLUDED_L10N