2012-04-21 18:17:42 +00:00
|
|
|
// locale.cxx -- FlightGear Localization Support
|
|
|
|
//
|
|
|
|
// Written by Thorsten Brehm, started April 2012.
|
|
|
|
//
|
|
|
|
// Copyright (C) 2012 Thorsten Brehm - brehmt (at) gmail com
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
2012-05-04 23:42:41 +00:00
|
|
|
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
2012-04-21 18:17:42 +00:00
|
|
|
|
2018-08-11 14:13:46 +00:00
|
|
|
#include <config.h>
|
2013-02-08 07:46:34 +00:00
|
|
|
|
2012-05-06 21:15:27 +00:00
|
|
|
#ifdef HAVE_WINDOWS_H
|
|
|
|
#include <windows.h>
|
|
|
|
#endif
|
|
|
|
|
2013-11-14 23:32:02 +00:00
|
|
|
#include <cstdio>
|
2017-04-26 20:00:34 +00:00
|
|
|
#include <cstring> // std::strlen()
|
2017-04-26 15:55:44 +00:00
|
|
|
#include <cstddef> // std::size_t
|
|
|
|
#include <cassert>
|
|
|
|
|
2020-07-14 10:38:09 +00:00
|
|
|
#include <simgear/misc/strutils.hxx>
|
2018-08-11 14:13:46 +00:00
|
|
|
#include <simgear/props/props.hxx>
|
2012-04-21 18:17:42 +00:00
|
|
|
#include <simgear/props/props_io.hxx>
|
|
|
|
#include <simgear/structure/exception.hxx>
|
|
|
|
|
|
|
|
#include "fg_props.hxx"
|
|
|
|
#include "locale.hxx"
|
2018-08-11 14:13:46 +00:00
|
|
|
#include "XLIFFParser.hxx"
|
2012-04-21 18:17:42 +00:00
|
|
|
|
2020-07-09 17:14:56 +00:00
|
|
|
#include <Add-ons/AddonMetadataParser.hxx>
|
|
|
|
|
2012-04-21 18:17:42 +00:00
|
|
|
using std::vector;
|
|
|
|
using std::string;
|
2020-07-14 10:38:09 +00:00
|
|
|
namespace strutils = simgear::strutils;
|
2012-04-21 18:17:42 +00:00
|
|
|
|
|
|
|
FGLocale::FGLocale(SGPropertyNode* root) :
|
2018-09-28 11:09:24 +00:00
|
|
|
_intl(root->getNode("/sim/intl", 0, true)),
|
|
|
|
_defaultLocale(_intl->getChild("locale", 0, true))
|
2012-04-21 18:17:42 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
FGLocale::~FGLocale()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-04-26 15:55:44 +00:00
|
|
|
// Static method
|
|
|
|
string FGLocale::removeEncodingPart(const string& locale)
|
|
|
|
{
|
|
|
|
string res;
|
|
|
|
std::size_t pos = locale.find('.');
|
|
|
|
|
|
|
|
if (pos != string::npos)
|
|
|
|
{
|
|
|
|
assert(pos > 0);
|
|
|
|
res = locale.substr(0, pos);
|
|
|
|
} else {
|
|
|
|
res = locale;
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2020-07-09 17:14:56 +00:00
|
|
|
string removeLocalePart(const string& locale)
|
|
|
|
{
|
|
|
|
std::size_t pos = locale.find('_');
|
|
|
|
if (pos == string::npos) {
|
|
|
|
pos = locale.find('-');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pos == string::npos)
|
|
|
|
return {};
|
|
|
|
|
|
|
|
return locale.substr(0, pos);
|
|
|
|
}
|
|
|
|
|
2012-05-08 21:04:53 +00:00
|
|
|
#ifdef _WIN32
|
2017-05-22 16:50:50 +00:00
|
|
|
|
|
|
|
|
2013-03-27 22:48:16 +00:00
|
|
|
string_list
|
2020-07-09 17:14:56 +00:00
|
|
|
FGLocale::getUserLanguages()
|
2012-05-06 21:15:27 +00:00
|
|
|
{
|
2018-09-28 11:09:24 +00:00
|
|
|
unsigned long bufSize = 128;
|
|
|
|
wchar_t* localeNameBuf = reinterpret_cast<wchar_t*>(alloca(bufSize));
|
|
|
|
unsigned long numLanguages = 0;
|
|
|
|
|
|
|
|
bool ok = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
|
|
|
|
localeNameBuf, &bufSize);
|
|
|
|
if (!ok) {
|
|
|
|
// if we have a lot of languages, can fail, allocate a bigger
|
|
|
|
// buffer
|
|
|
|
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
|
|
|
bufSize = 0;
|
|
|
|
GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
|
|
|
|
nullptr, &bufSize);
|
|
|
|
localeNameBuf = reinterpret_cast<wchar_t*>(alloca(bufSize));
|
|
|
|
ok = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
|
|
|
|
localeNameBuf, &bufSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ok) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "Failed to detected user locale via GetUserPreferredUILanguages");
|
|
|
|
return{};
|
|
|
|
}
|
|
|
|
|
|
|
|
string_list result;
|
|
|
|
result.reserve(numLanguages);
|
|
|
|
for (unsigned int l = 0; l < numLanguages; ++l) {
|
|
|
|
std::wstring ws(localeNameBuf);
|
|
|
|
if (ws.empty())
|
|
|
|
break;
|
|
|
|
|
|
|
|
// skip to next string, past this string and trailing NULL
|
|
|
|
localeNameBuf += (ws.size() + 1);
|
|
|
|
result.push_back(simgear::strutils::convertWStringToUtf8(ws));
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "User langauge " << l << ":" << result.back());
|
|
|
|
}
|
2012-05-06 21:15:27 +00:00
|
|
|
|
2013-03-27 22:48:16 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
#elif __APPLE__
|
2013-11-19 22:01:11 +00:00
|
|
|
// implemented in CocoaHelpers.mm
|
2012-05-06 21:15:27 +00:00
|
|
|
#else
|
|
|
|
/**
|
2013-03-27 22:48:16 +00:00
|
|
|
* Determine locale/language settings on Linux/Unix.
|
2012-05-06 21:15:27 +00:00
|
|
|
*/
|
2013-03-27 22:48:16 +00:00
|
|
|
string_list
|
2020-07-09 17:14:56 +00:00
|
|
|
FGLocale::getUserLanguages()
|
2012-05-06 21:15:27 +00:00
|
|
|
{
|
2013-03-27 22:48:16 +00:00
|
|
|
string_list result;
|
|
|
|
const char* langEnv = ::getenv("LANG");
|
2017-04-26 15:55:44 +00:00
|
|
|
|
2013-03-27 22:48:16 +00:00
|
|
|
if (langEnv) {
|
2017-04-26 15:55:44 +00:00
|
|
|
// Remove character encoding from the locale spec, i.e. "de_DE.UTF-8"
|
|
|
|
// becomes "de_DE". This is for consistency with the Windows and MacOS
|
|
|
|
// implementations of this method.
|
|
|
|
result.push_back(removeEncodingPart(langEnv));
|
2013-03-27 22:48:16 +00:00
|
|
|
}
|
2017-04-26 15:55:44 +00:00
|
|
|
|
2013-03-27 22:48:16 +00:00
|
|
|
return result;
|
2012-05-06 21:15:27 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-04-21 18:17:42 +00:00
|
|
|
// Search property tree for matching locale description
|
|
|
|
SGPropertyNode*
|
2017-04-26 15:55:44 +00:00
|
|
|
FGLocale::findLocaleNode(const string& localeSpec)
|
2012-04-21 18:17:42 +00:00
|
|
|
{
|
2017-04-26 20:00:34 +00:00
|
|
|
SGPropertyNode* node = nullptr;
|
2017-04-26 15:55:44 +00:00
|
|
|
// Remove the character encoding part of the locale spec, i.e.,
|
|
|
|
// "de_DE.utf8" => "de_DE"
|
|
|
|
string language = removeEncodingPart(localeSpec);
|
2012-04-21 18:17:42 +00:00
|
|
|
|
2017-04-26 15:55:44 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_DEBUG,
|
|
|
|
"Searching language resource for locale: '" << language << "'");
|
2012-04-21 18:17:42 +00:00
|
|
|
// search locale using full string
|
|
|
|
vector<SGPropertyNode_ptr> localeList = _intl->getChildren("locale");
|
|
|
|
|
|
|
|
for (size_t i = 0; i < localeList.size(); i++)
|
|
|
|
{
|
|
|
|
vector<SGPropertyNode_ptr> langList = localeList[i]->getChildren("lang");
|
|
|
|
|
|
|
|
for (size_t j = 0; j < langList.size(); j++)
|
|
|
|
{
|
2012-05-08 21:04:53 +00:00
|
|
|
if (!language.compare(langList[j]->getStringValue()))
|
|
|
|
{
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "Found language resource for: " << language);
|
|
|
|
return localeList[i];
|
|
|
|
}
|
2012-04-21 18:17:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-14 10:38:09 +00:00
|
|
|
// try country's default resource, i.e. "de_DE" => "de"
|
2020-07-09 17:14:56 +00:00
|
|
|
const auto justTheLanguage = removeLocalePart(language);
|
|
|
|
if (!justTheLanguage.empty()) {
|
|
|
|
node = findLocaleNode(justTheLanguage);
|
2012-05-08 21:04:53 +00:00
|
|
|
if (node)
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
2017-04-26 20:00:34 +00:00
|
|
|
return nullptr;
|
2012-04-21 18:17:42 +00:00
|
|
|
}
|
|
|
|
|
2017-04-26 20:00:34 +00:00
|
|
|
// Select the language. When no language is given (nullptr),
|
2012-04-21 18:17:42 +00:00
|
|
|
// a default is determined matching the system locale.
|
2020-06-17 15:15:01 +00:00
|
|
|
bool FGLocale::selectLanguage(const std::string& language)
|
2012-04-21 18:17:42 +00:00
|
|
|
{
|
2020-07-09 17:14:56 +00:00
|
|
|
_languages = getUserLanguages();
|
|
|
|
if (_languages.empty()) {
|
2013-03-27 22:48:16 +00:00
|
|
|
// Use plain C locale if nothing is available.
|
2012-05-09 21:47:34 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "Unable to detect system language" );
|
2020-07-09 17:14:56 +00:00
|
|
|
_languages.push_back("C");
|
2013-03-27 22:48:16 +00:00
|
|
|
}
|
2017-04-26 20:00:34 +00:00
|
|
|
|
2013-03-27 22:48:16 +00:00
|
|
|
// if we were passed a language option, try it first
|
2020-06-17 15:15:01 +00:00
|
|
|
if (!language.empty()) {
|
2020-07-14 10:38:09 +00:00
|
|
|
const auto normalizedLang = strutils::replace(language, "-", "_");
|
|
|
|
_languages.insert(_languages.begin(), normalizedLang);
|
2012-04-21 18:17:42 +00:00
|
|
|
}
|
|
|
|
|
2020-07-09 17:14:56 +00:00
|
|
|
_currentLocaleString = removeEncodingPart(_languages.front());
|
2017-04-26 19:56:37 +00:00
|
|
|
if (_currentLocaleString == "C") {
|
|
|
|
_currentLocaleString.clear();
|
|
|
|
}
|
|
|
|
|
2017-04-26 20:00:34 +00:00
|
|
|
_currentLocale = nullptr;
|
|
|
|
|
2020-07-09 17:14:56 +00:00
|
|
|
for (const string& lang : _languages) {
|
2017-04-26 20:00:34 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_DEBUG,
|
|
|
|
"Trying to find locale for '" << lang << "'");
|
2014-12-04 08:18:41 +00:00
|
|
|
_currentLocale = findLocaleNode(lang);
|
2017-04-26 20:00:34 +00:00
|
|
|
|
2014-12-04 08:18:41 +00:00
|
|
|
if (_currentLocale) {
|
2017-04-26 20:00:34 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_DEBUG,
|
|
|
|
"Found locale for '" << lang << "' at '" <<
|
|
|
|
_currentLocale->getPath() << "'");
|
2013-03-27 22:48:16 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-07-09 17:14:56 +00:00
|
|
|
|
2018-08-11 14:13:46 +00:00
|
|
|
if (_currentLocale && _currentLocale->hasChild("xliff")) {
|
|
|
|
parseXLIFF(_currentLocale);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-09-21 17:36:25 +00:00
|
|
|
// load resource for system messages (translations for fgfs internal messages)
|
|
|
|
loadResource("sys");
|
2014-02-19 13:24:34 +00:00
|
|
|
loadResource("atc");
|
2017-02-26 21:59:05 +00:00
|
|
|
loadResource("tips");
|
2020-06-17 15:15:01 +00:00
|
|
|
loadResource("weather-scenarios");
|
2017-02-26 21:59:05 +00:00
|
|
|
|
2020-06-17 09:57:34 +00:00
|
|
|
_inited = true;
|
|
|
|
if (!_currentLocale && !_currentLocaleString.empty()) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN,
|
|
|
|
"System locale not found or no internationalization settings specified in defaults.xml. Using default (en).");
|
|
|
|
return false;
|
2014-12-04 08:18:41 +00:00
|
|
|
}
|
|
|
|
|
2012-04-21 18:17:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-06-17 15:15:01 +00:00
|
|
|
void FGLocale::clear()
|
|
|
|
{
|
|
|
|
_inited = false;
|
|
|
|
_currentLocaleString.clear();
|
2020-07-09 17:14:56 +00:00
|
|
|
_languages.clear();
|
2020-06-17 15:15:01 +00:00
|
|
|
|
2020-06-18 11:57:04 +00:00
|
|
|
if (_currentLocale && (_currentLocale != _defaultLocale)) {
|
2020-06-17 15:15:01 +00:00
|
|
|
// remove loaded strings, so we don't duplicate
|
|
|
|
_currentLocale->removeChild("strings");
|
|
|
|
}
|
|
|
|
|
|
|
|
_currentLocale.clear();
|
|
|
|
}
|
|
|
|
|
2017-04-26 19:56:37 +00:00
|
|
|
// Return the preferred language according to user choice and/or settings
|
|
|
|
// (e.g., 'fr_FR', or the empty string if nothing could be found).
|
|
|
|
std::string
|
|
|
|
FGLocale::getPreferredLanguage() const
|
|
|
|
{
|
|
|
|
return _currentLocaleString;
|
|
|
|
}
|
|
|
|
|
2018-08-11 14:13:46 +00:00
|
|
|
void FGLocale::parseXLIFF(SGPropertyNode* localeNode)
|
|
|
|
{
|
|
|
|
SGPath path(globals->get_fg_root());
|
|
|
|
SGPath xliffPath = path / localeNode->getStringValue("xliff");
|
|
|
|
if (!xliffPath.exists()) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "No XLIFF file at " << xliffPath);
|
|
|
|
} else {
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "Loading XLIFF file at " << xliffPath);
|
|
|
|
SGPropertyNode_ptr stringNode = localeNode->getNode("strings", 0, true);
|
|
|
|
try {
|
|
|
|
flightgear::XLIFFParser visitor(stringNode);
|
|
|
|
readXML(xliffPath, visitor);
|
|
|
|
} catch (sg_io_exception& ex) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "failure parsing XLIFF: " << path <<
|
|
|
|
"\n\t" << ex.getMessage() << "\n\tat:" << ex.getLocation().asString());
|
|
|
|
} catch (sg_exception& ex) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "failure parsing XLIFF: " << path <<
|
|
|
|
"\n\t" << ex.getMessage());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-04-21 18:17:42 +00:00
|
|
|
// Load strings for requested resource and locale.
|
|
|
|
// Result is stored below "strings" in the property tree of the given locale.
|
|
|
|
bool
|
|
|
|
FGLocale::loadResource(SGPropertyNode* localeNode, const char* resource)
|
|
|
|
{
|
2017-01-05 05:45:26 +00:00
|
|
|
SGPath path( globals->get_fg_root() );
|
2012-04-21 18:17:42 +00:00
|
|
|
|
2018-08-11 14:13:46 +00:00
|
|
|
// already loaded
|
|
|
|
if (localeNode->hasChild("xliff")) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-04-21 18:17:42 +00:00
|
|
|
SGPropertyNode* stringNode = localeNode->getNode("strings", 0, true);
|
2020-06-18 11:57:04 +00:00
|
|
|
SGPropertyNode* resourceNode = stringNode->getNode(resource);
|
|
|
|
|
|
|
|
if (!resourceNode)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (resourceNode->getBoolValue("__loaded")) {
|
|
|
|
// already loaded previously
|
|
|
|
return true;
|
|
|
|
}
|
2012-04-21 18:17:42 +00:00
|
|
|
|
2020-06-18 11:57:04 +00:00
|
|
|
const char* path_str = resourceNode->getStringValue();
|
2012-04-21 18:17:42 +00:00
|
|
|
if (!path_str)
|
|
|
|
{
|
2012-05-06 21:15:27 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "No path in " << stringNode->getPath() << "/" << resource << ".");
|
2014-05-05 20:24:47 +00:00
|
|
|
return false;
|
2012-04-21 18:17:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
path.append(path_str);
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "Reading localized strings for '" <<
|
|
|
|
localeNode->getStringValue("lang", "<none>")
|
2016-06-23 13:26:34 +00:00
|
|
|
<<"' from " << path);
|
2012-04-21 18:17:42 +00:00
|
|
|
|
|
|
|
// load the actual file
|
|
|
|
try
|
|
|
|
{
|
2020-06-18 11:57:04 +00:00
|
|
|
readProperties(path, resourceNode);
|
2012-04-21 18:17:42 +00:00
|
|
|
} catch (const sg_exception &e)
|
|
|
|
{
|
2016-06-23 13:26:34 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "Unable to read the localized strings from " << path <<
|
2012-04-21 18:17:42 +00:00
|
|
|
". Error: " << e.getFormattedMessage());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-06-18 11:57:04 +00:00
|
|
|
// set marker to indicate the data has been loaded
|
|
|
|
resourceNode->setBoolValue("__loaded", true);
|
|
|
|
return true;
|
2012-04-21 18:17:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load strings for requested resource (for current and default locale).
|
|
|
|
// Result is stored below "strings" in the property tree of the locales.
|
|
|
|
bool
|
|
|
|
FGLocale::loadResource(const char* resource)
|
|
|
|
{
|
|
|
|
// load defaults first
|
|
|
|
bool Ok = loadResource(_defaultLocale, resource);
|
|
|
|
|
|
|
|
// also load language specific resource, unless identical
|
2018-10-13 20:22:18 +00:00
|
|
|
if ((_currentLocale != nullptr) && (_defaultLocale != _currentLocale))
|
2012-04-21 18:17:42 +00:00
|
|
|
{
|
|
|
|
Ok &= loadResource(_currentLocale, resource);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok;
|
|
|
|
}
|
|
|
|
|
2017-02-26 21:59:05 +00:00
|
|
|
std::string
|
2020-06-17 15:15:01 +00:00
|
|
|
FGLocale::innerGetLocalizedString(SGPropertyNode* localeNode, const char* id, const char* context, int index) const
|
2012-04-21 18:17:42 +00:00
|
|
|
{
|
|
|
|
SGPropertyNode *n = localeNode->getNode("strings",0, true)->getNode(context);
|
2017-02-26 21:59:05 +00:00
|
|
|
if (!n) {
|
|
|
|
return std::string();
|
|
|
|
}
|
|
|
|
|
|
|
|
n = n->getNode(id, index);
|
|
|
|
if (n && n->hasValue()) {
|
|
|
|
return std::string(n->getStringValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::string();
|
2012-04-21 18:17:42 +00:00
|
|
|
}
|
|
|
|
|
2020-06-17 15:15:01 +00:00
|
|
|
std::string
|
|
|
|
FGLocale::getLocalizedString(const std::string& id, const char* resource, const std::string& defaultValue)
|
|
|
|
{
|
|
|
|
return getLocalizedString(id.c_str(), resource, defaultValue.c_str());
|
|
|
|
}
|
|
|
|
|
2017-02-26 21:59:05 +00:00
|
|
|
std::string
|
2012-04-21 18:17:42 +00:00
|
|
|
FGLocale::getLocalizedString(const char* id, const char* resource, const char* Default)
|
|
|
|
{
|
2020-06-17 09:57:34 +00:00
|
|
|
assert(_inited);
|
2012-04-21 18:17:42 +00:00
|
|
|
if (id && resource)
|
|
|
|
{
|
2017-02-26 21:59:05 +00:00
|
|
|
std::string s;
|
|
|
|
if (_currentLocale) {
|
2020-06-17 15:15:01 +00:00
|
|
|
s = innerGetLocalizedString(_currentLocale, id, resource, 0);
|
2017-02-26 21:59:05 +00:00
|
|
|
if (!s.empty()) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
}
|
2012-04-21 18:17:42 +00:00
|
|
|
|
2017-02-26 21:59:05 +00:00
|
|
|
if (_defaultLocale) {
|
2020-06-17 15:15:01 +00:00
|
|
|
s = innerGetLocalizedString(_defaultLocale, id, resource, 0);
|
2017-02-26 21:59:05 +00:00
|
|
|
if (!s.empty()) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
}
|
2012-04-21 18:17:42 +00:00
|
|
|
}
|
2017-02-26 21:59:05 +00:00
|
|
|
|
|
|
|
return (Default == nullptr) ? std::string() : std::string(Default);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string
|
|
|
|
FGLocale::getLocalizedStringWithIndex(const char* id, const char* resource, unsigned int index) const
|
|
|
|
{
|
2020-06-17 09:57:34 +00:00
|
|
|
assert(_inited);
|
2017-02-26 21:59:05 +00:00
|
|
|
if (id && resource) {
|
|
|
|
std::string s;
|
|
|
|
if (_currentLocale) {
|
2020-06-17 15:15:01 +00:00
|
|
|
s = innerGetLocalizedString(_currentLocale, id, resource, index);
|
2017-02-26 21:59:05 +00:00
|
|
|
if (!s.empty()) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_defaultLocale) {
|
2020-06-17 15:15:01 +00:00
|
|
|
s = innerGetLocalizedString(_defaultLocale, id, resource, index);
|
2017-02-26 21:59:05 +00:00
|
|
|
if (!s.empty()) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::string();
|
2012-04-21 18:17:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
simgear::PropertyList
|
|
|
|
FGLocale::getLocalizedStrings(SGPropertyNode *localeNode, const char* id, const char* context)
|
|
|
|
{
|
|
|
|
SGPropertyNode *n = localeNode->getNode("strings",0, true)->getNode(context);
|
|
|
|
if (n)
|
|
|
|
{
|
|
|
|
return n->getChildren(id);
|
|
|
|
}
|
|
|
|
return simgear::PropertyList();
|
|
|
|
}
|
|
|
|
|
2017-02-26 21:59:05 +00:00
|
|
|
size_t FGLocale::getLocalizedStringCount(const char* id, const char* resource) const
|
|
|
|
{
|
2020-06-17 09:57:34 +00:00
|
|
|
assert(_inited);
|
2017-02-26 21:59:05 +00:00
|
|
|
if (_currentLocale) {
|
|
|
|
SGPropertyNode* resourceNode = _currentLocale->getNode("strings",0, true)->getNode(resource);
|
|
|
|
if (resourceNode) {
|
|
|
|
const size_t count = resourceNode->getChildren(id).size();
|
|
|
|
if (count > 0) {
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_defaultLocale) {
|
2020-06-18 11:57:04 +00:00
|
|
|
auto resourceNode = _defaultLocale->getNode("strings", 0, true)->getNode(resource);
|
|
|
|
if (!resourceNode)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
size_t count = resourceNode->getChildren(id).size();
|
2017-02-26 21:59:05 +00:00
|
|
|
if (count > 0) {
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-04-21 18:17:42 +00:00
|
|
|
simgear::PropertyList
|
|
|
|
FGLocale::getLocalizedStrings(const char* id, const char* resource)
|
|
|
|
{
|
2020-06-17 09:57:34 +00:00
|
|
|
assert(_inited);
|
2012-04-21 18:17:42 +00:00
|
|
|
if (id && resource)
|
|
|
|
{
|
|
|
|
if (_currentLocale)
|
|
|
|
{
|
|
|
|
simgear::PropertyList s = getLocalizedStrings(_currentLocale, id, resource);
|
2013-03-22 02:42:22 +00:00
|
|
|
if (! s.empty())
|
2012-04-21 18:17:42 +00:00
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_defaultLocale)
|
|
|
|
{
|
|
|
|
simgear::PropertyList s = getLocalizedStrings(_defaultLocale, id, resource);
|
2013-03-22 02:42:22 +00:00
|
|
|
if (! s.empty())
|
2012-04-21 18:17:42 +00:00
|
|
|
return s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return simgear::PropertyList();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for localized font
|
|
|
|
const char*
|
|
|
|
FGLocale::getDefaultFont(const char* fallbackFont)
|
|
|
|
{
|
2020-06-17 09:57:34 +00:00
|
|
|
assert(_inited);
|
2017-04-26 20:00:34 +00:00
|
|
|
const char* font = nullptr;
|
2012-04-21 18:17:42 +00:00
|
|
|
if (_currentLocale)
|
|
|
|
{
|
|
|
|
font = _currentLocale->getStringValue("font", "");
|
|
|
|
if (font[0] != 0)
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
if (_defaultLocale)
|
|
|
|
{
|
|
|
|
font = _defaultLocale->getStringValue("font", "");
|
|
|
|
if (font[0] != 0)
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fallbackFont;
|
|
|
|
}
|
|
|
|
|
2013-11-14 16:22:13 +00:00
|
|
|
std::string FGLocale::localizedPrintf(const char* id, const char* resource, ... )
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
va_start(args, resource);
|
|
|
|
string r = vlocalizedPrintf(id, resource, args);
|
|
|
|
va_end(args);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string FGLocale::vlocalizedPrintf(const char* id, const char* resource, va_list args)
|
|
|
|
{
|
2020-06-17 09:57:34 +00:00
|
|
|
assert(_inited);
|
2017-02-26 21:59:05 +00:00
|
|
|
std::string format = getLocalizedString(id, resource);
|
2018-10-10 07:24:02 +00:00
|
|
|
int len = ::vsnprintf(nullptr, 0, format.c_str(), args);
|
2013-11-14 16:22:13 +00:00
|
|
|
char* buf = (char*) alloca(len);
|
2017-02-26 21:59:05 +00:00
|
|
|
::vsprintf(buf, format.c_str(), args);
|
2013-11-14 16:22:13 +00:00
|
|
|
return std::string(buf);
|
|
|
|
}
|
|
|
|
|
2012-04-21 18:17:42 +00:00
|
|
|
// Simple UTF8 to Latin1 encoder.
|
|
|
|
void FGLocale::utf8toLatin1(string& s)
|
|
|
|
{
|
|
|
|
size_t pos = 0;
|
|
|
|
|
|
|
|
// map '0xc3..' utf8 characters to Latin1
|
|
|
|
while ((string::npos != (pos = s.find('\xc3',pos)))&&
|
|
|
|
(pos+1 < s.size()))
|
|
|
|
{
|
|
|
|
char c='*';
|
|
|
|
unsigned char p = s[pos+1];
|
|
|
|
if ((p>=0x80)&&(p<0xc0))
|
|
|
|
c = 0x40 + p;
|
|
|
|
char v[2];
|
|
|
|
v[0]=c;
|
|
|
|
v[1]=0;
|
|
|
|
s.replace(pos, 2, v);
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DEBUG_ENCODING
|
|
|
|
printf("'%s': ", s.c_str());
|
|
|
|
for (pos = 0;pos<s.size();pos++)
|
|
|
|
printf("%02x ", (unsigned char) s[pos]);
|
|
|
|
printf("\n");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// hack: also map some Latin2 characters to plain-text ASCII
|
|
|
|
pos = 0;
|
|
|
|
while ((string::npos != (pos = s.find('\xc5',pos)))&&
|
|
|
|
(pos+1 < s.size()))
|
|
|
|
{
|
|
|
|
char c='*';
|
|
|
|
unsigned char p = s[pos+1];
|
|
|
|
switch(p)
|
|
|
|
{
|
|
|
|
case 0x82:c='l';break;
|
|
|
|
case 0x9a:c='S';break;
|
|
|
|
case 0x9b:c='s';break;
|
|
|
|
case 0xba:c='z';break;
|
|
|
|
case 0xbc:c='z';break;
|
|
|
|
}
|
|
|
|
char v[2];
|
|
|
|
v[0]=c;
|
|
|
|
v[1]=0;
|
|
|
|
s.replace(pos, 2, v);
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
}
|
2013-11-14 16:22:13 +00:00
|
|
|
|
2017-02-26 21:59:05 +00:00
|
|
|
std::string fgTrMsg(const char* key)
|
2013-11-14 16:22:13 +00:00
|
|
|
{
|
|
|
|
return globals->get_locale()->getLocalizedString(key, "message");
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string fgTrPrintfMsg(const char* key, ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
va_start(args, key);
|
|
|
|
string r = globals->get_locale()->vlocalizedPrintf(key, "message", args);
|
|
|
|
va_end(args);
|
|
|
|
return r;
|
|
|
|
}
|
2020-07-09 17:14:56 +00:00
|
|
|
|
|
|
|
SGPropertyNode_ptr FGLocale::selectLanguageNode(SGPropertyNode* langs) const
|
|
|
|
{
|
|
|
|
if (!langs)
|
|
|
|
return {};
|
|
|
|
|
|
|
|
for (auto l : _languages) {
|
2020-07-14 10:38:09 +00:00
|
|
|
// Only accept the hyphen separator in PropertyList node names between
|
|
|
|
// language and territory
|
|
|
|
const auto langNoEncoding = strutils::replace(removeEncodingPart(l),
|
|
|
|
"_", "-");
|
2020-07-09 17:14:56 +00:00
|
|
|
if (langs->hasChild(langNoEncoding)) {
|
|
|
|
return langs->getChild(langNoEncoding);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto justLang = removeLocalePart(langNoEncoding);
|
|
|
|
if (langs->hasChild(justLang)) {
|
|
|
|
return langs->getChild(justLang);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|