Initial version of the Python scripts to manage l10n using the XLIFF format
Add the following files: python3-flightgear/README-l10n.txt python3-flightgear/fg-convert-translation-files python3-flightgear/fg-new-translations python3-flightgear/fg-update-translation-files python3-flightgear/flightgear/__init__.py python3-flightgear/flightgear/meta/__init__.py python3-flightgear/flightgear/meta/exceptions.py python3-flightgear/flightgear/meta/i18n.py python3-flightgear/flightgear/meta/logging.py python3-flightgear/flightgear/meta/misc.py They should work on Python 3.4 and later (tested with 3.5.3). The folder structure is chosen so that other FG support modules can insert themselves here, and possibly be used together. I put all of these inside 'flightgear.meta', because I don't expect them to be needed at FG runtime (neither now nor in the future), probably not even by the CMake build system. To declare that a string has plural forms, simply set the attribute 'with-plural' to 'true' on the corresponding element of the default translation (and as in Qt, use %n as a placeholder for the number that determines which singular or plural form to use).
This commit is contained in:
parent
89948603de
commit
c6eb59eb42
10 changed files with 2634 additions and 0 deletions
83
python3-flightgear/README-l10n.txt
Normal file
83
python3-flightgear/README-l10n.txt
Normal file
|
@ -0,0 +1,83 @@
|
|||
Quick start for the localization (l10n) scripts
|
||||
===============================================
|
||||
|
||||
The following assumes that all of these are in present in
|
||||
$FG_ROOT/Translations:
|
||||
- the default translation (default/*.xml);
|
||||
- the legacy FlightGear XML localization files (<language_code>/*.xml);
|
||||
- except for 'fg-convert-translation-files' which creates them, existing
|
||||
XLIFF 1.2 files (<language_code>/FlightGear-nonQt.xlf).
|
||||
|
||||
Note: the legacy FlightGear XML localization files are only needed by
|
||||
'fg-convert-translation-files' when migrating to the XLIFF format. The
|
||||
other scripts only need the default translation and obviously, for
|
||||
'fg-update-translation-files', the current XLIFF files.
|
||||
|
||||
To get the initial XLIFF files (generated from the default translation in
|
||||
$FG_ROOT/Translations/default as well as the legacy FlightGear XML
|
||||
localization files in $FG_ROOT/Translations/<language_code>):
|
||||
|
||||
languages="de en_US es fr it nl pl pt zh_CN"
|
||||
|
||||
# Your shell must expand $languages as several words. POSIX shell does that,
|
||||
# but not zsh for instance. Otherwise, don't use a shell variable.
|
||||
fg-convert-translation-files --transl-dir="$FG_ROOT/Translations" $languages
|
||||
|
||||
# Add strings found in the default translation but missing in the legacy FG
|
||||
# XML l10n files
|
||||
fg-update-translation-files --transl-dir="$FG_ROOT/Translations" \
|
||||
merge-new-master $languages
|
||||
|
||||
When master strings[1] have changed (in a large sense, i.e.: strings added,
|
||||
modified or removed, or categories added or removed[2]):
|
||||
|
||||
fg-update-translation-files --transl-dir="$FG_ROOT/Translations" \
|
||||
merge-new-master $languages
|
||||
|
||||
To remove unused translated strings (not to be done too often in my opinion):
|
||||
|
||||
fg-update-translation-files --transl-dir="$FG_ROOT/Translations" \
|
||||
remove-unused $languages
|
||||
|
||||
(you may replace 'remove-unused' with 'mark-unused' to just mark the strings
|
||||
as not-to-be-translated, however 'merge-new-master' presented above already
|
||||
does that)
|
||||
|
||||
To create skeleton translations for new languages (e.g., for fr_BE, en_AU and
|
||||
ca):
|
||||
|
||||
1) Check (add if necessary) that flightgear/meta/i18n.py knows the plural
|
||||
forms used in the new languages. This is done by editing PLURAL_FORMS
|
||||
towards the top of this i18n.py file (very easy). If the existing entry
|
||||
for, e.g., "zh" is sufficient for zh_TW or zh_HK, just let "zh" handle
|
||||
them: it will be tried as fallback if there is no perfect match on
|
||||
language and territory.
|
||||
|
||||
2) Run a command such as:
|
||||
|
||||
fg-new-translations --transl-dir="$FG_ROOT/Translations" fr_BE en_AU ca
|
||||
|
||||
(if you do this for only one language at a time, you can use the -o
|
||||
option to precisely control where the output goes, otherwise
|
||||
fg-new-translations chooses an appropriate place based on the value
|
||||
specified for --transl-dir)
|
||||
|
||||
fg-convert-translation-files, fg-update-translation-files and
|
||||
fg-new-translations all support the --help option for more detailed
|
||||
information.
|
||||
|
||||
|
||||
Footnotes
|
||||
---------
|
||||
|
||||
[1] Strings in the default translation.
|
||||
|
||||
[2] Only empty categories are removed by this command. An obsolete category
|
||||
can be made empty by manual editing (easy, just locate the right
|
||||
<group>) or this way:
|
||||
|
||||
fg-update-translation-files --transl-dir=... mark-unused
|
||||
fg-update-translation-files --transl-dir=... remove-unused
|
||||
|
||||
(note that this will remove *all* strings marked as unused in the first
|
||||
step, not only those in some particular category!)
|
186
python3-flightgear/fg-convert-translation-files
Executable file
186
python3-flightgear/fg-convert-translation-files
Executable file
|
@ -0,0 +1,186 @@
|
|||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# fg-convert-translation-files --- Convert FlightGear's translation files
|
||||
# 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 collections
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import xml.etree.ElementTree as et
|
||||
except ImportError:
|
||||
import elementtree.ElementTree as et
|
||||
|
||||
import flightgear.meta.logging
|
||||
import flightgear.meta.i18n as fg_i18n
|
||||
|
||||
|
||||
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)
|
||||
|
||||
debug = logger.debug
|
||||
info = logger.info
|
||||
notice = logger.notice
|
||||
warning = logger.warning
|
||||
error = logger.error
|
||||
critical = logger.critical
|
||||
|
||||
|
||||
# We could use Translation.__str__(): not as readable (for now) but more
|
||||
# accurate on metadata
|
||||
def printPlainText(l10nResPoolMgr, translations):
|
||||
"""Print output suitable for a quick review (by the programmer)."""
|
||||
firstLang = True
|
||||
|
||||
for langCode, (transl, nbWhitespacePbs) in translations.items():
|
||||
# 'transl' is a Translation instance
|
||||
if firstLang:
|
||||
firstLang = False
|
||||
else:
|
||||
print()
|
||||
|
||||
print("-" * 78 + "\n" + langCode + "\n" + "-" * 78)
|
||||
print("\nNumber of leading and/or trailing whitespace problems: {}"
|
||||
.format(nbWhitespacePbs))
|
||||
|
||||
for cat in transl:
|
||||
print("\nCategory: {cat}\n{underline}".format(
|
||||
cat=cat, underline="~"*(len("Category: ") + len(cat))))
|
||||
t = transl[cat]
|
||||
|
||||
for tid, translUnit in sorted(t.items()):
|
||||
# - Using '{master!r}' and '{transl!r}' prints stuff such as
|
||||
# \xa0 for nobreak spaces, which can lead to the erroneous
|
||||
# conclusion that there was an encoding problem.
|
||||
# - Only printing the first target text here (no plural forms)
|
||||
print("\n{id}\n '{sourceText}'\n '{targetText}'"
|
||||
.format(id=tid.id(), sourceText=translUnit.sourceText,
|
||||
targetText=translUnit.targetTexts[0]))
|
||||
|
||||
|
||||
def writeXliff(l10nResPoolMgr, translations):
|
||||
formatHandler = fg_i18n.XliffFormatHandler()
|
||||
|
||||
for langCode, translData in translations.items():
|
||||
translation = translData.transl # Translation instance
|
||||
|
||||
if params.output_dir is None:
|
||||
# Use default locations for the written xliff files
|
||||
l10nResPoolMgr.writeTranslation(formatHandler, translation)
|
||||
else:
|
||||
basename = "{}-{}.{}".format(
|
||||
formatHandler.defaultFileStem(langCode),
|
||||
langCode,
|
||||
formatHandler.standardExtension)
|
||||
filePath = os.path.join(params.output_dir, basename)
|
||||
formatHandler.writeTranslation(translation, filePath)
|
||||
|
||||
|
||||
def processCommandLine():
|
||||
params = argparse.Namespace()
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
usage="""\
|
||||
%(prog)s [OPTION ...] LANGUAGE_CODE...
|
||||
Convert FlightGear's old XML translation files into other formats.""",
|
||||
description="""\
|
||||
Most notably, XLIFF format can be chosen for output. The script performs
|
||||
a few automated checks on the input files too.""",
|
||||
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("lang_code", metavar="LANGUAGE_CODE", nargs="+",
|
||||
help="""\
|
||||
codes of languages to read translations for (don't
|
||||
specify {default!r} this way, it is special and not a
|
||||
language code)"""
|
||||
.format(default=fg_i18n.DEFAULT_LANG_DIR))
|
||||
parser.add_argument("-o", "--output-dir",
|
||||
help="""\
|
||||
output directory for written XLIFF files
|
||||
(default: for each output file, use a suitable location
|
||||
under TRANSL_DIR)""")
|
||||
parser.add_argument("-f", "--output-format", default="xliff",
|
||||
choices=("xliff", "text"), help="""\
|
||||
format to use for the output files""")
|
||||
parser.add_argument("--help", action="help",
|
||||
help="display this message and exit")
|
||||
|
||||
params = parser.parse_args(namespace=params)
|
||||
|
||||
if params.transl_dir is None:
|
||||
error("--transl-dir must be given, aborting")
|
||||
sys.exit(1)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def main():
|
||||
global params
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
params = processCommandLine()
|
||||
|
||||
l10nResPoolMgr = fg_i18n.L10NResourcePoolManager(params.transl_dir, logger)
|
||||
# English version of all translatable strings
|
||||
masterTransl, nbWhitespaceProblemsInMaster = \
|
||||
l10nResPoolMgr.readFgMasterTranslation()
|
||||
translations = collections.OrderedDict()
|
||||
|
||||
# Sort elements of 'translations' according to language code (= the keys)
|
||||
for langCode in sorted(params.lang_code):
|
||||
translationData = l10nResPoolMgr.readFgTranslation(masterTransl,
|
||||
langCode)
|
||||
translations[translationData.transl.targetLanguage] = translationData
|
||||
|
||||
if params.output_format == "xliff":
|
||||
writeFunc = writeXliff # write to files
|
||||
elif params.output_format == "text":
|
||||
writeFunc = printPlainText # print to stdout
|
||||
else:
|
||||
assert False, \
|
||||
"Unexpected output format: '{}'".format(params.output_format)
|
||||
|
||||
writeFunc(l10nResPoolMgr, translations)
|
||||
|
||||
nbWhitespaceProblemsInTransl = sum(
|
||||
(translData.nbWhitespacePbs for translData in translations.values() ))
|
||||
info("total number of leading and/or trailing whitespace problems: {}"
|
||||
.format(nbWhitespaceProblemsInMaster + nbWhitespaceProblemsInTransl))
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__": main()
|
125
python3-flightgear/fg-new-translations
Executable file
125
python3-flightgear/fg-new-translations
Executable file
|
@ -0,0 +1,125 @@
|
|||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# fg-new-translations --- Create new translations for FlightGear
|
||||
# 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 collections
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import xml.etree.ElementTree as et
|
||||
except ImportError:
|
||||
import elementtree.ElementTree as et
|
||||
|
||||
import flightgear.meta.logging
|
||||
import flightgear.meta.i18n as fg_i18n
|
||||
|
||||
|
||||
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="""\
|
||||
%(prog)s [OPTION ...] LANGUAGE_CODE...
|
||||
Write the skeleton of XLIFF translation files.""",
|
||||
description="""\
|
||||
This program writes XLIFF translation files with the strings to translate
|
||||
for the specified languages (target strings are empty). This is what you need
|
||||
to start a translation for a new language.""",
|
||||
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("lang_code", metavar="LANGUAGE_CODE", nargs="+",
|
||||
help="""\
|
||||
codes of languages to create translations for (e.g., fr,
|
||||
fr_BE, en_GB, it, es_ES...)""")
|
||||
parser.add_argument("-o", "--output-file",
|
||||
help="""\
|
||||
where to write the output to (use '-' for standard
|
||||
output); if not specified, a suitable file under
|
||||
TRANSL_DIR will be chosen for each LANGUAGE_CODE.
|
||||
Note: this option can only be given when exactly one
|
||||
LANGUAGE_CODE has been specified on the command
|
||||
line (it doesn't make sense otherwise).""")
|
||||
parser.add_argument("--output-format", default="xliff",
|
||||
choices=fg_i18n.FORMAT_HANDLERS_NAMES,
|
||||
help="format to use for the output files")
|
||||
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)
|
||||
|
||||
if params.output_file is not None and len(params.lang_code) > 1:
|
||||
logger.error("--output-file can only be given when exactly one "
|
||||
"LANGUAGE_CODE has been specified on the command line "
|
||||
"(it doesn't make sense otherwise)")
|
||||
sys.exit(1)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def main():
|
||||
global params
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
params = processCommandLine()
|
||||
|
||||
l10nResPoolMgr = fg_i18n.L10NResourcePoolManager(params.transl_dir, logger)
|
||||
xliffFormatHandler = fg_i18n.FORMAT_HANDLERS_MAP[params.output_format]()
|
||||
|
||||
if params.output_file is not None:
|
||||
assert len(params.lang_code) == 1, params.lang_code
|
||||
# Output to one file or to stdout
|
||||
l10nResPoolMgr.writeSkeletonTranslation(
|
||||
xliffFormatHandler, params.lang_code[0],
|
||||
filePath=params.output_file)
|
||||
else:
|
||||
# Output to several files
|
||||
for langCode in params.lang_code:
|
||||
l10nResPoolMgr.writeSkeletonTranslation(xliffFormatHandler,
|
||||
langCode)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__": main()
|
184
python3-flightgear/fg-update-translation-files
Executable file
184
python3-flightgear/fg-update-translation-files
Executable file
|
@ -0,0 +1,184 @@
|
|||
#! /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
|
||||
|
||||
try:
|
||||
import xml.etree.ElementTree as et
|
||||
except ImportError:
|
||||
import elementtree.ElementTree as et
|
||||
|
||||
import flightgear.meta.logging
|
||||
import flightgear.meta.i18n as fg_i18n
|
||||
|
||||
|
||||
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="""\
|
||||
%(prog)s [OPTION ...] ACTION LANGUAGE_CODE...
|
||||
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.
|
||||
|
||||
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)""")
|
||||
parser.add_argument("lang_code", metavar="LANGUAGE_CODE", nargs="+",
|
||||
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)
|
||||
|
||||
|
||||
def markOrRemoveUnused(l10nResPoolMgr, action):
|
||||
formatHandler = fg_i18n.XliffFormatHandler()
|
||||
masterTransl = l10nResPoolMgr.readFgMasterTranslation().transl
|
||||
|
||||
for langCode in params.lang_code:
|
||||
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
|
||||
|
||||
for langCode in params.lang_code:
|
||||
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()
|
0
python3-flightgear/flightgear/__init__.py
Normal file
0
python3-flightgear/flightgear/__init__.py
Normal file
0
python3-flightgear/flightgear/meta/__init__.py
Normal file
0
python3-flightgear/flightgear/meta/__init__.py
Normal file
58
python3-flightgear/flightgear/meta/exceptions.py
Normal file
58
python3-flightgear/flightgear/meta/exceptions.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# exceptions.py --- Simple, general-purpose subclass of Exception
|
||||
#
|
||||
# Copyright (C) 2015, 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.
|
||||
|
||||
"""Simple, general-purpose Exception subclass."""
|
||||
|
||||
|
||||
class FGPyException(Exception):
|
||||
def __init__(self, message=None, *, mayCapitalizeMsg=True):
|
||||
"""Initialize an FGPyException instance.
|
||||
|
||||
Except in cases where 'message' starts with a proper noun or
|
||||
something like that, its first character should be given in
|
||||
lower case. Automated treatments of this exception may print the
|
||||
message with its first character changed to upper case, unless
|
||||
'mayCapitalizeMsg' is False. In other words, if the case of the
|
||||
first character of 'message' must not be changed under any
|
||||
circumstances, set 'mayCapitalizeMsg' to False.
|
||||
|
||||
"""
|
||||
self.message = message
|
||||
self.mayCapitalizeMsg = mayCapitalizeMsg
|
||||
|
||||
def __str__(self):
|
||||
return self.completeMessage()
|
||||
|
||||
def __repr__(self):
|
||||
return "{}.{}({!r})".format(__name__, type(self).__name__, self.message)
|
||||
|
||||
# Typically overridden by subclasses with a custom constructor
|
||||
def detail(self):
|
||||
return self.message
|
||||
|
||||
def completeMessage(self):
|
||||
if self.message:
|
||||
return "{shortDesc}: {detail}".format(
|
||||
shortDesc=self.ExceptionShortDescription,
|
||||
detail=self.detail())
|
||||
else:
|
||||
return self.ExceptionShortDescription
|
||||
|
||||
ExceptionShortDescription = "FlightGear Python generic exception"
|
1822
python3-flightgear/flightgear/meta/i18n.py
Normal file
1822
python3-flightgear/flightgear/meta/i18n.py
Normal file
File diff suppressed because it is too large
Load diff
95
python3-flightgear/flightgear/meta/logging.py
Normal file
95
python3-flightgear/flightgear/meta/logging.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# logging.py --- Simple logging infrastructure (mostly taken from FFGo)
|
||||
# Copyright (C) 2015, 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 sys
|
||||
|
||||
from . import misc
|
||||
|
||||
|
||||
class LogLevel(misc.OrderedEnum):
|
||||
debug, info, notice, warning, error, critical = range(6)
|
||||
|
||||
# List containing the above log levels as strings in increasing priority order
|
||||
allLogLevels = [member.name for member in LogLevel]
|
||||
allLogLevels.sort(key=lambda n: LogLevel[n].value)
|
||||
|
||||
|
||||
def _logFuncFactory(level):
|
||||
def logFunc(self, *args, **kwargs):
|
||||
self.log(LogLevel[level], True, *args, **kwargs)
|
||||
|
||||
def logFunc_noPrefix(self, *args, **kwargs):
|
||||
self.log(LogLevel[level], False, *args, **kwargs)
|
||||
|
||||
return (logFunc, logFunc_noPrefix)
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, progname=None, logLevel=LogLevel.notice,
|
||||
defaultOutputStream=sys.stdout, logFile=None):
|
||||
self.progname = progname
|
||||
self.logLevel = logLevel
|
||||
self.defaultOutputStream = defaultOutputStream
|
||||
self.logFile = logFile
|
||||
|
||||
def setLogFile(self, *args, **kwargs):
|
||||
self.logFile = open(*args, **kwargs)
|
||||
|
||||
def log(self, level, printLogLevel, *args, **kwargs):
|
||||
if printLogLevel and level >= LogLevel.warning and args:
|
||||
args = [level.name.upper() + ": " + args[0]] + list(args[1:])
|
||||
|
||||
if level >= self.logLevel:
|
||||
if (self.progname is not None) and args:
|
||||
tArgs = [self.progname + ": " + args[0]] + list(args[1:])
|
||||
else:
|
||||
tArgs = args
|
||||
|
||||
kwargs["file"] = self.defaultOutputStream
|
||||
print(*tArgs, **kwargs)
|
||||
|
||||
if self.logFile is not None:
|
||||
kwargs["file"] = self.logFile
|
||||
print(*args, **kwargs)
|
||||
|
||||
# Don't overload log() with too many tests or too much indirection for
|
||||
# little use
|
||||
def logToFile(self, *args, **kwargs):
|
||||
kwargs["file"] = self.logFile
|
||||
print(*args, **kwargs)
|
||||
|
||||
# NP functions are “no prefix” variants which never prepend the log level
|
||||
# (otherwise, it is only prepended for warning and higher levels).
|
||||
debug, debugNP = _logFuncFactory("debug")
|
||||
info, infoNP = _logFuncFactory("info")
|
||||
notice, noticeNP = _logFuncFactory("notice")
|
||||
warning, warningNP = _logFuncFactory("warning")
|
||||
error, errorNP = _logFuncFactory("error")
|
||||
critical, criticalNP = _logFuncFactory("critical")
|
||||
|
||||
|
||||
class DummyLogger(Logger):
|
||||
def setLogFile(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def log(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def logToFile(self, *args, **kwargs):
|
||||
pass
|
81
python3-flightgear/flightgear/meta/misc.py
Normal file
81
python3-flightgear/flightgear/meta/misc.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# misc.py --- Miscellaneous classes and/or functions
|
||||
# Copyright (C) 2015-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 enum
|
||||
|
||||
# Based on an example from the 'enum' documentation
|
||||
class OrderedEnum(enum.Enum):
|
||||
"""Base class for enumerations whose members can be ordered.
|
||||
|
||||
Contrary to enum.IntEnum, this class maintains normal enum.Enum
|
||||
invariants, such as members not being comparable to members of other
|
||||
enumerations (nor of any other class, actually).
|
||||
|
||||
"""
|
||||
def __ge__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value >= other.value
|
||||
return NotImplemented
|
||||
|
||||
def __gt__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value > other.value
|
||||
return NotImplemented
|
||||
|
||||
def __le__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value <= other.value
|
||||
return NotImplemented
|
||||
|
||||
def __lt__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value < other.value
|
||||
return NotImplemented
|
||||
|
||||
def __eq__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value == other.value
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value != other.value
|
||||
return NotImplemented
|
||||
|
||||
|
||||
# Taken from <http://effbot.org/zone/element-lib.htm#prettyprint> and modified
|
||||
# by Florent Rougon
|
||||
def indentXmlTree(elem, level=0, basicOffset=2, lastChild=False):
|
||||
def indentation(level):
|
||||
return "\n" + level*basicOffset*" "
|
||||
|
||||
if len(elem):
|
||||
if not elem.text or not elem.text.strip():
|
||||
elem.text = indentation(level+1)
|
||||
|
||||
for e in elem[:-1]:
|
||||
indentXmlTree(e, level+1, basicOffset, False)
|
||||
if len(elem):
|
||||
indentXmlTree(elem[-1], level+1, basicOffset, True)
|
||||
|
||||
if level and (not elem.tail or not elem.tail.strip()):
|
||||
if lastChild:
|
||||
elem.tail = indentation(level-1)
|
||||
else:
|
||||
elem.tail = indentation(level)
|
Loading…
Add table
Reference in a new issue