Merge lp:~jtv/launchpad/validate-translations-file into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Brad Crittenden
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~jtv/launchpad/validate-translations-file
Merge into: lp:launchpad
Diff against target: 461 lines (+317/-10)
7 files modified
lib/lp/translations/scripts/tests/test-data/minimal.pot (+8/-0)
lib/lp/translations/scripts/tests/test_validate_translations_file.py (+131/-0)
lib/lp/translations/scripts/validate_translations_file.py (+130/-0)
lib/lp/translations/utilities/mozilla_xpi_importer.py (+2/-0)
lib/lp/translations/utilities/tests/test_xpi_manifest.py (+21/-10)
lib/lp/translations/utilities/xpi_manifest.py (+7/-0)
scripts/rosetta/validate-translations-file.py (+18/-0)
To merge this branch: bzr merge lp:~jtv/launchpad/validate-translations-file
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+16866@code.launchpad.net

Commit message

Validator script for translations files. Also, a leading newline in an XPI manifest is now recognized as an error.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

= Bug 503382 =

For Firefox in particular, in order to detect problems with broken translation files early and stay tightly coupled to upstream, the Ubuntu folks need to test translation files for syntax errors and such that would prevent them from importing into Launchpad.

In the case of Firefox (which uses XPI archives for translation, not gettext), a complication is that the upstream files are available as raw files that, on build, would go into an XPI archive. The main type of file to check is DTD files. We need to check these without building full XPI files, but the organization of files within XPI archives can be different from the directory hierarchies in the revision-controlled source tree.

This branch adds a script that parses translations files in a variety of formats. Only DTD and manifest files were requested, but adding a few formats was easy enough and may come in handy.

Note that this is not a full LaunchpadScript, although I did borrow a few snippets of setup code from there. It does not need locking, activity monitoring, etc. but standard options like -v can be useful.

One of the tests re-uses the XPI helpers that currently live in lp.translations.utilities.tests. Maybe those should be moved, though I'm not sure where.

Test with:
{{{
./bin/test -vv -t validate_translations_file
}}}

No lint. To Q/A, run the new script against a variety of files in these formats.

Jeroen

Revision history for this message
Brad Crittenden (bac) wrote :
Download full text (6.3 KiB)

Hi Jeroen,

Thanks for this branch and new tool. It looks good except for a few minor things. r=bac, merge-conditional

> === added file 'lib/lp/translations/scripts/tests/test_validate_translations_file.py'
> --- lib/lp/translations/scripts/tests/test_validate_translations_file.py 1970-01-01 00:00:00 +0000
> +++ lib/lp/translations/scripts/tests/test_validate_translations_file.py 2010-01-05 17:14:16 +0000
> @@ -0,0 +1,133 @@
> +#! /usr/bin/python2.5
> +#
> +# Copyright 2010 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).

You win brownie points for getting the year right!

> +"""Test the validate-translations-file script."""
> +
> +import logging
> +import os.path
> +from textwrap import dedent
> +from unittest import TestCase, TestLoader
> +
> +from canonical.launchpad.ftests.script import run_script
> +
> +import lp.translations
> +
> +from lp.translations.scripts.validate_translations_file import (
> + UnknownFileType, ValidateTranslationsFile)
> +from lp.translations.utilities.tests.xpi_helpers import (
> + get_en_US_xpi_file_to_import)
> +
> +
> +class TestValidateTranslationsFile(TestCase):
> +
> + def _makeValidator(self, test_args=None):
> + """Produce a ValidateTranslationsFile."""
> + if test_args is None:
> + test_args = []
> + validator = ValidateTranslationsFile(test_args=test_args)
> + validator.logger.setLevel(logging.CRITICAL)
> + return validator
> +
> + def _strip(self, file_contents):
> + """Remove leading newlines & indentation from file_contents."""
> + return dedent(file_contents.strip())
> +
> + def _findTestData(self):
> + """Return base path to this test's test data."""
> + return os.path.join(
> + os.path.dirname(lp.translations.__file__),
> + 'scripts/tests/test-data')
> +
> + def test_validate_unknown(self):
> + # Unknown filename extensions result in UnknownFileType.
> + validator = self._makeValidator(['foo.bar'])

The argument list here is confusing. Does it serve any purpose? If
not please remove it.

