1
0
Fork 0

Move locale code to separate module.

Introduce convenience methods to handle localized string resources
and obtain strings.
This commit is contained in:
ThorstenB 2012-04-21 20:17:42 +02:00
parent 056b85bc1f
commit 470552fab1
11 changed files with 464 additions and 168 deletions

View file

@ -41,6 +41,7 @@
#include <Main/main.hxx>
#include <Main/globals.hxx>
#include <Main/locale.hxx>
#include <Main/fg_props.hxx>
#include <Main/WindowSystemAdapter.hxx>
#include <GUI/new_gui.hxx>
@ -78,8 +79,7 @@ public:
FGFontCache *fc = globals->get_fontcache();
fc->initializeFonts();
puFont *GuiFont
= fc->get(globals->get_locale()->getStringValue("font",
"typewriter.txf"),
= fc->get(globals->get_locale()->getDefaultFont("typewriter.txf"),
15);
puSetDefaultFonts(*GuiFont, *GuiFont);
guiFnt = puGetDefaultLabelFont();

View file

@ -4,9 +4,30 @@
#include "menubar.hxx"
#include <Main/locale.hxx>
#include <Main/globals.hxx>
FGMenuBar::FGMenuBar()
{
// load locale's menu resources (default and current language)
globals->get_locale()->loadResource("menu");
}
FGMenuBar::~FGMenuBar ()
{
}
const char*
FGMenuBar::getLocalizedLabel(SGPropertyNode* node)
{
const char* name = node->getStringValue("name", 0);
const char* translated = globals->get_locale()->getLocalizedString(name, "menu");
if (translated)
return translated;
// return default
return node->getStringValue("label");
}
// end of menubar.cxx

View file

@ -3,6 +3,7 @@
#ifndef __MENUBAR_HXX
#define __MENUBAR_HXX 1
class SGPropertyNode;
/**
* XML-configured menu bar interface
@ -15,7 +16,7 @@
class FGMenuBar
{
public:
FGMenuBar();
/**
* Destructor.
@ -45,6 +46,13 @@ public:
*/
virtual bool isVisible () const = 0;
/**
* Read a menu label from the menu's property tree.
* Take care of mapping it to the appropriate translation, if available.
* Returns an UTF-8 encoded string.
*/
const char* getLocalizedLabel(SGPropertyNode* node);
};
#endif // __MENUBAR_HXX

View file

