Load translations from XLIFF format
When an <xliff> entry is present inside a locale specification, this takes precedence over the legacy translations.
This commit is contained in:
parent
d534a5ba36
commit
f5117109fe
5 changed files with 223 additions and 5 deletions
|
@ -28,6 +28,7 @@ set(SOURCES
|
||||||
screensaver_control.cxx
|
screensaver_control.cxx
|
||||||
subsystemFactory.cxx
|
subsystemFactory.cxx
|
||||||
util.cxx
|
util.cxx
|
||||||
|
XLIFFParser.cxx
|
||||||
${MS_RESOURCE_FILE}
|
${MS_RESOURCE_FILE}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ set(HEADERS
|
||||||
screensaver_control.hxx
|
screensaver_control.hxx
|
||||||
subsystemFactory.hxx
|
subsystemFactory.hxx
|
||||||
util.hxx
|
util.hxx
|
||||||
|
XLIFFParser.hxx
|
||||||
)
|
)
|
||||||
|
|
||||||
flightgear_component(Main "${SOURCES}" "${HEADERS}")
|
flightgear_component(Main "${SOURCES}" "${HEADERS}")
|
||||||
|
|
122
src/Main/XLIFFParser.cxx
Normal file
122
src/Main/XLIFFParser.cxx
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// XLIFFParser.cxx -- parse an XLIFF 1.2 XML file
|
||||||
|
////
|
||||||
|
// Copyright (C) 2018 James Turner <james@flightgear.org>
|
||||||
|
//
|
||||||
|
// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "XLIFFParser.hxx"
|
||||||
|
|
||||||
|
// simgear
|
||||||
|
#include <simgear/debug/logstream.hxx>
|
||||||
|
#include <simgear/props/props.hxx>
|
||||||
|
|
||||||
|
using namespace flightgear;
|
||||||
|
|
||||||
|
XLIFFParser::XLIFFParser(SGPropertyNode_ptr lroot) :
|
||||||
|
_localeRoot(lroot)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void XLIFFParser::startXML()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void XLIFFParser::endXML()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void XLIFFParser::startElement(const char *name, const XMLAttributes &atts)
|
||||||
|
{
|
||||||
|
_text.clear();
|
||||||
|
std::string tag(name);
|
||||||
|
if (tag == "trans-unit") {
|
||||||
|
_unitId = atts.getValue("id");
|
||||||
|
if (_unitId.empty()) {
|
||||||
|
SG_LOG(SG_GENERAL, SG_WARN, "XLIFF trans-unit with missing ID: line "
|
||||||
|
<< getLine() << " of " << getPath());
|
||||||
|
}
|
||||||
|
} else if (tag == "group") {
|
||||||
|
_resource = atts.getValue("resname");
|
||||||
|
if (_resource.empty()) {
|
||||||
|
SG_LOG(SG_GENERAL, SG_WARN, "XLIFF group with missing resname: line "
|
||||||
|
<< getLine() << " of " << getPath());
|
||||||
|
} else {
|
||||||
|
_resourceNode = _localeRoot->getChild(_resource, 0, true /* create */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void XLIFFParser::endElement(const char* name)
|
||||||
|
{
|
||||||
|
std::string tag(name);
|
||||||
|
if (tag == "source") {
|
||||||
|
_source = _text;
|
||||||
|
} else if (tag == "target") {
|
||||||
|
_target = _text;
|
||||||
|
} else if (tag == "trans-unit") {
|
||||||
|
finishTransUnit();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void XLIFFParser::finishTransUnit()
|
||||||
|
{
|
||||||
|
if (!_resourceNode) {
|
||||||
|
SG_LOG(SG_GENERAL, SG_WARN, "XLIFF trans-unit without enclosing resource group: line "
|
||||||
|
<< getLine() << " of " << getPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto slashPos = _unitId.find('/');
|
||||||
|
const auto indexPos = _unitId.find(':');
|
||||||
|
|
||||||
|
if (slashPos == std::string::npos) {
|
||||||
|
SG_LOG(SG_GENERAL, SG_WARN, "XLIFF trans-unit id without resource: '" <<
|
||||||
|
_unitId << "' at line " << getLine() << " of " << getPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto res = _unitId.substr(0, slashPos);
|
||||||
|
if (res != _resource) {
|
||||||
|
// this implies the <group> node resname doesn't match the
|
||||||
|
// id resource prefix. For now just warn and skip, we could decide
|
||||||
|
// that one or the other takes precedence here?
|
||||||
|
SG_LOG(SG_GENERAL, SG_WARN, "XLIFF trans-unit with inconsistent resource: line "
|
||||||
|
<< getLine() << " of " << getPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto id = _unitId.substr(slashPos + 1, indexPos - (slashPos + 1));
|
||||||
|
_resourceNode->setStringValue(id, _target);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XLIFFParser::data (const char * s, int len)
|
||||||
|
{
|
||||||
|
_text += std::string(s, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void XLIFFParser::pi (const char * target, const char * data) {
|
||||||
|
//cout << "Processing instruction " << target << ' ' << data << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XLIFFParser::warning (const char * message, int line, int column) {
|
||||||
|
SG_LOG(SG_GENERAL, SG_WARN, "Warning: " << message << " (" << line << ',' << column << ')');
|
||||||
|
}
|
53
src/Main/XLIFFParser.hxx
Normal file
53
src/Main/XLIFFParser.hxx
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// XLIFFParser.hxx -- parse an XLIFF 1.2 XML file
|
||||||
|
////
|
||||||
|
// Copyright (C) 2018 James Turner <james@flightgear.org>
|
||||||
|
//
|
||||||
|
// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
#include <simgear/xml/easyxml.hxx>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <simgear/props/propsfwd.hxx>
|
||||||
|
|
||||||
|
namespace flightgear
|
||||||
|
{
|
||||||
|
|
||||||
|
class XLIFFParser : public XMLVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
XLIFFParser(SGPropertyNode_ptr lroot);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void startXML () override;
|
||||||
|
void endXML () override;
|
||||||
|
void startElement (const char * name, const XMLAttributes &atts) override;
|
||||||
|
void endElement (const char * name) override;
|
||||||
|
void data (const char * s, int len) override;
|
||||||
|
void pi (const char * target, const char * data) override;
|
||||||
|
void warning (const char * message, int line, int column) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void finishTransUnit();
|
||||||
|
|
||||||
|
SGPropertyNode_ptr _localeRoot;
|
||||||
|
SGPropertyNode_ptr _resourceNode;
|
||||||
|
|
||||||
|
std::string _text;
|
||||||
|
std::string _unitId, _resource;
|
||||||
|
std::string _source, _target;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -18,9 +18,7 @@
|
||||||
// along with this program; if not, write to the Free Software
|
// along with this program; if not, write to the Free Software
|
||||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#include <config.h>
|
||||||
# include <config.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HAVE_WINDOWS_H
|
#ifdef HAVE_WINDOWS_H
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
@ -31,11 +29,13 @@
|
||||||
#include <cstddef> // std::size_t
|
#include <cstddef> // std::size_t
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
|
#include <simgear/props/props.hxx>
|
||||||
#include <simgear/props/props_io.hxx>
|
#include <simgear/props/props_io.hxx>
|
||||||
#include <simgear/structure/exception.hxx>
|
#include <simgear/structure/exception.hxx>
|
||||||
|
|
||||||
#include "fg_props.hxx"
|
#include "fg_props.hxx"
|
||||||
#include "locale.hxx"
|
#include "locale.hxx"
|
||||||
|
#include "XLIFFParser.hxx"
|
||||||
|
|
||||||
using std::vector;
|
using std::vector;
|
||||||
using std::string;
|
using std::string;
|
||||||
|
@ -188,7 +188,12 @@ FGLocale::selectLanguage(const char *language)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_currentLocale && _currentLocale->hasChild("xliff")) {
|
||||||
|
parseXLIFF(_currentLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// load resource for system messages (translations for fgfs internal messages)
|
// load resource for system messages (translations for fgfs internal messages)
|
||||||
loadResource("sys");
|
loadResource("sys");
|
||||||
|
|
||||||
|
@ -215,6 +220,28 @@ FGLocale::getPreferredLanguage() const
|
||||||
return _currentLocaleString;
|
return _currentLocaleString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load strings for requested resource and locale.
|
// Load strings for requested resource and locale.
|
||||||
// Result is stored below "strings" in the property tree of the given locale.
|
// Result is stored below "strings" in the property tree of the given locale.
|
||||||
bool
|
bool
|
||||||
|
@ -222,6 +249,11 @@ FGLocale::loadResource(SGPropertyNode* localeNode, const char* resource)
|
||||||
{
|
{
|
||||||
SGPath path( globals->get_fg_root() );
|
SGPath path( globals->get_fg_root() );
|
||||||
|
|
||||||
|
// already loaded
|
||||||
|
if (localeNode->hasChild("xliff")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
SGPropertyNode* stringNode = localeNode->getNode("strings", 0, true);
|
SGPropertyNode* stringNode = localeNode->getNode("strings", 0, true);
|
||||||
|
|
||||||
const char *path_str = stringNode->getStringValue(resource, nullptr);
|
const char *path_str = stringNode->getStringValue(resource, nullptr);
|
||||||
|
|
|
@ -24,7 +24,11 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstdarg> // for va_start/_end
|
#include <cstdarg> // for va_start/_end
|
||||||
|
|
||||||
#include <simgear/props/props.hxx>
|
#include <simgear/props/propsfwd.hxx>
|
||||||
|
#include <simgear/misc/strutils.hxx>
|
||||||
|
|
||||||
|
// forward decls
|
||||||
|
class SGPath;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// FGLocale //////////////////////////////////////////////////////////////////
|
// FGLocale //////////////////////////////////////////////////////////////////
|
||||||
|
@ -138,6 +142,11 @@ protected:
|
||||||
SGPropertyNode_ptr _defaultLocale;
|
SGPropertyNode_ptr _defaultLocale;
|
||||||
std::string _currentLocaleString;
|
std::string _currentLocaleString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an XLIFF 1.2 file into the standard property structure underneath
|
||||||
|
* the provided locale node
|
||||||
|
*/
|
||||||
|
void parseXLIFF(SGPropertyNode* node);
|
||||||
private:
|
private:
|
||||||
/** Return a new string with the character encoding part of the locale
|
/** Return a new string with the character encoding part of the locale
|
||||||
* spec removed., i.e., "de_DE.UTF-8" becomes "de_DE". If there is no
|
* spec removed., i.e., "de_DE.UTF-8" becomes "de_DE". If there is no
|
||||||
|
|
Loading…
Reference in a new issue