Changeset View
Changeset View
Standalone View
Standalone View
source/i18n/L10n.cpp
/* Copyright (C) 2019 Wildfire Games. | /* Copyright (C) 2020 Wildfire Games. | ||||
* | * | ||||
* Permission is hereby granted, free of charge, to any person obtaining | * Permission is hereby granted, free of charge, to any person obtaining | ||||
* a copy of this software and associated documentation files (the | * a copy of this software and associated documentation files (the | ||||
* "Software"), to deal in the Software without restriction, including | * "Software"), to deal in the Software without restriction, including | ||||
* without limitation the rights to use, copy, modify, merge, publish, | * without limitation the rights to use, copy, modify, merge, publish, | ||||
* distribute, sublicense, and/or sell copies of the Software, and to | * distribute, sublicense, and/or sell copies of the Software, and to | ||||
* permit persons to whom the Software is furnished to do so, subject to | * permit persons to whom the Software is furnished to do so, subject to | ||||
* the following conditions: | * the following conditions: | ||||
Show All 28 Lines | |||||
#include "ps/GameSetup/GameSetup.h" | #include "ps/GameSetup/GameSetup.h" | ||||
static Status ReloadChangedFileCB(void* param, const VfsPath& path) | static Status ReloadChangedFileCB(void* param, const VfsPath& path) | ||||
{ | { | ||||
return static_cast<L10n*>(param)->ReloadChangedFile(path); | return static_cast<L10n*>(param)->ReloadChangedFile(path); | ||||
} | } | ||||
L10n::L10n() | L10n::L10n() | ||||
: dictionary(new tinygettext::Dictionary()), currentLocaleIsOriginalGameLocale(false), useLongStrings(false) | : m_Dictionary(new tinygettext::Dictionary()), currentLocaleIsOriginalGameLocale(false), useLongStrings(false) | ||||
{ | { | ||||
// Determine whether or not to print tinygettext messages to the standard | // Determine whether or not to print tinygettext messages to the standard | ||||
// error output, which it tinygettext’s default behavior, but not ours. | // error output, which it tinygettext’s default behavior, but not ours. | ||||
bool tinygettext_debug = false; | bool tinygettext_debug = false; | ||||
CFG_GET_VAL("tinygettext.debug", tinygettext_debug); | CFG_GET_VAL("tinygettext.debug", tinygettext_debug); | ||||
if (!tinygettext_debug) | if (!tinygettext_debug) | ||||
{ | { | ||||
tinygettext::Log::log_info_callback = 0; | tinygettext::Log::log_info_callback = 0; | ||||
tinygettext::Log::log_warning_callback = 0; | tinygettext::Log::log_warning_callback = 0; | ||||
tinygettext::Log::log_error_callback = 0; | tinygettext::Log::log_error_callback = 0; | ||||
} | } | ||||
LoadListOfAvailableLocales(); | LoadListOfAvailableLocales(); | ||||
ReevaluateCurrentLocaleAndReload(); | ReevaluateCurrentLocaleAndReload(); | ||||
// Handle hotloading | // Handle hotloading | ||||
RegisterFileReloadFunc(ReloadChangedFileCB, this); | RegisterFileReloadFunc(ReloadChangedFileCB, this); | ||||
} | } | ||||
L10n::~L10n() | L10n::~L10n() | ||||
{ | { | ||||
UnregisterFileReloadFunc(ReloadChangedFileCB, this); | UnregisterFileReloadFunc(ReloadChangedFileCB, this); | ||||
for (icu::Locale* const& locale : availableLocales) | |||||
delete locale; | |||||
delete dictionary; | |||||
} | } | ||||
icu::Locale L10n::GetCurrentLocale() const | icu::Locale L10n::GetCurrentLocale() const | ||||
{ | { | ||||
return currentLocale; | return currentLocale; | ||||
} | } | ||||
bool L10n::SaveLocale(const std::string& localeCode) const | bool L10n::SaveLocale(const std::string& localeCode) const | ||||
▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
return GetFallbackToAvailableDictLocale(icu::Locale::createCanonical(locale.c_str())); | return GetFallbackToAvailableDictLocale(icu::Locale::createCanonical(locale.c_str())); | ||||
} | } | ||||
std::wstring L10n::GetFallbackToAvailableDictLocale(const icu::Locale& locale) const | std::wstring L10n::GetFallbackToAvailableDictLocale(const icu::Locale& locale) const | ||||
{ | { | ||||
std::wstringstream stream; | std::wstringstream stream; | ||||
std::function<bool(const icu::Locale* const&)> checkLangAndCountry = [&locale](const icu::Locale* const& l) { | std::function<bool(const std::unique_ptr<icu::Locale>&)> checkLangAndCountry = [&locale](const std::unique_ptr<icu::Locale>& l) { | ||||
wraitii: This is one of these situations where I think `auto` improves readability. | |||||
return strcmp(locale.getLanguage(), l->getLanguage()) == 0 | return strcmp(locale.getLanguage(), l->getLanguage()) == 0 | ||||
&& strcmp(locale.getCountry(), l->getCountry()) == 0; | && strcmp(locale.getCountry(), l->getCountry()) == 0; | ||||
}; | }; | ||||
if (strcmp(locale.getCountry(), "") != 0 | if (strcmp(locale.getCountry(), "") != 0 | ||||
&& std::find_if(availableLocales.begin(), availableLocales.end(), checkLangAndCountry) != availableLocales.end()) | && std::find_if(availableLocales.begin(), availableLocales.end(), checkLangAndCountry) != availableLocales.end()) | ||||
{ | { | ||||
stream << locale.getLanguage() << L"_" << locale.getCountry(); | stream << locale.getLanguage() << L"_" << locale.getCountry(); | ||||
return stream.str(); | return stream.str(); | ||||
} | } | ||||
std::function<bool(const icu::Locale* const&)> checkLang = [&locale](const icu::Locale* const& l) { | std::function<bool(const std::unique_ptr<icu::Locale>&)> checkLang = [&locale](const std::unique_ptr<icu::Locale>& l) { | ||||
Done Inline ActionsLikewise. wraitii: Likewise. | |||||
return strcmp(locale.getLanguage(), l->getLanguage()) == 0; | return strcmp(locale.getLanguage(), l->getLanguage()) == 0; | ||||
}; | }; | ||||
if (std::find_if(availableLocales.begin(), availableLocales.end(), checkLang) != availableLocales.end()) | if (std::find_if(availableLocales.begin(), availableLocales.end(), checkLang) != availableLocales.end()) | ||||
{ | { | ||||
stream << locale.getLanguage(); | stream << locale.getLanguage(); | ||||
return stream.str(); | return stream.str(); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 67 Lines • ▼ Show 20 Lines | |||||
bool L10n::UseLongStrings() const | bool L10n::UseLongStrings() const | ||||
{ | { | ||||
return useLongStrings; | return useLongStrings; | ||||
}; | }; | ||||
std::vector<std::string> L10n::GetSupportedLocaleBaseNames() const | std::vector<std::string> L10n::GetSupportedLocaleBaseNames() const | ||||
{ | { | ||||
std::vector<std::string> supportedLocaleCodes; | std::vector<std::string> supportedLocaleCodes; | ||||
for (icu::Locale* const& locale : availableLocales) | for (const std::unique_ptr<icu::Locale>& locale : availableLocales) | ||||
{ | { | ||||
if (!InDevelopmentCopy() && strcmp(locale->getBaseName(), "long") == 0) | if (!InDevelopmentCopy() && strcmp(locale->getBaseName(), "long") == 0) | ||||
continue; | continue; | ||||
supportedLocaleCodes.push_back(locale->getBaseName()); | supportedLocaleCodes.push_back(locale->getBaseName()); | ||||
} | } | ||||
return supportedLocaleCodes; | return supportedLocaleCodes; | ||||
} | } | ||||
std::vector<std::wstring> L10n::GetSupportedLocaleDisplayNames() const | std::vector<std::wstring> L10n::GetSupportedLocaleDisplayNames() const | ||||
{ | { | ||||
std::vector<std::wstring> supportedLocaleDisplayNames; | std::vector<std::wstring> supportedLocaleDisplayNames; | ||||
for (icu::Locale* const& locale : availableLocales) | for (const std::unique_ptr<icu::Locale>& locale : availableLocales) | ||||
{ | { | ||||
if (strcmp(locale->getBaseName(), "long") == 0) | if (strcmp(locale->getBaseName(), "long") == 0) | ||||
{ | { | ||||
if (InDevelopmentCopy()) | if (InDevelopmentCopy()) | ||||
supportedLocaleDisplayNames.push_back(wstring_from_utf8(Translate("Long strings"))); | supportedLocaleDisplayNames.push_back(wstring_from_utf8(Translate("Long strings"))); | ||||
continue; | continue; | ||||
} | } | ||||
Show All 36 Lines | |||||
{ | { | ||||
icu::Locale loc = icu::Locale::createCanonical(locale.c_str()); | icu::Locale loc = icu::Locale::createCanonical(locale.c_str()); | ||||
return loc.getScript(); | return loc.getScript(); | ||||
} | } | ||||
std::string L10n::Translate(const std::string& sourceString) const | std::string L10n::Translate(const std::string& sourceString) const | ||||
{ | { | ||||
if (!currentLocaleIsOriginalGameLocale) | if (!currentLocaleIsOriginalGameLocale) | ||||
return dictionary->translate(sourceString); | return m_Dictionary->translate(sourceString); | ||||
return sourceString; | return sourceString; | ||||
} | } | ||||
std::string L10n::TranslateWithContext(const std::string& context, const std::string& sourceString) const | std::string L10n::TranslateWithContext(const std::string& context, const std::string& sourceString) const | ||||
{ | { | ||||
if (!currentLocaleIsOriginalGameLocale) | if (!currentLocaleIsOriginalGameLocale) | ||||
return dictionary->translate_ctxt(context, sourceString); | return m_Dictionary->translate_ctxt(context, sourceString); | ||||
return sourceString; | return sourceString; | ||||
} | } | ||||
std::string L10n::TranslatePlural(const std::string& singularSourceString, const std::string& pluralSourceString, int number) const | std::string L10n::TranslatePlural(const std::string& singularSourceString, const std::string& pluralSourceString, int number) const | ||||
{ | { | ||||
if (!currentLocaleIsOriginalGameLocale) | if (!currentLocaleIsOriginalGameLocale) | ||||
return dictionary->translate_plural(singularSourceString, pluralSourceString, number); | return m_Dictionary->translate_plural(singularSourceString, pluralSourceString, number); | ||||
if (number == 1) | if (number == 1) | ||||
return singularSourceString; | return singularSourceString; | ||||
return pluralSourceString; | return pluralSourceString; | ||||
} | } | ||||
std::string L10n::TranslatePluralWithContext(const std::string& context, const std::string& singularSourceString, const std::string& pluralSourceString, int number) const | std::string L10n::TranslatePluralWithContext(const std::string& context, const std::string& singularSourceString, const std::string& pluralSourceString, int number) const | ||||
{ | { | ||||
if (!currentLocaleIsOriginalGameLocale) | if (!currentLocaleIsOriginalGameLocale) | ||||
return dictionary->translate_ctxt_plural(context, singularSourceString, pluralSourceString, number); | return m_Dictionary->translate_ctxt_plural(context, singularSourceString, pluralSourceString, number); | ||||
if (number == 1) | if (number == 1) | ||||
return singularSourceString; | return singularSourceString; | ||||
return pluralSourceString; | return pluralSourceString; | ||||
} | } | ||||
std::string L10n::TranslateLines(const std::string& sourceString) const | std::string L10n::TranslateLines(const std::string& sourceString) const | ||||
▲ Show 20 Lines • Show All 117 Lines • ▼ Show 20 Lines | Status L10n::ReloadChangedFile(const VfsPath& path) | ||||
CVFSFile file; | CVFSFile file; | ||||
if (file.Load(g_VFS, path) != PSRETURN_OK) | if (file.Load(g_VFS, path) != PSRETURN_OK) | ||||
{ | { | ||||
LOGERROR("Failed to read translations from '%s'", path.string8()); | LOGERROR("Failed to read translations from '%s'", path.string8()); | ||||
return ERR::FAIL; | return ERR::FAIL; | ||||
} | } | ||||
std::string content = file.DecodeUTF8(); | std::string content = file.DecodeUTF8(); | ||||
ReadPoIntoDictionary(content, dictionary); | ReadPoIntoDictionary(content, m_Dictionary.get()); | ||||
if (g_GUI) | if (g_GUI) | ||||
g_GUI->ReloadAllPages(); | g_GUI->ReloadAllPages(); | ||||
return INFO::OK; | return INFO::OK; | ||||
} | } | ||||
void L10n::LoadDictionaryForCurrentLocale() | void L10n::LoadDictionaryForCurrentLocale() | ||||
{ | { | ||||
delete dictionary; | m_Dictionary.reset(); | ||||
dictionary = new tinygettext::Dictionary(); | m_Dictionary = std::move(std::unique_ptr<tinygettext::Dictionary>(new tinygettext::Dictionary())); | ||||
Done Inline ActionsI believe you can just wraitii: I believe you can just
`m_Dictionary.reset(new tinygettext::Dictionary());` | |||||
VfsPaths filenames; | VfsPaths filenames; | ||||
if (useLongStrings) | if (useLongStrings) | ||||
{ | { | ||||
if (vfs::GetPathnames(g_VFS, L"l10n/", L"long.*.po", filenames) < 0) | if (vfs::GetPathnames(g_VFS, L"l10n/", L"long.*.po", filenames) < 0) | ||||
return; | return; | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
std::wstring dictName = GetFallbackToAvailableDictLocale(currentLocale); | std::wstring dictName = GetFallbackToAvailableDictLocale(currentLocale); | ||||
if (vfs::GetPathnames(g_VFS, L"l10n/", dictName.append(L".*.po").c_str(), filenames) < 0) | 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!"); | LOGERROR("No files for the dictionary found, but at this point the input should already be validated!"); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
for (const VfsPath& path : filenames) | for (const VfsPath& path : filenames) | ||||
{ | { | ||||
CVFSFile file; | CVFSFile file; | ||||
file.Load(g_VFS, path); | file.Load(g_VFS, path); | ||||
std::string content = file.DecodeUTF8(); | std::string content = file.DecodeUTF8(); | ||||
ReadPoIntoDictionary(content, dictionary); | ReadPoIntoDictionary(content, m_Dictionary.get()); | ||||
} | } | ||||
} | } | ||||
void L10n::LoadListOfAvailableLocales() | void L10n::LoadListOfAvailableLocales() | ||||
{ | { | ||||
for (icu::Locale* const& locale : availableLocales) | for (std::unique_ptr<icu::Locale>& locale : availableLocales) | ||||
delete locale; | locale.reset(); | ||||
Done Inline Actionsyou can remove these two lines, the clear below will call the destructor of the vector members, which calls the destructors of unique_ptr, which deletes the locale pointers. wraitii: you can remove these two lines, the `clear` below will call the destructor of the vector… | |||||
availableLocales.clear(); | availableLocales.clear(); | ||||
icu::Locale* defaultLocale = new icu::Locale(icu::Locale::getUS()); | std::unique_ptr<icu::Locale> defaultLocale = std::unique_ptr<icu::Locale>(new icu::Locale(icu::Locale::getUS())); | ||||
availableLocales.push_back(defaultLocale); // Always available. | availableLocales.push_back(std::move(defaultLocale)); // Always available. | ||||
Done Inline ActionsThink you can wraitii: Think you can
`availableLocales.emplace_back(new icu::Locale(icu::Locale::getUS()))` | |||||
VfsPaths filenames; | VfsPaths filenames; | ||||
if (vfs::GetPathnames(g_VFS, L"l10n/", L"*.po", filenames) < 0) | if (vfs::GetPathnames(g_VFS, L"l10n/", L"*.po", filenames) < 0) | ||||
return; | return; | ||||
for (const VfsPath& path : filenames) | for (const VfsPath& path : filenames) | ||||
{ | { | ||||
// Note: PO files follow this naming convention: “l10n/<locale code>.<mod name>.po”. For example: “l10n/gl.public.po”. | // Note: PO files follow this naming convention: “l10n/<locale code>.<mod name>.po”. For example: “l10n/gl.public.po”. | ||||
std::string filename = utf8_from_wstring(path.string()).substr(strlen("l10n/")); | std::string filename = utf8_from_wstring(path.string()).substr(strlen("l10n/")); | ||||
size_t lengthToFirstDot = filename.find('.'); | size_t lengthToFirstDot = filename.find('.'); | ||||
std::string localeCode = filename.substr(0, lengthToFirstDot); | std::string localeCode = filename.substr(0, lengthToFirstDot); | ||||
icu::Locale* locale = new icu::Locale(icu::Locale::createCanonical(localeCode.c_str())); | std::unique_ptr<icu::Locale> locale = std::unique_ptr<icu::Locale>(new icu::Locale(icu::Locale::createCanonical(localeCode.c_str()))); | ||||
Done Inline Actionsstd::unique_ptr<icu::Locale> locale(new icu::Locale(icu::Locale::createCanonical(localeCode.c_str()))) wraitii: `std::unique_ptr<icu::Locale> locale(new icu::Locale(icu::Locale::createCanonical(localeCode. | |||||
std::vector<icu::Locale*>::iterator it = std::find_if(availableLocales.begin(), availableLocales.end(), [&locale](icu::Locale* const& l) { | std::vector<std::unique_ptr<icu::Locale>>::iterator it = std::find_if(availableLocales.begin(), availableLocales.end(), [&locale](const std::unique_ptr<icu::Locale>& l) { | ||||
return *locale == *l; | return locale.get() == l.get(); | ||||
Done Inline ActionsI'd auto also it here, to shorten the line. wraitii: I'd auto also it here, to shorten the line. | |||||
Done Inline ActionsNo need to .get, unique_ptr::operator= does it for you wraitii: No need to .get, `unique_ptr::operator=` does it for you | |||||
}); | }); | ||||
if (it != availableLocales.end()) | if (it != availableLocales.end()) | ||||
{ | { | ||||
delete locale; | locale.reset(); | ||||
Done Inline Actionsno need to reset, unique_ptr's destructor does it for you. wraitii: no need to reset, unique_ptr's destructor does it for you. | |||||
continue; | continue; | ||||
} | } | ||||
availableLocales.push_back(locale); | availableLocales.push_back(std::move(locale)); | ||||
} | } | ||||
} | } | ||||
void L10n::ReadPoIntoDictionary(const std::string& poContent, tinygettext::Dictionary* dictionary) const | void L10n::ReadPoIntoDictionary(const std::string& poContent, tinygettext::Dictionary* dictionary) const | ||||
{ | { | ||||
try | try | ||||
{ | { | ||||
std::istringstream inputStream(poContent); | std::istringstream inputStream(poContent); | ||||
Show All 23 Lines |
Wildfire Games · Phabricator
This is one of these situations where I think auto improves readability.