Skip to content

Commit

Permalink
Merge branch '2613-fix-po-checker'
Browse files Browse the repository at this point in the history
Conflicts:
	ckan/i18n/check_po_files.py
  • Loading branch information
joetsoi committed Nov 16, 2015
2 parents f745750 + 2a3d575 commit 998af9c
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 56 deletions.
87 changes: 34 additions & 53 deletions ckan/i18n/check_po_files.py
Expand Up @@ -10,9 +10,11 @@
pip install polib
'''
import polib
import re
import paste.script.command


def simple_conv_specs(s):
'''Return the simple Python string conversion specifiers in the string s.
Expand All @@ -24,27 +26,6 @@ def simple_conv_specs(s):
simple_conv_specs_re = re.compile('\%\w')
return simple_conv_specs_re.findall(s)

def test_simple_conv_specs():
assert simple_conv_specs("Authorization function not found: %s") == (
['%s'])
assert simple_conv_specs("Problem purging revision %s: %s") == (
['%s', '%s'])
assert simple_conv_specs(
"Cannot create new entity of this type: %s %s") == ['%s', '%s']
assert simple_conv_specs("Could not read parameters: %r") == ['%r']
assert simple_conv_specs("User %r not authorized to edit %r") == (
['%r', '%r'])
assert simple_conv_specs(
"Please <a href=\"%s\">update your profile</a> and add your email "
"address and your full name. "
"%s uses your email address if you need to reset your password.") == (
['%s', '%s'])
assert simple_conv_specs(
"You can use %sMarkdown formatting%s here.") == ['%s', '%s']
assert simple_conv_specs(
"Name must be a maximum of %i characters long") == ['%i']
assert simple_conv_specs("Blah blah %s blah %(key)s blah %i") == (
['%s', '%i'])

def mapping_keys(s):
'''Return a sorted list of the mapping keys in the string s.
Expand All @@ -57,20 +38,6 @@ def mapping_keys(s):
mapping_keys_re = re.compile('\%\([^\)]*\)\w')
return sorted(mapping_keys_re.findall(s))

def test_mapping_keys():
assert mapping_keys(
"You have requested your password on %(site_title)s to be reset.\n"
"\n"
"Please click the following link to confirm this request:\n"
"\n"
" %(reset_link)s\n") == ['%(reset_link)s', '%(site_title)s']
assert mapping_keys(
"The input field %(name)s was not expected.") == ['%(name)s']
assert mapping_keys(
"[1:You searched for \"%(query)s\". ]%(number_of_results)s "
"datasets found.") == ['%(number_of_results)s', '%(query)s']
assert mapping_keys("Blah blah %s blah %(key)s blah %i") == (
['%(key)s']), mapping_keys("Blah blah %s blah %(key)s blah %i")

def replacement_fields(s):
'''Return a sorted list of the Python replacement fields in the string s.
Expand All @@ -83,11 +50,6 @@ def replacement_fields(s):
repl_fields_re = re.compile('\{[^\}]*\}')
return sorted(repl_fields_re.findall(s))

def test_replacement_fields():
assert replacement_fields(
"{actor} added the tag {object} to the dataset {target}") == (
['{actor}', '{object}', '{target}'])
assert replacement_fields("{actor} updated their profile") == ['{actor}']

class CheckPoFiles(paste.script.command.Command):

Expand All @@ -97,19 +59,38 @@ class CheckPoFiles(paste.script.command.Command):
parser = paste.script.command.Command.standard_parser(verbose=True)

def command(self):
import polib

test_simple_conv_specs()
test_mapping_keys()
test_replacement_fields()
for path in self.args:
print u'Checking file {}'.format(path)
po = polib.pofile(path)
for entry in po.translated_entries():
if not entry.msgstr:
continue
for function in (simple_conv_specs, mapping_keys,
replacement_fields):
if not function(entry.msgid) == function(entry.msgstr):
print " Format specifiers don't match:"
print u' {0} -> {1}'.format(entry.msgid, entry.msgstr).encode('latin7', 'ignore')
errors = check_po_file(path)
if errors:
for msgid, msgstr in errors:
print 'Format specifiers don\'t match:'
print u' {0} -> {1}'.format(entry.msgid, entry.msgstr).encode('latin7', 'ignore')


def check_po_file(path):
errors = []

def check_translation(validator, msgid, msgstr):
if not validator(msgid) == validator(msgstr):
errors.append((msgid, msgstr))

po = polib.pofile(path)
for entry in po.translated_entries():
if entry.msgid_plural and entry.msgstr_plural:
for function in (simple_conv_specs, mapping_keys,
replacement_fields):
for key, msgstr in entry.msgstr_plural.iteritems():
if key == '0':
check_translation(function, entry.msgid,
entry.msgstr_plural[key])
else:
check_translation(function, entry.msgid_plural,
entry.msgstr_plural[key])
elif entry.msgstr:
for function in (simple_conv_specs, mapping_keys,
replacement_fields):
check_translation(function, entry.msgid, entry.msgstr)

