# Buggy code
def find_catalog_source_files(
    locale_dirs, locale, domains=None, gettext_compact=False, force_all=False
):
    """
    :param list locale_dirs:
       list of path as `['locale_dir1', 'locale_dir2', ...]` to find
       translation catalogs. Each path contains a structure such as
       `<locale>/LC_MESSAGES/domain.po`.
    :param str locale: a language as `'en'`
    :param list domains: list of domain names to get. If empty list or None
       is specified, get all domain names. default is None.
    :param boolean gettext_compact:
       * False: keep domains directory structure (default).
       * True: domains in the sub directory will be merged into 1 file.
    :param boolean force_all:
       Set True if you want to get all catalogs rather than updated catalogs.
       default is False.
    :return: [CatalogInfo(), ...]
    """
    if not locale:
        return []  # locale is not specified

    catalogs = set()
    for locale_dir in locale_dirs:
        if not locale_dir:
            continue  # skip system locale directory

        base_dir = path.join(locale_dir, locale, "LC_MESSAGES")

        if not path.exists(base_dir):
            continue  # locale path is not found

        for dirpath, dirnames, filenames in walk(base_dir, followlinks=True):
            filenames = [f for f in filenames if f.endswith(".po")]
            for filename in filenames:
                base = path.splitext(filename)[0]
                domain = path.relpath(path.join(dirpath, base), base_dir)
                if gettext_compact and path.sep in domain:
                    domain = path.split(domain)[0]
                domain = domain.replace(path.sep, SEP)
                if domains and domain not in domains:
                    continue
                cat = CatalogInfo(base_dir, domain)
                if force_all or cat.is_outdated():
                    catalogs.add(cat)

    return catalogs


# Traceback (UnicodeDecodeError)
D:\test>set SPHINXOPTS=-T

D:\test>make.bat html
Running Sphinx v1.3.1
loading translations [ja]... done
loading pickled environment... not yet created
building [mo]: targets for 1 po files that are out of date
writing output... [100%] D:\test\locale\ja\LC_MESSAGES\index.mo
Traceback (most recent call last):
File "C:\Develop\pytools\sphinx-13\lib\site-packages\sphinx\cmdline.py", line 245, in main
app.build(opts.force_all, filenames)
File "C:\Develop\pytools\sphinx-13\lib\site-packages\sphinx\application.py", line 263, in build
self.builder.compile_update_catalogs()
File "C:\Develop\pytools\sphinx-13\lib\site-packages\sphinx\builders\__init__.py", line 199, in compile_update_catalogs
self.compile_catalogs(catalogs, message)
File "C:\Develop\pytools\sphinx-13\lib\site-packages\sphinx\builders\__init__.py", line 167, in compile_catalogs
catalog.write_mo(self.config.language)
File "C:\Develop\pytools\sphinx-13\lib\site-packages\sphinx\util\i18n.py", line 51, in write_mo
write_mo(mo, read_po(po, locale))
File "C:\Develop\pytools\sphinx-13\lib\site-packages\babel\messages\pofile.py", line 204, in read_po
for lineno, line in enumerate(fileobj.readlines()):
UnicodeDecodeError: 'cp932' codec can't decode byte 0x86 in position 576: illegal multibyte sequence

Encoding error:
'cp932' codec can't decode byte 0x86 in position 576: illegal multibyte sequence
The full traceback has been saved in D:\Temp\sphinx-err-8vfmzqmf.log, if you want to report the issue to the developers.

# Fixed code
def find_catalog_source_files(
    locale_dirs,
    locale,
    domains=None,
    gettext_compact=False,
    charset="utf-8",
    force_all=False,
):
    """
    :param list locale_dirs:
       list of path as `['locale_dir1', 'locale_dir2', ...]` to find
       translation catalogs. Each path contains a structure such as
       `<locale>/LC_MESSAGES/domain.po`.
    :param str locale: a language as `'en'`
    :param list domains: list of domain names to get. If empty list or None
       is specified, get all domain names. default is None.
    :param boolean gettext_compact:
       * False: keep domains directory structure (default).
       * True: domains in the sub directory will be merged into 1 file.
    :param boolean force_all:
       Set True if you want to get all catalogs rather than updated catalogs.
       default is False.
    :return: [CatalogInfo(), ...]
    """
    if not locale:
        return []  # locale is not specified

    catalogs = set()
    for locale_dir in locale_dirs:
        if not locale_dir:
            continue  # skip system locale directory

        base_dir = path.join(locale_dir, locale, "LC_MESSAGES")

        if not path.exists(base_dir):
            continue  # locale path is not found

        for dirpath, dirnames, filenames in walk(base_dir, followlinks=True):
            filenames = [f for f in filenames if f.endswith(".po")]
            for filename in filenames:
                base = path.splitext(filename)[0]
                domain = path.relpath(path.join(dirpath, base), base_dir)
                if gettext_compact and path.sep in domain:
                    domain = path.split(domain)[0]
                domain = domain.replace(path.sep, SEP)
                if domains and domain not in domains:
                    continue
                cat = CatalogInfo(base_dir, domain, charset)
                if force_all or cat.is_outdated():
                    catalogs.add(cat)

    return catalogs