# HG changeset patch # User palgarvio # Date 1214828608 0 # Node ID 6129d2e770bc2cf40580dea8894d98e5f03c956d # Parent 48f46a943be97e083f9ab3fcd882143b1cc62503 We no longer neglect `catalog.plurals`. Added tests for it. Fixes #120. Added tests for the plural forms checker which will all now pass due to the above fix. diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py --- a/babel/messages/frontend.py +++ b/babel/messages/frontend.py @@ -440,7 +440,9 @@ infile = open(self.input_file, 'r') try: - catalog = read_po(infile) + # Although reading from the catalog template, read_po must be fed + # the locale in order to correcly calculate plurals + catalog = read_po(infile, locale=self.locale) finally: infile.close() diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -147,8 +147,14 @@ msgid = tuple([denormalize(m) for m in messages]) else: msgid = denormalize(messages[0]) - if len(translations) > 1: - string = tuple([denormalize(t[1]) for t in translations]) + if isinstance(msgid, (list, tuple)): + string = [] + for idx in range(catalog.num_plurals): + try: + string.append(translations[idx]) + except IndexError: + string.append((idx, '')) + string = tuple([denormalize(t[1]) for t in string]) else: string = denormalize(translations[0][1]) if context: @@ -396,9 +402,14 @@ _write('%smsgid_plural %s\n' % ( prefix, _normalize(message.id[1], prefix) )) - for i, string in enumerate(message.string): + + for idx in range(catalog.num_plurals): + try: + string = message.string[idx] + except IndexError: + string = '' _write('%smsgstr[%d] %s\n' % ( - prefix, i, _normalize(message.string[i], prefix) + prefix, idx, _normalize(string, prefix) )) else: _write('%smsgid %s\n' % (prefix, _normalize(message.id, prefix))) diff --git a/babel/messages/tests/__init__.py b/babel/messages/tests/__init__.py --- a/babel/messages/tests/__init__.py +++ b/babel/messages/tests/__init__.py @@ -15,7 +15,7 @@ def suite(): from babel.messages.tests import catalog, extract, frontend, mofile, \ - plurals, pofile + plurals, pofile, checkers suite = unittest.TestSuite() suite.addTest(catalog.suite()) suite.addTest(extract.suite()) @@ -23,6 +23,7 @@ suite.addTest(mofile.suite()) suite.addTest(plurals.suite()) suite.addTest(pofile.suite()) + suite.addTest(checkers.suite()) return suite if __name__ == '__main__': diff --git a/babel/messages/tests/checkers.py b/babel/messages/tests/checkers.py new file mode 100644 --- /dev/null +++ b/babel/messages/tests/checkers.py @@ -0,0 +1,371 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2008 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +from datetime import datetime +import time +import unittest +from StringIO import StringIO + +from babel import __version__ as VERSION +from babel.core import Locale, UnknownLocaleError +from babel.dates import format_datetime +from babel.messages import checkers +from babel.messages.plurals import PLURALS +from babel.messages.pofile import read_po +from babel.util import LOCALTZ + +class CheckersTestCase(unittest.TestCase): + # the last msgstr[idx] is always missing except for singular plural forms + + def test_1_num_plurals_checkers(self): + for _locale in [p for p in PLURALS if PLURALS[p][0]==1]: + try: + locale = Locale.parse(_locale) + except UnknownLocaleError: + # Just an alias? Not what we're testing here, let's continue + continue + po_file = (ur"""\ +# %(english_name)s translations for TestProject. +# Copyright (C) 2007 FooBar, Inc. +# This file is distributed under the same license as the TestProject +# project. +# FIRST AUTHOR , 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: TestProject 0.1\n" +"Report-Msgid-Bugs-To: bugs.address@email.tld\n" +"POT-Creation-Date: 2007-04-01 15:30+0200\n" +"PO-Revision-Date: %(date)s\n" +"Last-Translator: FULL NAME \n" +"Language-Team: %(locale)s \n" +"Plural-Forms: nplurals=%(num_plurals)s; plural=%(plural_expr)s\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel %(version)s\n" + +#. This will be a translator comment, +#. that will include several lines +#: project/file1.py:8 +msgid "bar" +msgstr "" + +#: project/file2.py:9 +msgid "foobar" +msgid_plural "foobars" +msgstr[0] "" + +""" % dict(locale = _locale, + english_name = locale.english_name, + version = VERSION, + year = time.strftime('%Y'), + date = format_datetime(datetime.now(LOCALTZ), + 'yyyy-MM-dd HH:mmZ', + tzinfo=LOCALTZ, locale=_locale), + num_plurals = PLURALS[_locale][0], + plural_expr = PLURALS[_locale][0])).encode('utf-8') + + # This test will fail for revisions <= 406 because so far + # catalog.num_plurals was neglected + catalog = read_po(StringIO(po_file), _locale) + message = catalog.get('foobar') + checkers.num_plurals(catalog, message) + + def test_2_num_plurals_checkers(self): + # in this testcase we add an extra msgstr[idx], we should be + # disregarding it + for _locale in [p for p in PLURALS if PLURALS[p][0]==2]: + if _locale in ['nn', 'no']: + _locale = 'nn_NO' + num_plurals = PLURALS[_locale.split('_')[0]][0] + plural_expr = PLURALS[_locale.split('_')[0]][1] + else: + num_plurals = PLURALS[_locale][0] + plural_expr = PLURALS[_locale][1] + try: + locale = Locale(_locale) + date = format_datetime(datetime.now(LOCALTZ), + 'yyyy-MM-dd HH:mmZ', + tzinfo=LOCALTZ, locale=_locale), + except UnknownLocaleError: + # Just an alias? Not what we're testing here, let's continue + continue + po_file = (ur"""\ +# %(english_name)s translations for TestProject. +# Copyright (C) 2007 FooBar, Inc. +# This file is distributed under the same license as the TestProject +# project. +# FIRST AUTHOR , 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: TestProject 0.1\n" +"Report-Msgid-Bugs-To: bugs.address@email.tld\n" +"POT-Creation-Date: 2007-04-01 15:30+0200\n" +"PO-Revision-Date: %(date)s\n" +"Last-Translator: FULL NAME \n" +"Language-Team: %(locale)s \n" +"Plural-Forms: nplurals=%(num_plurals)s; plural=%(plural_expr)s\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel %(version)s\n" + +#. This will be a translator comment, +#. that will include several lines +#: project/file1.py:8 +msgid "bar" +msgstr "" + +#: project/file2.py:9 +msgid "foobar" +msgid_plural "foobars" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +""" % dict(locale = _locale, + english_name = locale.english_name, + version = VERSION, + year = time.strftime('%Y'), + date = date, + num_plurals = num_plurals, + plural_expr = plural_expr)).encode('utf-8') + # we should be adding the missing msgstr[0] + + # This test will fail for revisions <= 406 because so far + # catalog.num_plurals was neglected + catalog = read_po(StringIO(po_file), _locale) + message = catalog.get('foobar') + checkers.num_plurals(catalog, message) + + + def test_3_num_plurals_checkers(self): + for _locale in [p for p in PLURALS if PLURALS[p][0]==3]: + po_file = r"""\ +# %(english_name)s translations for TestProject. +# Copyright (C) 2007 FooBar, Inc. +# This file is distributed under the same license as the TestProject +# project. +# FIRST AUTHOR , 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: TestProject 0.1\n" +"Report-Msgid-Bugs-To: bugs.address@email.tld\n" +"POT-Creation-Date: 2007-04-01 15:30+0200\n" +"PO-Revision-Date: %(date)s\n" +"Last-Translator: FULL NAME \n" +"Language-Team: %(locale)s \n" +"Plural-Forms: nplurals=%(num_plurals)s; plural=%(plural_expr)s\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel %(version)s\n" + +#. This will be a translator comment, +#. that will include several lines +#: project/file1.py:8 +msgid "bar" +msgstr "" + +#: project/file2.py:9 +msgid "foobar" +msgid_plural "foobars" +msgstr[0] "" +msgstr[1] "" + +""" % dict(locale = _locale, + english_name = Locale.parse(_locale).english_name, + version = VERSION, + year = time.strftime('%Y'), + date = format_datetime(datetime.now(LOCALTZ), + 'yyyy-MM-dd HH:mmZ', + tzinfo=LOCALTZ, locale=_locale), + num_plurals = PLURALS[_locale][0], + plural_expr = PLURALS[_locale][0]) + + # This test will fail for revisions <= 406 because so far + # catalog.num_plurals was neglected + catalog = read_po(StringIO(po_file), _locale) + message = catalog.get('foobar') + checkers.num_plurals(catalog, message) + + def test_4_num_plurals_checkers(self): + for _locale in [p for p in PLURALS if PLURALS[p][0]==4]: + po_file = r"""\ +# %(english_name)s translations for TestProject. +# Copyright (C) 2007 FooBar, Inc. +# This file is distributed under the same license as the TestProject +# project. +# FIRST AUTHOR , 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: TestProject 0.1\n" +"Report-Msgid-Bugs-To: bugs.address@email.tld\n" +"POT-Creation-Date: 2007-04-01 15:30+0200\n" +"PO-Revision-Date: %(date)s\n" +"Last-Translator: FULL NAME \n" +"Language-Team: %(locale)s \n" +"Plural-Forms: nplurals=%(num_plurals)s; plural=%(plural_expr)s\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel %(version)s\n" + +#. This will be a translator comment, +#. that will include several lines +#: project/file1.py:8 +msgid "bar" +msgstr "" + +#: project/file2.py:9 +msgid "foobar" +msgid_plural "foobars" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +""" % dict(locale = _locale, + english_name = Locale.parse(_locale).english_name, + version = VERSION, + year = time.strftime('%Y'), + date = format_datetime(datetime.now(LOCALTZ), + 'yyyy-MM-dd HH:mmZ', + tzinfo=LOCALTZ, locale=_locale), + num_plurals = PLURALS[_locale][0], + plural_expr = PLURALS[_locale][0]) + + # This test will fail for revisions <= 406 because so far + # catalog.num_plurals was neglected + catalog = read_po(StringIO(po_file), _locale) + message = catalog.get('foobar') + checkers.num_plurals(catalog, message) + + def test_5_num_plurals_checkers(self): + for _locale in [p for p in PLURALS if PLURALS[p][0]==5]: + po_file = r"""\ +# %(english_name)s translations for TestProject. +# Copyright (C) 2007 FooBar, Inc. +# This file is distributed under the same license as the TestProject +# project. +# FIRST AUTHOR , 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: TestProject 0.1\n" +"Report-Msgid-Bugs-To: bugs.address@email.tld\n" +"POT-Creation-Date: 2007-04-01 15:30+0200\n" +"PO-Revision-Date: %(date)s\n" +"Last-Translator: FULL NAME \n" +"Language-Team: %(locale)s \n" +"Plural-Forms: nplurals=%(num_plurals)s; plural=%(plural_expr)s\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel %(version)s\n" + +#. This will be a translator comment, +#. that will include several lines +#: project/file1.py:8 +msgid "bar" +msgstr "" + +#: project/file2.py:9 +msgid "foobar" +msgid_plural "foobars" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + +""" % dict(locale = _locale, + english_name = Locale.parse(_locale).english_name, + version = VERSION, + year = time.strftime('%Y'), + date = format_datetime(datetime.now(LOCALTZ), + 'yyyy-MM-dd HH:mmZ', + tzinfo=LOCALTZ, locale=_locale), + num_plurals = PLURALS[_locale][0], + plural_expr = PLURALS[_locale][0]) + + # This test will fail for revisions <= 406 because so far + # catalog.num_plurals was neglected + catalog = read_po(StringIO(po_file), _locale) + message = catalog.get('foobar') + checkers.num_plurals(catalog, message) + + def test_6_num_plurals_checkers(self): + for _locale in [p for p in PLURALS if PLURALS[p][0]==6]: + po_file = r"""\ +# %(english_name)s translations for TestProject. +# Copyright (C) 2007 FooBar, Inc. +# This file is distributed under the same license as the TestProject +# project. +# FIRST AUTHOR , 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: TestProject 0.1\n" +"Report-Msgid-Bugs-To: bugs.address@email.tld\n" +"POT-Creation-Date: 2007-04-01 15:30+0200\n" +"PO-Revision-Date: %(date)s\n" +"Last-Translator: FULL NAME \n" +"Language-Team: %(locale)s \n" +"Plural-Forms: nplurals=%(num_plurals)s; plural=%(plural_expr)s\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel %(version)s\n" + +#. This will be a translator comment, +#. that will include several lines +#: project/file1.py:8 +msgid "bar" +msgstr "" + +#: project/file2.py:9 +msgid "foobar" +msgid_plural "foobars" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" + +""" % dict(locale = _locale, + english_name = Locale.parse(_locale).english_name, + version = VERSION, + year = time.strftime('%Y'), + date = format_datetime(datetime.now(LOCALTZ), + 'yyyy-MM-dd HH:mmZ', + tzinfo=LOCALTZ, locale=_locale), + num_plurals = PLURALS[_locale][0], + plural_expr = PLURALS[_locale][0]) + + # This test will fail for revisions <= 406 because so far + # catalog.num_plurals was neglected + catalog = read_po(StringIO(po_file), _locale) + message = catalog.get('foobar') + checkers.num_plurals(catalog, message) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(CheckersTestCase)) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff --git a/babel/messages/tests/frontend.py b/babel/messages/tests/frontend.py --- a/babel/messages/tests/frontend.py +++ b/babel/messages/tests/frontend.py @@ -348,8 +348,7 @@ class InitCatalogNonFuzzyTestCase(unittest.TestCase): - # FIXME: what is this test case about? - + # init catalog keeps the catalog non fuzzy def setUp(self): self.olddir = os.getcwd() self.datadir = os.path.join(os.path.dirname(__file__), 'data') @@ -420,6 +419,151 @@ 'date': format_datetime(datetime.now(LOCALTZ), 'yyyy-MM-dd HH:mmZ', tzinfo=LOCALTZ, locale='en')}, open(po_file, 'U').read()) + +class InitCatalogMoreThan2PluralForms(unittest.TestCase): + def setUp(self): + self.olddir = os.getcwd() + self.datadir = os.path.join(os.path.dirname(__file__), 'data') + os.chdir(self.datadir) + _global_log.threshold = 5 # shut up distutils logging + + self.dist = Distribution(dict( + name='TestProject', + version='0.1', + packages=['project'] + )) + self.cmd = frontend.init_catalog(self.dist) + self.cmd.initialize_options() + + def tearDown(self): + locale_dir = os.path.join(self.datadir, 'project', 'i18n', 'lv_LV') + if os.path.isdir(locale_dir): + shutil.rmtree(locale_dir) + + os.chdir(self.olddir) + + def test_correct_init_plurals(self): + self.cmd.input_file = 'project/i18n/messages.pot' + self.cmd.locale = 'lv_LV' + self.cmd.output_dir = 'project/i18n' + + self.cmd.finalize_options() + self.cmd.run() + + po_file = os.path.join(self.datadir, 'project', 'i18n', 'lv_LV', + 'LC_MESSAGES', 'messages.po') + assert os.path.isfile(po_file) + + self.assertEqual( +r"""# Latvian (Latvia) translations for TestProject. +# Copyright (C) 2007 FooBar, Inc. +# This file is distributed under the same license as the TestProject +# project. +# FIRST AUTHOR , 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: TestProject 0.1\n" +"Report-Msgid-Bugs-To: bugs.address@email.tld\n" +"POT-Creation-Date: 2007-04-01 15:30+0200\n" +"PO-Revision-Date: %(date)s\n" +"Last-Translator: FULL NAME \n" +"Language-Team: lv_LV \n" +"Plural-Forms: nplurals=3; plural=(n%%10==1 && n%%100!=11 ? 0 : n != 0 ? 1 :" +" 2)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel %(version)s\n" + +#. This will be a translator coment, +#. that will include several lines +#: project/file1.py:8 +msgid "bar" +msgstr "" + +#: project/file2.py:9 +msgid "foobar" +msgid_plural "foobars" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +""" % {'version': VERSION, + 'date': format_datetime(datetime.now(LOCALTZ), 'yyyy-MM-dd HH:mmZ', + tzinfo=LOCALTZ, locale='en')}, + open(po_file, 'U').read()) + +class InitCatalogSingularPluralForms(unittest.TestCase): + def setUp(self): + self.olddir = os.getcwd() + self.datadir = os.path.join(os.path.dirname(__file__), 'data') + os.chdir(self.datadir) + _global_log.threshold = 5 # shut up distutils logging + + self.dist = Distribution(dict( + name='TestProject', + version='0.1', + packages=['project'] + )) + self.cmd = frontend.init_catalog(self.dist) + self.cmd.initialize_options() + + def tearDown(self): + locale_dir = os.path.join(self.datadir, 'project', 'i18n', 'ja_JP') + if os.path.isdir(locale_dir): + shutil.rmtree(locale_dir) + + os.chdir(self.olddir) + + def test_correct_init_plurals(self): + self.cmd.input_file = 'project/i18n/messages.pot' + self.cmd.locale = 'ja_JP' + self.cmd.output_dir = 'project/i18n' + + self.cmd.finalize_options() + self.cmd.run() + + po_file = os.path.join(self.datadir, 'project', 'i18n', 'ja_JP', + 'LC_MESSAGES', 'messages.po') + assert os.path.isfile(po_file) + + self.assertEqual( +r"""# Japanese (Japan) translations for TestProject. +# Copyright (C) 2007 FooBar, Inc. +# This file is distributed under the same license as the TestProject +# project. +# FIRST AUTHOR , 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: TestProject 0.1\n" +"Report-Msgid-Bugs-To: bugs.address@email.tld\n" +"POT-Creation-Date: 2007-04-01 15:30+0200\n" +"PO-Revision-Date: %(date)s\n" +"Last-Translator: FULL NAME \n" +"Language-Team: ja_JP \n" +"Plural-Forms: nplurals=1; plural=0\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel %(version)s\n" + +#. This will be a translator coment, +#. that will include several lines +#: project/file1.py:8 +msgid "bar" +msgstr "" + +#: project/file2.py:9 +msgid "foobar" +msgid_plural "foobars" +msgstr[0] "" + +""" % {'version': VERSION, + 'date': format_datetime(datetime.now(LOCALTZ), 'yyyy-MM-dd HH:mmZ', + tzinfo=LOCALTZ, locale='ja_JP')}, + open(po_file, 'U').read()) class CommandLineInterfaceTestCase(unittest.TestCase): @@ -684,6 +828,8 @@ suite.addTest(unittest.makeSuite(ExtractMessagesTestCase)) suite.addTest(unittest.makeSuite(InitCatalogTestCase)) suite.addTest(unittest.makeSuite(InitCatalogNonFuzzyTestCase)) + suite.addTest(unittest.makeSuite(InitCatalogMoreThan2PluralForms)) + suite.addTest(unittest.makeSuite(InitCatalogSingularPluralForms)) suite.addTest(unittest.makeSuite(CommandLineInterfaceTestCase)) return suite diff --git a/babel/messages/tests/pofile.py b/babel/messages/tests/pofile.py --- a/babel/messages/tests/pofile.py +++ b/babel/messages/tests/pofile.py @@ -163,6 +163,30 @@ message = catalog.get('bar', context='Menu') self.assertEqual('Menu', message.context) + def test_singlular_plural_form(self): + buf = StringIO(r'''msgid "foo" +msgid_plural "foo" +msgstr[0] "Voh" +msgstr[1] "Vohs"''') # This is a bad po, ja_JP only uses msgstr[0] + catalog = pofile.read_po(buf, locale='ja_JP') + self.assertEqual(1, len(catalog)) + self.assertEqual(1, catalog.num_plurals) + message = catalog.get('foo') + self.assertEqual(1, len(message.string)) + + def test_more_than_two_plural_forms(self): + buf = StringIO(r'''msgid "foo" +msgid_plural "foo" +msgstr[0] "Voh" +msgstr[1] "Vohs"''') # last translation form is missing +#msgstr[2] "Vohss"''') + catalog = pofile.read_po(buf, locale='lv_LV') + self.assertEqual(1, len(catalog)) + self.assertEqual(3, catalog.num_plurals) + message = catalog.get('foo') + self.assertEqual(3, len(message.string)) + self.assertEqual('', message.string[2]) + class WritePoTestCase(unittest.TestCase):