2017-06-26 08:45:57 +00:00
|
|
|
#! /usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# fg-update-translation-files --- Merge new default translation,
|
|
|
|
# remove obsolete strings from a translation
|
|
|
|
# Copyright (C) 2017 Florent Rougon
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import enum
|
|
|
|
import locale
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
|
|
|
|
import flightgear.meta.logging
|
|
|
|
import flightgear.meta.i18n as fg_i18n
|
2020-05-08 08:33:35 +00:00
|
|
|
from flightgear.meta.i18n import XliffFormatHandler
|
2017-06-26 08:45:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
PROGNAME = os.path.basename(sys.argv[0])
|
|
|
|
|
|
|
|
# Only messages with severity >= info will be printed to the terminal (it's
|
|
|
|
# possible to also log all messages to a file regardless of their level, see
|
|
|
|
# the Logger class). Of course, there is also the standard logging module...
|
|
|
|
logger = flightgear.meta.logging.Logger(
|
|
|
|
progname=PROGNAME,
|
|
|
|
logLevel=flightgear.meta.logging.LogLevel.info,
|
|
|
|
defaultOutputStream=sys.stderr)
|
|
|
|
|
|
|
|
|
|
|
|
def processCommandLine():
|
|
|
|
params = argparse.Namespace()
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
usage="""\
|
2020-05-08 08:33:35 +00:00
|
|
|
%(prog)s [OPTION ...] ACTION [LANGUAGE_CODE]...
|
2017-06-26 08:45:57 +00:00
|
|
|
Update FlightGear XLIFF localization files.""",
|
|
|
|
description="""\
|
|
|
|
This program performs the following operations (actions) on FlightGear XLIFF
|
|
|
|
translation files (*.xlf):
|
|
|
|
|
|
|
|
- [merge-new-master]
|
|
|
|
Read the default translation[1], add new translated strings it contains to
|
|
|
|
the XLIFF localization files corresponding to the specified language(s),
|
|
|
|
mark the translated strings in said files that need review (modified in
|
|
|
|
the default translation) as well as those that are not used anymore
|
|
|
|
(disappeared in the default translation, or marked in a way that says they
|
|
|
|
don't need to be translated);
|
|
|
|
|
|
|
|
- [mark-unused]
|
|
|
|
Read the default translation and mark translated strings (in the XLIFF
|
|
|
|
localization files corresponding to the specified language(s)) that are
|
|
|
|
not used anymore;
|
|
|
|
|
|
|
|
- [remove-unused]
|
|
|
|
In the XLIFF localization files corresponding to the specified
|
|
|
|
language(s), remove all translated strings that are marked as unused.
|
|
|
|
|
2020-05-08 08:33:35 +00:00
|
|
|
If no LANGUAGE_CODE is provided as an argument, then assuming $transl_dir
|
|
|
|
represents the value passed to --transl-dir, all directories $d such that a
|
|
|
|
file named FlightGear-nonQt.xlf is found in $transl_dir/$d will be acted on as
|
|
|
|
if they had been passed as LANGUAGE_CODE arguments (actually, the directory
|
|
|
|
$transl_dir/default is not considered as a candidate; it is simply skipped).
|
|
|
|
Typically, $transl_dir is /path/to/FGData/Translations.
|
|
|
|
|
2017-06-26 08:45:57 +00:00
|
|
|
A translated string that is marked as unused is still present in the XLIFF
|
|
|
|
localization file; it is just presented in a way that tells translators they
|
|
|
|
don't need to worry about it. On the other hand, when a translated string is
|
|
|
|
removed, translators don't see it anymore and the translation is lost, except
|
|
|
|
if rescued by external means such as backups or version control systems (Git,
|
|
|
|
Subversion, etc.)
|
|
|
|
|
|
|
|
Note that the 'remove-unused' action does *not* imply 'mark-unused'. It only
|
|
|
|
removes translation units that are already marked as unused (i.e., with
|
|
|
|
translate="no"). Thus, it makes sense to do 'mark-unused' followed by
|
|
|
|
'remove-unused' if you really want to get rid of old translations (you need to
|
|
|
|
invoke the program twice, or make a small change for this). Leaving unused
|
|
|
|
translated strings marked as such in XLIFF files shouldn't harm much in
|
|
|
|
general on the short or mid-term: they only take some space.
|
|
|
|
|
|
|
|
[1] FlightGear XML files in $FG_ROOT/Translations/default containing strings
|
|
|
|
used for the default locale (English).""",
|
|
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
|
# I want --help but not -h (it might be useful for something else)
|
|
|
|
add_help=False)
|
|
|
|
|
|
|
|
parser.add_argument("-t", "--transl-dir",
|
|
|
|
help="""\
|
|
|
|
directory containing all translation subdirs (such as
|
|
|
|
{default!r}, 'en_GB', 'fr_FR', 'de', 'it'...). This
|
|
|
|
"option" MUST be specified.""".format(
|
|
|
|
default=fg_i18n.DEFAULT_LANG_DIR))
|
|
|
|
parser.add_argument("action", metavar="ACTION",
|
|
|
|
choices=("merge-new-master",
|
|
|
|
"mark-unused",
|
|
|
|
"remove-unused"),
|
|
|
|
help="""\
|
|
|
|
what to do: merge a new default (= master)
|
|
|
|
translation, or mark unused translation units, or
|
|
|
|
remove those already marked as unused from the XLIFF
|
|
|
|
files corresponding to each given LANGUAGE_CODE (i.e.,
|
|
|
|
those that are not in the default translation)""")
|
2020-05-08 08:33:35 +00:00
|
|
|
parser.add_argument("lang_code", metavar="LANGUAGE_CODE", nargs="*",
|
2017-06-26 08:45:57 +00:00
|
|
|
help="""\
|
|
|
|
codes of languages to operate on (e.g., fr, en_GB, it,
|
|
|
|
es_ES...)""")
|
|
|
|
parser.add_argument("--help", action="help",
|
|
|
|
help="display this message and exit")
|
|
|
|
|
|
|
|
params = parser.parse_args(namespace=params)
|
|
|
|
|
|
|
|
if params.transl_dir is None:
|
|
|
|
logger.error("--transl-dir must be given, aborting")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
return params
|
|
|
|
|
|
|
|
|
|
|
|
class MarkOrRemoveUnusedAction(enum.Enum):
|
|
|
|
mark, remove = range(2)
|
|
|
|
|
|
|
|
|
2020-05-08 08:33:35 +00:00
|
|
|
def langCodesToActOn():
|
|
|
|
"""Return an iterable of all language codes we were told to work on."""
|
|
|
|
if params.lang_code:
|
|
|
|
return params.lang_code
|
|
|
|
else:
|
|
|
|
return XliffFormatHandler.availableTranslations(params.transl_dir)
|
|
|
|
|
|
|
|
|
2017-06-26 08:45:57 +00:00
|
|
|
def markOrRemoveUnused(l10nResPoolMgr, action):
|
|
|
|
formatHandler = fg_i18n.XliffFormatHandler()
|
|
|
|
masterTransl = l10nResPoolMgr.readFgMasterTranslation().transl
|
|
|
|
|
2020-05-08 08:33:35 +00:00
|
|
|
for langCode in langCodesToActOn():
|
2017-06-26 08:45:57 +00:00
|
|
|
xliffPath = formatHandler.defaultFilePath(params.transl_dir, langCode)
|
|
|
|
transl = formatHandler.readTranslation(xliffPath)
|
|
|
|
|
|
|
|
if action == MarkOrRemoveUnusedAction.mark:
|
|
|
|
transl.markObsoleteOrVanished(masterTransl, logger=logger)
|
|
|
|
elif action == MarkOrRemoveUnusedAction.remove:
|
|
|
|
transl.removeObsoleteOrVanished(logger=logger)
|
|
|
|
else:
|
|
|
|
assert False, "unexpected action: {!r}".format(action)
|
|
|
|
|
|
|
|
l10nResPoolMgr.writeTranslation(formatHandler, transl,
|
|
|
|
filePath=xliffPath)
|
|
|
|
|
|
|
|
|
|
|
|
def mergeNewMaster(l10nResPoolMgr):
|
|
|
|
formatHandler = fg_i18n.XliffFormatHandler()
|
|
|
|
masterTransl = l10nResPoolMgr.readFgMasterTranslation().transl
|
|
|
|
|
2020-05-08 08:33:35 +00:00
|
|
|
for langCode in langCodesToActOn():
|
2017-06-26 08:45:57 +00:00
|
|
|
xliffPath = formatHandler.defaultFilePath(params.transl_dir, langCode)
|
|
|
|
transl = formatHandler.readTranslation(xliffPath)
|
|
|
|
transl.mergeMasterTranslation(masterTransl, logger=logger)
|
|
|
|
l10nResPoolMgr.writeTranslation(formatHandler, transl,
|
|
|
|
filePath=xliffPath)
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
global params
|
|
|
|
|
|
|
|
locale.setlocale(locale.LC_ALL, '')
|
|
|
|
params = processCommandLine()
|
|
|
|
|
|
|
|
l10nResPoolMgr = fg_i18n.L10NResourcePoolManager(params.transl_dir, logger)
|
|
|
|
|
|
|
|
if params.action == "mark-unused":
|
|
|
|
markOrRemoveUnused(l10nResPoolMgr, MarkOrRemoveUnusedAction.mark)
|
|
|
|
elif params.action == "remove-unused":
|
|
|
|
markOrRemoveUnused(l10nResPoolMgr, MarkOrRemoveUnusedAction.remove)
|
|
|
|
elif params.action == "merge-new-master":
|
|
|
|
mergeNewMaster(l10nResPoolMgr)
|
|
|
|
else:
|
|
|
|
assert False, "Bug: unexpected action: {!r}".format(params.action)
|
|
|
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": main()
|