@ -21,6 +21,7 @@ set(SOURCES
fg_props.cxx
fgviewer.cxx
globals.cxx
locale.cxx
logger.cxx
main.cxx
options.cxx
@ -43,6 +44,7 @@ set(HEADERS
fg_props.hxx
fgviewer.hxx
globals.hxx
locale.hxx
logger.hxx
main.hxx
options.hxx

View file

@ -149,136 +149,6 @@ string fgBasePackageVersion() {
return version;
}
//
// Select the proper language based on the given locale/language name.
//
static SGPropertyNode* findLocale(SGPropertyNode *intl, const char* language)
{
vector<SGPropertyNode_ptr> localeList = intl->getChildren("locale");
for (unsigned int i = 0; i < localeList.size(); i++) {
vector<SGPropertyNode_ptr> lang = localeList[i]->getChildren("lang");
for (unsigned int j = 0; j < lang.size(); j++) {
if (!strcmp(lang[j]->getStringValue(), language)) {
return localeList[i];
}
}
}
return NULL;
}
// Initialize the localization
SGPropertyNode *fgInitLocale(const char *language) {
SGPropertyNode *c_node = NULL, *d_node = NULL;
SGPropertyNode *intl = fgGetNode("/sim/intl");
SG_LOG(SG_GENERAL, SG_INFO, "Selecting language: " << language );
// localization not defined
if (!intl)
return NULL;
c_node = findLocale(intl, language);
if (!c_node)
{
/* be tolerant about locale names, i.e. instead of "de_DE.utf8" also
* consider "de_DE" ... */
string l = language;
size_t pos = l.find(".");
if ((pos != string::npos)&&(pos>0))
c_node = findLocale(intl, l.substr(0, pos).c_str());
/* finally consider country alone, i.e. "de" */
pos = l.find("_");
if ((!c_node)&&(pos != string::npos)&&(pos>0))
c_node = findLocale(intl, l.substr(0, pos).c_str());
}
// Get the defaults
d_node = intl->getChild("locale");
if (!c_node)
c_node = d_node;
// Check for localized font
SGPropertyNode *font_n = c_node->getNode("font", true);
if ( !strcmp(font_n->getStringValue(), "") )
font_n->setStringValue(d_node->getStringValue("font", "typewriter.txf"));
//
// Load the default strings
//
SGPath d_path( globals->get_fg_root() );
const char *d_path_str = d_node->getStringValue("strings");
if (!d_path_str) {
SG_LOG(SG_GENERAL, SG_ALERT, "No path in " << d_node->getPath() << "/strings.");
return NULL;
}
d_path.append(d_path_str);
SG_LOG(SG_GENERAL, SG_INFO, "Reading localized strings from " << d_path.str());
SGPropertyNode *strings = c_node->getNode("strings");
try {
readProperties(d_path.str(), strings);
} catch (const sg_exception &) {
SG_LOG(SG_GENERAL, SG_ALERT, "Unable to read the localized strings");
return NULL;
}
//
// Load the language specific strings
//
if (c_node != d_node) {
SGPath c_path( globals->get_fg_root() );
const char *c_path_str = c_node->getStringValue("strings");
if (!c_path_str) {
SG_LOG(SG_GENERAL, SG_ALERT, "No path in " << c_node->getPath() << "/strings");
return NULL;
}
c_path.append(c_path_str);
SG_LOG(SG_GENERAL, SG_INFO, "Reading localized strings from " << c_path.str());
try {
readProperties(c_path.str(), strings);
} catch (const sg_exception &) {
SG_LOG(SG_GENERAL, SG_ALERT,
"Unable to read the localized strings from " << c_path.str());
return NULL;
}
}
return c_node;
}
// Initialize the localization routines
bool fgDetectLanguage() {
const char *language = ::getenv("LANG");
if (language == NULL) {
SG_LOG(SG_GENERAL, SG_INFO, "Unable to detect the language" );
language = "C";
}
SGPropertyNode *locale = fgInitLocale(language);
if (!locale) {
SG_LOG(SG_GENERAL, SG_ALERT,
"No internationalization settings specified in preferences.xml" );
return false;
}
globals->set_locale( locale );
return true;
}
template <class T>
bool fgFindAircraftInDir(const SGPath& dirPath, T* obj, bool (T::*pred)(const SGPath& p))
@ -561,11 +431,6 @@ bool fgInitConfig ( int argc, char **argv )
fgLoadProps("preferences.xml", globals->get_props());
SG_LOG(SG_INPUT, SG_INFO, "Finished Reading global preferences");
// Detect the required language as early as possible
if ( !fgDetectLanguage() ) {
return false;
}
globals->loadUserSettings(dataPath);
// Scan user config files and command line for a specified aircraft.

View file

@ -59,6 +59,7 @@
#include "globals.hxx"
#include "renderer.hxx"
#include "viewmgr.hxx"
#include "locale.hxx"
#include "fg_props.hxx"
#include "fg_io.hxx"
@ -122,7 +123,7 @@ FGGlobals *globals;
FGGlobals::FGGlobals() :
props( new SGPropertyNode ),
initial_state( NULL ),
locale( NULL ),
locale( new FGLocale(props) ),
renderer( new FGRenderer ),
subsystem_mgr( new SGSubsystemMgr ),
event_mgr( new SGEventMgr ),
@ -211,6 +212,9 @@ FGGlobals::~FGGlobals()
delete carrierlist;
delete channellist;
delete sound;
delete locale;
locale = NULL;
}

View file

@ -61,6 +61,7 @@ class FGControls;
class FGFlightPlanDispatcher;
class FGNavList;
class FGTACANList;
class FGLocale;
class FGModelMgr;
class FGRouteMgr;
class FGScenery;
@ -86,7 +87,7 @@ private:
SGPropertyNode_ptr initial_state;
// localization
SGPropertyNode_ptr locale;
FGLocale* locale;
FGRenderer *renderer;
SGSubsystemMgr *subsystem_mgr;
@ -255,8 +256,7 @@ public:
inline SGPropertyNode *get_props () { return props; }
inline void set_props( SGPropertyNode *n ) { props = n; }
inline SGPropertyNode *get_locale () { return locale; }
inline void set_locale( SGPropertyNode *n ) { locale = n; }
inline FGLocale* get_locale () { return locale; }
inline SGCommandMgr *get_commands () { return commands; }

294
src/Main/locale.cxx Normal file
View file

@ -0,0 +1,294 @@
// 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
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include "fg_props.hxx"
#include "locale.hxx"
using std::vector;
using std::string;
FGLocale::FGLocale(SGPropertyNode* root) :
_intl(root->getNode("/sim/intl",0, true)),
_defaultLocale(_intl->getChild("locale",0, true))
{
}
FGLocale::~FGLocale()
{
}
// Search property tree for matching locale description
SGPropertyNode*
FGLocale::findLocaleNode(const string& language)
{
SG_LOG(SG_GENERAL, SG_INFO, "Searching language resource for locale: " << language);
SGPropertyNode* node = NULL;
// remove character encoding from the locale spec, i.e. "de_DE.utf8" => "de_DE"
size_t pos = language.find(".");
if ((pos != string::npos)&&(pos>0))
{
node = findLocaleNode(language.substr(0, pos));
if (node)
return node;
}
// try country's default resource, i.e. "de_DE" => "de"
pos = language.find("_");
if ((pos != string::npos)&&(pos>0))
{
node = findLocaleNode(language.substr(0, pos));
if (node)
return node;
}
// 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++)
{
if (!language.compare(langList[j]->getStringValue()))
return localeList[i];
}
}
return NULL;
}
// Select the language. When no language is given (NULL),
// a default is determined matching the system locale.
bool
FGLocale::selectLanguage(const char *language)
{
// Use environment setting when no language is given.
if ((language == NULL)||(language[0]==0))
language = ::getenv("LANG");
// Use plain C locale if nothing is available.
if (language == NULL)
{
SG_LOG(SG_GENERAL, SG_INFO, "Unable to detect the language" );
language = "C";
}
SGPropertyNode *locale = findLocaleNode(language);
if (!locale)
{
SG_LOG(SG_GENERAL, SG_ALERT,
"No internationalization settings specified in preferences.xml" );
return false;
}
_currentLocale = locale;
return true;
}
// 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)
{
SGPath path( globals->get_fg_root() );
SGPropertyNode* stringNode = localeNode->getNode("strings", 0, true);
const char *path_str = stringNode->getStringValue(resource, NULL);
if (!path_str)
{
SG_LOG(SG_GENERAL, SG_ALERT, "No path in " << stringNode->getPath() << "/" << resource << ".");
return NULL;
}
path.append(path_str);
SG_LOG(SG_GENERAL, SG_INFO, "Reading localized strings for '" <<
localeNode->getStringValue("lang", "<none>")
<<"' from " << path.str());
// load the actual file
try
{
readProperties(path.str(), stringNode->getNode(resource, 0, true));
} catch (const sg_exception &e)
{
SG_LOG(SG_GENERAL, SG_ALERT, "Unable to read the localized strings from " << path.str() <<
". Error: " << e.getFormattedMessage());
return false;
}
return true;
}
// 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
if ((_currentLocale!=0)&&
(_defaultLocale != _currentLocale))
{
Ok &= loadResource(_currentLocale, resource);
}
return Ok;
}
const char*
FGLocale::getLocalizedString(SGPropertyNode *localeNode, const char* id, const char* context)
{
SGPropertyNode *n = localeNode->getNode("strings",0, true)->getNode(context);
if (n)
return n->getStringValue(id, NULL);
return NULL;
}
const char*
FGLocale::getLocalizedString(const char* id, const char* resource, const char* Default)
{
if (id && resource)
{
const char* s = NULL;
if (_currentLocale)
s = getLocalizedString(_currentLocale, id, resource);
if (s && s[0]!=0)
return s;
if (_defaultLocale)
s = getLocalizedString(_defaultLocale, id, resource);
if (s && s[0]!=0)
return s;
}
return Default;
}
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();
}
simgear::PropertyList
FGLocale::getLocalizedStrings(const char* id, const char* resource)
{
if (id && resource)
{
if (_currentLocale)
{
simgear::PropertyList s = getLocalizedStrings(_currentLocale, id, resource);
if (s.size())
return s;
}
if (_defaultLocale)
{
simgear::PropertyList s = getLocalizedStrings(_defaultLocale, id, resource);
if (s.size())
return s;
}
}
return simgear::PropertyList();
}
// Check for localized font
const char*
FGLocale::getDefaultFont(const char* fallbackFont)
{
const char* font = NULL;
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;
}
// 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++;
}
}

