def test_copy_message_replaces_string(self): old_message = Message("id", string="a", locations=[("file1", "2")]) new_message = copy_message(old_message, string="b") expected_message = Message("id", string="b", locations=[("file1", "2")]) assert_messages_deeply_equal(new_message, expected_message)
def test_checker_works(): m = Message(id=u'hello', string=u'hello %(num)d') errors = [err for err in m.check()] eq_(len(errors), 1) eq_( str(errors[0]), 'Translation contains named placeholder but ' 'source doesn\'t: hello %(num)d')
def test_find_corresponding_message_exists(self): catalog = Catalog() catalog.add("id", string="Text") corresponding = find_corresponding_message(catalog, Message("id")) self.assertTrue(corresponding) assert_messages_deeply_equal(corresponding, catalog.get("id")) self.assertEqual(find_corresponding_string(catalog, Message("id")), "Text")
def test_find_corresponding_message_not_exists(self): catalog = Catalog() catalog.add("id", string="Text") corresponding = find_corresponding_message(catalog, Message("other id")) self.assertIsNone(corresponding) self.assertIsNone( find_corresponding_string(catalog, Message("other id")))
def _add_message(self): """ Add a message to the catalog based on the current parser state and clear the state ready to process the next message. """ self.translations.sort() if len(self.messages) > 1: msgid = tuple([m.denormalize() for m in self.messages]) else: msgid = self.messages[0].denormalize() if isinstance(msgid, (list, tuple)): string = ['' for _ in range(self.catalog.num_plurals)] for idx, translation in self.translations: if idx >= self.catalog.num_plurals: self._invalid_pofile("", self.offset, "msg has more translations than num_plurals of catalog") continue string[idx] = translation.denormalize() string = tuple(string) else: string = self.translations[0][1].denormalize() if self.context: msgctxt = self.context.denormalize() else: msgctxt = None message = Message(msgid, string, list(self.locations), set(self.flags), self.auto_comments, self.user_comments, lineno=self.offset + 1, context=msgctxt) if self.obsolete: if not self.ignore_obsolete: self.catalog.obsolete[msgid] = message else: self.catalog[msgid] = message self.counter += 1 self._reset_message_state()
def test_build_pot(minimal_i18n_settings, temp_builds_dir, fixtures_settings): """ Force to rebuild POT file """ basepath = temp_builds_dir.join("i18n_build_pot") # Copy sample project to temporary dir samplename = "minimal_i18n" samplepath = os.path.join(fixtures_settings.fixtures_path, samplename) destination = os.path.join(basepath.strpath, samplename) shutil.copytree(samplepath, destination) # Get settings settings = minimal_i18n_settings(destination) manager = I18NManager(settings) pot = manager.build_pot(force=True) assert pot.header_comment == ("""# Translations template for """ """minimal_i18n project""" """\n# Created by Optimus""") assert ("Hello World!" in pot) is True assert pot["Hello World!"] == Message("Hello World!")
def test_pot_attribute_getter(minimal_i18n_settings, temp_builds_dir, fixtures_settings): """ Reach the pot attribute that may trigger the build_pot when POT does not allready exists """ basepath = temp_builds_dir.join("i18n_pot_attribute_getter") # Copy sample project to temporary dir samplename = "minimal_i18n" samplepath = os.path.join(fixtures_settings.fixtures_path, samplename) destination = os.path.join(basepath.strpath, samplename) shutil.copytree(samplepath, destination) # Get settings settings = minimal_i18n_settings(destination) manager = I18NManager(settings) # Remove locale dir so the POT doesnt exists anymore shutil.rmtree(settings.LOCALES_DIR) # Recreate just locale base dir os.makedirs(settings.LOCALES_DIR) # Access pot through pot attribute pot = manager.pot assert pot.header_comment == ("""# Translations template for """ """minimal_i18n project""" """\n# Created by Optimus""") assert ("Hello World!" in pot) is True assert pot["Hello World!"] == Message("Hello World!")
def validate(message: Message, catalog: Catalog) -> List[str]: errors = _validate_fuzzy(message) errors.extend([f' {str(err)}' for err in message.check(catalog)]) if message.python_format and not message.pluralizable and message.string: errors.extend(_validate_cfmt(message.id, message.string)) return errors
def test_po_with_multiline_obsolete_message(self): catalog = Catalog() catalog.add(u'foo', u'Voh', locations=[('main.py', 1)]) msgid = r"""Here's a message that covers multiple lines, and should still be handled correctly. """ msgstr = r"""Here's a message that covers multiple lines, and should still be handled correctly. """ catalog.obsolete[msgid] = Message(msgid, msgstr, locations=[('utils.py', 3)]) buf = BytesIO() pofile.write_po(buf, catalog, omit_header=True) self.assertEqual(b'''#: main.py:1 msgid "foo" msgstr "Voh" #~ msgid "" #~ "Here's a message that covers\\n" #~ "multiple lines, and should still be handled\\n" #~ "correctly.\\n" #~ msgstr "" #~ "Here's a message that covers\\n" #~ "multiple lines, and should still be handled\\n" #~ "correctly.\\n"''', buf.getvalue().strip())
def _add_message(): translations.sort() if len(messages) > 1: msgid = tuple([denormalize(m) for m in messages]) else: msgid = denormalize(messages[0]) 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: msgctxt = denormalize('\n'.join(context)) else: msgctxt = None message = Message(msgid, string, list(locations), set(flags), auto_comments, user_comments, lineno=offset[0] + 1, context=msgctxt) if obsolete[0]: if not ignore_obsolete: catalog.obsolete[msgid] = message else: catalog[msgid] = message del messages[:]; del translations[:]; del context[:]; del locations[:]; del flags[:]; del auto_comments[:]; del user_comments[:]; obsolete[0] = False counter[0] += 1
def test_add_or_update_message_update(self): target_catalog = Catalog() target_catalog.add("id1", string="Text1") add_or_update_message(target_catalog, Message("id1", string="Text2")) expected_catalog = Catalog() expected_catalog.add("id1", string="Text2") assert_catalogs_deeply_equal(target_catalog, expected_catalog)
def xgettext_yaml(filelist, outputfilename): output = open(outputfilename, 'wb') print output, outputfilename cat = Catalog() for f in open(filelist, 'rb').readlines(): f = f.strip() assert os.path.isfile(f), f for s, lineno in yield_translatable_yaml_strings(f): cat[s] = Message(s, locations=[(f, lineno)]) write_po(output, cat, width=None, sort_by_file=True)
def test_po_with_obsolete_message_ignored(self): catalog = Catalog() catalog.add(u'foo', u'Voh', locations=[('main.py', 1)]) catalog.obsolete['bar'] = Message(u'bar', u'Bahr', locations=[('utils.py', 3)], user_comments=['User comment']) buf = BytesIO() pofile.write_po(buf, catalog, omit_header=True, ignore_obsolete=True) self.assertEqual(b'''#: main.py:1 msgid "foo" msgstr "Voh"''', buf.getvalue().strip())
def test_overwrite_po_success(minimal_i18n_settings, temp_builds_dir, fixtures_settings): """ safe_write_po usage for overwritting file """ basepath = temp_builds_dir.join("i18n_overwrite_po_success") # Copy sample project to temporary dir samplename = "minimal_i18n" samplepath = os.path.join(fixtures_settings.fixtures_path, samplename) destination = os.path.join(basepath.strpath, samplename) shutil.copytree(samplepath, destination) dummy_name = "dummy_pot.pot" dummy_pot = os.path.join(destination, dummy_name) # Get manager with settings settings = minimal_i18n_settings(destination) manager = I18NManager(settings) # Create a dummy catalog to write catalog = Catalog(header_comment="# Foobar") catalog.add("foo %(name)s", locations=[("main.py", 1)], flags=("fuzzy", )) # Write it manager.safe_write_po(catalog, dummy_pot) # Check it with io.open(dummy_pot, "rb") as f: dummy_catalog = read_po(f) assert dummy_catalog.header_comment == "# Foobar" # New dummy catalog to overwrite previous one catalog = Catalog(header_comment="# Zob") catalog.add("ping", string="pong", locations=[("nope.py", 42)]) # Write it manager.safe_write_po(catalog, dummy_pot) # List existing pot file at root pots = [item for item in os.listdir(destination) if item.endswith(".pot")] # No other pot file assert pots == [dummy_name] # Check it again with io.open(dummy_pot, "rb") as f: dummy_catalog = read_po(f) assert dummy_catalog.header_comment == "# Zob" assert dummy_catalog["ping"] == Message("ping", string="pong")
def copy_message(message: Message, **kwargs) -> Message: """Copies a message, replacing any of its attributes given in kwargs""" return Message( **{ "id": kwargs.get("id", message.id), "context": kwargs.get("context", message.context), "string": kwargs.get("string", message.string), "flags": kwargs.get("flags", message.flags), "locations": kwargs.get("locations", message.locations), "user_comments": kwargs.get("user_comments", message.user_comments), "auto_comments": kwargs.get("auto_comments", message.auto_comments), })
def test_sign_in(self): fake_msg = Message('Email address verification - Liberapay', 'Vous avez du pain ?') LOCALES['fr'].catalog[fake_msg.id].string = fake_msg.string r = self.sign_in(HTTP_ACCEPT_LANGUAGE='fr') assert r.code == 302, r.text assert SESSION in r.headers.cookie # Check that an email was sent, in the user's preferred language Participant.dequeue_emails() last_email = self.get_last_email() username = good_data['sign-in.username'] assert last_email['subject'] == fake_msg.string # Check that the new user has an avatar p = Participant.from_username(username) assert p.avatar_url
def _add_message(self): """ Add a message to the catalog based on the current parser state and clear the state ready to process the next message. """ self.translations.sort() if len(self.messages) > 1: msgid = tuple([denormalize(m) for m in self.messages]) else: msgid = denormalize(self.messages[0]) if isinstance(msgid, (list, tuple)): string = [] for idx in range(self.catalog.num_plurals): try: string.append(self.translations[idx]) except IndexError: string.append((idx, '')) string = tuple([denormalize(t[1]) for t in string]) else: string = denormalize(self.translations[0][1]) if self.context: msgctxt = denormalize('\n'.join(self.context)) else: msgctxt = None message = Message(msgid, string, list(self.locations), set(self.flags), self.auto_comments, self.user_comments, lineno=self.offset + 1, context=msgctxt) if self.obsolete: if not self.ignore_obsolete: self.catalog.obsolete[msgid] = message else: self.catalog[msgid] = message del self.messages[:] del self.translations[:] del self.context[:] del self.locations[:] del self.flags[:] del self.auto_comments[:] del self.user_comments[:] self.obsolete = False self.counter += 1
def test_po_with_obsolete_message(self): catalog = Catalog() catalog.add(u('foo'), u('Voh'), locations=[('main.py', 1)]) catalog.obsolete['bar'] = Message(u('bar'), u('Bahr'), locations=[('utils.py', 3)], user_comments=['User comment']) buf = BytesIO() pofile.write_po(buf, catalog, omit_header=True) self.assertEqual( b('''#: main.py:1 msgid "foo" msgstr "Voh" # User comment #~ msgid "bar" #~ msgstr "Bahr"'''), buf.getvalue().strip())
def test_po_with_obsolete_message(self): catalog = Catalog() catalog.add(u'foo', u'Voh', locations=[('main.py', 1)]) catalog.obsolete['bar'] = Message(u'bar', u'Bahr', locations=[('utils.py', 3)], flags=['fuzzy'], extracted_comments=['Developer Comment'], translator_comments=['User comment'], previous_id=u'foo', previous_context='previous context', context='context') buf = BytesIO() pofile.write_po(buf, catalog, omit_header=True) self.assertEqual(b'''#: main.py:1 msgid "foo" msgstr "Voh" # User comment #. Developer Comment #: utils.py:3 #, fuzzy #~ msgctxt "context" #~ msgid "bar" #~ msgstr "Bahr"''', buf.getvalue().strip()) buf = BytesIO() pofile.write_po(buf, catalog, omit_header=True, include_previous=True) self.assertEqual(b'''#: main.py:1 msgid "foo" msgstr "Voh" # User comment #. Developer Comment #: utils.py:3 #, fuzzy #| msgctxt "previous context" #| msgid "foo" #~ msgctxt "context" #~ msgid "bar" #~ msgstr "Bahr"''', buf.getvalue().strip())
def test_clone_pot(minimal_i18n_settings, temp_builds_dir, fixtures_settings): """ Check POT cloning """ basepath = temp_builds_dir.join("i18n_clone_pot") # Copy sample project to temporary dir samplename = "minimal_i18n" samplepath = os.path.join(fixtures_settings.fixtures_path, samplename) destination = os.path.join(basepath.strpath, samplename) shutil.copytree(samplepath, destination) # Get manager with settings settings = minimal_i18n_settings(destination) manager = I18NManager(settings) manager.build_pot(force=True) cloned = manager.clone_pot() assert cloned.header_comment == ("""# Translations template for """ """minimal_i18n project""" """\n# Created by Optimus""") assert ("Hello World!" in cloned) is True assert cloned["Hello World!"] == Message("Hello World!")
def test_checker_works(): m = Message(id=u'hello', string=u'hello %(num)d') errors = [err for err in m.check()] eq_(len(errors), 1) eq_(str(errors[0]), 'Translation contains named placeholder but ' 'source doesn\'t: hello %(num)d')
def test_message_unique_identifier_with_context(self): self.assertEqual( message_unique_identifier( Message("id", string="Text", context="ctxt")), ("id", "ctxt"), )
def test_message_unique_identifier_no_context(self): self.assertEqual( message_unique_identifier(Message("id", string="Text")), "id")
def read_mo(fileobj): """Read a binary MO file from the given file-like object and return a corresponding `Catalog` object. :param fileobj: the file-like object to read the MO file from :note: The implementation of this function is heavily based on the ``GNUTranslations._parse`` method of the ``gettext`` module in the standard library. """ catalog = Catalog() headers = {} filename = getattr(fileobj, 'name', '') buf = fileobj.read() buflen = len(buf) unpack = struct.unpack # Parse the .mo file header, which consists of 5 little endian 32 # bit words. magic = unpack('<I', buf[:4])[0] # Are we big endian or little endian? if magic == LE_MAGIC: version, msgcount, origidx, transidx = unpack('<4I', buf[4:20]) ii = '<II' elif magic == BE_MAGIC: version, msgcount, origidx, transidx = unpack('>4I', buf[4:20]) ii = '>II' else: raise IOError(0, 'Bad magic number', filename) # Now put all messages from the .mo file buffer into the catalog # dictionary for i in range_type(0, msgcount): mlen, moff = unpack(ii, buf[origidx:origidx + 8]) mend = moff + mlen tlen, toff = unpack(ii, buf[transidx:transidx + 8]) tend = toff + tlen if mend < buflen and tend < buflen: msg = buf[moff:mend] tmsg = buf[toff:tend] else: raise IOError(0, 'File is corrupt', filename) # See if we're looking at GNU .mo conventions for metadata if mlen == 0: # Catalog description lastkey = key = None for item in tmsg.splitlines(): item = item.strip() if not item: continue if b':' in item: key, value = item.split(b':', 1) lastkey = key = key.strip().lower() headers[key] = value.strip() elif lastkey: headers[lastkey] += b'\n' + item if b'\x04' in msg: # context ctxt, msg = msg.split(b'\x04') else: ctxt = None if b'\x00' in msg: # plural forms msg = msg.split(b'\x00') tmsg = tmsg.split(b'\x00') if catalog.charset: msg = [x.decode(catalog.charset) for x in msg] tmsg = [x.decode(catalog.charset) for x in tmsg] else: if catalog.charset: msg = msg.decode(catalog.charset) tmsg = tmsg.decode(catalog.charset) catalog[msg] = Message(msg, tmsg, context=ctxt) # advance to next entry in the seek tables origidx += 8 transidx += 8 catalog.mime_headers = headers.items() return catalog
def test_po_with_multiline_obsolete_message(self): catalog = Catalog() catalog.add(u'foo', u'Voh', locations=[('main.py', 1)]) msgid = r"""Here's a message that covers multiple lines, and should still be handled correctly. """ msgstr = r"""Here's a message that covers multiple lines, and should still be handled correctly. """ extracted_comment = r"""Here's a developer comment that covers multiple lines, and should still be handled correctly. """ translator_comment = r"""Here's a user comment that covers multiple lines, and should still be handled correctly. """ previous_id = r"""Here's a previous message that covers multiple lines, and should still be handled correctly. """ previous_context = r"""Here's a previous context that covers multiple lines, and should still be handled correctly. """ context = r"""Here's a context that covers multiple lines, and should still be handled correctly. """ catalog.obsolete[msgid] = Message(msgid, msgstr, locations=[('utils.py', 3)], flags=['fuzzy'], extracted_comments=[extracted_comment], translator_comments=[translator_comment], previous_id=previous_id, previous_context=previous_context, context=context) buf = BytesIO() pofile.write_po(buf, catalog, omit_header=True) self.assertEqual(b'''#: main.py:1 msgid "foo" msgstr "Voh" # Here's a user comment that covers multiple lines, and should still be # handled correctly. #. Here's a developer comment that covers multiple lines, and should still be #. handled correctly. #: utils.py:3 #, fuzzy #~ msgctxt "" #~ "Here's a context that covers\\n" #~ "multiple lines, and should still be handled\\n" #~ "correctly.\\n" #~ msgid "" #~ "Here's a message that covers\\n" #~ "multiple lines, and should still be handled\\n" #~ "correctly.\\n" #~ msgstr "" #~ "Here's a message that covers\\n" #~ "multiple lines, and should still be handled\\n" #~ "correctly.\\n"''', buf.getvalue().strip()) buf = BytesIO() pofile.write_po(buf, catalog, omit_header=True, include_previous=True) self.assertEqual(b'''#: main.py:1 msgid "foo" msgstr "Voh" # Here's a user comment that covers multiple lines, and should still be # handled correctly. #. Here's a developer comment that covers multiple lines, and should still be #. handled correctly. #: utils.py:3 #, fuzzy #| msgctxt "" #| "Here's a previous context that covers\\n" #| "multiple lines, and should still be handled\\n" #| "correctly.\\n" #| msgid "" #| "Here's a previous message that covers\\n" #| "multiple lines, and should still be handled\\n" #| "correctly.\\n" #~ msgctxt "" #~ "Here's a context that covers\\n" #~ "multiple lines, and should still be handled\\n" #~ "correctly.\\n" #~ msgid "" #~ "Here's a message that covers\\n" #~ "multiple lines, and should still be handled\\n" #~ "correctly.\\n" #~ msgstr "" #~ "Here's a message that covers\\n" #~ "multiple lines, and should still be handled\\n" #~ "correctly.\\n"''', buf.getvalue().strip())