Skip to content

Commit 739724f

Browse files
committed
Added a helper script for managing django translations
1 parent 7cb0cd5 commit 739724f

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

scripts/manage_translations.py

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#!/usr/bin/env python
2+
#
3+
# This python file contains utility scripts to manage Django translations.
4+
# It has to be run inside the django git root directory.
5+
#
6+
# The following commands are available:
7+
#
8+
# * update_catalogs: check for new strings in core and contrib catalogs, and
9+
# output how much strings are new/changed.
10+
#
11+
# * lang_stats: output statistics for each catalog/language combination
12+
#
13+
# * fetch: fetch translations from transifex.com
14+
#
15+
# Each command support the --languages and --resources options to limit their
16+
# operation to the specified language or resource. For example, to get stats
17+
# for Spanish in contrib.admin, run:
18+
#
19+
# $ python scripts/manage_translations.py lang_stats --language=es --resources=admin
20+
21+
import os
22+
from optparse import OptionParser
23+
from subprocess import call, Popen, PIPE
24+
25+
from django.core.management import call_command
26+
27+
28+
HAVE_JS = ['admin']
29+
30+
def _get_locale_dirs(include_core=True):
31+
"""
32+
Return a tuple (contrib name, absolute path) for all locale directories,
33+
optionally including the django core catalog.
34+
"""
35+
contrib_dir = os.path.join(os.getcwd(), 'django', 'contrib')
36+
dirs = []
37+
for contrib_name in os.listdir(contrib_dir):
38+
path = os.path.join(contrib_dir, contrib_name, 'locale')
39+
if os.path.isdir(path):
40+
dirs.append((contrib_name, path))
41+
if contrib_name in HAVE_JS:
42+
dirs.append(("%s-js" % contrib_name, path))
43+
if include_core:
44+
dirs.insert(0, ('core', os.path.join(os.getcwd(), 'django', 'conf', 'locale')))
45+
return dirs
46+
47+
def _tx_resource_for_name(name):
48+
""" Return the Transifex resource name """
49+
if name == 'core':
50+
return "django.core"
51+
else:
52+
return "django.contrib-%s" % name
53+
54+
def _check_diff(cat_name, base_path):
55+
"""
56+
Output the approximate number of changed/added strings in the en catalog.
57+
"""
58+
po_path = '%(path)s/en/LC_MESSAGES/django%(ext)s.po' % {
59+
'path': base_path, 'ext': 'js' if cat_name.endswith('-js') else ''}
60+
p = Popen("git diff -U0 %s | egrep -v '^@@|^[-+]#|^..POT-Creation' | wc -l" % po_path,
61+
stdout=PIPE, stderr=PIPE, shell=True)
62+
output, errors = p.communicate()
63+
num_changes = int(output.strip()) - 4
64+
print("%d changed/added messages in '%s' catalog." % (num_changes, cat_name))
65+
66+
67+
def update_catalogs(resources=None, languages=None):
68+
"""
69+
Update the en/LC_MESSAGES/django.po (main and contrib) files with
70+
new/updated translatable strings.
71+
"""
72+
contrib_dirs = _get_locale_dirs(include_core=False)
73+
74+
os.chdir(os.path.join(os.getcwd(), 'django'))
75+
print("Updating main en catalog")
76+
call_command('makemessages', locale='en')
77+
_check_diff('core', os.path.join(os.getcwd(), 'conf', 'locale'))
78+
79+
# Contrib catalogs
80+
for name, dir_ in contrib_dirs:
81+
if resources and not name in resources:
82+
continue
83+
os.chdir(os.path.join(dir_, '..'))
84+
print("Updating en catalog in %s" % dir_)
85+
if name.endswith('-js'):
86+
call_command('makemessages', locale='en', domain='djangojs')
87+
else:
88+
call_command('makemessages', locale='en')
89+
_check_diff(name, dir_)
90+
91+
92+
def lang_stats(resources=None, languages=None):
93+
"""
94+
Output language statistics of committed translation files for each
95+
Django catalog.
96+
If resources is provided, it should be a list of translation resource to
97+
limit the output (e.g. ['core', 'gis']).
98+
"""
99+
locale_dirs = _get_locale_dirs()
100+
101+
for name, dir_ in locale_dirs:
102+
if resources and not name in resources:
103+
continue
104+
print("\nShowing translations stats for '%s':" % name)
105+
langs = sorted([d for d in os.listdir(dir_) if not d.startswith('_')])
106+
for lang in langs:
107+
if languages and not lang in languages:
108+
continue
109+
# TODO: merge first with the latest en catalog
110+
p = Popen("msgfmt -vc -o /dev/null %(path)s/%(lang)s/LC_MESSAGES/django%(ext)s.po" % {
111+
'path': dir_, 'lang': lang, 'ext': 'js' if name.endswith('-js') else ''},
112+
stdout=PIPE, stderr=PIPE, shell=True)
113+
output, errors = p.communicate()
114+
if p.returncode == 0:
115+
# msgfmt output stats on stderr
116+
print("%s: %s" % (lang, errors.strip()))
117+
118+
119+
def fetch(resources=None, languages=None):
120+
"""
121+
Fetch translations from Transifex, wrap long lines, generate mo files.
122+
"""
123+
locale_dirs = _get_locale_dirs()
124+
125+
for name, dir_ in locale_dirs:
126+
if resources and not name in resources:
127+
continue
128+
129+
# Transifex pull
130+
if languages is None:
131+
call('tx pull -r %(res)s -a -f' % {'res': _tx_resource_for_name(name)}, shell=True)
132+
languages = sorted([d for d in os.listdir(dir_) if not d.startswith('_')])
133+
else:
134+
for lang in languages:
135+
call('tx pull -r %(res)s -f -l %(lang)s' % {
136+
'res': _tx_resource_for_name(name), 'lang': lang}, shell=True)
137+
138+
# msgcat to wrap lines and msgfmt for compilation of .mo file
139+
for lang in languages:
140+
po_path = '%(path)s/%(lang)s/LC_MESSAGES/django%(ext)s.po' % {
141+
'path': dir_, 'lang': lang, 'ext': 'js' if name.endswith('-js') else ''}
142+
call('msgcat -o %s %s' % (po_path, po_path), shell=True)
143+
mo_path = '%s.mo' % po_path[:-3]
144+
call('msgfmt -o %s %s' % (mo_path, po_path), shell=True)
145+
146+
147+
if __name__ == "__main__":
148+
RUNABLE_SCRIPTS = ('update_catalogs', 'lang_stats', 'fetch')
149+
150+
parser = OptionParser(usage="usage: %prog [options] cmd")
151+
parser.add_option("-r", "--resources", action='append',
152+
help="limit operation to the specified resources")
153+
parser.add_option("-l", "--languages", action='append',
154+
help="limit operation to the specified languages")
155+
options, args = parser.parse_args()
156+
157+
if not args:
158+
parser.print_usage()
159+
exit(1)
160+
161+
if args[0] in RUNABLE_SCRIPTS:
162+
eval(args[0])(options.resources, options.languages)
163+
else:
164+
print("Available commands are: %s" % ", ".join(RUNABLE_SCRIPTS))

0 commit comments

Comments
 (0)