Commit 31b64ad5 by David Baumgold

Merge pull request #2933 from edx/cale/quieter-i18n-tests

Make i18n tests quieter
parents 2d6bfbad d93238d8
import re import re
import itertools import itertools
class Converter(object): class Converter(object):
"""Converter is an abstract class that transforms strings. """Converter is an abstract class that transforms strings.
It hides embedded tags (HTML or Python sequences) from transformation It hides embedded tags (HTML or Python sequences) from transformation
...@@ -20,7 +21,8 @@ class Converter(object): ...@@ -20,7 +21,8 @@ class Converter(object):
# matches tags like these: # matches tags like these:
# HTML: <B>, </B>, <BR/>, <textformat leading="10"> # HTML: <B>, </B>, <BR/>, <textformat leading="10">
# Python: %(date)s, %(name)s # Python: %(date)s, %(name)s
tag_pattern = re.compile(r''' tag_pattern = re.compile(
r'''
(<[^>]+>) | # <tag> (<[^>]+>) | # <tag>
({[^}]+}) | # {tag} ({[^}]+}) | # {tag}
(%\([\w]+\)\w) | # %(tag)s (%\([\w]+\)\w) | # %(tag)s
...@@ -28,7 +30,7 @@ class Converter(object): ...@@ -28,7 +30,7 @@ class Converter(object):
(&\#\d+;) | # &#1234; (&\#\d+;) | # &#1234;
(&\#x[0-9a-f]+;) # &#xABCD; (&\#x[0-9a-f]+;) # &#xABCD;
''', ''',
re.IGNORECASE|re.VERBOSE re.IGNORECASE | re.VERBOSE
) )
def convert(self, string): def convert(self, string):
...@@ -55,7 +57,7 @@ class Converter(object): ...@@ -55,7 +57,7 @@ class Converter(object):
tags = [''.join(tag) for tag in tags] tags = [''.join(tag) for tag in tags]
(new, nfound) = self.tag_pattern.subn(count, string) (new, nfound) = self.tag_pattern.subn(count, string)
if len(tags) != nfound: if len(tags) != nfound:
raise Exception('tags dont match:'+string) raise Exception('tags dont match:' + string)
return (new, tags) return (new, tags)
def retag_string(self, string, tags): def retag_string(self, string, tags):
...@@ -65,7 +67,6 @@ class Converter(object): ...@@ -65,7 +67,6 @@ class Converter(object):
string = re.sub(p, tag, string, 1) string = re.sub(p, tag, string, 1)
return string return string
# ------------------------------ # ------------------------------
# Customize this in subclasses of Converter # Customize this in subclasses of Converter
......
...@@ -22,15 +22,15 @@ $ ./dummy.py ...@@ -22,15 +22,15 @@ $ ./dummy.py
generates output conf/locale/$DUMMY_LOCALE/LC_MESSAGES, generates output conf/locale/$DUMMY_LOCALE/LC_MESSAGES,
where $DUMMY_LOCALE is the dummy_locale value set in the i18n config where $DUMMY_LOCALE is the dummy_locale value set in the i18n config
""" """
from __future__ import print_function
import re import re
import sys import sys
import argparse
import polib import polib
from path import path from path import path
from i18n.config import CONFIGURATION from i18n.config import CONFIGURATION
from i18n.execute import create_dir_if_necessary
from i18n.converter import Converter from i18n.converter import Converter
...@@ -186,7 +186,7 @@ def make_dummy(filename, locale, converter): ...@@ -186,7 +186,7 @@ def make_dummy(filename, locale, converter):
pofile.metadata['Plural-Forms'] = 'nplurals=2; plural=(n != 1);' pofile.metadata['Plural-Forms'] = 'nplurals=2; plural=(n != 1);'
new_file = new_filename(filename, locale) new_file = new_filename(filename, locale)
create_dir_if_necessary(new_file) new_file.parent.makedirs_p()
pofile.save(new_file) pofile.save(new_file)
...@@ -197,18 +197,25 @@ def new_filename(original_filename, new_locale): ...@@ -197,18 +197,25 @@ def new_filename(original_filename, new_locale):
return new_file.abspath() return new_file.abspath()
def main(): def main(verbosity=1):
""" """
Generate dummy strings for all source po files. Generate dummy strings for all source po files.
""" """
SOURCE_MSGS_DIR = CONFIGURATION.source_messages_dir SOURCE_MSGS_DIR = CONFIGURATION.source_messages_dir
for locale, converter in zip(CONFIGURATION.dummy_locales, [Dummy(), Dummy2()]): for locale, converter in zip(CONFIGURATION.dummy_locales, [Dummy(), Dummy2()]):
print "Processing source language files into dummy strings, locale {}:".format(locale) if verbosity:
print('Processing source language files into dummy strings, locale "{}"'.format(locale))
for source_file in CONFIGURATION.source_messages_dir.walkfiles('*.po'): for source_file in CONFIGURATION.source_messages_dir.walkfiles('*.po'):
print ' ', source_file.relpath() if verbosity:
print(' ', source_file.relpath())
make_dummy(SOURCE_MSGS_DIR.joinpath(source_file), locale, converter) make_dummy(SOURCE_MSGS_DIR.joinpath(source_file), locale, converter)
print if verbosity:
print()
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) # pylint: disable=invalid-name
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--verbose", "-v", action="count", default=0)
args = parser.parse_args()
main(verbosity=args.verbose)
import os, subprocess, logging """
Utility library file for executing shell commands
"""
import os
import subprocess
import logging
from i18n.config import BASE_DIR from i18n.config import BASE_DIR
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def execute(command, working_directory=BASE_DIR):
def execute(command, working_directory=BASE_DIR, stderr=subprocess.STDOUT):
""" """
Executes shell command in a given working_directory. Executes shell command in a given working_directory.
Command is a string to pass to the shell. Command is a string to pass to the shell.
...@@ -12,7 +18,7 @@ def execute(command, working_directory=BASE_DIR): ...@@ -12,7 +18,7 @@ def execute(command, working_directory=BASE_DIR):
""" """
LOG.info("Executing in %s ...", working_directory) LOG.info("Executing in %s ...", working_directory)
LOG.info(command) LOG.info(command)
subprocess.check_call(command, cwd=working_directory, stderr=subprocess.STDOUT, shell=True) subprocess.check_call(command, cwd=working_directory, stderr=stderr, shell=True)
def call(command, working_directory=BASE_DIR): def call(command, working_directory=BASE_DIR):
...@@ -28,12 +34,6 @@ def call(command, working_directory=BASE_DIR): ...@@ -28,12 +34,6 @@ def call(command, working_directory=BASE_DIR):
return (out, err) return (out, err)
def create_dir_if_necessary(pathname):
dirname = os.path.dirname(pathname)
if not os.path.exists(dirname):
os.makedirs(dirname)
def remove_file(filename, verbose=True): def remove_file(filename, verbose=True):
""" """
Attempt to delete filename. Attempt to delete filename.
......
...@@ -21,49 +21,68 @@ import os ...@@ -21,49 +21,68 @@ import os
import os.path import os.path
import logging import logging
import sys import sys
import argparse
from path import path from path import path
from polib import pofile from polib import pofile
from i18n.config import BASE_DIR, LOCALE_DIR, CONFIGURATION from i18n.config import BASE_DIR, LOCALE_DIR, CONFIGURATION
from i18n.execute import execute, create_dir_if_necessary, remove_file from i18n.execute import execute, remove_file
from i18n.segment import segment_pofiles from i18n.segment import segment_pofiles
EDX_MARKER = "edX translation file" EDX_MARKER = "edX translation file"
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
DEVNULL = open(os.devnull, 'wb')
def base(path1, *paths): def base(path1, *paths):
"""Return a relative path from BASE_DIR to path1 / paths[0] / ... """ """Return a relative path from BASE_DIR to path1 / paths[0] / ... """
return BASE_DIR.relpathto(path1.joinpath(*paths)) return BASE_DIR.relpathto(path1.joinpath(*paths))
def main():
def main(verbosity=1):
"""
Main entry point of script
"""
logging.basicConfig(stream=sys.stdout, level=logging.INFO) logging.basicConfig(stream=sys.stdout, level=logging.INFO)
create_dir_if_necessary(LOCALE_DIR) LOCALE_DIR.parent.makedirs_p()
source_msgs_dir = CONFIGURATION.source_messages_dir source_msgs_dir = CONFIGURATION.source_messages_dir
remove_file(source_msgs_dir.joinpath('django.po')) remove_file(source_msgs_dir.joinpath('django.po'))
# Extract strings from mako templates. # Extract strings from mako templates.
babel_mako_cmd = 'pybabel extract -F {config} -c "Translators:" . -o {output}' verbosity_map = {
0: "-q",
1: "",
2: "-v",
}
babel_verbosity = verbosity_map.get(verbosity, "")
babel_mako_cmd = 'pybabel {verbosity} extract -F {config} -c "Translators:" . -o {output}'
babel_mako_cmd = babel_mako_cmd.format( babel_mako_cmd = babel_mako_cmd.format(
verbosity=babel_verbosity,
config=base(LOCALE_DIR, 'babel_mako.cfg'), config=base(LOCALE_DIR, 'babel_mako.cfg'),
output=base(CONFIGURATION.source_messages_dir, 'mako.po'), output=base(CONFIGURATION.source_messages_dir, 'mako.po'),
) )
execute(babel_mako_cmd, working_directory=BASE_DIR) if verbosity:
stderr = None
else:
stderr = DEVNULL
makemessages = "django-admin.py makemessages -l en" execute(babel_mako_cmd, working_directory=BASE_DIR, stderr=stderr)
makemessages = "django-admin.py makemessages -l en -v{}".format(verbosity)
ignores = " ".join('--ignore="{}/*"'.format(d) for d in CONFIGURATION.ignore_dirs) ignores = " ".join('--ignore="{}/*"'.format(d) for d in CONFIGURATION.ignore_dirs)
if ignores: if ignores:
makemessages += " " + ignores makemessages += " " + ignores
# Extract strings from django source files, including .py files. # Extract strings from django source files, including .py files.
make_django_cmd = makemessages + ' --extension html' make_django_cmd = makemessages + ' --extension html'
execute(make_django_cmd, working_directory=BASE_DIR) execute(make_django_cmd, working_directory=BASE_DIR, stderr=stderr)
# Extract strings from Javascript source files. # Extract strings from Javascript source files.
make_djangojs_cmd = makemessages + ' -d djangojs --extension js' make_djangojs_cmd = makemessages + ' -d djangojs --extension js'
execute(make_djangojs_cmd, working_directory=BASE_DIR) execute(make_djangojs_cmd, working_directory=BASE_DIR, stderr=stderr)
# makemessages creates 'django.po'. This filename is hardcoded. # makemessages creates 'django.po'. This filename is hardcoded.
# Rename it to django-partial.po to enable merging into django.po later. # Rename it to django-partial.po to enable merging into django.po later.
...@@ -90,13 +109,14 @@ def main(): ...@@ -90,13 +109,14 @@ def main():
output_file = source_msgs_dir / (app_name + ".po") output_file = source_msgs_dir / (app_name + ".po")
files_to_clean.add(output_file) files_to_clean.add(output_file)
babel_cmd = 'pybabel extract -F {config} -c "Translators:" {app} -o {output}' babel_cmd = 'pybabel {verbosity} extract -F {config} -c "Translators:" {app} -o {output}'
babel_cmd = babel_cmd.format( babel_cmd = babel_cmd.format(
verbosity=babel_verbosity,
config=LOCALE_DIR / 'babel_third_party.cfg', config=LOCALE_DIR / 'babel_third_party.cfg',
app=app_name, app=app_name,
output=output_file, output=output_file,
) )
execute(babel_cmd, working_directory=app_dir) execute(babel_cmd, working_directory=app_dir, stderr=stderr)
# Segment the generated files. # Segment the generated files.
segmented_files = segment_pofiles("en") segmented_files = segment_pofiles("en")
...@@ -132,20 +152,24 @@ def fix_header(po): ...@@ -132,20 +152,24 @@ def fix_header(po):
fixes = ( fixes = (
('SOME DESCRIPTIVE TITLE', EDX_MARKER), ('SOME DESCRIPTIVE TITLE', EDX_MARKER),
('Translations template for PROJECT.', EDX_MARKER), ('Translations template for PROJECT.', EDX_MARKER),
('YEAR', '%s' % datetime.utcnow().year), ('YEAR', str(datetime.utcnow().year)),
('ORGANIZATION', 'edX'), ('ORGANIZATION', 'edX'),
("THE PACKAGE'S COPYRIGHT HOLDER", "EdX"), ("THE PACKAGE'S COPYRIGHT HOLDER", "EdX"),
('This file is distributed under the same license as the PROJECT project.', (
'This file is distributed under the GNU AFFERO GENERAL PUBLIC LICENSE.'), 'This file is distributed under the same license as the PROJECT project.',
('This file is distributed under the same license as the PACKAGE package.', 'This file is distributed under the GNU AFFERO GENERAL PUBLIC LICENSE.'
'This file is distributed under the GNU AFFERO GENERAL PUBLIC LICENSE.'), ),
('FIRST AUTHOR <EMAIL@ADDRESS>', (
'EdX Team <info@edx.org>') 'This file is distributed under the same license as the PACKAGE package.',
) 'This file is distributed under the GNU AFFERO GENERAL PUBLIC LICENSE.'
),
('FIRST AUTHOR <EMAIL@ADDRESS>', 'EdX Team <info@edx.org>'),
)
for src, dest in fixes: for src, dest in fixes:
header = header.replace(src, dest) header = header.replace(src, dest)
po.header = header po.header = header
def fix_metadata(po): def fix_metadata(po):
""" """
Replace default metadata with edX metadata Replace default metadata with edX metadata
...@@ -168,12 +192,13 @@ def fix_metadata(po): ...@@ -168,12 +192,13 @@ def fix_metadata(po):
'PO-Revision-Date': datetime.utcnow(), 'PO-Revision-Date': datetime.utcnow(),
'Report-Msgid-Bugs-To': 'openedx-translation@googlegroups.com', 'Report-Msgid-Bugs-To': 'openedx-translation@googlegroups.com',
'Project-Id-Version': '0.1a', 'Project-Id-Version': '0.1a',
'Language' : 'en', 'Language': 'en',
'Last-Translator' : '', 'Last-Translator': '',
'Language-Team': 'openedx-translation <openedx-translation@googlegroups.com>', 'Language-Team': 'openedx-translation <openedx-translation@googlegroups.com>',
} }
po.metadata.update(fixes) po.metadata.update(fixes)
def strip_key_strings(po): def strip_key_strings(po):
""" """
Removes all entries in PO which are key strings. Removes all entries in PO which are key strings.
...@@ -183,6 +208,7 @@ def strip_key_strings(po): ...@@ -183,6 +208,7 @@ def strip_key_strings(po):
del po[:] del po[:]
po += newlist po += newlist
def is_key_string(string): def is_key_string(string):
""" """
returns True if string is a key string. returns True if string is a key string.
...@@ -190,5 +216,10 @@ def is_key_string(string): ...@@ -190,5 +216,10 @@ def is_key_string(string):
""" """
return len(string) > 1 and string[0] == '_' return len(string) > 1 and string[0] == '_'
if __name__ == '__main__': if __name__ == '__main__':
main() # pylint: disable=invalid-name
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--verbose', '-v', action='count', default=0)
args = parser.parse_args()
main(verbosity=args.verbose)
...@@ -24,6 +24,7 @@ from i18n.config import BASE_DIR, CONFIGURATION ...@@ -24,6 +24,7 @@ from i18n.config import BASE_DIR, CONFIGURATION
from i18n.execute import execute from i18n.execute import execute
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
DEVNULL = open(os.devnull, "wb")
def merge(locale, target='django.po', sources=('django-partial.po',), fail_if_missing=True): def merge(locale, target='django.po', sources=('django-partial.po',), fail_if_missing=True):
...@@ -45,7 +46,7 @@ def merge(locale, target='django.po', sources=('django-partial.po',), fail_if_mi ...@@ -45,7 +46,7 @@ def merge(locale, target='django.po', sources=('django-partial.po',), fail_if_mi
except Exception, e: except Exception, e:
if not fail_if_missing: if not fail_if_missing:
return return
raise e raise
# merged file is merged.po # merged file is merged.po
merge_cmd = 'msgcat -o merged.po ' + ' '.join(sources) merge_cmd = 'msgcat -o merged.po ' + ' '.join(sources)
...@@ -110,23 +111,31 @@ def validate_files(dir, files_to_merge): ...@@ -110,23 +111,31 @@ def validate_files(dir, files_to_merge):
raise Exception("I18N: Cannot generate because file not found: {0}".format(pathname)) raise Exception("I18N: Cannot generate because file not found: {0}".format(pathname))
def main(argv=None): def main(strict=True, verbosity=1):
logging.basicConfig(stream=sys.stdout, level=logging.INFO) """
Main entry point for script
parser = argparse.ArgumentParser(description="Generate merged and compiled message files.") """
parser.add_argument("--strict", action='store_true', help="Complain about missing files.")
args = parser.parse_args(argv or [])
for locale in CONFIGURATION.translated_locales: for locale in CONFIGURATION.translated_locales:
merge_files(locale, fail_if_missing=args.strict) merge_files(locale, fail_if_missing=strict)
# Dummy text is not required. Don't raise exception if files are missing. # Dummy text is not required. Don't raise exception if files are missing.
for locale in CONFIGURATION.dummy_locales: for locale in CONFIGURATION.dummy_locales:
merge_files(locale, fail_if_missing=False) merge_files(locale, fail_if_missing=False)
compile_cmd = 'django-admin.py compilemessages' compile_cmd = 'django-admin.py compilemessages -v{}'.format(verbosity)
execute(compile_cmd, working_directory=BASE_DIR) if verbosity:
stderr = None
else:
stderr = DEVNULL
execute(compile_cmd, working_directory=BASE_DIR, stderr=stderr)
if __name__ == '__main__': if __name__ == '__main__':
main(sys.argv[1:]) logging.basicConfig(stream=sys.stdout, level=logging.INFO)
# pylint: disable=invalid-name
parser = argparse.ArgumentParser(description="Generate merged and compiled message files.")
parser.add_argument("--strict", action='store_true', help="Complain about missing files.")
parser.add_argument("--verbose", "-v", action="count", default=0)
args = parser.parse_args()
main(strict=args.strict, verbosity=args.verbose)
...@@ -8,8 +8,9 @@ import copy ...@@ -8,8 +8,9 @@ import copy
import fnmatch import fnmatch
import logging import logging
import sys import sys
import argparse
import polib import polib
import textwrap
from i18n.config import CONFIGURATION from i18n.config import CONFIGURATION
...@@ -116,27 +117,32 @@ def segment_pofile(filename, segments): ...@@ -116,27 +117,32 @@ def segment_pofile(filename, segments):
return files_written return files_written
def main(argv): def main(locales=None, verbosity=1): # pylint: disable=unused-argument
""" """
$ segment.py LOCALE [...] Main entry point of script
Segment the .po files in LOCALE(s) based on the segmenting rules in
config.yaml.
Note that segmenting is *not* idempotent: it modifies the input file, so
be careful that you don't run it twice on the same file.
""" """
# This is used as a tool only to segment translation files when adding a # This is used as a tool only to segment translation files when adding a
# new segment. In the regular workflow, the work is done by the extract # new segment. In the regular workflow, the work is done by the extract
# phase calling the functions above. # phase calling the functions above.
locales = locales or []
logging.basicConfig(stream=sys.stdout, level=logging.INFO) for locale in locales:
if len(argv) < 2:
sys.exit("Need a locale to segment")
for locale in argv[1:]:
segment_pofiles(locale) segment_pofiles(locale)
if __name__ == "__main__": if __name__ == "__main__":
main(sys.argv) logging.basicConfig(stream=sys.stdout, level=logging.INFO)
# pylint: disable=invalid-name
description = textwrap.dedent("""
Segment the .po files in LOCALE(s) based on the segmenting rules in
config.yaml.
Note that segmenting is *not* idempotent: it modifies the input file, so
be careful that you don't run it twice on the same file.
""".strip())
parser = argparse.ArgumentParser(description=description)
parser.add_argument("locale", nargs="+", help="a locale to segment")
parser.add_argument("--verbose", "-v", action="count", default=0)
args = parser.parse_args()
main(locales=args.locale, verbosity=args.verbose)
...@@ -33,7 +33,7 @@ class TestExtract(TestCase): ...@@ -33,7 +33,7 @@ class TestExtract(TestCase):
super(TestExtract, self).setUp() super(TestExtract, self).setUp()
if not SETUP_HAS_RUN: if not SETUP_HAS_RUN:
# Run extraction script. Warning, this takes 1 minute or more # Run extraction script. Warning, this takes 1 minute or more
extract.main() extract.main(verbosity=0)
SETUP_HAS_RUN = True SETUP_HAS_RUN = True
def get_files(self): def get_files(self):
......
from datetime import datetime, timedelta from datetime import datetime, timedelta
import os import os
import sys
import string import string
import random import random
import re import re
...@@ -23,8 +24,13 @@ class TestGenerate(TestCase): ...@@ -23,8 +24,13 @@ class TestGenerate(TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
extract.main() sys.stderr.write(
dummy.main() "\nExtracting i18n strings and generating dummy translations; "
"this may take a few minutes\n"
)
sys.stderr.flush()
extract.main(verbosity=0)
dummy.main(verbosity=0)
def setUp(self): def setUp(self):
# Subtract 1 second to help comparisons with file-modify time succeed, # Subtract 1 second to help comparisons with file-modify time succeed,
...@@ -50,7 +56,7 @@ class TestGenerate(TestCase): ...@@ -50,7 +56,7 @@ class TestGenerate(TestCase):
.mo files should exist, and be recently created (modified .mo files should exist, and be recently created (modified
after start of test suite) after start of test suite)
""" """
generate.main() generate.main(verbosity=0, strict=False)
for locale in CONFIGURATION.translated_locales: for locale in CONFIGURATION.translated_locales:
for filename in ('django', 'djangojs'): for filename in ('django', 'djangojs'):
mofile = filename+'.mo' mofile = filename+'.mo'
......
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import print_function
import sys import sys
from polib import pofile from polib import pofile
import argparse
from i18n.config import CONFIGURATION from i18n.config import CONFIGURATION
from i18n.execute import execute from i18n.execute import execute
from i18n.extract import EDX_MARKER from i18n.extract import EDX_MARKER
TRANSIFEX_HEADER = 'edX community translations have been downloaded from %s' TRANSIFEX_HEADER = 'edX community translations have been downloaded from {}'
TRANSIFEX_URL = 'https://www.transifex.com/projects/p/edx-platform/' TRANSIFEX_URL = 'https://www.transifex.com/projects/p/edx-platform/'
...@@ -16,7 +17,7 @@ def push(): ...@@ -16,7 +17,7 @@ def push():
def pull(): def pull():
print "Pulling languages from transifex..." print("Pulling languages from transifex...")
execute('tx pull --mode=reviewed --all') execute('tx pull --mode=reviewed --all')
clean_translated_locales() clean_translated_locales()
...@@ -57,18 +58,22 @@ def clean_file(filename): ...@@ -57,18 +58,22 @@ def clean_file(filename):
def get_new_header(po): def get_new_header(po):
team = po.metadata.get('Language-Team', None) team = po.metadata.get('Language-Team', None)
if not team: if not team:
return TRANSIFEX_HEADER % TRANSIFEX_URL return TRANSIFEX_HEADER.format(TRANSIFEX_URL)
else: else:
return TRANSIFEX_HEADER % team return TRANSIFEX_HEADER.format(team)
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) < 2: # pylint: disable=invalid-name
raise Exception("missing argument: push or pull") parser = argparse.ArgumentParser()
arg = sys.argv[1] parser.add_argument("command", help="push or pull")
if arg == 'push': parser.add_argument("--verbose", "-v")
args = parser.parse_args()
# pylint: enable=invalid-name
if args.command == "push":
push() push()
elif arg == 'pull': elif args.command == "pull":
pull() pull()
else: else:
raise Exception("unknown argument: (%s)" % arg) raise Exception("unknown command ({cmd})".format(cmd=args.command))
...@@ -16,6 +16,7 @@ from i18n.converter import Converter ...@@ -16,6 +16,7 @@ from i18n.converter import Converter
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def validate_po_files(root, report_empty=False): def validate_po_files(root, report_empty=False):
""" """
Validate all of the po files found in the root directory. Validate all of the po files found in the root directory.
...@@ -148,20 +149,14 @@ def check_messages(filename, report_empty=False): ...@@ -148,20 +149,14 @@ def check_messages(filename, report_empty=False):
log.info(" No problems found in {0}".format(filename)) log.info(" No problems found in {0}".format(filename))
def parse_args(argv): def get_parser():
""" """
Parse command line arguments, returning a dict of Returns an argument parser for this script.
valid options:
{
'empty': BOOLEAN,
'verbose': BOOLEAN,
'language': str
}
where 'language' is a language code, eg "fr"
""" """
parser = argparse.ArgumentParser(description="Automatically finds translation errors in all edx-platform *.po files, for all languages, unless one or more language(s) is specified to check.") parser = argparse.ArgumentParser(description=( # pylint: disable=redefined-outer-name
"Automatically finds translation errors in all edx-platform *.po files, "
"for all languages, unless one or more language(s) is specified to check."
))
parser.add_argument( parser.add_argument(
'-l', '--language', '-l', '--language',
...@@ -178,39 +173,44 @@ def parse_args(argv): ...@@ -178,39 +173,44 @@ def parse_args(argv):
parser.add_argument( parser.add_argument(
'-v', '--verbose', '-v', '--verbose',
action='store_true', action='count', default=0,
help="Turns on info-level logging." help="Turns on info-level logging."
) )
return vars(parser.parse_args(argv)) return parser
def main(): def main(languages=None, empty=False, verbosity=1): # pylint: disable=unused-argument
"""Main entry point for the tool.""" """
Main entry point for script
"""
languages = languages or []
args_dict = parse_args(sys.argv[1:]) if not languages:
if args_dict['verbose']: root = LOCALE_DIR
logging.basicConfig(stream=sys.stdout, level=logging.INFO) validate_po_files(root, empty)
else: return
logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
langs = args_dict['language'] # languages will be a list of language codes; test each language.
for language in languages:
root = LOCALE_DIR / language
# Assert that a directory for this language code exists on the system
if not root.isdir():
log.error(" {0} is not a valid directory.\nSkipping language '{1}'".format(root, language))
continue
# If we found the language code's directory, validate the files.
validate_po_files(root, empty)
if langs is not None:
# lang will be a list of language codes; test each language.
for lang in langs:
root = LOCALE_DIR / lang
# Assert that a directory for this language code exists on the system
if not os.path.isdir(root):
log.error(" {0} is not a valid directory.\nSkipping language '{1}'".format(root, lang))
continue
# If we found the language code's directory, validate the files.
validate_po_files(root, args_dict['empty'])
if __name__ == '__main__':
# pylint: disable=invalid-name
parser = get_parser()
args = parser.parse_args()
if args.verbose:
log_level = logging.INFO
else: else:
# If lang is None, we walk all of the .po files under root, and test each one. log_level = logging.WARNING
root = LOCALE_DIR logging.basicConfig(stream=sys.stdout, level=log_level)
validate_po_files(root, args_dict['empty']) # pylint: enable=invalid-name
if __name__ == '__main__': main(languages=args.language, empty=args.empty, verbosity=args.verbose)
main()
...@@ -10,7 +10,11 @@ namespace :i18n do ...@@ -10,7 +10,11 @@ namespace :i18n do
desc "Extract localizable strings from sources" desc "Extract localizable strings from sources"
task :extract => ["i18n:validate:gettext", "assets:coffee"] do task :extract => ["i18n:validate:gettext", "assets:coffee"] do
sh(File.join(REPO_ROOT, "i18n", "extract.py")) command = File.join(REPO_ROOT, "i18n", "extract.py")
if verbose == true
command += " -vv"
end
sh(command)
end end
desc "Compile localizable strings from sources, extracting strings first." desc "Compile localizable strings from sources, extracting strings first."
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment