From f5117109fec4dffdd2ec64c840629f3e5bc3c213 Mon Sep 17 00:00:00 2001 From: James Turner Date: Sat, 11 Aug 2018 16:13:46 +0200 Subject: [PATCH] Load translations from XLIFF format When an entry is present inside a locale specification, this takes precedence over the legacy translations. --- src/Main/CMakeLists.txt | 2 + src/Main/XLIFFParser.cxx | 122 +++++++++++++++++++++++++++++++++++++++ src/Main/XLIFFParser.hxx | 53 +++++++++++++++++ src/Main/locale.cxx | 40 +++++++++++-- src/Main/locale.hxx | 11 +++- 5 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 src/Main/XLIFFParser.cxx create mode 100644 src/Main/XLIFFParser.hxx diff --git a/src/Main/CMakeLists.txt b/src/Main/CMakeLists.txt index 9976a9833..896adc52d 100644 --- a/src/Main/CMakeLists.txt +++ b/src/Main/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES screensaver_control.cxx subsystemFactory.cxx util.cxx + XLIFFParser.cxx ${MS_RESOURCE_FILE} ) @@ -47,6 +48,7 @@ set(HEADERS screensaver_control.hxx subsystemFactory.hxx util.hxx + XLIFFParser.hxx ) flightgear_component(Main "${SOURCES}" "${HEADERS}") diff --git a/src/Main/XLIFFParser.cxx b/src/Main/XLIFFParser.cxx new file mode 100644 index 000000000..a428b7ffa --- /dev/null +++ b/src/Main/XLIFFParser.cxx @@ -0,0 +1,122 @@ +// XLIFFParser.cxx -- parse an XLIFF 1.2 XML file +//// +// Copyright (C) 2018 James Turner +// +// 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 +#include + +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 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 << ')'); +} diff --git a/src/Main/XLIFFParser.hxx b/src/Main/XLIFFParser.hxx new file mode 100644 index 000000000..57b6ff9b8 --- /dev/null +++ b/src/Main/XLIFFParser.hxx @@ -0,0 +1,53 @@ +// XLIFFParser.hxx -- parse an XLIFF 1.2 XML file +//// +// Copyright (C) 2018 James Turner +// +// 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 + +#include + +#include + +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; +}; + +} diff --git a/src/Main/locale.cxx b/src/Main/locale.cxx index 8e467aa29..1c3c01a20 100644 --- a/src/Main/locale.cxx +++ b/src/Main/locale.cxx @@ -18,9 +18,7 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. -#ifdef HAVE_CONFIG_H -# include -#endif +#include #ifdef HAVE_WINDOWS_H #include @@ -31,11 +29,13 @@ #include // std::size_t #include +#include #include #include #include "fg_props.hxx" #include "locale.hxx" +#include "XLIFFParser.hxx" using std::vector; using std::string; @@ -188,7 +188,12 @@ FGLocale::selectLanguage(const char *language) break; } } - + + if (_currentLocale && _currentLocale->hasChild("xliff")) { + parseXLIFF(_currentLocale); + } + + // load resource for system messages (translations for fgfs internal messages) loadResource("sys"); @@ -215,6 +220,28 @@ FGLocale::getPreferredLanguage() const 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. // Result is stored below "strings" in the property tree of the given locale. bool @@ -222,6 +249,11 @@ FGLocale::loadResource(SGPropertyNode* localeNode, const char* resource) { SGPath path( globals->get_fg_root() ); + // already loaded + if (localeNode->hasChild("xliff")) { + return true; + } + SGPropertyNode* stringNode = localeNode->getNode("strings", 0, true); const char *path_str = stringNode->getStringValue(resource, nullptr); diff --git a/src/Main/locale.hxx b/src/Main/locale.hxx index 1d686b9a5..b720954af 100644 --- a/src/Main/locale.hxx +++ b/src/Main/locale.hxx @@ -24,7 +24,11 @@ #include #include // for va_start/_end -#include +#include +#include + +// forward decls +class SGPath; /////////////////////////////////////////////////////////////////////////////// // FGLocale ////////////////////////////////////////////////////////////////// @@ -138,6 +142,11 @@ protected: SGPropertyNode_ptr _defaultLocale; std::string _currentLocaleString; + /** + * Parse an XLIFF 1.2 file into the standard property structure underneath + * the provided locale node + */ + void parseXLIFF(SGPropertyNode* node); private: /** 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