# Buggy code
def write_po(
    fileobj,
    catalog,
    width=76,
    no_location=False,
    omit_header=False,
    sort_output=False,
    sort_by_file=False,
    ignore_obsolete=False,
    include_previous=False,
    include_lineno=True,
):
    r"""Write a ``gettext`` PO (portable object) template file for a given
    message catalog to the provided file-like object.

    >>> catalog = Catalog()
    >>> catalog.add(u'foo %(name)s', locations=[('main.py', 1)],
    ...             flags=('fuzzy',))
    <Message...>
    >>> catalog.add((u'bar', u'baz'), locations=[('main.py', 3)])
    <Message...>
    >>> from babel._compat import BytesIO
    >>> buf = BytesIO()
    >>> write_po(buf, catalog, omit_header=True)
    >>> print(buf.getvalue().decode("utf8"))
    #: main.py:1
    #, fuzzy, python-format
    msgid "foo %(name)s"
    msgstr ""
    <BLANKLINE>
    #: main.py:3
    msgid "bar"
    msgid_plural "baz"
    msgstr[0] ""
    msgstr[1] ""
    <BLANKLINE>
    <BLANKLINE>

    :param fileobj: the file-like object to write to
    :param catalog: the `Catalog` instance
    :param width: the maximum line width for the generated output; use `None`,
                  0, or a negative number to completely disable line wrapping
    :param no_location: do not emit a location comment for every message
    :param omit_header: do not include the ``msgid ""`` entry at the top of the
                        output
    :param sort_output: whether to sort the messages in the output by msgid
    :param sort_by_file: whether to sort the messages in the output by their
                         locations
    :param ignore_obsolete: whether to ignore obsolete messages and not include
                            them in the output; by default they are included as
                            comments
    :param include_previous: include the old msgid as a comment when
                             updating the catalog
    :param include_lineno: include line number in the location comment
    """

    def _normalize(key, prefix=""):
        return normalize(key, prefix=prefix, width=width)

    def _write(text):
        if isinstance(text, text_type):
            text = text.encode(catalog.charset, "backslashreplace")
        fileobj.write(text)

    def _write_comment(comment, prefix=""):
        # xgettext always wraps comments even if --no-wrap is passed;
        # provide the same behaviour
        if width and width > 0:
            _width = width
        else:
            _width = 76
        for line in wraptext(comment, _width):
            _write("#%s %s\n" % (prefix, line.strip()))

    def _write_message(message, prefix=""):
        if isinstance(message.id, (list, tuple)):
            if message.context:
                _write("%smsgctxt %s\n" % (prefix, _normalize(message.context, prefix)))
            _write("%smsgid %s\n" % (prefix, _normalize(message.id[0], prefix)))
            _write("%smsgid_plural %s\n" % (prefix, _normalize(message.id[1], prefix)))

            for idx in range(catalog.num_plurals):
                try:
                    string = message.string[idx]
                except IndexError:
                    string = ""
                _write("%smsgstr[%d] %s\n" % (prefix, idx, _normalize(string, prefix)))
        else:
            if message.context:
                _write("%smsgctxt %s\n" % (prefix, _normalize(message.context, prefix)))
            _write("%smsgid %s\n" % (prefix, _normalize(message.id, prefix)))
            _write("%smsgstr %s\n" % (prefix, _normalize(message.string or "", prefix)))

    sort_by = None
    if sort_output:
        sort_by = "message"
    elif sort_by_file:
        sort_by = "location"

    for message in _sort_messages(catalog, sort_by=sort_by):
        if not message.id:  # This is the header "message"
            if omit_header:
                continue
            comment_header = catalog.header_comment
            if width and width > 0:
                lines = []
                for line in comment_header.splitlines():
                    lines += wraptext(line, width=width, subsequent_indent="# ")
                comment_header = "\n".join(lines)
            _write(comment_header + "\n")

        for comment in message.user_comments:
            _write_comment(comment)
        for comment in message.auto_comments:
            _write_comment(comment, prefix=".")

        if not no_location:
            locs = []
            for filename, lineno in sorted(message.locations):
                if lineno and include_lineno:
                    locs.append("%s:%d" % (filename.replace(os.sep, "/"), lineno))
                else:
                    locs.append("%s" % filename.replace(os.sep, "/"))
            _write_comment(" ".join(locs), prefix=":")
        if message.flags:
            _write("#%s\n" % ", ".join([""] + sorted(message.flags)))

        if message.previous_id and include_previous:
            _write_comment("msgid %s" % _normalize(message.previous_id[0]), prefix="|")
            if len(message.previous_id) > 1:
                _write_comment(
                    "msgid_plural %s" % _normalize(message.previous_id[1]), prefix="|"
                )

        _write_message(message)
        _write("\n")

    if not ignore_obsolete:
        for message in _sort_messages(catalog.obsolete.values(), sort_by=sort_by):
            for comment in message.user_comments:
                _write_comment(comment)
            _write_message(message, prefix="#~ ")
            _write("\n")