return errors
2 changes: 1 addition & 1 deletion ckan/lib/email_notifications.py
Expand Up @@ -98,7 +98,7 @@ def _notifications_for_activities(activities, user_dict):
# certain types of activity to be sent in their own individual emails,
# etc.
subject = ungettext(
"1 new activity from {site_title}",
"{n} new activity from {site_title}",
"{n} new activities from {site_title}",
len(activities)).format(
site_title=pylons.config.get('ckan.site_title'),
Expand Down
@@ -1,4 +1,4 @@
{% set num = activities|length %}{{ ungettext("You have 1 new activity on your {site_title} dashboard", "You have {num} new activities on your {site_title} dashboard", num).format(site_title=g.site_title, num=num) }} {{ _('To view your dashboard, click on this link:') }}
{% set num = activities|length %}{{ ungettext("You have {num} new activity on your {site_title} dashboard", "You have {num} new activities on your {site_title} dashboard", num).format(site_title=g.site_title, num=num) }} {{ _('To view your dashboard, click on this link:') }}

{{ g.site_url + '/dashboard' }}

Expand Down
124 changes: 124 additions & 0 deletions ckan/tests/i18n/test_check_po_files.py
@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
import nose

from ckan.i18n.check_po_files import (check_po_file,
simple_conv_specs,
mapping_keys,
replacement_fields)

eq_ = nose.tools.eq_


PO_OK = '''
#: ckan/lib/formatters.py:57
msgid "November"
msgstr "Noiembrie"
#: ckan/lib/formatters.py:61
msgid "December"
msgstr "Decembrie"
'''

PO_WRONG = '''
#: ckan/templates/snippets/search_result_text.html:15
msgid "{number} dataset found for {query}"
msgstr "צביר נתונים אחד נמצא עבור {query}"
'''

PO_PLURALS_OK = '''
#: ckan/lib/formatters.py:114
msgid "{hours} hour ago"
msgid_plural "{hours} hours ago"
msgstr[0] "Fa {hours} hora"
msgstr[1] "Fa {hours} hores"
'''

PO_WRONG_PLURALS = '''
#: ckan/lib/formatters.py:114
msgid "{hours} hour ago"
msgid_plural "{hours} hours ago"
msgstr[0] "o oră în urmă"
msgstr[1] "cîteva ore în urmă"
msgstr[2] "{hours} ore în urmă"
'''


class TestCheckPoFiles(object):

def test_basic(self):

errors = check_po_file(PO_OK)

eq_(errors, [])

def test_wrong(self):

errors = check_po_file(PO_WRONG)

eq_(len(errors), 1)

eq_(errors[0][0], '{number} dataset found for {query}')

def test_plurals_ok(self):

errors = check_po_file(PO_PLURALS_OK)

eq_(errors, [])

def test_wrong_plurals(self):

errors = check_po_file(PO_WRONG_PLURALS)

eq_(len(errors), 2)

for error in errors:
assert error[0] in ('{hours} hour ago', '{hours} hours ago')


class TestValidators(object):

def test_simple_conv_specs(self):
eq_(simple_conv_specs("Authorization function not found: %s"),
(['%s']))
eq_(simple_conv_specs("Problem purging revision %s: %s"),
(['%s', '%s']))
eq_(simple_conv_specs("Cannot create new entity of this type: %s %s"),
['%s', '%s'])
eq_(simple_conv_specs("Could not read parameters: %r"), ['%r'])
eq_(simple_conv_specs("User %r not authorized to edit %r"),
(['%r', '%r']))
eq_(simple_conv_specs(
"Please <a href=\"%s\">update your profile</a> and add your email "
"address and your full name. "
"%s uses your email address if you need to reset your password."),
(['%s', '%s']))
eq_(simple_conv_specs("You can use %sMarkdown formatting%s here."),
['%s', '%s'])
eq_(simple_conv_specs("Name must be a maximum of %i characters long"),
['%i'])
eq_(simple_conv_specs("Blah blah %s blah %(key)s blah %i"),
(['%s', '%i']))

def test_replacement_fields(self):
eq_(replacement_fields(
"{actor} added the tag {object} to the dataset {target}"),
(['{actor}', '{object}', '{target}']))
eq_(replacement_fields("{actor} updated their profile"), ['{actor}'])

def test_mapping_keys(self):
eq_(mapping_keys(
"You have requested your password on %(site_title)s to be reset.\n"
"\n"
"Please click the following link to confirm this request:\n"
"\n"
" %(reset_link)s\n"),
['%(reset_link)s', '%(site_title)s'])
eq_(mapping_keys(
"The input field %(name)s was not expected."),
['%(name)s'])
eq_(mapping_keys(
"[1:You searched for \"%(query)s\". ]%(number_of_results)s "
"datasets found."),
['%(number_of_results)s', '%(query)s'])
eq_(mapping_keys("Blah blah %s blah %(key)s blah %i"),
(['%(key)s']), mapping_keys("Blah blah %s blah %(key)s blah %i"))
1 change: 0 additions & 1 deletion ckan/tests/legacy/test_coding_standards.py
Expand Up @@ -379,7 +379,6 @@ class TestPep8(object):
'ckan/controllers/admin.py',
'ckan/controllers/revision.py',
'ckan/exceptions.py',
'ckan/i18n/check_po_files.py',
'ckan/include/rcssmin.py',
'ckan/include/rjsmin.py',
'ckan/lib/activity_streams.py',
Expand Down

0 comments on commit 998af9c

Please sign in to comment.