"""Test message patching for internationalization purposes. Runs the text builder in the test root. """ import os import os.path import re import shutil import time from pathlib import Path import pytest from babel.messages import mofile, pofile from babel.messages.catalog import Catalog from docutils import nodes from sphinx import locale from sphinx.testing.util import assert_node, etree_parse, strip_escseq from sphinx.util.nodes import NodeMatcher sphinx_intl = pytest.mark.sphinx( testroot='intl', confoverrides={ 'language': 'xx', 'locale_dirs': ['.'], 'gettext_compact': False, }, ) def read_po(pathname): with open(pathname, encoding='utf-8') as f: return pofile.read_po(f) def write_mo(pathname, po): with open(pathname, 'wb') as f: return mofile.write_mo(f, po) @pytest.fixture(autouse=True) def _setup_intl(app_params): assert isinstance(app_params.kwargs['srcdir'], Path) srcdir = app_params.kwargs['srcdir'] for dirpath, _dirs, files in os.walk(srcdir): dirpath = Path(dirpath) for f in [f for f in files if f.endswith('.po')]: po = str(dirpath / f) mo = srcdir / 'xx' / 'LC_MESSAGES' / ( os.path.relpath(po[:-3], srcdir) + '.mo') if not mo.parent.exists(): mo.parent.mkdir(parents=True, exist_ok=True) if not mo.exists() or os.stat(mo).st_mtime < os.stat(po).st_mtime: # compile .mo file only if needed write_mo(mo, read_po(po)) @pytest.fixture(autouse=True) def _info(app): yield print('# language:', app.config.language) print('# locale_dirs:', app.config.locale_dirs) def elem_gettexts(elem): return [_f for _f in [s.strip() for s in elem.itertext()] if _f] def elem_getref(elem): return elem.attrib.get('refid') or elem.attrib.get('refuri') def assert_elem(elem, texts=None, refs=None, names=None): if texts is not None: _texts = elem_gettexts(elem) assert _texts == texts if refs is not None: _refs = [elem_getref(x) for x in elem.findall('reference')] assert _refs == refs if names is not None: _names = elem.attrib.get('names').split() assert _names == names def assert_count(expected_expr, result, count): find_pair = (expected_expr, result) assert len(re.findall(*find_pair)) == count, find_pair @sphinx_intl @pytest.mark.sphinx('text') @pytest.mark.test_params(shared_result='test_intl_basic') def test_text_emit_warnings(app, warning): app.build() # test warnings in translation warnings = getwarning(warning) warning_expr = ('.*/warnings.txt:4::1: ' 'WARNING: Inline literal start-string without end-string.\n') assert re.search(warning_expr, warnings), f'{warning_expr!r} did not match {warnings!r}' @sphinx_intl @pytest.mark.sphinx('text') @pytest.mark.test_params(shared_result='test_intl_basic') def test_text_warning_node(app): app.build() # test warnings in translation result = (app.outdir / 'warnings.txt').read_text(encoding='utf8') expect = ("3. I18N WITH REST WARNINGS" "\n**************************\n" "\nLINE OF >>``< IMG][image]\n" "\n" " [image: IMG -> I18N][image]\n" "\n" "\n" "14.3. IMAGE ON SUBSTITUTION\n" "===========================\n" "\n" "\n" "14.4. IMAGE UNDER NOTE\n" "======================\n" "\n" "Note:\n" "\n" " [image: i18n under note][image]\n" "\n" " [image: img under note][image]\n") assert result == expect @sphinx_intl @pytest.mark.sphinx('text') @pytest.mark.test_params(shared_result='test_intl_basic') def test_text_rubric(app): app.build() # --- rubric: regression test for pull request #190 result = (app.outdir / 'rubric.txt').read_text(encoding='utf8') expect = ("I18N WITH RUBRIC" "\n****************\n" "\n-[ RUBRIC TITLE ]-\n" "\n" "\nRUBRIC IN THE BLOCK" "\n===================\n" "\nBLOCK\n" "\n -[ RUBRIC TITLE ]-\n") assert result == expect @sphinx_intl @pytest.mark.sphinx('text') @pytest.mark.test_params(shared_result='test_intl_basic') def test_text_docfields(app): app.build() # --- docfields result = (app.outdir / 'docfields.txt').read_text(encoding='utf8') expect = ("21. I18N WITH DOCFIELDS" "\n***********************\n" "\nclass Cls1\n" "\n Parameters:" "\n **param** -- DESCRIPTION OF PARAMETER param\n" "\nclass Cls2\n" "\n Parameters:" "\n * **foo** -- DESCRIPTION OF PARAMETER foo\n" "\n * **bar** -- DESCRIPTION OF PARAMETER bar\n" "\nclass Cls3(values)\n" "\n Raises:" "\n **ValueError** -- IF THE VALUES ARE OUT OF RANGE\n" "\nclass Cls4(values)\n" "\n Raises:" "\n * **TypeError** -- IF THE VALUES ARE NOT VALID\n" "\n * **ValueError** -- IF THE VALUES ARE OUT OF RANGE\n" "\nclass Cls5\n" "\n Returns:" '\n A NEW "Cls3" INSTANCE\n') assert result == expect @sphinx_intl @pytest.mark.sphinx('text') @pytest.mark.test_params(shared_result='test_intl_basic') def test_text_admonitions(app): app.build() # --- admonitions # #1206: gettext did not translate admonition directive's title # seealso: https://docutils.sourceforge.io/docs/ref/rst/directives.html#admonitions result = (app.outdir / 'admonitions.txt').read_text(encoding='utf8') directives = ( "attention", "caution", "danger", "error", "hint", "important", "note", "tip", "warning", "admonition") for d in directives: assert d.upper() + " TITLE" in result assert d.upper() + " BODY" in result # for #4938 `1. ` prefixed admonition title assert "1. ADMONITION TITLE" in result @sphinx_intl @pytest.mark.sphinx('gettext') @pytest.mark.test_params(shared_result='test_intl_gettext') def test_gettext_toctree(app): app.build() # --- toctree (index.rst) expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'index.po') actual = read_po(app.outdir / 'index.pot') for expect_msg in [m for m in expect if m.id]: assert expect_msg.id in [m.id for m in actual if m.id] # --- toctree (toctree.rst) expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po') actual = read_po(app.outdir / 'toctree.pot') for expect_msg in [m for m in expect if m.id]: assert expect_msg.id in [m.id for m in actual if m.id] @sphinx_intl @pytest.mark.sphinx('gettext') @pytest.mark.test_params(shared_result='test_intl_gettext') def test_gettext_table(app): app.build() # --- toctree expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'table.po') actual = read_po(app.outdir / 'table.pot') for expect_msg in [m for m in expect if m.id]: assert expect_msg.id in [m.id for m in actual if m.id] @sphinx_intl @pytest.mark.sphinx('text') @pytest.mark.test_params(shared_result='test_intl_basic') def test_text_table(app): app.build() # --- toctree result = (app.outdir / 'table.txt').read_text(encoding='utf8') expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'table.po') for expect_msg in [m for m in expect if m.id]: assert expect_msg.string in result @sphinx_intl @pytest.mark.sphinx('text') @pytest.mark.test_params(shared_result='test_intl_basic') def test_text_toctree(app): app.build() # --- toctree (index.rst) # Note: index.rst contains contents that is not shown in text. result = (app.outdir / 'index.txt').read_text(encoding='utf8') assert 'CONTENTS' in result assert 'TABLE OF CONTENTS' in result # --- toctree (toctree.rst) result = (app.outdir / 'toctree.txt').read_text(encoding='utf8') expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po') for expect_msg in (m for m in expect if m.id): assert expect_msg.string in result @sphinx_intl @pytest.mark.sphinx('gettext') @pytest.mark.test_params(shared_result='test_intl_gettext') def test_gettext_topic(app): app.build() # --- topic expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'topic.po') actual = read_po(app.outdir / 'topic.pot') for expect_msg in [m for m in expect if m.id]: assert expect_msg.id in [m.id for m in actual if m.id] @sphinx_intl @pytest.mark.sphinx('text') @pytest.mark.test_params(shared_result='test_intl_basic') def test_text_topic(app): app.build() # --- topic result = (app.outdir / 'topic.txt').read_text(encoding='utf8') expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'topic.po') for expect_msg in [m for m in expect if m.id]: assert expect_msg.string in result @sphinx_intl @pytest.mark.sphinx('gettext') @pytest.mark.test_params(shared_result='test_intl_gettext') def test_gettext_definition_terms(app): app.build() # --- definition terms: regression test for #2198, #2205 expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'definition_terms.po') actual = read_po(app.outdir / 'definition_terms.pot') for expect_msg in [m for m in expect if m.id]: assert expect_msg.id in [m.id for m in actual if m.id] @sphinx_intl @pytest.mark.sphinx('gettext') @pytest.mark.test_params(shared_result='test_intl_gettext') def test_gettext_glossary_terms(app, warning): app.build() # --- glossary terms: regression test for #1090 expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'glossary_terms.po') actual = read_po(app.outdir / 'glossary_terms.pot') for expect_msg in [m for m in expect if m.id]: assert expect_msg.id in [m.id for m in actual if m.id] warnings = warning.getvalue().replace(os.sep, '/') assert 'term not in glossary' not in warnings @sphinx_intl @pytest.mark.sphinx('gettext') @pytest.mark.test_params(shared_result='test_intl_gettext') def test_gettext_glossary_term_inconsistencies(app): app.build() # --- glossary term inconsistencies: regression test for #1090 expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'glossary_terms_inconsistency.po') actual = read_po(app.outdir / 'glossary_terms_inconsistency.pot') for expect_msg in [m for m in expect if m.id]: assert expect_msg.id in [m.id for m in actual if m.id] @sphinx_intl @pytest.mark.sphinx('gettext') @pytest.mark.test_params(shared_result='test_intl_gettext') def test_gettext_literalblock(app): app.build() # --- gettext builder always ignores ``only`` directive expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'literalblock.po') actual = read_po(app.outdir / 'literalblock.pot') for expect_msg in [m for m in expect if m.id]: if len(expect_msg.id.splitlines()) == 1: # compare translations only labels assert expect_msg.id in [m.id for m in actual if m.id] else: pass # skip code-blocks and literalblocks @sphinx_intl @pytest.mark.sphinx('gettext') @pytest.mark.test_params(shared_result='test_intl_gettext') def test_gettext_buildr_ignores_only_directive(app): app.build() # --- gettext builder always ignores ``only`` directive expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'only.po') actual = read_po(app.outdir / 'only.pot') for expect_msg in [m for m in expect if m.id]: assert expect_msg.id in [m.id for m in actual if m.id] @sphinx_intl def test_node_translated_attribute(app): app.builder.build_specific([app.srcdir / 'translation_progress.txt']) doctree = app.env.get_doctree('translation_progress') translated_nodes = sum(1 for _ in doctree.findall(NodeMatcher(translated=True))) assert translated_nodes == 10 + 1 # 10 lines + title untranslated_nodes = sum(1 for _ in doctree.findall(NodeMatcher(translated=False))) assert untranslated_nodes == 2 + 2 + 1 # 2 lines + 2 lines + substitution reference @sphinx_intl def test_translation_progress_substitution(app): app.builder.build_specific([app.srcdir / 'translation_progress.txt']) doctree = app.env.get_doctree('translation_progress') assert doctree[0][19][0] == '68.75%' # 11 out of 16 lines are translated @pytest.mark.sphinx(testroot='intl', freshenv=True, confoverrides={ 'language': 'xx', 'locale_dirs': ['.'], 'gettext_compact': False, 'translation_progress_classes': True, }) def test_translation_progress_classes_true(app): app.builder.build_specific([app.srcdir / 'translation_progress.txt']) doctree = app.env.get_doctree('translation_progress') # title assert 'translated' in doctree[0][0]['classes'] # translated lines assert 'translated' in doctree[0][1]['classes'] assert 'translated' in doctree[0][2]['classes'] assert 'translated' in doctree[0][3]['classes'] assert 'translated' in doctree[0][4]['classes'] assert 'translated' in doctree[0][5]['classes'] assert 'translated' in doctree[0][6]['classes'] assert 'translated' in doctree[0][7]['classes'] assert 'translated' in doctree[0][8]['classes'] assert doctree[0][9]['classes'] == [] # comment node # idempotent assert 'translated' in doctree[0][10]['classes'] assert 'translated' in doctree[0][11]['classes'] assert doctree[0][12]['classes'] == [] # comment node # untranslated assert 'untranslated' in doctree[0][13]['classes'] assert 'untranslated' in doctree[0][14]['classes'] assert doctree[0][15]['classes'] == [] # comment node # missing assert 'untranslated' in doctree[0][16]['classes'] assert 'untranslated' in doctree[0][17]['classes'] assert doctree[0][18]['classes'] == [] # comment node # substitution reference assert 'untranslated' in doctree[0][19]['classes'] assert len(doctree[0]) == 20 @sphinx_intl # use individual shared_result directory to avoid "incompatible doctree" error @pytest.mark.sphinx(testroot='builder-gettext-dont-rebuild-mo') def test_gettext_dont_rebuild_mo(make_app, app_params): # --- don't rebuild by .mo mtime def get_update_targets(app_): app_.env.find_files(app_.config, app_.builder) added, changed, removed = app_.env.get_outdated_files(config_changed=False) return added, changed, removed args, kwargs = app_params # phase1: build document with non-gettext builder and generate mo file in srcdir app0 = make_app('dummy', *args, **kwargs) app0.build() time.sleep(0.01) assert (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').exists() # Since it is after the build, the number of documents to be updated is 0 update_targets = get_update_targets(app0) assert update_targets[1] == set(), update_targets # When rewriting the timestamp of mo file, the number of documents to be # updated will be changed. mtime = (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').stat().st_mtime os.utime(app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo', (mtime + 5, mtime + 5)) update_targets = get_update_targets(app0) assert update_targets[1] == {'bom'}, update_targets # Because doctree for gettext builder can not be shared with other builders, # erase doctreedir before gettext build. shutil.rmtree(app0.doctreedir) # phase2: build document with gettext builder. # The mo file in the srcdir directory is retained. app = make_app('gettext', *args, **kwargs) app.build() time.sleep(0.01) # Since it is after the build, the number of documents to be updated is 0 update_targets = get_update_targets(app) assert update_targets[1] == set(), update_targets # Even if the timestamp of the mo file is updated, the number of documents # to be updated is 0. gettext builder does not rebuild because of mo update. os.utime(app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo', (mtime + 10, mtime + 10)) update_targets = get_update_targets(app) assert update_targets[1] == set(), update_targets @sphinx_intl @pytest.mark.sphinx('html') @pytest.mark.test_params(shared_result='test_intl_basic') def test_html_meta(app): app.build() # --- test for meta result = (app.outdir / 'index.html').read_text(encoding='utf8') expected_expr = '' assert expected_expr in result expected_expr = '' assert expected_expr in result expected_expr = '

