validate i18n strings when running "make lint"

* Add bootstrap-autogen depdenency to lint target to force
  generated files to be created.

* Add validate-src-strings to lint rules

* Add validate-src-strings as dependency to lint targett

* Remove obsolete test_lang frm test target

* Add diagnostic message to validation command in i18n.py
  that outputs how many objects were scanned. Formerly it only
  output a message if there were errors. This made it impossible to
  distinguish an empty file from one with no errors.

* While adding the validation counts it was discovered plurals had
  been omitted for some of the validation checks. Added the missing
  checks for plural forms.

* Also distinguished between errors and warnings. Permit warnings to
  be emitted but do not fail the validatition unless actual errors
  were also detected.
This commit is contained in:
John Dennis
2012-04-16 15:51:42 -04:00
committed by Martin Kosek
parent 3ba9cc8eb4
commit 81c65ee0b2
3 changed files with 137 additions and 29 deletions

View File

@@ -88,11 +88,12 @@ client-dirs:
echo "Without those directories ipa-client-install will fail" ; \ echo "Without those directories ipa-client-install will fail" ; \
fi fi
lint: lint: bootstrap-autogen
./make-lint $(LINT_OPTIONS) ./make-lint $(LINT_OPTIONS)
$(MAKE) -C install/po validate-src-strings
test: test:
$(MAKE) -C install/po test_lang
./make-testcert ./make-testcert
./make-test ./make-test

View File

@@ -160,7 +160,7 @@ install: $(mo_files)
done done
mostlyclean: mostlyclean:
rm -rf *.mo test.po test_locale rm -rf *.mo test.po test_locale tmp.pot
rm -f $(DOMAIN).pot.update $(DOMAIN).pot.update.tmp $(DOMAIN).pot.tmp rm -f $(DOMAIN).pot.update $(DOMAIN).pot.update.tmp $(DOMAIN).pot.tmp
clean: mostlyclean clean: mostlyclean
@@ -179,6 +179,14 @@ validate-pot:
validate-po: validate-po:
$(IPA_TEST_I18N) --show-strings --validate-po $(po_files) $(IPA_TEST_I18N) --show-strings --validate-po $(po_files)
validate-src-strings:
@rm -f tmp.pot
@touch tmp.pot
@$(MAKE) DOMAIN=tmp update-pot; \
status=$$?; \
rm tmp.pot; \
exit $$status
debug: debug:
@echo Python potfiles: @echo Python potfiles:
@echo PY_FILES = $(PY_FILES) @echo PY_FILES = $(PY_FILES)

View File

