Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/build_config/obsoleteTranslations.py
169674 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2011-2025 German Aerospace Center (DLR) and others.
4
# This program and the accompanying materials are made available under the
5
# terms of the Eclipse Public License 2.0 which is available at
6
# https://www.eclipse.org/legal/epl-2.0/
7
# This Source Code may also be made available under the following Secondary
8
# Licenses when the conditions for such availability set forth in the Eclipse
9
# Public License 2.0 are satisfied: GNU General Public License, version 2
10
# or later which is available at
11
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
14
# @file obsoleteTranslations.py
15
# @author Mirko Barthauer
16
# @date 2023-07-04
17
18
"""
19
Collect information about lost translations due to changes in the original gettext msgid.
20
"""
21
from __future__ import absolute_import
22
from __future__ import print_function
23
import os
24
import io
25
import polib
26
import i18n
27
from glob import glob
28
from argparse import ArgumentParser
29
30
SUMO_HOME = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
31
SUMO_LIBRARIES = os.environ.get("SUMO_LIBRARIES", os.path.join(os.path.dirname(SUMO_HOME), "SUMOLibraries"))
32
33
34
def getOptions(args=None):
35
ap = ArgumentParser()
36
ap.add_argument("-l", "--lang", nargs='*', help="languages to process (using the short codes like fr, de)")
37
ap.add_argument("--sumo-home", default=SUMO_HOME, help="SUMO root directory to use")
38
ap.add_argument("-o", "--output", type=str, help="path to output file (protocol of obsolete translations)")
39
ap.add_argument("--clear", default=False, action="store_true", help="remove obsolete entries from .po files")
40
ap.add_argument("--patch", nargs="*", type=str,
41
help="restore obsolete (but still present) translations with sequence of the original \
42
(odd position) and then the new string (even position) from the source code (= gettext msgid)")
43
options = ap.parse_args(args)
44
if options.lang is not None and "en" in options.lang:
45
options.lang.remove("en")
46
return options
47
48
49
def main(args=None):
50
options = getOptions(args)
51
if options.lang is None:
52
options.lang = [os.path.basename(p)[:-8] for p in glob(options.sumo_home + "/data/po/*_sumo.po")]
53
if options.patch is not None and len(options.patch) > 0 and len(options.patch) % 2 == 1:
54
print("The new string replacing the old msgid '%s' has not been given - the translation \
55
cannot be patched." % options.patch[-1])
56
# run i18n.py to update the translation files
57
args = ['--sumo-home', options.sumo_home, '--lang']
58
args.extend(options.lang)
59
print("Run i18n.py ...")
60
i18n.main(args=args)
61
pot_file = options.sumo_home + "/data/po/sumo.pot"
62
gui_pot_file = options.sumo_home + "/data/po/gui.pot"
63
py_pot_file = options.sumo_home + "/data/po/py.pot"
64
potFiles = [pot_file, gui_pot_file, py_pot_file]
65
if options.output is not None:
66
if os.path.exists(options.output):
67
os.remove(options.output) # clear output file
68
for potFile in potFiles:
69
print("Check pot file '%s'..." % potFile)
70
checkPotFile(potFile, options)
71
72
73
def checkPotFile(potFile, options):
74
# loop through translated po files and collect the obsolete translations
75
result = {}
76
for langCode in options.lang:
77
poFilePath = os.path.join(os.path.dirname(potFile), "%s_" % langCode + os.path.basename(potFile)[:-1])
78
if not os.path.exists(poFilePath):
79
continue
80
po = polib.pofile(poFilePath)
81
obsoleteEntries = po.obsolete_entries()
82
for entry in obsoleteEntries:
83
if entry.msgid not in result:
84
result[entry.msgid] = []
85
result[entry.msgid].append(langCode)
86
# optionally patch obsolete entry with updated msgid
87
patched = False
88
if options.patch is not None:
89
entriesToRemove = []
90
for i in range(0, len(options.patch), 2):
91
if options.patch[i] in result:
92
updatedEntry = po.find(options.patch[i+1])
93
translatedEntry = po.find(options.patch[i], include_obsolete_entries=True)
94
if updatedEntry is not None and translatedEntry is not None:
95
if len(updatedEntry.msgstr) == 0:
96
updatedEntry.msgstr = translatedEntry.msgstr
97
patched = True
98
entriesToRemove.append(translatedEntry)
99
print("Patched '%s' for %s" % (options.patch[i], langCode))
100
else:
101
print("Has already been translated again: '%s' > '%s'"
102
% (options.patch[i+1], updatedEntry.msgstr))
103
for entry in entriesToRemove:
104
po.remove(entry)
105
# optionally overwrite obsolete entries completely
106
if options.clear:
107
print("Removing obsolete entries from %s..." % poFilePath)
108
for entry in obsoleteEntries:
109
po.remove(entry)
110
if options.clear or patched:
111
po.save(poFilePath)
112
if options.output is not None: # write protocol
113
with io.open(options.output, "a", encoding="utf-8") as f:
114
for msgid, langCodes in result.items():
115
f.write("msgid \"%s\" has obsolete translations: %s\n" % (msgid, ', '.join(langCodes)))
116
117
118
if __name__ == "__main__":
119
main()
120
121