HIDDEN TOC

' assert expected_expr in result @sphinx_intl @pytest.mark.sphinx('html') @pytest.mark.test_params(shared_result='test_intl_basic') def test_html_footnotes(app): app.build() # --- test for #955 cant-build-html-with-footnotes-when-using # expect no error by build (app.outdir / 'footnote.html').read_text(encoding='utf8') @sphinx_intl @pytest.mark.sphinx('html') @pytest.mark.test_params(shared_result='test_intl_basic') def test_html_undefined_refs(app): app.build() # --- links to undefined reference result = (app.outdir / 'refs_inconsistency.html').read_text(encoding='utf8') expected_expr = ('reference') assert len(re.findall(expected_expr, result)) == 2 expected_expr = ('reference') assert len(re.findall(expected_expr, result)) == 0 expected_expr = ('I18N WITH ' 'REFS INCONSISTENCY') assert len(re.findall(expected_expr, result)) == 1 @sphinx_intl @pytest.mark.sphinx('html') @pytest.mark.test_params(shared_result='test_intl_basic') def test_html_index_entries(app): app.build() # --- index entries: regression test for #976 result = (app.outdir / 'genindex.html').read_text(encoding='utf8') def wrap(tag, keyword): start_tag = "<%s[^>]*>" % tag end_tag = "" % tag return fr"{start_tag}\s*{keyword}\s*{end_tag}" def wrap_nest(parenttag, childtag, keyword): start_tag1 = "<%s[^>]*>" % parenttag start_tag2 = "<%s[^>]*>" % childtag return fr"{start_tag1}\s*{keyword}\s*{start_tag2}" expected_exprs = [ wrap('a', 'NEWSLETTER'), wrap('a', 'MAILING LIST'), wrap('a', 'RECIPIENTS LIST'), wrap('a', 'FIRST SECOND'), wrap('a', 'SECOND THIRD'), wrap('a', 'THIRD, FIRST'), wrap_nest('li', 'ul', 'ENTRY'), wrap_nest('li', 'ul', 'SEE'), ] for expr in expected_exprs: assert re.search(expr, result, re.MULTILINE), f'{expr!r} did not match {result!r}' @sphinx_intl @pytest.mark.sphinx('html') @pytest.mark.test_params(shared_result='test_intl_basic') def test_html_versionchanges(app): app.build() # --- versionchanges result = (app.outdir / 'versionchange.html').read_text(encoding='utf8') def get_content(result, name): matched = re.search(r'
\n*(.*?)
' % name, result, re.DOTALL) if matched: return matched.group(1) else: return '' expect1 = ( """