@@ -29,6 +29,7 @@ import re
import os import os
import traceback import traceback
import polib import polib
from collections import namedtuple
''' '''
We test our translations by taking the original untranslated string We test our translations by taking the original untranslated string
@@ -379,51 +380,138 @@ def validate_file(file_path, validation_mode):
Returns the number of entries with errors. Returns the number of entries with errors.
''' '''
def emit_messages():
if n_warnings:
warning_lines.insert(0, section_seperator)
warning_lines.insert(1, "%d validation warnings in %s" % (n_warnings, file_path))
print '\n'.join(warning_lines)
if n_errors:
error_lines.insert(0, section_seperator)
error_lines.insert(1, "%d validation errors in %s" % (n_errors, file_path))
print '\n'.join(error_lines)
Result = namedtuple('ValidateFileResult', ['n_entries', 'n_msgids', 'n_msgstrs', 'n_warnings', 'n_errors'])
warning_lines = []
error_lines = [] error_lines = []
n_entries_with_errors = 0 n_entries = 0
n_msgids = 0
n_msgstrs = 0
n_entries = 0
n_warnings = 0
n_errors = 0
n_plural_forms = 0
if not os.path.isfile(file_path): if not os.path.isfile(file_path):
print >>sys.stderr, 'file does not exist "%s"' % (file_path) error_lines.append(entry_seperator)
return 1 error_lines.append('file does not exist "%s"' % (file_path))
n_errors += 1
emit_messages()
return Result(n_entries=n_entries, n_msgids=n_msgids, n_msgstrs=n_msgstrs, n_warnings=n_warnings, n_errors=n_errors)
try: try:
po = polib.pofile(file_path) po = polib.pofile(file_path)
except Exception, e: except Exception, e:
print >>sys.stderr, 'Unable to parse file "%s": %s' % (file_path, e) error_lines.append(entry_seperator)
return 1 error_lines.append('Unable to parse file "%s": %s' % (file_path, e))
n_errors += 1
emit_messages()
return Result(n_entries=n_entries, n_msgids=n_msgids, n_msgstrs=n_msgstrs, n_warnings=n_warnings, n_errors=n_errors)
for entry in po:
entry_errors = []
msgid = entry.msgid
msgstr = entry.msgstr
have_msgid = msgid.strip() != ''
have_msgstr = msgstr.strip() != ''
if validation_mode == 'pot':
if have_msgid:
prog_langs = get_prog_langs(entry)
errors = validate_positional_substitutions(msgid, prog_langs, 'msgid')
entry_errors.extend(errors)
if validation_mode == 'po': if validation_mode == 'po':
if have_msgid and have_msgstr: plural_forms = po.metadata.get('Plural-Forms')
errors = validate_substitutions_match(msgid, msgstr, 'msgid', 'msgstr') if not plural_forms:
error_lines.append(entry_seperator)
error_lines.append("%s: does not have Plural-Forms header" % file_path)
n_errors += 1
match = re.search(r'\bnplurals\s*=\s*(\d+)', plural_forms)
if match:
n_plural_forms = int(match.group(1))
else:
error_lines.append(entry_seperator)
error_lines.append("%s: does not specify integer nplurals in Plural-Forms header" % file_path)
n_errors += 1
n_entries = len(po)
for entry in po:
entry_warnings = []
entry_errors = []
have_msgid = entry.msgid.strip() != ''
have_msgid_plural = entry.msgid_plural.strip() != ''
have_msgstr = entry.msgstr.strip() != ''
if have_msgid:
n_msgids += 1
if have_msgid_plural:
n_msgids += 1
if have_msgstr:
n_msgstrs += 1
if validation_mode == 'pot':
prog_langs = get_prog_langs(entry)
if have_msgid:
errors = validate_positional_substitutions(entry.msgid, prog_langs, 'msgid')
entry_errors.extend(errors) entry_errors.extend(errors)
if have_msgid_plural:
errors = validate_positional_substitutions(entry.msgid_plural, prog_langs, 'msgid_plural')
entry_errors.extend(errors)
elif validation_mode == 'po':
if have_msgid:
if have_msgstr:
errors = validate_substitutions_match(entry.msgid, entry.msgstr, 'msgid', 'msgstr')
entry_errors.extend(errors)
if have_msgid_plural and have_msgstr:
n_plurals = 0
for index, msgstr in entry.msgstr_plural.items():
have_msgstr_plural = msgstr.strip() != ''
if have_msgstr_plural:
n_plurals += 1
errors = validate_substitutions_match(entry.msgid_plural, msgstr, 'msgid_plural', 'msgstr_plural[%s]' % index)
entry_errors.extend(errors)
else:
entry_errors.append('msgstr_plural[%s] is empty' % (index))
if n_plural_forms != n_plurals:
entry_errors.append('%d plural forms specified, but this entry has %d plurals' % (n_plural_forms, n_plurals))
if pedantic: if pedantic:
if have_msgid: if have_msgid:
errors = validate_substitution_syntax(msgid, 'msgid') errors = validate_substitution_syntax(entry.msgid, 'msgid')
entry_errors.extend(errors) entry_warnings.extend(errors)
if have_msgid_plural:
errors = validate_substitution_syntax(entry.msgid_plural, 'msgid_plural')
entry_warnings.extend(errors)
errors = validate_substitutions_match(entry.msgid, entry.msgid_plural, 'msgid', 'msgid_plural')
entry_warnings.extend(errors)
for index, msgstr in entry.msgstr_plural.items():
have_msgstr_plural = msgstr.strip() != ''
if have_msgstr_plural:
errors = validate_substitution_syntax(msgstr, 'msgstr_plural[%s]' % index)
entry_warnings.extend(errors)
if have_msgstr: if have_msgstr:
errors = validate_substitution_syntax(msgstr, 'msgstr') errors = validate_substitution_syntax(entry.msgstr, 'msgstr')
entry_errors.extend(errors) entry_warnings.extend(errors)
if entry_warnings:
warning_lines.append(entry_seperator)
warning_lines.append('locations: %s' % (', '.join(["%s:%d" % (x[0], int(x[1])) for x in entry.occurrences])))
warning_lines.extend(entry_warnings)
n_warnings += 1
if entry_errors: if entry_errors:
error_lines.append(entry_seperator) error_lines.append(entry_seperator)
error_lines.append('locations: %s' % (', '.join(["%s:%d" % (x[0], int(x[1])) for x in entry.occurrences]))) error_lines.append('locations: %s' % (', '.join(["%s:%d" % (x[0], int(x[1])) for x in entry.occurrences])))
error_lines.extend(entry_errors) error_lines.extend(entry_errors)
n_entries_with_errors += 1 n_errors += 1
if n_entries_with_errors:
error_lines.insert(0, section_seperator)
error_lines.insert(1, "%d validation errors in %s" % (n_entries_with_errors, file_path))
print '\n'.join(error_lines)
return n_entries_with_errors emit_messages()
return Result(n_entries=n_entries, n_msgids=n_msgids, n_msgstrs=n_msgstrs, n_warnings=n_warnings, n_errors=n_errors)
#---------------------------------------------------------------------- #----------------------------------------------------------------------
@@ -676,10 +764,21 @@ def main():
print >> sys.stderr, 'ERROR: unknown validation mode "%s"' % (options.mode) print >> sys.stderr, 'ERROR: unknown validation mode "%s"' % (options.mode)
return 1 return 1
total_entries = 0
total_msgids = 0
total_msgstrs = 0
total_warnings = 0
total_errors = 0 total_errors = 0
for f in files: for f in files:
n_errors = validate_file(f, validation_mode) result = validate_file(f, validation_mode)
total_errors += n_errors total_entries += result.n_entries
total_msgids += result.n_msgids
total_msgstrs += result.n_msgstrs
total_warnings += result.n_warnings
total_errors += result.n_errors
print "%s: %d entries, %d msgid, %d msgstr, %d warnings %d errors" % \
(f, result.n_entries, result.n_msgids, result.n_msgstrs, result.n_warnings, result.n_errors)
if total_errors: if total_errors:
print section_seperator print section_seperator
print "%d errors in %d files" % (total_errors, len(files)) print "%d errors in %d files" % (total_errors, len(files))