# Traceback (TypeError)
Not Changed: locale/vi/LC_MESSAGES/animation/actions.po
Traceback (most recent call last):
File "/usr/local/bin/sphinx-intl", line 11, in <module>
sys.exit(main())
File "/usr/local/lib64/python3.6/site-packages/click/core.py", line 764, in __call__
return self.main(*args, **kwargs)
File "/usr/local/lib64/python3.6/site-packages/click/core.py", line 717, in main
rv = self.invoke(ctx)
File "/usr/local/lib64/python3.6/site-packages/click/core.py", line 1137, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/usr/local/lib64/python3.6/site-packages/click/core.py", line 956, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/usr/local/lib64/python3.6/site-packages/click/core.py", line 555, in invoke
return callback(*args, **kwargs)
File "/usr/local/lib/python3.6/site-packages/sphinx_intl/commands.py", line 256, in update
basic.update(locale_dir, pot_dir, languages)
File "/usr/local/lib/python3.6/site-packages/sphinx_intl/basic.py", line 53, in update
cat = c.load_po(po_file)
File "/usr/local/lib/python3.6/site-packages/sphinx_intl/catalog.py", line 17, in load_po
cat = pofile.read_po(f)
File "/usr/local/lib64/python3.6/site-packages/babel/messages/pofile.py", line 345, in read_po
parser.parse(fileobj)
File "/usr/local/lib64/python3.6/site-packages/babel/messages/pofile.py", line 276, in parse
self._process_comment(line)
File "/usr/local/lib64/python3.6/site-packages/babel/messages/pofile.py", line 235, in _process_comment
self._finish_current_message()
File "/usr/local/lib64/python3.6/site-packages/babel/messages/pofile.py", line 175, in _finish_current_message
self._add_message()
File "/usr/local/lib64/python3.6/site-packages/babel/messages/pofile.py", line 143, in _add_message
self.translations.sort()
TypeError: '<' not supported between instances of '_NormalizedString' and '_NormalizedString'