> === added file 'lib/lp/translations/scripts/validate_translations_file.py'
> --- lib/lp/translations/scripts/validate_translations_file.py 1970-01-01 00:00:00 +0000
> +++ lib/lp/translations/scripts/validate_translations_file.py 2010-01-05 17:14:16 +0000
> @@ -0,0 +1,131 @@
> +# Copyright 2010 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +__metaclass__ = type
> +
> +__all__ = [
> + 'UnknownFileType',
> + 'ValidateTranslationsFile',
> + ]
> +
> +from cStringIO import StringIO
> +import logging
> +from optparse import OptionParser
> +import os.path
> +
> +from canonical.launchpad import scripts
> +from lp.translations.utilities.gettext_po_parser import POParser
> +from lp.translations.utilities.mozilla_xpi_importer import (
> + DtdFile, MozillaZipImportParser)
> +from lp.translations.utilities.xpi_manifest import XpiManifest
> +
> +
> +class UnknownFileType(Exception):
> + """File's type is not recognized."""...

Read more...

review: Approve (code)
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Thank you. You were absolutely right about the confusing test_args; they were mostly meant to be illustrative here but I thought I'd need them at some point. Turns out I don't, and I just removed the entire mechanism. Simple is beautiful.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'lib/lp/translations/scripts/tests/test-data'
=== added file 'lib/lp/translations/scripts/tests/test-data/minimal.pot'
--- lib/lp/translations/scripts/tests/test-data/minimal.pot 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/scripts/tests/test-data/minimal.pot 2010-01-06 06:21:21 +0000
@@ -0,0 +1,8 @@
1msgid ""
2msgstr ""
3"MIME-Version: 1.0\n"
4"Content-Type: text/plan; charset=UTF-8\n"
5"Content-Transfer-Encoding: 8bit\n"
6
7msgid "A translatable string."
8msgstr ""
09
=== added file 'lib/lp/translations/scripts/tests/test_validate_translations_file.py'
--- lib/lp/translations/scripts/tests/test_validate_translations_file.py 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/scripts/tests/test_validate_translations_file.py 2010-01-06 06:21:21 +0000
@@ -0,0 +1,131 @@
1#! /usr/bin/python2.5
2#
3# Copyright 2010 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).
5
6"""Test the validate-translations-file script."""
7
8import logging
9import os.path
10from textwrap import dedent
11from unittest import TestCase, TestLoader
12
13from canonical.launchpad.ftests.script import run_script
14
15import lp.translations
16
17from lp.translations.scripts.validate_translations_file import (
18 UnknownFileType, ValidateTranslationsFile)
19from lp.translations.utilities.tests.xpi_helpers import (
20 get_en_US_xpi_file_to_import)
21
22
23class TestValidateTranslationsFile(TestCase):
24
25 def _makeValidator(self):
26 """Produce a ValidateTranslationsFile."""
27 validator = ValidateTranslationsFile(test_args=[])
28 validator.logger.setLevel(logging.CRITICAL)
29 return validator
30
31 def _strip(self, file_contents):
32 """Remove leading newlines & indentation from file_contents."""
33 return dedent(file_contents.strip())
34
35 def _findTestData(self):
36 """Return base path to this test's test data."""
37 return os.path.join(
38 os.path.dirname(lp.translations.__file__),
39 'scripts/tests/test-data')
40
41 def test_validate_unknown(self):
42 # Unknown filename extensions result in UnknownFileType.
43 validator = self._makeValidator()
44 self.assertRaises(
45 UnknownFileType, validator._validateContent, 'foo.bar', 'content')
46
47 def test_validate_dtd_good(self):
48 validator = self._makeValidator()
49 result = validator._validateContent(
50 'test.dtd', '<!ENTITY a.translatable.string "A string">\n')
51 self.assertTrue(result)
52
53 def test_validate_dtd_bad(self):
54 validator = self._makeValidator()
55 result = validator._validateContent(
56 'test.dtd', '<!ENTIT etc.')
57 self.assertFalse(result)
58
59 def test_validate_xpi_manifest_good(self):
60 validator = self._makeValidator()
61 result = validator._validateContent(
62 'chrome.manifest', 'locale foo nl jar:chrome/nl.jar!/foo/')
63 self.assertTrue(result)
64
65 def test_validate_xpi_manifest_bad(self):
66 # XPI manifests must not begin with newline.
67 validator = self._makeValidator()
68 result = validator._validateContent('chrome.manifest', '\nlocale')
69 self.assertFalse(result)
70
71 def test_validate_po_good(self):
72 validator = self._makeValidator()
73 result = validator._validateContent('nl.po', self._strip(r"""
74 msgid ""
75 msgstr ""
76 "MIME-Version: 1.0\n"
77 "Content-Type: text/plan; charset=UTF-8\n"
78 "Content-Transfer-Encoding: 8bit\n"
79
80 msgid "foo"
81 msgstr "bar"
82 """))
83 self.assertTrue(result)
84
85 def test_validate_po_bad(self):
86 validator = self._makeValidator()
87 result = validator._validateContent('nl.po', self._strip("""
88 msgid "no header here"
89 msgstr "hier geen kopje"
90 """))
91 self.assertFalse(result)
92
93 def test_validate_pot_good(self):
94 validator = self._makeValidator()
95 result = validator._validateContent('test.pot', self._strip(r"""
96 msgid ""
97 msgstr ""
98 "MIME-Version: 1.0\n"
99 "Content-Type: text/plan; charset=UTF-8\n"
100 "Content-Transfer-Encoding: 8bit\n"
101
102 msgid "foo"
103 msgstr ""
104 """))
105 self.assertTrue(result)
106
107 def test_validate_pot_bad(self):
108 validator = self._makeValidator()
109 result = validator._validateContent('test.pot', 'garble')
110 self.assertFalse(result)
111
112 def test_validate_xpi_good(self):
113 validator = self._makeValidator()
114 xpi_content = get_en_US_xpi_file_to_import('en-US').read()
115 result = validator._validateContent('pl.xpi', xpi_content)
116 self.assertTrue(result)
117
118 def test_validate_xpi_bad(self):
119 validator = self._makeValidator()
120 result = validator._validateContent('de.xpi', 'garble')
121 self.assertFalse(result)
122
123 def test_script(self):
124 test_input = os.path.join(self._findTestData(), 'minimal.pot')
125 script = 'scripts/rosetta/validate-translations-file.py'
126 result, out, err = run_script(script, [test_input])
127 self.assertEqual(0, result)
128
129
130def test_suite():
131 return TestLoader().loadTestsFromName(__name__)
0132
=== added file 'lib/lp/translations/scripts/validate_translations_file.py'
--- lib/lp/translations/scripts/validate_translations_file.py 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/scripts/validate_translations_file.py 2010-01-06 06:21:21 +0000
@@ -0,0 +1,130 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4__metaclass__ = type
5
6__all__ = [
7 'UnknownFileType',
8 'ValidateTranslationsFile',
9 ]
10
11from cStringIO import StringIO
12import logging
13from optparse import OptionParser
14import os.path
15
16from canonical.launchpad import scripts
17from lp.translations.utilities.gettext_po_parser import POParser
18from lp.translations.utilities.mozilla_xpi_importer import (
19 DtdFile, MozillaZipImportParser)
20from lp.translations.utilities.xpi_manifest import XpiManifest
21
22
23class UnknownFileType(Exception):
24 """File's type is not recognized."""
25
26
27def validate_unknown_file_type(filename, content):
28 """Fail validation: unknown file type."""
29 raise UnknownFileType("Unrecognized file type for '%s'." % filename)
30
31
32def validate_dtd(filename, content):
33 """Validate XPI DTD file."""
34 DtdFile(filename, filename, content)
35
36
37def validate_po(filename, content):
38 """Validate a gettext PO or POT file."""
39 POParser().parse(content)
40
41
42def validate_xpi(filename, content):
43 """Validate an XPI archive."""
44 MozillaZipImportParser(filename, StringIO(content))
45
46
47def validate_xpi_manifest(filename, content):
48 """Validate XPI manifest."""
49 XpiManifest(content)
50
51
52class ValidateTranslationsFile:
53 """Parse translations files to see if they are well-formed."""
54
55 name = 'validate-translations-file'
56
57 validators = {
58 'dtd': validate_dtd,
59 'manifest': validate_xpi_manifest,
60 'po': validate_po,
61 'pot': validate_po,
62 'xpi': validate_xpi,
63 }
64
65 def __init__(self, test_args=None):
66 """Set up basic facilities, similar to `LaunchpadScript`."""
67 self.parser = OptionParser()
68 scripts.logger_options(self.parser, default=logging.INFO)
69 self.options, self.args = self.parser.parse_args(args=test_args)
70 self.logger = scripts.logger(self.options, self.name)
71
72 def main(self):
73 """Validate file(s)."""
74 failures = 0
75 files = len(self.args)
76 self.logger.info("Validating %d file(s)." % files)
77
78 for filename in self.args:
79 if not self._readAndValidate(filename):
80 failures += 1
81
82 if failures == 0:
83 self.logger.info("OK.")
84 elif failures > 1:
85 self.logger.error("%d failures in %d files." % (failures, files))
86 elif files > 1:
87 self.logger.error("1 failure in %d files." % files)
88 else:
89 self.logger.error("Validation failed.")
90
91 if failures == 0:
92 return 0
93 else:
94 return 1
95
96 def _pickValidator(self, filename):
97 """Select the appropriate validator for a file."""
98 base, ext = os.path.splitext(filename)
99 if ext is not None and ext.startswith('.'):
100 ext = ext[1:]
101 return self.validators.get(ext, validate_unknown_file_type)
102
103 def _validateContent(self, filename, content):
104 """Validate in-memory file contents.
105
106 :param filename: Name of this file.
107 :param content: Contents of this file, as raw bytes.
108 :return: Whether the file was parsed successfully.
109 """
110 validator = self._pickValidator(filename)
111 try:
112 validator(filename, content)
113 except (SystemError, AssertionError):
114 raise
115 except UnknownFileType:
116 raise
117 except Exception, e:
118 self.logger.warn("Failure in '%s': %s" % (filename, e))
119 return False
120
121 return True
122
123 def _readAndValidate(self, filename):
124 """Read given file and validate it.
125
126 :param filename: Name of a file to read.
127 :return: Whether the file was parsed successfully.
128 """
129 content = file(filename).read()
130 return self._validateContent(filename, content)
0131
=== modified file 'lib/lp/translations/utilities/mozilla_xpi_importer.py'
--- lib/lp/translations/utilities/mozilla_xpi_importer.py 2009-10-14 18:43:26 +0000
+++ lib/lp/translations/utilities/mozilla_xpi_importer.py 2010-01-06 06:21:21 +0000
@@ -4,7 +4,9 @@
4__metaclass__ = type4__metaclass__ = type
55
6__all__ = [6__all__ = [
7 'DtdFile',
7 'MozillaXpiImporter',8 'MozillaXpiImporter',
9 'MozillaZipImportParser',
8 ]10 ]
911
10from cStringIO import StringIO12from cStringIO import StringIO
1113
=== modified file 'lib/lp/translations/utilities/tests/test_xpi_manifest.py'
--- lib/lp/translations/utilities/tests/test_xpi_manifest.py 2009-07-17 00:26:05 +0000
+++ lib/lp/translations/utilities/tests/test_xpi_manifest.py 2010-01-06 06:21:21 +0000
@@ -9,6 +9,9 @@
99
10from lp.translations.utilities.xpi_manifest import XpiManifest10from lp.translations.utilities.xpi_manifest import XpiManifest
1111
12from lp.translations.interfaces.translationimporter import (
13 TranslationFormatSyntaxError)
14
1215
13class XpiManifestTestCase(unittest.TestCase):16class XpiManifestTestCase(unittest.TestCase):
14 """Test `XpiManifest`."""17 """Test `XpiManifest`."""
@@ -38,7 +41,7 @@
38 There are no usable41 There are no usable
39 locale lines42 locale lines
40 in this file.43 in this file.
41 """)44 """.lstrip())
42 self.assertEqual(len(manifest._locales), 0)45 self.assertEqual(len(manifest._locales), 0)
43 chrome_path, locale = manifest.getChromePathAndLocale('lines')46 chrome_path, locale = manifest.getChromePathAndLocale('lines')
44 self.failIf(chrome_path is not None, "Empty manifest matched a path.")47 self.failIf(chrome_path is not None, "Empty manifest matched a path.")
@@ -61,7 +64,7 @@
61 locale bar en-US bardir/64 locale bar en-US bardir/
62 locale ixx en-US ixxdir/65 locale ixx en-US ixxdir/
63 locale gna en-US gnadir/66 locale gna en-US gnadir/
64 """)67 """.lstrip())
65 self.assertEqual(len(manifest._locales), 4)68 self.assertEqual(len(manifest._locales), 4)
66 self._checkSortOrder(manifest)69 self._checkSortOrder(manifest)
67 for dir in ['gna', 'bar', 'ixx', 'foo']:70 for dir in ['gna', 'bar', 'ixx', 'foo']:
@@ -107,7 +110,7 @@
107 locale okay fr foodir/110 locale okay fr foodir/
108 locale overlong fr foordir/ etc. etc. etc.111 locale overlong fr foordir/ etc. etc. etc.
109 locale incomplete fr112 locale incomplete fr
110 """)113 """.lstrip())
111 self.assertEqual(len(manifest._locales), 1)114 self.assertEqual(len(manifest._locales), 1)
112 chrome_path, locale = manifest.getChromePathAndLocale('foodir/x')115 chrome_path, locale = manifest.getChromePathAndLocale('foodir/x')
113 self.failIf(chrome_path is None, "Garbage lines messed up match.")116 self.failIf(chrome_path is None, "Garbage lines messed up match.")
@@ -119,7 +122,7 @@
119 manifest = XpiManifest("""122 manifest = XpiManifest("""
120 locale dup fy boppe123 locale dup fy boppe
121 locale dup fy boppe124 locale dup fy boppe
122 """)125 """.lstrip())
123 self.assertEqual(len(manifest._locales), 1)126 self.assertEqual(len(manifest._locales), 1)
124127
125 def _checkLookup(self, manifest, path, chrome_path, locale):128 def _checkLookup(self, manifest, path, chrome_path, locale):
@@ -162,7 +165,7 @@
162 manifest = XpiManifest("""165 manifest = XpiManifest("""
163 locale short el /ploink/squit166 locale short el /ploink/squit
164 locale long he /ploink/squittle167 locale long he /ploink/squittle
165 """)168 """.lstrip())
166 self._checkSortOrder(manifest)169 self._checkSortOrder(manifest)
167 self._checkLookup(manifest, 'ploink/squit/x', 'short/x', 'el')170 self._checkLookup(manifest, 'ploink/squit/x', 'short/x', 'el')
168 self._checkLookup(manifest, '/ploink/squittle/x', 'long/x', 'he')171 self._checkLookup(manifest, '/ploink/squittle/x', 'long/x', 'he')
@@ -175,7 +178,7 @@
175 locale foo2 ca a/b/178 locale foo2 ca a/b/
176 locale foo3 ca a/b/c/x1179 locale foo3 ca a/b/c/x1
177 locale foo4 ca a/b/c/x2180 locale foo4 ca a/b/c/x2
178 """)181 """.lstrip())
179 self._checkSortOrder(manifest)182 self._checkSortOrder(manifest)
180 self._checkLookup(manifest, 'a/bb', 'foo1/bb', 'ca')183 self._checkLookup(manifest, 'a/bb', 'foo1/bb', 'ca')
181 self._checkLookup(manifest, 'a/bb/c', 'foo1/bb/c', 'ca')184 self._checkLookup(manifest, 'a/bb/c', 'foo1/bb/c', 'ca')
@@ -190,7 +193,7 @@
190 manifest = XpiManifest("""193 manifest = XpiManifest("""
191 locale foo en_GB jar:foo.jar!/dir/194 locale foo en_GB jar:foo.jar!/dir/
192 locale bar id jar:bar.jar!/195 locale bar id jar:bar.jar!/
193 """)196 """.lstrip())
194 self._checkSortOrder(manifest)197 self._checkSortOrder(manifest)
195 self._checkLookup(198 self._checkLookup(
196 manifest, 'jar:foo.jar!/dir/file', 'foo/file', 'en_GB')199 manifest, 'jar:foo.jar!/dir/file', 'foo/file', 'en_GB')
@@ -227,7 +230,7 @@
227 locale croatian hr jar:translations.jar!/hr/230 locale croatian hr jar:translations.jar!/hr/
228 locale docs sr jar:docs.jar!/sr/231 locale docs sr jar:docs.jar!/sr/
229 locale docs hr jar:docs.jar!/hr/232 locale docs hr jar:docs.jar!/hr/
230 """)233 """.lstrip())
231 self._checkSortOrder(manifest)234 self._checkSortOrder(manifest)
232 self._checkLookup(235 self._checkLookup(
233 manifest, 'jar:translations.jar!/sr/x', 'serbian/x', 'sr')236 manifest, 'jar:translations.jar!/sr/x', 'serbian/x', 'sr')
@@ -242,7 +245,7 @@
242 locale x it jar:dir/x.jar!/subdir/y.jar!/245 locale x it jar:dir/x.jar!/subdir/y.jar!/
243 locale y it jar:dir/x.jar!/subdir/y.jar!/deep/246 locale y it jar:dir/x.jar!/subdir/y.jar!/deep/
244 locale z it jar:dir/x.jar!/subdir/z.jar!/247 locale z it jar:dir/x.jar!/subdir/z.jar!/
245 """)248 """.lstrip())
246 self._checkSortOrder(manifest)249 self._checkSortOrder(manifest)
247 self._checkLookup(250 self._checkLookup(
248 manifest, 'jar:dir/x.jar!/subdir/y.jar!/foo', 'x/foo', 'it')251 manifest, 'jar:dir/x.jar!/subdir/y.jar!/foo', 'x/foo', 'it')
@@ -291,10 +294,18 @@
291 locale browser en-US jar:locales/294 locale browser en-US jar:locales/
292 locale browser en-US jar:locales/en-US.jar!/chrome/295 locale browser en-US jar:locales/en-US.jar!/chrome/
293 locale browser en-US jar:locales/en-US.jar!/296 locale browser en-US jar:locales/en-US.jar!/
294 """)297 """.lstrip())
295 path = manifest.findMatchingXpiPath('browser/gui/print.dtd', 'en-US')298 path = manifest.findMatchingXpiPath('browser/gui/print.dtd', 'en-US')
296 self.assertEqual(path, "jar:locales/en-US.jar!/chrome/gui/print.dtd")299 self.assertEqual(path, "jar:locales/en-US.jar!/chrome/gui/print.dtd")
297300
301 def test_blank_line(self):
302 # Manifests must not begin with newline.
303 self.assertRaises(
304 TranslationFormatSyntaxError,
305 XpiManifest, """
306 locale browser en-US jar:locales
307 """)
308
298309
299def test_suite():310def test_suite():
300 return unittest.defaultTestLoader.loadTestsFromName(__name__)311 return unittest.defaultTestLoader.loadTestsFromName(__name__)
301312
=== modified file 'lib/lp/translations/utilities/xpi_manifest.py'
--- lib/lp/translations/utilities/xpi_manifest.py 2009-07-17 00:26:05 +0000
+++ lib/lp/translations/utilities/xpi_manifest.py 2010-01-06 06:21:21 +0000
@@ -9,6 +9,9 @@
9import logging9import logging
10import re10import re
1111
12from lp.translations.interfaces.translationimporter import (
13 TranslationFormatSyntaxError)
14
1215
13def normalize_path(path):16def normalize_path(path):
14 """Normalize filesystem path within XPI file."""17 """Normalize filesystem path within XPI file."""
@@ -127,6 +130,10 @@
127130
128 def __init__(self, content):131 def __init__(self, content):
129 """Initialize: parse `content` as a manifest file."""132 """Initialize: parse `content` as a manifest file."""
133 if content.startswith('\n'):
134 raise TranslationFormatSyntaxError(
135 message="Manifest begins with newline.")
136
130 locales = []137 locales = []
131 for line in content.splitlines():138 for line in content.splitlines():
132 words = line.split()139 words = line.split()
133140
=== added file 'scripts/rosetta/validate-translations-file.py'
--- scripts/rosetta/validate-translations-file.py 1970-01-01 00:00:00 +0000
+++ scripts/rosetta/validate-translations-file.py 2010-01-06 06:21:21 +0000
@@ -0,0 +1,18 @@
1#! /usr/bin/python2.5
2# Copyright 2010 Canonical Ltd. This software is licensed under the
3# GNU Affero General Public License version 3 (see the file LICENSE).
4
5# pylint: disable-msg=W0403
6
7__metaclass__ = type
8
9import _pythonpath
10
11import sys
12
13from lp.translations.scripts.validate_translations_file import (
14 ValidateTranslationsFile)
15
16
17if __name__ == "__main__":
18 sys.exit(ValidateTranslationsFile().main())