# HG changeset patch # User cmlenz # Date 1183467164 0 # Node ID 982d7e704fdc253b744d87a8955f4229d06a452c # Parent c8c900ec63d87d25c8cc2375e303372c6388fe7a Fix for #35, and a minor improvement to how we parse the catalog fuzzy bit. diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -66,8 +66,8 @@ self.user_comments = list(user_comments) def __repr__(self): - return '<%s %r (Flags: %r)>' % (type(self).__name__, self.id, - ', '.join([flag for flag in self.flags])) + return '<%s %r (flags: %r)>' % (type(self).__name__, self.id, + list(self.flags)) def fuzzy(self): return 'fuzzy' in self.flags @@ -80,7 +80,7 @@ >>> msg.fuzzy True >>> msg - + :type: `bool` """) @@ -122,6 +122,7 @@ # FIRST AUTHOR , YEAR. #""" + class Catalog(object): """Representation of a message catalog.""" @@ -421,7 +422,7 @@ >>> catalog = Catalog() >>> catalog[u'foo'] = Message(u'foo') >>> catalog[u'foo'] - + If a message with that ID is already in the catalog, it is updated to include the locations and flags of the new message. @@ -456,6 +457,7 @@ self.mime_headers = headers.items() self.header_comment = '\n'.join(['# %s' % comment for comment in message.user_comments]) + self.fuzzy = message.fuzzy else: if isinstance(id, (list, tuple)): assert isinstance(message.string, (list, tuple)) @@ -468,7 +470,7 @@ >>> catalog = Catalog() >>> catalog.add(u'foo') >>> catalog[u'foo'] - + This method simply constructs a `Message` object with the given arguments and invokes `__setitem__` with that object. @@ -527,7 +529,7 @@ >>> 'head' in catalog False >>> catalog.obsolete.values() - [] + [] :param template: the reference catalog, usually read from a POT file :param fuzzy_matching: whether to use fuzzy matching of message IDs diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py --- a/babel/messages/frontend.py +++ b/babel/messages/frontend.py @@ -471,23 +471,30 @@ if not self.output_file and not self.output_dir: raise DistutilsOptionError('you must specify the output file or ' 'directory') + if self.output_file and not self.locale: + raise DistutilsOptionError('you must specify the locale') def run(self): po_files = [] if not self.output_file: if self.locale: - po_files.append(os.path.join(self.output_dir, self.locale, - 'LC_MESSAGES', - self.domain + '.po')) + po_files.append((self.locale, + os.path.join(self.output_dir, self.locale, + 'LC_MESSAGES', + self.domain + '.po'))) else: for locale in os.listdir(self.output_dir): po_file = os.path.join(self.output_dir, locale, 'LC_MESSAGES', self.domain + '.po') if os.path.exists(po_file): - po_files.append(po_file) + po_files.append((locale, po_file)) else: - po_files.append(self.output_file) + po_files.append((self.locale, self.output_file)) + + domain = self.domain + if not domain: + domain = os.path.splitext(os.path.basename(self.input_file))[0] infile = open(self.input_file, 'U') try: @@ -495,18 +502,18 @@ finally: infile.close() - for po_file in po_files: - log.info('updating catalog %r based on %r', po_file, + for locale, filename in po_files: + log.info('updating catalog %r based on %r', filename, self.input_file) - infile = open(po_file, 'U') + infile = open(filename, 'U') try: - catalog = read_po(infile) + catalog = read_po(infile, locale=locale, domain=domain) finally: infile.close() rest = catalog.update(template) - outfile = open(po_file, 'w') + outfile = open(filename, 'w') try: write_po(outfile, catalog, ignore_obsolete=self.ignore_obsolete) finally: @@ -867,25 +874,31 @@ if not options.input_file: parser.error('you must specify the input file') - if not options.output_file and not options.output_dir: parser.error('you must specify the output file or directory') + if options.output_file and not options.locale: + parser.error('you must specify the loicale') po_files = [] if not options.output_file: if options.locale: - po_files.append(os.path.join(options.output_dir, options.locale, - 'LC_MESSAGES', - options.domain + '.po')) + po_files.append((options.locale, + os.path.join(options.output_dir, + options.locale, 'LC_MESSAGES', + options.domain + '.po'))) else: for locale in os.listdir(options.output_dir): po_file = os.path.join(options.output_dir, locale, 'LC_MESSAGES', options.domain + '.po') if os.path.exists(po_file): - po_files.append(po_file) + po_files.append((locale, po_file)) else: - po_files.append(options.output_file) + po_files.append((options.locale, options.output_file)) + + domain = options.domain + if not domain: + domain = os.path.splitext(os.path.basename(options.input_file))[0] infile = open(options.input_file, 'U') try: @@ -893,18 +906,18 @@ finally: infile.close() - for po_file in po_files: - print 'updating catalog %r based on %r' % (po_file, + for locale, filename in po_files: + print 'updating catalog %r based on %r' % (filename, options.input_file) - infile = open(po_file, 'U') + infile = open(filename, 'U') try: - catalog = read_po(infile) + catalog = read_po(infile, locale=locale, domain=domain) finally: infile.close() rest = catalog.update(template) - outfile = open(po_file, 'w') + outfile = open(filename, 'w') try: write_po(outfile, catalog, ignore_obsolete=options.ignore_obsolete) diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -83,7 +83,7 @@ else: return unescape(string) -def read_po(fileobj): +def read_po(fileobj, locale=None, domain=None): """Read messages from a ``gettext`` PO (portable object) file from the given file-like object and return a `Catalog`. @@ -118,11 +118,16 @@ ([u'A user comment'], [u'An auto comment']) :param fileobj: the file-like object to read the PO file from + :param locale: the locale identifier or `Locale` object, or `None` + if the catalog is not bound to a locale (which basically + means it's a template) + :param domain: the message domain :return: an iterator over ``(message, translation, location)`` tuples :rtype: ``iterator`` """ - catalog = Catalog() + catalog = Catalog(locale=locale, domain=domain) + counter = [0] messages = [] translations = [] locations = [] @@ -130,8 +135,6 @@ user_comments = [] auto_comments = [] in_msgid = in_msgstr = False - fuzzy_header = False - in_header = True def _add_message(): translations.sort() @@ -147,6 +150,7 @@ list(auto_comments), list(user_comments)) del messages[:]; del translations[:]; del locations[:]; del flags[:]; del auto_comments[:]; del user_comments[:] + counter[0] += 1 for line in fileobj.readlines(): line = line.strip().decode(catalog.charset) @@ -160,12 +164,8 @@ locations.append((filename, int(lineno))) elif line[1:].startswith(','): for flag in line[2:].lstrip().split(','): - if in_header: - if flag.strip() == 'fuzzy': - fuzzy_header = True flags.append(flag.strip()) - - + elif line[1:].startswith('.'): # These are called auto-comments comment = line[2:].strip() @@ -183,8 +183,6 @@ elif line.startswith('msgid'): in_msgid = True txt = line[5:].lstrip() - if txt == '""': - in_header = True if messages: _add_message() messages.append(txt) @@ -199,15 +197,20 @@ translations.append([0, msg]) elif line.startswith('"'): if in_msgid: - in_header = False messages[-1] += u'\n' + line.rstrip() elif in_msgstr: translations[-1][1] += u'\n' + line.rstrip() if messages: _add_message() - - catalog.fuzzy = fuzzy_header + + # No actual messages found, but there was some info in comments, from which + # we'll construct an empty header message + elif not counter[0] and (flags or user_comments or auto_comments): + messages.append(u'') + translations.append([0, u'']) + _add_message() + return catalog WORD_SEP = re.compile('(' 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 @@ -22,6 +22,18 @@ class ReadPoTestCase(unittest.TestCase): + def test_preserve_locale(self): + buf = StringIO(r'''msgid "foo" +msgstr "Voh"''') + catalog = pofile.read_po(buf, locale='en_US') + self.assertEqual('en_US', catalog.locale) + + def test_preserve_domain(self): + buf = StringIO(r'''msgid "foo" +msgstr "Voh"''') + catalog = pofile.read_po(buf, domain='mydomain') + self.assertEqual('mydomain', catalog.domain) + def test_read_multiline(self): buf = StringIO(r'''msgid "" "Here's some text that\n" @@ -35,7 +47,7 @@ self.assertEqual("Here's some text that\nincludesareallylongwordthat" "mightbutshouldnt throw us into an infinite loop\n", message.id) - + def test_fuzzy_header(self): buf = StringIO(r'''\ # Translations template for AReallyReallyLongNameForAProject. @@ -49,7 +61,7 @@ catalog = pofile.read_po(buf) self.assertEqual(1, len(list(catalog))) self.assertEqual(True, list(catalog)[0].fuzzy) - + def test_not_fuzzy_header(self): buf = StringIO(r'''\ # Translations template for AReallyReallyLongNameForAProject.