# Fixed code
def write_po(
    fileobj,
    catalog,
    width=76,
    no_location=False,
    omit_header=False,
    sort_output=False,
    sort_by_file=False,
    ignore_obsolete=False,
    include_previous=False,
    include_lineno=True,
):
    r"""Write a ``gettext`` PO (portable object) template file for a given
    message catalog to the provided file-like object.

    >>> catalog = Catalog()
    >>> catalog.add(u'foo %(name)s', locations=[('main.py', 1)],
    ...             flags=('fuzzy',))
    <Message...>
    >>> catalog.add((u'bar', u'baz'), locations=[('main.py', 3)])
    <Message...>
    >>> from babel._compat import BytesIO
    >>> buf = BytesIO()
    >>> write_po(buf, catalog, omit_header=True)
    >>> print(buf.getvalue().decode("utf8"))
    #: main.py:1
    #, fuzzy, python-format
    msgid "foo %(name)s"
    msgstr ""
    <BLANKLINE>
    #: main.py:3
    msgid "bar"
    msgid_plural "baz"
    msgstr[0] ""
    msgstr[1] ""
    <BLANKLINE>
    <BLANKLINE>

    :param fileobj: the file-like object to write to
    :param catalog: the `Catalog` instance
    :param width: the maximum line width for the generated output; use `None`,
                  0, or a negative number to completely disable line wrapping
    :param no_location: do not emit a location comment for every message
    :param omit_header: do not include the ``msgid ""`` entry at the top of the
                        output
    :param sort_output: whether to sort the messages in the output by msgid
    :param sort_by_file: whether to sort the messages in the output by their
                         locations
    :param ignore_obsolete: whether to ignore obsolete messages and not include
                            them in the output; by default they are included as
                            comments
    :param include_previous: include the old msgid as a comment when
                             updating the catalog
    :param include_lineno: include line number in the location comment
    """

    def _normalize(key, prefix=""):
        return normalize(key, prefix=prefix, width=width)

    def _write(text):
        if isinstance(text, text_type):
            text = text.encode(catalog.charset, "backslashreplace")
        fileobj.write(text)

    def _write_comment(comment, prefix=""):
        # xgettext always wraps comments even if --no-wrap is passed;
        # provide the same behaviour
        if width and width > 0:
            _width = width
        else:
            _width = 76
        for line in wraptext(comment, _width):
            _write("#%s %s\n" % (prefix, line.strip()))

    def _write_message(message, prefix=""):
        if isinstance(message.id, (list, tuple)):
            if message.context:
                _write("%smsgctxt %s\n" % (prefix, _normalize(message.context, prefix)))
            _write("%smsgid %s\n" % (prefix, _normalize(message.id[0], prefix)))
            _write("%smsgid_plural %s\n" % (prefix, _normalize(message.id[1], prefix)))

            for idx in range(catalog.num_plurals):
                try:
                    string = message.string[idx]
                except IndexError:
                    string = ""
                _write("%smsgstr[%d] %s\n" % (prefix, idx, _normalize(string, prefix)))
        else:
            if message.context:
                _write("%smsgctxt %s\n" % (prefix, _normalize(message.context, prefix)))
            _write("%smsgid %s\n" % (prefix, _normalize(message.id, prefix)))
            _write("%smsgstr %s\n" % (prefix, _normalize(message.string or "", prefix)))

    sort_by = None
    if sort_output:
        sort_by = "message"
    elif sort_by_file:
        sort_by = "location"

    for message in _sort_messages(catalog, sort_by=sort_by):
        if not message.id:  # This is the header "message"
            if omit_header:
                continue
            comment_header = catalog.header_comment
            if width and width > 0:
                lines = []
                for line in comment_header.splitlines():
                    lines += wraptext(line, width=width, subsequent_indent="# ")
                comment_header = "\n".join(lines)
            _write(comment_header + "\n")

        for comment in message.user_comments:
            _write_comment(comment)
        for comment in message.auto_comments:
            _write_comment(comment, prefix=".")

        if not no_location:
            locs = []

            # Attempt to sort the locations.  If we can't do that, for instance
            # because there are mixed integers and Nones or whatnot (see issue #606)
            # then give up, but also don't just crash.
            try:
                locations = sorted(message.locations)
            except TypeError:  # e.g. "TypeError: unorderable types: NoneType() < int()"
                locations = message.locations

            for filename, lineno in locations:
                if lineno and include_lineno:
                    locs.append("%s:%d" % (filename.replace(os.sep, "/"), lineno))
                else:
                    locs.append("%s" % filename.replace(os.sep, "/"))
            _write_comment(" ".join(locs), prefix=":")
        if message.flags:
            _write("#%s\n" % ", ".join([""] + sorted(message.flags)))

        if message.previous_id and include_previous:
            _write_comment("msgid %s" % _normalize(message.previous_id[0]), prefix="|")
            if len(message.previous_id) > 1:
                _write_comment(
                    "msgid_plural %s" % _normalize(message.previous_id[1]), prefix="|"
                )

        _write_message(message)
        _write("\n")

    if not ignore_obsolete:
        for message in _sort_messages(catalog.obsolete.values(), sort_by=sort_by):
            for comment in message.user_comments:
                _write_comment(comment)
            _write_message(message, prefix="#~ ")
            _write("\n")