Commit c3dc3732 by Sarina Canelake

Merge pull request #2250 from edx/sarina/i18n_from_commandline

Make test_validate into separate command-line script (i18n)
parents d35a56bc 8f3ba327
"""Tests that validate .po files.""" """Tests that validate .po files."""
import argparse
import codecs import codecs
import logging import logging
import os import os
...@@ -13,23 +14,25 @@ from i18n.execute import call ...@@ -13,23 +14,25 @@ from i18n.execute import call
from i18n.converter import Converter from i18n.converter import Converter
def test_po_files(root=LOCALE_DIR): log = logging.getLogger(__name__)
def validate_po_files(root, report_empty=False):
""" """
This is a generator. It yields all of the .po files under root, and tests each one. Validate all of the po files found in the root directory.
""" """
log = logging.getLogger(__name__)
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
for dirpath, __, filenames in os.walk(root): for dirpath, __, filenames in os.walk(root):
for name in filenames: for name in filenames:
__, ext = os.path.splitext(name) __, ext = os.path.splitext(name)
if ext.lower() == '.po': if ext.lower() == '.po':
filename = os.path.join(dirpath, name) filename = os.path.join(dirpath, name)
yield msgfmt_check_po_file, filename, log # First validate the format of this file
yield check_messages, filename msgfmt_check_po_file(filename)
# Now, check that the translated strings are valid, and optionally check for empty translations
check_messages(filename, report_empty)
def msgfmt_check_po_file(filename, log): def msgfmt_check_po_file(filename):
""" """
Call GNU msgfmt -c on each .po file to validate its format. Call GNU msgfmt -c on each .po file to validate its format.
Any errors caught by msgfmt are logged to log. Any errors caught by msgfmt are logged to log.
...@@ -70,12 +73,14 @@ def astral(msg): ...@@ -70,12 +73,14 @@ def astral(msg):
return any(ord(c) > 0xFFFF for c in msg) return any(ord(c) > 0xFFFF for c in msg)
def check_messages(filename): def check_messages(filename, report_empty=False):
""" """
Checks messages in various ways: Checks messages in various ways:
Translations must have the same slots as the English. The translation Translations must have the same slots as the English. Messages can't have astral
must not be empty. Messages can't have astral characters in them. characters in them.
If report_empty is True, will also report empty translation strings.
""" """
# Don't check English files. # Don't check English files.
...@@ -95,7 +100,7 @@ def check_messages(filename): ...@@ -95,7 +100,7 @@ def check_messages(filename):
if msg.msgid_plural: if msg.msgid_plural:
# Plurals: two strings in, N strings out. # Plurals: two strings in, N strings out.
source = msg.msgid + " | " + msg.msgid_plural source = msg.msgid + " | " + msg.msgid_plural
translation = " | ".join(v for k,v in sorted(msg.msgstr_plural.items())) translation = " | ".join(v for k, v in sorted(msg.msgstr_plural.items()))
empty = any(not t.strip() for t in msg.msgstr_plural.values()) empty = any(not t.strip() for t in msg.msgstr_plural.values())
else: else:
# Singular: just one string in and one string out. # Singular: just one string in and one string out.
...@@ -104,10 +109,13 @@ def check_messages(filename): ...@@ -104,10 +109,13 @@ def check_messages(filename):
empty = not msg.msgstr.strip() empty = not msg.msgstr.strip()
if empty: if empty:
problems.append(("Empty translation", source)) if report_empty:
problems.append(("Empty translation", source))
else: else:
id_tags = tags_in_string(source) id_tags = tags_in_string(source)
tx_tags = tags_in_string(translation) tx_tags = tags_in_string(translation)
# Check if tags don't match
if id_tags != tx_tags: if id_tags != tx_tags:
id_has = u", ".join(u'"{}"'.format(t) for t in id_tags - tx_tags) id_has = u", ".join(u'"{}"'.format(t) for t in id_tags - tx_tags)
tx_has = u", ".join(u'"{}"'.format(t) for t in tx_tags - id_tags) tx_has = u", ".join(u'"{}"'.format(t) for t in tx_tags - id_tags)
...@@ -136,4 +144,74 @@ def check_messages(filename): ...@@ -136,4 +144,74 @@ def check_messages(filename):
prob_file.write(u"{}\n".format(tx_filler.fill(translation))) prob_file.write(u"{}\n".format(tx_filler.fill(translation)))
prob_file.write(u"\n") prob_file.write(u"\n")
assert not problems, "Found %d problems in %s, details in .prob file" % (len(problems), filename) log.error(" {0} problems in {1}, details in .prob file".format(len(problems), filename))
else:
log.info(" No problems found in {0}".format(filename))
def parse_args(argv):
"""
Parse command line arguments, returning a dict of
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.add_argument(
'-l', '--language',
type=str,
nargs='*',
help="Specify one or more specific language code(s) to check (eg 'ko_KR')."
)
parser.add_argument(
'-e', '--empty',
action='store_true',
help="Includes empty translation strings in .prob files."
)
parser.add_argument(
'-v', '--verbose',
action='store_true',
help="Turns on info-level logging."
)
return vars(parser.parse_args(argv))
def main():
"""Main entry point for the tool."""
args_dict = parse_args(sys.argv[1:])
if args_dict['verbose']:
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
else:
logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
langs = args_dict['language']
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'])
else:
# If lang is None, we walk all of the .po files under root, and test each one.
root = LOCALE_DIR
validate_po_files(root, args_dict['empty'])
if __name__ == '__main__':
main()
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