Deprecated since version 1.0: """ """THIS IS THE FIRST PARAGRAPH OF DEPRECATED.

\n""" """

THIS IS THE SECOND PARAGRAPH OF DEPRECATED.

\n""") matched_content = get_content(result, "deprecated") assert expect1 == matched_content expect2 = ( """

New in version 1.0: """ """THIS IS THE FIRST PARAGRAPH OF VERSIONADDED.

\n""") matched_content = get_content(result, "versionadded") assert expect2 == matched_content expect3 = ( """

Changed in version 1.0: """ """THIS IS THE FIRST PARAGRAPH OF VERSIONCHANGED.

\n""") matched_content = get_content(result, "versionchanged") assert expect3 == matched_content @sphinx_intl @pytest.mark.sphinx('html') @pytest.mark.test_params(shared_result='test_intl_basic') def test_html_docfields(app): app.build() # --- docfields # expect no error by build (app.outdir / 'docfields.html').read_text(encoding='utf8') @sphinx_intl @pytest.mark.sphinx('html') @pytest.mark.test_params(shared_result='test_intl_basic') def test_html_template(app): app.build() # --- gettext template result = (app.outdir / 'contents.html').read_text(encoding='utf8') assert "WELCOME" in result assert "SPHINX 2013.120" in result @sphinx_intl @pytest.mark.sphinx('html') @pytest.mark.test_params(shared_result='test_intl_basic') def test_html_rebuild_mo(app): app.build() # --- rebuild by .mo mtime app.builder.build_update() app.env.find_files(app.config, app.builder) _, updated, _ = app.env.get_outdated_files(config_changed=False) assert len(updated) == 0 mtime = (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').stat().st_mtime os.utime(app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo', (mtime + 5, mtime + 5)) app.env.find_files(app.config, app.builder) _, updated, _ = app.env.get_outdated_files(config_changed=False) assert len(updated) == 1 @sphinx_intl @pytest.mark.sphinx('xml') @pytest.mark.test_params(shared_result='test_intl_basic') def test_xml_footnotes(app, warning): app.build() # --- footnotes: regression test for fix #955, #1176 et = etree_parse(app.outdir / 'footnote.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') assert_elem( para0[0], ['I18N WITH FOOTNOTE', 'INCLUDE THIS CONTENTS', '2', '[ref]', '1', '100', '*', '. SECOND FOOTNOTE_REF', '100', '.'], ['i18n-with-footnote', 'ref']) # check node_id for footnote_references which refer same footnote (refs: #3002) assert para0[0][4].text == para0[0][6].text == '100' assert para0[0][4].attrib['ids'] != para0[0][6].attrib['ids'] footnote0 = secs[0].findall('footnote') assert_elem( footnote0[0], ['1', 'THIS IS A AUTO NUMBERED FOOTNOTE.'], None, ['1']) assert_elem( footnote0[1], ['100', 'THIS IS A NUMBERED FOOTNOTE.'], None, ['100']) assert_elem( footnote0[2], ['2', 'THIS IS A AUTO NUMBERED NAMED FOOTNOTE.'], None, ['named']) assert_elem( footnote0[3], ['*', 'THIS IS A AUTO SYMBOL FOOTNOTE.'], None, None) citation0 = secs[0].findall('citation') assert_elem( citation0[0], ['ref', 'THIS IS A NAMED FOOTNOTE.'], None, ['ref']) warnings = getwarning(warning) warning_expr = '.*/footnote.xml:\\d*: SEVERE: Duplicate ID: ".*".\n' assert not re.search(warning_expr, warnings), f'{warning_expr!r} did match {warnings!r}' @sphinx_intl @pytest.mark.sphinx('xml') @pytest.mark.test_params(shared_result='test_intl_basic') def test_xml_footnote_backlinks(app): app.build() # --- footnote backlinks: i18n test for #1058 et = etree_parse(app.outdir / 'footnote.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') refs0 = para0[0].findall('footnote_reference') refid2id = {r.attrib.get('refid'): r.attrib.get('ids') for r in refs0} footnote0 = secs[0].findall('footnote') for footnote in footnote0: ids = footnote.attrib.get('ids') backrefs = footnote.attrib.get('backrefs').split() assert refid2id[ids] in backrefs @sphinx_intl @pytest.mark.sphinx('xml') @pytest.mark.test_params(shared_result='test_intl_basic') def test_xml_refs_in_python_domain(app): app.build() # --- refs in the Python domain et = etree_parse(app.outdir / 'refs_python_domain.xml') secs = et.findall('section') # regression test for fix #1363 para0 = secs[0].findall('paragraph') assert_elem( para0[0], ['SEE THIS DECORATOR:', 'sensitive_variables()', '.'], ['sensitive.sensitive_variables']) @sphinx_intl @pytest.mark.sphinx('xml') @pytest.mark.test_params(shared_result='test_intl_basic') def test_xml_keep_external_links(app): app.build() # --- keep external links: regression test for #1044 et = etree_parse(app.outdir / 'external_links.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') # external link check assert_elem( para0[0], ['EXTERNAL LINK TO', 'Python', '.'], ['http://python.org/index.html']) # internal link check assert_elem( para0[1], ['EXTERNAL LINKS', 'IS INTERNAL LINK.'], ['i18n-with-external-links']) # inline link check assert_elem( para0[2], ['INLINE LINK BY', 'THE SPHINX SITE', '.'], ['http://sphinx-doc.org']) # unnamed link check assert_elem( para0[3], ['UNNAMED', 'LINK', '.'], ['http://google.com']) # link target swapped translation para1 = secs[1].findall('paragraph') assert_elem( para1[0], ['LINK TO', 'external2', 'AND', 'external1', '.'], ['https://www.google.com/external2', 'https://www.google.com/external1']) assert_elem( para1[1], ['LINK TO', 'THE PYTHON SITE', 'AND', 'THE SPHINX SITE', '.'], ['http://python.org', 'http://sphinx-doc.org']) # multiple references in the same line para2 = secs[2].findall('paragraph') assert_elem( para2[0], ['LINK TO', 'EXTERNAL LINKS', ',', 'Python', ',', 'THE SPHINX SITE', ',', 'UNNAMED', 'AND', 'THE PYTHON SITE', '.'], ['i18n-with-external-links', 'http://python.org/index.html', 'http://sphinx-doc.org', 'http://google.com', 'http://python.org']) @sphinx_intl @pytest.mark.sphinx('xml') @pytest.mark.test_params(shared_result='test_intl_basic') def test_xml_role_xref(app): app.build() # --- role xref: regression test for #1090, #1193 et = etree_parse(app.outdir / 'role_xref.xml') sec1, sec2 = et.findall('section') para1, = sec1.findall('paragraph') assert_elem( para1, ['LINK TO', "I18N ROCK'N ROLE XREF", ',', 'CONTENTS', ',', 'SOME NEW TERM', '.'], ['i18n-role-xref', 'index', 'glossary_terms#term-Some-term']) para2 = sec2.findall('paragraph') assert_elem( para2[0], ['LINK TO', 'SOME OTHER NEW TERM', 'AND', 'SOME NEW TERM', '.'], ['glossary_terms#term-Some-other-term', 'glossary_terms#term-Some-term']) assert_elem( para2[1], ['LINK TO', 'LABEL', 'AND', 'SAME TYPE LINKS', 'AND', 'SAME TYPE LINKS', '.'], ['i18n-role-xref', 'same-type-links', 'same-type-links']) assert_elem( para2[2], ['LINK TO', 'I18N WITH GLOSSARY TERMS', 'AND', 'CONTENTS', '.'], ['glossary_terms', 'index']) assert_elem( para2[3], ['LINK TO', '--module', 'AND', '-m', '.'], ['cmdoption-module', 'cmdoption-m']) assert_elem( para2[4], ['LINK TO', 'env2', 'AND', 'env1', '.'], ['envvar-env2', 'envvar-env1']) assert_elem( para2[5], ['LINK TO', 'token2', 'AND', 'token1', '.'], []) # TODO: how do I link token role to productionlist? assert_elem( para2[6], ['LINK TO', 'same-type-links', 'AND', "i18n-role-xref", '.'], ['same-type-links', 'i18n-role-xref']) @sphinx_intl @pytest.mark.sphinx('xml') @pytest.mark.test_params(shared_result='test_intl_basic') def test_xml_warnings(app, warning): app.build() # warnings warnings = getwarning(warning) assert 'term not in glossary' not in warnings assert 'undefined label' not in warnings assert 'unknown document' not in warnings @sphinx_intl @pytest.mark.sphinx('xml') @pytest.mark.test_params(shared_result='test_intl_basic') def test_xml_label_targets(app): app.build() # --- label targets: regression test for #1193, #1265 et = etree_parse(app.outdir / 'label_target.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') assert_elem( para0[0], ['X SECTION AND LABEL', 'POINT TO', 'implicit-target', 'AND', 'X SECTION AND LABEL', 'POINT TO', 'section-and-label', '.'], ['implicit-target', 'section-and-label']) para1 = secs[1].findall('paragraph') assert_elem( para1[0], ['X EXPLICIT-TARGET', 'POINT TO', 'explicit-target', 'AND', 'X EXPLICIT-TARGET', 'POINT TO DUPLICATED ID LIKE', 'id1', '.'], ['explicit-target', 'id1']) para2 = secs[2].findall('paragraph') assert_elem( para2[0], ['X IMPLICIT SECTION NAME', 'POINT TO', 'implicit-section-name', '.'], ['implicit-section-name']) sec2 = secs[2].findall('section') para2_0 = sec2[0].findall('paragraph') assert_elem( para2_0[0], ['`X DUPLICATED SUB SECTION`_', 'IS BROKEN LINK.'], []) para3 = secs[3].findall('paragraph') assert_elem( para3[0], ['X', 'bridge label', 'IS NOT TRANSLATABLE BUT LINKED TO TRANSLATED ' + 'SECTION TITLE.'], ['label-bridged-target-section']) assert_elem( para3[1], ['X', 'bridge label', 'POINT TO', 'LABEL BRIDGED TARGET SECTION', 'AND', 'bridge label2', 'POINT TO', 'SECTION AND LABEL', '. THE SECOND APPEARED', 'bridge label2', 'POINT TO CORRECT TARGET.'], ['label-bridged-target-section', 'section-and-label', 'section-and-label']) @sphinx_intl @pytest.mark.sphinx('html') @pytest.mark.test_params(shared_result='test_intl_basic') def test_additional_targets_should_not_be_translated(app): app.build() # [literalblock.txt] result = (app.outdir / 'literalblock.html').read_text(encoding='utf8') # title should be translated expected_expr = 'CODE-BLOCKS' assert_count(expected_expr, result, 2) # ruby code block should not be translated but be highlighted expected_expr = """'result'""" assert_count(expected_expr, result, 1) # C code block without lang should not be translated and *ruby* highlighted expected_expr = """#include <stdlib.h>""" assert_count(expected_expr, result, 1) # C code block with lang should not be translated but be *C* highlighted expected_expr = ("""#include""" """ """ """<stdio.h>""") assert_count(expected_expr, result, 1) # literal block in list item should not be translated expected_expr = ("""literal""" """-""" """block\n""" """in""" """ """ """list""") assert_count(expected_expr, result, 1) # doctest block should not be translated but be highlighted expected_expr = ( """>>> """ """import sys """ """# sys importing""") assert_count(expected_expr, result, 1) # [raw.txt] result = (app.outdir / 'raw.html').read_text(encoding='utf8') # raw block should not be translated expected_expr = """""" assert_count(expected_expr, result, 1) # [figure.txt] result = (app.outdir / 'figure.html').read_text(encoding='utf8') # src for image block should not be translated (alt is translated) expected_expr = """I18N -> IMG""" assert_count(expected_expr, result, 1) # src for figure block should not be translated (alt is translated) expected_expr = """IMG -> I18N""" assert_count(expected_expr, result, 1) @sphinx_intl @pytest.mark.sphinx( 'html', srcdir='test_additional_targets_should_be_translated', confoverrides={ 'language': 'xx', 'locale_dirs': ['.'], 'gettext_compact': False, 'gettext_additional_targets': [ 'index', 'literal-block', 'doctest-block', 'raw', 'image', ], }, ) def test_additional_targets_should_be_translated(app): app.build() # [literalblock.txt] result = (app.outdir / 'literalblock.html').read_text(encoding='utf8') # title should be translated expected_expr = 'CODE-BLOCKS' assert_count(expected_expr, result, 2) # ruby code block should be translated and be highlighted expected_expr = """'RESULT'""" assert_count(expected_expr, result, 1) # C code block without lang should be translated and *ruby* highlighted expected_expr = """#include <STDLIB.H>""" assert_count(expected_expr, result, 1) # C code block with lang should be translated and be *C* highlighted expected_expr = ("""#include""" """ """ """<STDIO.H>""") assert_count(expected_expr, result, 1) # literal block in list item should be translated expected_expr = ("""LITERAL""" """-""" """BLOCK\n""" """IN""" """ """ """LIST""") assert_count(expected_expr, result, 1) # doctest block should not be translated but be highlighted expected_expr = ( """>>> """ """import sys """ """# SYS IMPORTING""") assert_count(expected_expr, result, 1) # '#noqa' should remain in literal blocks. assert_count("#noqa", result, 1) # [raw.txt] result = (app.outdir / 'raw.html').read_text(encoding='utf8') # raw block should be translated expected_expr = """""" assert_count(expected_expr, result, 1) # [figure.txt] result = (app.outdir / 'figure.html').read_text(encoding='utf8') # alt and src for image block should be translated expected_expr = """I18N -> IMG""" assert_count(expected_expr, result, 1) # alt and src for figure block should be translated expected_expr = """IMG -> I18N""" assert_count(expected_expr, result, 1) @pytest.mark.sphinx( 'html', testroot='intl_substitution_definitions', confoverrides={ 'language': 'xx', 'locale_dirs': ['.'], 'gettext_compact': False, 'gettext_additional_targets': [ 'index', 'literal-block', 'doctest-block', 'raw', 'image', ], }, ) def test_additional_targets_should_be_translated_substitution_definitions(app): app.builder.build_all() # [prolog_epilog_substitution.txt] result = (app.outdir / 'prolog_epilog_substitution.html').read_text(encoding='utf8') # alt and src for image block should be translated expected_expr = """SUBST_PROLOG_2 TRANSLATED""" assert_count(expected_expr, result, 1) # alt and src for image block should be translated expected_expr = """SUBST_EPILOG_2 TRANSLATED""" assert_count(expected_expr, result, 1) @sphinx_intl @pytest.mark.sphinx('text') @pytest.mark.test_params(shared_result='test_intl_basic') def test_text_references(app, warning): app.builder.build_specific([app.srcdir / 'refs.txt']) warnings = warning.getvalue().replace(os.sep, '/') warning_expr = 'refs.txt:\\d+: ERROR: Unknown target name:' assert_count(warning_expr, warnings, 0) @pytest.mark.sphinx( 'text', testroot='intl_substitution_definitions', confoverrides={ 'language': 'xx', 'locale_dirs': ['.'], 'gettext_compact': False, }, ) def test_text_prolog_epilog_substitution(app): app.build() result = (app.outdir / 'prolog_epilog_substitution.txt').read_text(encoding='utf8') assert result == """\ 1. I18N WITH PROLOGUE AND EPILOGUE SUBSTITUTIONS ************************************************ THIS IS CONTENT THAT CONTAINS prologue substitute text. SUBSTITUTED IMAGE [image: SUBST_PROLOG_2 TRANSLATED][image] HERE. THIS IS CONTENT THAT CONTAINS epilogue substitute text. SUBSTITUTED IMAGE [image: SUBST_EPILOG_2 TRANSLATED][image] HERE. """ @pytest.mark.sphinx( 'dummy', testroot='images', srcdir='test_intl_images', confoverrides={'language': 'xx'}, ) def test_image_glob_intl(app): app.build() # index.rst doctree = app.env.get_doctree('index') assert_node(doctree[0][1], nodes.image, uri='rimg.xx.png', candidates={'*': 'rimg.xx.png'}) assert isinstance(doctree[0][2], nodes.figure) assert_node(doctree[0][2][0], nodes.image, uri='rimg.xx.png', candidates={'*': 'rimg.xx.png'}) assert_node(doctree[0][3], nodes.image, uri='img.*', candidates={'application/pdf': 'img.pdf', 'image/gif': 'img.gif', 'image/png': 'img.png'}) assert isinstance(doctree[0][4], nodes.figure) assert_node(doctree[0][4][0], nodes.image, uri='img.*', candidates={'application/pdf': 'img.pdf', 'image/gif': 'img.gif', 'image/png': 'img.png'}) # subdir/index.rst doctree = app.env.get_doctree('subdir/index') assert_node(doctree[0][1], nodes.image, uri='subdir/rimg.xx.png', candidates={'*': 'subdir/rimg.xx.png'}) assert_node(doctree[0][2], nodes.image, uri='subdir/svgimg.*', candidates={'application/pdf': 'subdir/svgimg.pdf', 'image/svg+xml': 'subdir/svgimg.xx.svg'}) assert isinstance(doctree[0][3], nodes.figure) assert_node(doctree[0][3][0], nodes.image, uri='subdir/svgimg.*', candidates={'application/pdf': 'subdir/svgimg.pdf', 'image/svg+xml': 'subdir/svgimg.xx.svg'}) @pytest.mark.sphinx( 'dummy', testroot='images', srcdir='test_intl_images', confoverrides={ 'language': 'xx', 'figure_language_filename': '{root}{ext}.{language}', }, ) def test_image_glob_intl_using_figure_language_filename(app): app.build() # index.rst doctree = app.env.get_doctree('index') assert_node(doctree[0][1], nodes.image, uri='rimg.png.xx', candidates={'*': 'rimg.png.xx'}) assert isinstance(doctree[0][2], nodes.figure) assert_node(doctree[0][2][0], nodes.image, uri='rimg.png.xx', candidates={'*': 'rimg.png.xx'}) assert_node(doctree[0][3], nodes.image, uri='img.*', candidates={'application/pdf': 'img.pdf', 'image/gif': 'img.gif', 'image/png': 'img.png'}) assert isinstance(doctree[0][4], nodes.figure) assert_node(doctree[0][4][0], nodes.image, uri='img.*', candidates={'application/pdf': 'img.pdf', 'image/gif': 'img.gif', 'image/png': 'img.png'}) # subdir/index.rst doctree = app.env.get_doctree('subdir/index') assert_node(doctree[0][1], nodes.image, uri='subdir/rimg.png', candidates={'*': 'subdir/rimg.png'}) assert_node(doctree[0][2], nodes.image, uri='subdir/svgimg.*', candidates={'application/pdf': 'subdir/svgimg.pdf', 'image/svg+xml': 'subdir/svgimg.svg'}) assert isinstance(doctree[0][3], nodes.figure) assert_node(doctree[0][3][0], nodes.image, uri='subdir/svgimg.*', candidates={'application/pdf': 'subdir/svgimg.pdf', 'image/svg+xml': 'subdir/svgimg.svg'}) def getwarning(warnings): return strip_escseq(warnings.getvalue().replace(os.sep, '/')) @pytest.mark.sphinx('html', testroot='basic', srcdir='gettext_allow_fuzzy_translations', confoverrides={ 'language': 'de', 'gettext_allow_fuzzy_translations': True, }) def test_gettext_allow_fuzzy_translations(app): locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES' locale_dir.mkdir(parents=True, exist_ok=True) with (locale_dir / 'index.po').open('wb') as f: catalog = Catalog() catalog.add('features', 'FEATURES', flags=('fuzzy',)) pofile.write_po(f, catalog) app.build() content = (app.outdir / 'index.html').read_text(encoding='utf8') assert 'FEATURES' in content @pytest.mark.sphinx('html', testroot='basic', srcdir='gettext_disallow_fuzzy_translations', confoverrides={ 'language': 'de', 'gettext_allow_fuzzy_translations': False, }) def test_gettext_disallow_fuzzy_translations(app): locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES' locale_dir.mkdir(parents=True, exist_ok=True) with (locale_dir / 'index.po').open('wb') as f: catalog = Catalog() catalog.add('features', 'FEATURES', flags=('fuzzy',)) pofile.write_po(f, catalog) app.build() content = (app.outdir / 'index.html').read_text(encoding='utf8') assert 'FEATURES' not in content @pytest.mark.sphinx('html', testroot='basic', confoverrides={'language': 'de'}) def test_customize_system_message(make_app, app_params, sphinx_test_tempdir): try: # clear translators cache locale.translators.clear() # prepare message catalog (.po) locale_dir = sphinx_test_tempdir / 'basic' / 'locales' / 'de' / 'LC_MESSAGES' locale_dir.mkdir(parents=True, exist_ok=True) with (locale_dir / 'sphinx.po').open('wb') as f: catalog = Catalog() catalog.add('Quick search', 'QUICK SEARCH') pofile.write_po(f, catalog) # construct application and convert po file to .mo args, kwargs = app_params app = make_app(*args, **kwargs) assert (locale_dir / 'sphinx.mo').exists() assert app.translator.gettext('Quick search') == 'QUICK SEARCH' app.build() content = (app.outdir / 'index.html').read_text(encoding='utf8') assert 'QUICK SEARCH' in content finally: locale.translators.clear() @pytest.mark.sphinx('html', testroot='intl', confoverrides={'today_fmt': '%Y-%m-%d'}) def test_customize_today_date_format(app, monkeypatch): with monkeypatch.context() as m: m.setenv('SOURCE_DATE_EPOCH', '1439131307') app.build() content = (app.outdir / 'refs.html').read_text(encoding='utf8') assert '2015-08-09' in content