1
0
Fork 0

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:
James Turner 2018-08-11 16:13:46 +02:00
parent d534a5ba36
commit f5117109fe
5 changed files with 223 additions and 5 deletions

View file

@ -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
View 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
View 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;
};
}

View file

@ -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);

View file

@ -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