# Buggy code
def find_catalog_source_files(
    locale_dirs,
    locale,
    domains=None,
    gettext_compact=False,
    charset="utf-8",
    force_all=False,
    excluded=Matcher([]),
):
    # type: (List[unicode], unicode, List[unicode], bool, unicode, bool, Matcher) -> Set[CatalogInfo]  # NOQA
    """
    :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(), ...]
    """
    catalogs = set()  # type: Set[CatalogInfo]

    if not locale:
        return catalogs  # locale is not specified

    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:
                if excluded(path.join(relpath(dirpath, base_dir), filename)):
                    continue
                base = path.splitext(filename)[0]
                domain = 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


# Traceback (FileNotFoundError)
# Sphinx version: 2.0.0+
# Python version: 3.6.5 (CPython)
# Docutils version: 0.14
# Jinja2 version: 2.10
# Last messages:
#   loading translations [en]...
#
#   done
#
#   making output directory...
#
#   building [mo]: targets for 1 po files that are out of date
#
#   writing output... [100%] locales/en/LC_MESSAGES/sphinx.mo
#
# Loaded extensions:
#   sphinx.ext.mathjax (2.0.0+) from /Users/tkomiya/work/sphinx/sphinx/ext/mathjax.py
#   alabaster (0.7.10) from /Users/tkomiya/work/tmp/lib/python3.6/site-packages/alabaster-0.7.10-py3.6.egg/alabaster/__init__.py
Traceback (most recent call last):
File "/Users/tkomiya/work/sphinx/sphinx/cmd/build.py", line 304, in build_main
app.build(args.force_all, filenames)
File "/Users/tkomiya/work/sphinx/sphinx/application.py", line 340, in build
self.builder.compile_update_catalogs()
File "/Users/tkomiya/work/sphinx/sphinx/builders/__init__.py", line 281, in compile_update_catalogs
self.compile_catalogs(catalogs, message)
File "/Users/tkomiya/work/sphinx/sphinx/builders/__init__.py", line 236, in compile_catalogs
catalog.write_mo(self.config.language)
File "/Users/tkomiya/work/sphinx/sphinx/util/i18n.py", line 71, in write_mo
with io.open(self.po_path, 'rt', encoding=self.charset) as file_po:
FileNotFoundError: [Errno 2] No such file or directory: '/Users/tkomiya/work/tmp/doc/test/locales/en/LC_MESSAGES/sphinx.po'

# Fixed code
def find_catalog_source_files(
    locale_dirs,
    locale,
    domains=None,
    gettext_compact=None,
    charset="utf-8",
    force_all=False,
    excluded=Matcher([]),
):
    # type: (List[unicode], unicode, List[unicode], bool, unicode, bool, Matcher) -> Set[CatalogInfo]  # NOQA
    """
    :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 force_all:
       Set True if you want to get all catalogs rather than updated catalogs.
       default is False.
    :return: [CatalogInfo(), ...]
    """
    if gettext_compact is not None:
        warnings.warn(
            "gettext_compact argument for find_catalog_source_files() is deprecated.",
            RemovedInSphinx30Warning,
        )

    catalogs = set()  # type: Set[CatalogInfo]

    if not locale:
        return catalogs  # locale is not specified

    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:
                if excluded(path.join(relpath(dirpath, base_dir), filename)):
                    continue
                base = path.splitext(filename)[0]
                domain = relpath(path.join(dirpath, base), base_dir).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