101
src/Main/locale.hxx Normal file
View file

@ -0,0 +1,101 @@
// locale.hxx -- 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
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#ifndef __FGLOCALE_HXX
#define __FGLOCALE_HXX
#include <string>
#include <simgear/props/props.hxx>
///////////////////////////////////////////////////////////////////////////////
// FGLocale //////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
class FGLocale
{
public:
FGLocale(SGPropertyNode* root);
virtual ~FGLocale();
/**
* Select the locale's primary language. When no language is given (NULL),
* a default is determined matching the system locale.
*/
bool selectLanguage (const char* language = NULL);
/**
* Load strings for requested resource, i.e. "menu", "options", "dialogs".
* Loads data for current and default locale (the latter is the fallback).
* Result is stored below the "strings" node in the property tree of the
* respective locale.
*/
bool loadResource (const char* resource);
/**
* Obtain a single string from the localized resource matching the given identifier.
* Selected context refers to "menu", "options", "dialog" etc.
*/
const char* getLocalizedString (const char* id, const char* resource, const char* Default=NULL);
/**
* Obtain a list of strings from the localized resource matching the given identifier.
* Selected context refers to "menu", "options", "dialog" etc.
* Returns a list of (string) properties.
*/
simgear::PropertyList getLocalizedStrings(const char* id, const char* resource);
/**
* Obtain default font for current locale.
*/
const char* getDefaultFont (const char* fallbackFont);
/**
* Simple UTF8 to Latin1 encoder.
*/
static void utf8toLatin1 (std::string& s);
protected:
/**
* Find property node matching given language.
*/
SGPropertyNode* findLocaleNode (const std::string& language);
/**
* Load resource data for given locale node.
*/
bool loadResource (SGPropertyNode* localeNode, const char* resource);
/**
* Obtain a single string from locale node matching the given identifier and context.
*/
const char* getLocalizedString (SGPropertyNode *localeNode, const char* id, const char* context);
/**
* Obtain a list of strings from locale node matching the given identifier and context.
*/
simgear::PropertyList getLocalizedStrings(SGPropertyNode *localeNode, const char* id, const char* context);
SGPropertyNode_ptr _intl;
SGPropertyNode_ptr _currentLocale;
SGPropertyNode_ptr _defaultLocale;
};
#endif // __FGLOCALE_HXX

View file

@ -499,7 +499,7 @@ int fgMainInit( int argc, char **argv ) {
#endif
SG_LOG( SG_GENERAL, SG_INFO, "FlightGear: Version "
<< version );
SG_LOG( SG_GENERAL, SG_INFO, "Built with " << SG_COMPILER_STR << std::endl );
SG_LOG( SG_GENERAL, SG_INFO, "Built with " << SG_COMPILER_STR << std::endl );
// Allocate global data structures. This needs to happen before
// we parse command line options

View file

@ -60,6 +60,7 @@
#include "viewmgr.hxx"
#include "main.hxx"
#include <Main/viewer.hxx>
#include <Main/locale.hxx>
#include <Environment/presets.hxx>
#include <osg/Version>
@ -1971,11 +1972,10 @@ string_list Options::valuesForOption(const std::string& key) const
void Options::processOptions()
{
// establish locale before showing help
if (isOptionSet("language")) {
globals->set_locale( fgInitLocale( valueForOption("language").c_str() ) );
}
// establish locale before showing help (this selects the default locale,
// when no explicit option was set)
globals->get_locale()->selectLanguage(valueForOption("language").c_str());
// now FG_ROOT is setup, process various command line options that bail us
// out quickly, but rely on aircraft / root settings
if (p->showHelp) {
@ -2064,12 +2064,12 @@ void Options::showUsage() const
{
fgOptLogLevel( "alert" );
SGPropertyNode *locale = globals->get_locale();
FGLocale *locale = globals->get_locale();
SGPropertyNode options_root;
SG_LOG( SG_GENERAL, SG_ALERT, "" ); // To popup the console on Windows
cout << endl;
try {
fgLoadProps("options.xml", &options_root);
} catch (const sg_exception &) {
@ -2080,17 +2080,23 @@ void Options::showUsage() const
exit(-1);
}
SGPropertyNode *options = options_root.getNode("options");
if (!options) {
SG_LOG( SG_GENERAL, SG_ALERT,
"Error reading options.xml: <options> directive not found." );
exit(-1);
}
SGPropertyNode *usage = locale->getNode(options->getStringValue("usage"));
if (!locale->loadResource("options"))
{
cout << "Unable to read the language resource." << endl;
exit(-1);
}
const char* usage = locale->getLocalizedString(options->getStringValue("usage"), "options");
if (usage) {
cout << usage->getStringValue() << endl;
cout << usage << endl;
}
vector<SGPropertyNode_ptr>section = options->getChildren("section");
@ -2131,21 +2137,17 @@ void Options::showUsage() const
msg += tmp + '\n';
msg.append(32, ' ');
}
// There may be more than one <description> tag assosiated
// There may be more than one <description> tag associated
// with one option
vector<SGPropertyNode_ptr> desc;
desc = option[k]->getChildren("description");
if (desc.size() > 0) {
for ( unsigned int l = 0; l < desc.size(); l++) {
// There may be more than one translation line.
string t = desc[l]->getStringValue();
SGPropertyNode *n = locale->getNode("strings");
vector<SGPropertyNode_ptr>trans_desc =
n->getChildren(t.substr(8).c_str());
// There may be more than one translation line.
vector<SGPropertyNode_ptr>trans_desc = locale->getLocalizedStrings(t.c_str(),"options");
for ( unsigned int m = 0; m < trans_desc.size(); m++ ) {
string t_str = trans_desc[m]->getStringValue();
@ -2171,19 +2173,18 @@ void Options::showUsage() const
}
}
SGPropertyNode *name;
name = locale->getNode(section[j]->getStringValue("name"));
const char* name = locale->getLocalizedString(section[j]->getStringValue("name"),"options");
if (!msg.empty() && name) {
cout << endl << name->getStringValue() << ":" << endl;
cout << endl << name << ":" << endl;
cout << msg;
msg.erase();
}
}
if ( !p->verbose ) {
cout << endl;
cout << "For a complete list of options use --help --verbose" << endl;
const char* verbose_help = locale->getLocalizedString(options->getStringValue("verbose-help"),"options");
if (verbose_help)
cout << endl << verbose_help << endl;
}
#ifdef _MSC_VER
std::cout << "Hit a key to continue..." << std::endl;