1"""
2    test_intl
3    ~~~~~~~~~
4
5    Test message patching for internationalization purposes.  Runs the text
6    builder in the test root.
7
8    :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
9    :license: BSD, see LICENSE for details.
10"""
11
12import os
13import re
14
15import pytest
16from babel.messages import mofile, pofile
17from babel.messages.catalog import Catalog
18from docutils import nodes
19
20from sphinx import locale
21from sphinx.testing.util import (assert_node, assert_not_re_search, assert_re_search,
22                                 assert_startswith, etree_parse, path, strip_escseq)
23
24sphinx_intl = pytest.mark.sphinx(
25    testroot='intl',
26    confoverrides={
27        'language': 'xx', 'locale_dirs': ['.'],
28        'gettext_compact': False,
29    },
30)
31
32
33def read_po(pathname):
34    with pathname.open() as f:
35        return pofile.read_po(f)
36
37
38def write_mo(pathname, po):
39    with pathname.open('wb') as f:
40        return mofile.write_mo(f, po)
41
42
43@pytest.fixture(autouse=True)
44def setup_intl(app_params):
45    srcdir = path(app_params.kwargs['srcdir'])
46    for dirpath, dirs, files in os.walk(srcdir):
47        dirpath = path(dirpath)
48        for f in [f for f in files if f.endswith('.po')]:
49            po = dirpath / f
50            mo = srcdir / 'xx' / 'LC_MESSAGES' / (
51                os.path.relpath(po[:-3], srcdir) + '.mo')
52            if not mo.parent.exists():
53                mo.parent.makedirs()
54
55            if not mo.exists() or mo.stat().st_mtime < po.stat().st_mtime:
56                # compile .mo file only if needed
57                write_mo(mo, read_po(po))
58
59
60@pytest.fixture(autouse=True)
61def _info(app):
62    yield
63    print('# language:', app.config.language)
64    print('# locale_dirs:', app.config.locale_dirs)
65
66
67def elem_gettexts(elem):
68    return [_f for _f in [s.strip() for s in elem.itertext()] if _f]
69
70
71def elem_getref(elem):
72    return elem.attrib.get('refid') or elem.attrib.get('refuri')
73
74
75def assert_elem(elem, texts=None, refs=None, names=None):
76    if texts is not None:
77        _texts = elem_gettexts(elem)
78        assert _texts == texts
79    if refs is not None:
80        _refs = [elem_getref(x) for x in elem.findall('reference')]
81        assert _refs == refs
82    if names is not None:
83        _names = elem.attrib.get('names').split()
84        assert _names == names
85
86
87def assert_count(expected_expr, result, count):
88    find_pair = (expected_expr, result)
89    assert len(re.findall(*find_pair)) == count, find_pair
90
91
92@sphinx_intl
93@pytest.mark.sphinx('text')
94@pytest.mark.test_params(shared_result='test_intl_basic')
95def test_text_emit_warnings(app, warning):
96    app.build()
97    # test warnings in translation
98    warnings = getwarning(warning)
99    warning_expr = ('.*/warnings.txt:4:<translated>:1: '
100                    'WARNING: Inline literal start-string without end-string.\n')
101    assert_re_search(warning_expr, warnings)
102
103
104@sphinx_intl
105@pytest.mark.sphinx('text')
106@pytest.mark.test_params(shared_result='test_intl_basic')
107def test_text_warning_node(app):
108    app.build()
109    # test warnings in translation
110    result = (app.outdir / 'warnings.txt').read_text()
111    expect = ("3. I18N WITH REST WARNINGS"
112              "\n**************************\n"
113              "\nLINE OF >>``<<BROKEN LITERAL MARKUP.\n")
114    assert result == expect
115
116
117@sphinx_intl
118@pytest.mark.sphinx('text')
119@pytest.mark.test_params(shared_result='test_intl_basic')
120@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows")
121def test_text_title_underline(app):
122    app.build()
123    # --- simple translation; check title underlines
124    result = (app.outdir / 'bom.txt').read_text()
125    expect = ("2. Datei mit UTF-8"
126              "\n******************\n"  # underline matches new translation
127              "\nThis file has umlauts: äöü.\n")
128    assert result == expect
129
130
131@sphinx_intl
132@pytest.mark.sphinx('text')
133@pytest.mark.test_params(shared_result='test_intl_basic')
134def test_text_subdirs(app):
135    app.build()
136    # --- check translation in subdirs
137    result = (app.outdir / 'subdir' / 'index.txt').read_text()
138    assert_startswith(result, "1. subdir contents\n******************\n")
139
140
141@sphinx_intl
142@pytest.mark.sphinx('text')
143@pytest.mark.test_params(shared_result='test_intl_basic')
144def test_text_inconsistency_warnings(app, warning):
145    app.build()
146    # --- check warnings for inconsistency in number of references
147    result = (app.outdir / 'refs_inconsistency.txt').read_text()
148    expect = ("8. I18N WITH REFS INCONSISTENCY"
149              "\n*******************************\n"
150              "\n* FOR CITATION [ref3].\n"
151              "\n* reference FOR reference.\n"
152              "\n* ORPHAN REFERENCE: I18N WITH REFS INCONSISTENCY.\n"
153              "\n[1] THIS IS A AUTO NUMBERED FOOTNOTE.\n"
154              "\n[ref2] THIS IS A CITATION.\n"
155              "\n[100] THIS IS A NUMBERED FOOTNOTE.\n")
156    assert result == expect
157
158    warnings = getwarning(warning)
159    warning_fmt = ('.*/refs_inconsistency.txt:\\d+: '
160                   'WARNING: inconsistent %(reftype)s in translated message.'
161                   ' original: %(original)s, translated: %(translated)s\n')
162    expected_warning_expr = (
163        warning_fmt % {
164            'reftype': 'footnote references',
165            'original': "\\['\\[#\\]_'\\]",
166            'translated': "\\[\\]"
167        } +
168        warning_fmt % {
169            'reftype': 'footnote references',
170            'original': "\\['\\[100\\]_'\\]",
171            'translated': "\\[\\]"
172        } +
173        warning_fmt % {
174            'reftype': 'references',
175            'original': "\\['reference_'\\]",
176            'translated': "\\['reference_', 'reference_'\\]"
177        } +
178        warning_fmt % {
179            'reftype': 'references',
180            'original': "\\[\\]",
181            'translated': "\\['`I18N WITH REFS INCONSISTENCY`_'\\]"
182        })
183    assert_re_search(expected_warning_expr, warnings)
184
185    expected_citation_warning_expr = (
186        '.*/refs_inconsistency.txt:\\d+: WARNING: Citation \\[ref2\\] is not referenced.\n' +
187        '.*/refs_inconsistency.txt:\\d+: WARNING: citation not found: ref3')
188    assert_re_search(expected_citation_warning_expr, warnings)
189
190
191@sphinx_intl
192@pytest.mark.sphinx('text')
193@pytest.mark.test_params(shared_result='test_intl_basic')
194def test_text_literalblock_warnings(app, warning):
195    app.build()
196    # --- check warning for literal block
197    result = (app.outdir / 'literalblock.txt').read_text()
198    expect = ("9. I18N WITH LITERAL BLOCK"
199              "\n**************************\n"
200              "\nCORRECT LITERAL BLOCK:\n"
201              "\n   this is"
202              "\n   literal block\n"
203              "\nMISSING LITERAL BLOCK:\n"
204              "\n<SYSTEM MESSAGE:")
205    assert_startswith(result, expect)
206
207    warnings = getwarning(warning)
208    expected_warning_expr = ('.*/literalblock.txt:\\d+: '
209                             'WARNING: Literal block expected; none found.')
210    assert_re_search(expected_warning_expr, warnings)
211
212
213@sphinx_intl
214@pytest.mark.sphinx('text')
215@pytest.mark.test_params(shared_result='test_intl_basic')
216def test_text_definition_terms(app):
217    app.build()
218    # --- definition terms: regression test for #975, #2198, #2205
219    result = (app.outdir / 'definition_terms.txt').read_text()
220    expect = ("13. I18N WITH DEFINITION TERMS"
221              "\n******************************\n"
222              "\nSOME TERM"
223              "\n   THE CORRESPONDING DEFINITION\n"
224              "\nSOME *TERM* WITH LINK"
225              "\n   THE CORRESPONDING DEFINITION #2\n"
226              "\nSOME **TERM** WITH : CLASSIFIER1 : CLASSIFIER2"
227              "\n   THE CORRESPONDING DEFINITION\n"
228              "\nSOME TERM WITH : CLASSIFIER[]"
229              "\n   THE CORRESPONDING DEFINITION\n")
230    assert result == expect
231
232
233@sphinx_intl
234@pytest.mark.sphinx('text')
235@pytest.mark.test_params(shared_result='test_intl_basic')
236def test_text_glossary_term(app, warning):
237    app.build()
238    # --- glossary terms: regression test for #1090
239    result = (app.outdir / 'glossary_terms.txt').read_text()
240    expect = ("18. I18N WITH GLOSSARY TERMS"
241              "\n****************************\n"
242              "\nSOME NEW TERM"
243              "\n   THE CORRESPONDING GLOSSARY\n"
244              "\nSOME OTHER NEW TERM"
245              "\n   THE CORRESPONDING GLOSSARY #2\n"
246              "\nLINK TO *SOME NEW TERM*.\n")
247    assert result == expect
248    warnings = getwarning(warning)
249    assert 'term not in glossary' not in warnings
250
251
252@sphinx_intl
253@pytest.mark.sphinx('text')
254@pytest.mark.test_params(shared_result='test_intl_basic')
255def test_text_glossary_term_inconsistencies(app, warning):
256    app.build()
257    # --- glossary term inconsistencies: regression test for #1090
258    result = (app.outdir / 'glossary_terms_inconsistency.txt').read_text()
259    expect = ("19. I18N WITH GLOSSARY TERMS INCONSISTENCY"
260              "\n******************************************\n"
261              "\n1. LINK TO *SOME NEW TERM*.\n")
262    assert result == expect
263
264    warnings = getwarning(warning)
265    expected_warning_expr = (
266        '.*/glossary_terms_inconsistency.txt:\\d+: '
267        'WARNING: inconsistent term references in translated message.'
268        " original: \\[':term:`Some term`', ':term:`Some other term`'\\],"
269        " translated: \\[':term:`SOME NEW TERM`'\\]\n")
270    assert_re_search(expected_warning_expr, warnings)
271
272
273@sphinx_intl
274@pytest.mark.sphinx('gettext')
275@pytest.mark.test_params(shared_result='test_intl_gettext')
276def test_gettext_section(app):
277    app.build()
278    # --- section
279    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'section.po')
280    actual = read_po(app.outdir / 'section.pot')
281    for expect_msg in [m for m in expect if m.id]:
282        assert expect_msg.id in [m.id for m in actual if m.id]
283
284
285@sphinx_intl
286@pytest.mark.sphinx('text')
287@pytest.mark.test_params(shared_result='test_intl_basic')
288def test_text_section(app):
289    app.build()
290    # --- section
291    result = (app.outdir / 'section.txt').read_text()
292    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'section.po')
293    for expect_msg in [m for m in expect if m.id]:
294        assert expect_msg.string in result
295
296
297@sphinx_intl
298@pytest.mark.sphinx('text')
299@pytest.mark.test_params(shared_result='test_intl_basic')
300def test_text_seealso(app):
301    app.build()
302    # --- seealso
303    result = (app.outdir / 'seealso.txt').read_text()
304    expect = ("12. I18N WITH SEEALSO"
305              "\n*********************\n"
306              "\nSee also: SHORT TEXT 1\n"
307              "\nSee also: LONG TEXT 1\n"
308              "\nSee also:\n"
309              "\n  SHORT TEXT 2\n"
310              "\n  LONG TEXT 2\n")
311    assert result == expect
312
313
314@sphinx_intl
315@pytest.mark.sphinx('text')
316@pytest.mark.test_params(shared_result='test_intl_basic')
317def test_text_figure_captions(app):
318    app.build()
319    # --- figure captions: regression test for #940
320    result = (app.outdir / 'figure.txt').read_text()
321    expect = ("14. I18N WITH FIGURE CAPTION"
322              "\n****************************\n"
323              "\n   [image]MY CAPTION OF THE FIGURE\n"
324              "\n   MY DESCRIPTION PARAGRAPH1 OF THE FIGURE.\n"
325              "\n   MY DESCRIPTION PARAGRAPH2 OF THE FIGURE.\n"
326              "\n"
327              "\n14.1. FIGURE IN THE BLOCK"
328              "\n=========================\n"
329              "\nBLOCK\n"
330              "\n      [image]MY CAPTION OF THE FIGURE\n"
331              "\n      MY DESCRIPTION PARAGRAPH1 OF THE FIGURE.\n"
332              "\n      MY DESCRIPTION PARAGRAPH2 OF THE FIGURE.\n"
333              "\n"
334              "\n"
335              "14.2. IMAGE URL AND ALT\n"
336              "=======================\n"
337              "\n"
338              "[image: i18n][image]\n"
339              "\n"
340              "   [image: img][image]\n"
341              "\n"
342              "\n"
343              "14.3. IMAGE ON SUBSTITUTION\n"
344              "===========================\n"
345              "\n"
346              "\n"
347              "14.4. IMAGE UNDER NOTE\n"
348              "======================\n"
349              "\n"
350              "Note:\n"
351              "\n"
352              "  [image: i18n under note][image]\n"
353              "\n"
354              "     [image: img under note][image]\n")
355    assert result == expect
356
357
358@sphinx_intl
359@pytest.mark.sphinx('text')
360@pytest.mark.test_params(shared_result='test_intl_basic')
361def test_text_rubric(app):
362    app.build()
363    # --- rubric: regression test for pull request #190
364    result = (app.outdir / 'rubric.txt').read_text()
365    expect = ("I18N WITH RUBRIC"
366              "\n****************\n"
367              "\n-[ RUBRIC TITLE ]-\n"
368              "\n"
369              "\nRUBRIC IN THE BLOCK"
370              "\n===================\n"
371              "\nBLOCK\n"
372              "\n   -[ RUBRIC TITLE ]-\n")
373    assert result == expect
374
375
376@sphinx_intl
377@pytest.mark.sphinx('text')
378@pytest.mark.test_params(shared_result='test_intl_basic')
379def test_text_docfields(app):
380    app.build()
381    # --- docfields
382    result = (app.outdir / 'docfields.txt').read_text()
383    expect = ("21. I18N WITH DOCFIELDS"
384              "\n***********************\n"
385              "\nclass Cls1\n"
386              "\n   Parameters:"
387              "\n      **param** -- DESCRIPTION OF PARAMETER param\n"
388              "\nclass Cls2\n"
389              "\n   Parameters:"
390              "\n      * **foo** -- DESCRIPTION OF PARAMETER foo\n"
391              "\n      * **bar** -- DESCRIPTION OF PARAMETER bar\n"
392              "\nclass Cls3(values)\n"
393              "\n   Raises:"
394              "\n      **ValueError** -- IF THE VALUES ARE OUT OF RANGE\n"
395              "\nclass Cls4(values)\n"
396              "\n   Raises:"
397              "\n      * **TypeError** -- IF THE VALUES ARE NOT VALID\n"
398              "\n      * **ValueError** -- IF THE VALUES ARE OUT OF RANGE\n"
399              "\nclass Cls5\n"
400              "\n   Returns:"
401              '\n      A NEW "Cls3" INSTANCE\n')
402    assert result == expect
403
404
405@sphinx_intl
406@pytest.mark.sphinx('text')
407@pytest.mark.test_params(shared_result='test_intl_basic')
408def test_text_admonitions(app):
409    app.build()
410    # --- admonitions
411    # #1206: gettext did not translate admonition directive's title
412    # seealso: http://docutils.sourceforge.net/docs/ref/rst/directives.html#admonitions
413    result = (app.outdir / 'admonitions.txt').read_text()
414    directives = (
415        "attention", "caution", "danger", "error", "hint",
416        "important", "note", "tip", "warning", "admonition")
417    for d in directives:
418        assert d.upper() + " TITLE" in result
419        assert d.upper() + " BODY" in result
420
421    # for #4938 `1. ` prefixed admonition title
422    assert "1. ADMONITION TITLE" in result
423
424
425@sphinx_intl
426@pytest.mark.sphinx('gettext')
427@pytest.mark.test_params(shared_result='test_intl_gettext')
428def test_gettext_toctree(app):
429    app.build()
430    # --- toctree (index.rst)
431    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'index.po')
432    actual = read_po(app.outdir / 'index.pot')
433    for expect_msg in [m for m in expect if m.id]:
434        assert expect_msg.id in [m.id for m in actual if m.id]
435    # --- toctree (toctree.rst)
436    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po')
437    actual = read_po(app.outdir / 'toctree.pot')
438    for expect_msg in [m for m in expect if m.id]:
439        assert expect_msg.id in [m.id for m in actual if m.id]
440
441
442@sphinx_intl
443@pytest.mark.sphinx('gettext')
444@pytest.mark.test_params(shared_result='test_intl_gettext')
445def test_gettext_table(app):
446    app.build()
447    # --- toctree
448    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'table.po')
449    actual = read_po(app.outdir / 'table.pot')
450    for expect_msg in [m for m in expect if m.id]:
451        assert expect_msg.id in [m.id for m in actual if m.id]
452
453
454@sphinx_intl
455@pytest.mark.sphinx('text')
456@pytest.mark.test_params(shared_result='test_intl_basic')
457def test_text_table(app):
458    app.build()
459    # --- toctree
460    result = (app.outdir / 'table.txt').read_text()
461    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'table.po')
462    for expect_msg in [m for m in expect if m.id]:
463        assert expect_msg.string in result
464
465
466@sphinx_intl
467@pytest.mark.sphinx('text')
468@pytest.mark.test_params(shared_result='test_intl_basic')
469def test_text_toctree(app):
470    app.build()
471    # --- toctree (index.rst)
472    # Note: index.rst contains contents that is not shown in text.
473    result = (app.outdir / 'index.txt').read_text()
474    assert 'CONTENTS' in result
475    assert 'TABLE OF CONTENTS' in result
476    # --- toctree (toctree.rst)
477    result = (app.outdir / 'toctree.txt').read_text()
478    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po')
479    for expect_msg in [m for m in expect if m.id]:
480        assert expect_msg.string in result
481
482
483@sphinx_intl
484@pytest.mark.sphinx('gettext')
485@pytest.mark.test_params(shared_result='test_intl_gettext')
486def test_gettext_topic(app):
487    app.build()
488    # --- topic
489    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'topic.po')
490    actual = read_po(app.outdir / 'topic.pot')
491    for expect_msg in [m for m in expect if m.id]:
492        assert expect_msg.id in [m.id for m in actual if m.id]
493
494
495@sphinx_intl
496@pytest.mark.sphinx('text')
497@pytest.mark.test_params(shared_result='test_intl_basic')
498def test_text_topic(app):
499    app.build()
500    # --- topic
501    result = (app.outdir / 'topic.txt').read_text()
502    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'topic.po')
503    for expect_msg in [m for m in expect if m.id]:
504        assert expect_msg.string in result
505
506
507@sphinx_intl
508@pytest.mark.sphinx('gettext')
509@pytest.mark.test_params(shared_result='test_intl_gettext')
510def test_gettext_definition_terms(app):
511    app.build()
512    # --- definition terms: regression test for #2198, #2205
513    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'definition_terms.po')
514    actual = read_po(app.outdir / 'definition_terms.pot')
515    for expect_msg in [m for m in expect if m.id]:
516        assert expect_msg.id in [m.id for m in actual if m.id]
517
518
519@sphinx_intl
520@pytest.mark.sphinx('gettext')
521@pytest.mark.test_params(shared_result='test_intl_gettext')
522def test_gettext_glossary_terms(app, warning):
523    app.build()
524    # --- glossary terms: regression test for #1090
525    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'glossary_terms.po')
526    actual = read_po(app.outdir / 'glossary_terms.pot')
527    for expect_msg in [m for m in expect if m.id]:
528        assert expect_msg.id in [m.id for m in actual if m.id]
529    warnings = warning.getvalue().replace(os.sep, '/')
530    assert 'term not in glossary' not in warnings
531
532
533@sphinx_intl
534@pytest.mark.sphinx('gettext')
535@pytest.mark.test_params(shared_result='test_intl_gettext')
536def test_gettext_glossary_term_inconsistencies(app):
537    app.build()
538    # --- glossary term inconsistencies: regression test for #1090
539    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'glossary_terms_inconsistency.po')
540    actual = read_po(app.outdir / 'glossary_terms_inconsistency.pot')
541    for expect_msg in [m for m in expect if m.id]:
542        assert expect_msg.id in [m.id for m in actual if m.id]
543
544
545@sphinx_intl
546@pytest.mark.sphinx('gettext')
547@pytest.mark.test_params(shared_result='test_intl_gettext')
548def test_gettext_literalblock(app):
549    app.build()
550    # --- gettext builder always ignores ``only`` directive
551    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'literalblock.po')
552    actual = read_po(app.outdir / 'literalblock.pot')
553    for expect_msg in [m for m in expect if m.id]:
554        if len(expect_msg.id.splitlines()) == 1:
555            # compare tranlsations only labels
556            assert expect_msg.id in [m.id for m in actual if m.id]
557        else:
558            pass  # skip code-blocks and literalblocks
559
560
561@sphinx_intl
562@pytest.mark.sphinx('gettext')
563@pytest.mark.test_params(shared_result='test_intl_gettext')
564def test_gettext_buildr_ignores_only_directive(app):
565    app.build()
566    # --- gettext builder always ignores ``only`` directive
567    expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'only.po')
568    actual = read_po(app.outdir / 'only.pot')
569    for expect_msg in [m for m in expect if m.id]:
570        assert expect_msg.id in [m.id for m in actual if m.id]
571
572
573@sphinx_intl
574# use individual shared_result directory to avoid "incompatible doctree" error
575@pytest.mark.sphinx(testroot='builder-gettext-dont-rebuild-mo')
576def test_gettext_dont_rebuild_mo(make_app, app_params):
577    # --- don't rebuild by .mo mtime
578    def get_number_of_update_targets(app_):
579        app_.env.find_files(app_.config, app_.builder)
580        _, updated, _ = app_.env.get_outdated_files(config_changed=False)
581        return len(updated)
582
583    args, kwargs = app_params
584
585    # phase1: build document with non-gettext builder and generate mo file in srcdir
586    app0 = make_app('dummy', *args, **kwargs)
587    app0.build()
588    assert (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').exists()
589    # Since it is after the build, the number of documents to be updated is 0
590    assert get_number_of_update_targets(app0) == 0
591    # When rewriting the timestamp of mo file, the number of documents to be
592    # updated will be changed.
593    mtime = (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').stat().st_mtime
594    (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').utime((mtime + 5, mtime + 5))
595    assert get_number_of_update_targets(app0) == 1
596
597    # Because doctree for gettext builder can not be shared with other builders,
598    # erase doctreedir before gettext build.
599    app0.doctreedir.rmtree()
600
601    # phase2: build document with gettext builder.
602    # The mo file in the srcdir directory is retained.
603    app = make_app('gettext', *args, **kwargs)
604    app.build()
605    # Since it is after the build, the number of documents to be updated is 0
606    assert get_number_of_update_targets(app) == 0
607    # Even if the timestamp of the mo file is updated, the number of documents
608    # to be updated is 0. gettext builder does not rebuild because of mo update.
609    (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').utime((mtime + 10, mtime + 10))
610    assert get_number_of_update_targets(app) == 0
611
612
613@sphinx_intl
614@pytest.mark.sphinx('html')
615@pytest.mark.test_params(shared_result='test_intl_basic')
616def test_html_meta(app):
617    app.build()
618    # --- test for meta
619    result = (app.outdir / 'index.html').read_text()
620    expected_expr = '<meta content="TESTDATA FOR I18N" name="description" />'
621    assert expected_expr in result
622    expected_expr = '<meta content="I18N, SPHINX, MARKUP" name="keywords" />'
623    assert expected_expr in result
624    expected_expr = '<p class="caption"><span class="caption-text">HIDDEN TOC</span></p>'
625    assert expected_expr in result
626
627
628@sphinx_intl
629@pytest.mark.sphinx('html')
630@pytest.mark.test_params(shared_result='test_intl_basic')
631def test_html_footnotes(app):
632    app.build()
633    # --- test for #955 cant-build-html-with-footnotes-when-using
634    # expect no error by build
635    (app.outdir / 'footnote.html').read_text()
636
637
638@sphinx_intl
639@pytest.mark.sphinx('html')
640@pytest.mark.test_params(shared_result='test_intl_basic')
641def test_html_undefined_refs(app):
642    app.build()
643    # --- links to undefined reference
644    result = (app.outdir / 'refs_inconsistency.html').read_text()
645
646    expected_expr = ('<a class="reference external" '
647                     'href="http://www.example.com">reference</a>')
648    assert len(re.findall(expected_expr, result)) == 2
649
650    expected_expr = ('<a class="reference internal" '
651                     'href="#reference">reference</a>')
652    assert len(re.findall(expected_expr, result)) == 0
653
654    expected_expr = ('<a class="reference internal" '
655                     'href="#i18n-with-refs-inconsistency">I18N WITH '
656                     'REFS INCONSISTENCY</a>')
657    assert len(re.findall(expected_expr, result)) == 1
658
659
660@sphinx_intl
661@pytest.mark.sphinx('html')
662@pytest.mark.test_params(shared_result='test_intl_basic')
663def test_html_index_entries(app):
664    app.build()
665    # --- index entries: regression test for #976
666    result = (app.outdir / 'genindex.html').read_text()
667
668    def wrap(tag, keyword):
669        start_tag = "<%s[^>]*>" % tag
670        end_tag = "</%s>" % tag
671        return r"%s\s*%s\s*%s" % (start_tag, keyword, end_tag)
672
673    def wrap_nest(parenttag, childtag, keyword):
674        start_tag1 = "<%s[^>]*>" % parenttag
675        start_tag2 = "<%s[^>]*>" % childtag
676        return r"%s\s*%s\s*%s" % (start_tag1, keyword, start_tag2)
677    expected_exprs = [
678        wrap('a', 'NEWSLETTER'),
679        wrap('a', 'MAILING LIST'),
680        wrap('a', 'RECIPIENTS LIST'),
681        wrap('a', 'FIRST SECOND'),
682        wrap('a', 'SECOND THIRD'),
683        wrap('a', 'THIRD, FIRST'),
684        wrap_nest('li', 'ul', 'ENTRY'),
685        wrap_nest('li', 'ul', 'SEE'),
686        wrap('a', 'MODULE'),
687        wrap('a', 'KEYWORD'),
688        wrap('a', 'OPERATOR'),
689        wrap('a', 'OBJECT'),
690        wrap('a', 'EXCEPTION'),
691        wrap('a', 'STATEMENT'),
692        wrap('a', 'BUILTIN'),
693    ]
694    for expr in expected_exprs:
695        assert_re_search(expr, result, re.M)
696
697
698@sphinx_intl
699@pytest.mark.sphinx('html')
700@pytest.mark.test_params(shared_result='test_intl_basic')
701def test_html_versionchanges(app):
702    app.build()
703    # --- versionchanges
704    result = (app.outdir / 'versionchange.html').read_text()
705
706    def get_content(result, name):
707        matched = re.search(r'<div class="%s">\n*(.*?)</div>' % name,
708                            result, re.DOTALL)
709        if matched:
710            return matched.group(1)
711        else:
712            return ''
713
714    expect1 = (
715        """<p><span class="versionmodified deprecated">Deprecated since version 1.0: </span>"""
716        """THIS IS THE <em>FIRST</em> PARAGRAPH OF DEPRECATED.</p>\n"""
717        """<p>THIS IS THE <em>SECOND</em> PARAGRAPH OF DEPRECATED.</p>\n""")
718    matched_content = get_content(result, "deprecated")
719    assert expect1 == matched_content
720
721    expect2 = (
722        """<p><span class="versionmodified added">New in version 1.0: </span>"""
723        """THIS IS THE <em>FIRST</em> PARAGRAPH OF VERSIONADDED.</p>\n""")
724    matched_content = get_content(result, "versionadded")
725    assert expect2 == matched_content
726
727    expect3 = (
728        """<p><span class="versionmodified changed">Changed in version 1.0: </span>"""
729        """THIS IS THE <em>FIRST</em> PARAGRAPH OF VERSIONCHANGED.</p>\n""")
730    matched_content = get_content(result, "versionchanged")
731    assert expect3 == matched_content
732
733
734@sphinx_intl
735@pytest.mark.sphinx('html')
736@pytest.mark.test_params(shared_result='test_intl_basic')
737def test_html_docfields(app):
738    app.build()
739    # --- docfields
740    # expect no error by build
741    (app.outdir / 'docfields.html').read_text()
742
743
744@sphinx_intl
745@pytest.mark.sphinx('html')
746@pytest.mark.test_params(shared_result='test_intl_basic')
747def test_html_template(app):
748    app.build()
749    # --- gettext template
750    result = (app.outdir / 'contents.html').read_text()
751    assert "WELCOME" in result
752    assert "SPHINX 2013.120" in result
753
754
755@sphinx_intl
756@pytest.mark.sphinx('html')
757@pytest.mark.test_params(shared_result='test_intl_basic')
758def test_html_rebuild_mo(app):
759    app.build()
760    # --- rebuild by .mo mtime
761    app.builder.build_update()
762    app.env.find_files(app.config, app.builder)
763    _, updated, _ = app.env.get_outdated_files(config_changed=False)
764    assert len(updated) == 0
765
766    mtime = (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').stat().st_mtime
767    (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').utime((mtime + 5, mtime + 5))
768    app.env.find_files(app.config, app.builder)
769    _, updated, _ = app.env.get_outdated_files(config_changed=False)
770    assert len(updated) == 1
771
772
773@sphinx_intl
774@pytest.mark.sphinx('xml')
775@pytest.mark.test_params(shared_result='test_intl_basic')
776def test_xml_footnotes(app, warning):
777    app.build()
778    # --- footnotes: regression test for fix #955, #1176
779    et = etree_parse(app.outdir / 'footnote.xml')
780    secs = et.findall('section')
781
782    para0 = secs[0].findall('paragraph')
783    assert_elem(
784        para0[0],
785        ['I18N WITH FOOTNOTE', 'INCLUDE THIS CONTENTS',
786         '2', '[ref]', '1', '100', '*', '. SECOND FOOTNOTE_REF', '100', '.'],
787        ['i18n-with-footnote', 'ref'])
788
789    # check node_id for footnote_references which refer same footnote (refs: #3002)
790    assert para0[0][4].text == para0[0][6].text == '100'
791    assert para0[0][4].attrib['ids'] != para0[0][6].attrib['ids']
792
793    footnote0 = secs[0].findall('footnote')
794    assert_elem(
795        footnote0[0],
796        ['1', 'THIS IS A AUTO NUMBERED FOOTNOTE.'],
797        None,
798        ['1'])
799    assert_elem(
800        footnote0[1],
801        ['100', 'THIS IS A NUMBERED FOOTNOTE.'],
802        None,
803        ['100'])
804    assert_elem(
805        footnote0[2],
806        ['2', 'THIS IS A AUTO NUMBERED NAMED FOOTNOTE.'],
807        None,
808        ['named'])
809    assert_elem(
810        footnote0[3],
811        ['*', 'THIS IS A AUTO SYMBOL FOOTNOTE.'],
812        None,
813        None)
814
815    citation0 = secs[0].findall('citation')
816    assert_elem(
817        citation0[0],
818        ['ref', 'THIS IS A NAMED FOOTNOTE.'],
819        None,
820        ['ref'])
821
822    warnings = getwarning(warning)
823    warning_expr = '.*/footnote.xml:\\d*: SEVERE: Duplicate ID: ".*".\n'
824    assert_not_re_search(warning_expr, warnings)
825
826
827@sphinx_intl
828@pytest.mark.sphinx('xml')
829@pytest.mark.test_params(shared_result='test_intl_basic')
830def test_xml_footnote_backlinks(app):
831    app.build()
832    # --- footnote backlinks: i18n test for #1058
833    et = etree_parse(app.outdir / 'footnote.xml')
834    secs = et.findall('section')
835
836    para0 = secs[0].findall('paragraph')
837    refs0 = para0[0].findall('footnote_reference')
838    refid2id = {r.attrib.get('refid'): r.attrib.get('ids') for r in refs0}
839
840    footnote0 = secs[0].findall('footnote')
841    for footnote in footnote0:
842        ids = footnote.attrib.get('ids')
843        backrefs = footnote.attrib.get('backrefs').split()
844        assert refid2id[ids] in backrefs
845
846
847@sphinx_intl
848@pytest.mark.sphinx('xml')
849@pytest.mark.test_params(shared_result='test_intl_basic')
850def test_xml_refs_in_python_domain(app):
851    app.build()
852    # --- refs in the Python domain
853    et = etree_parse(app.outdir / 'refs_python_domain.xml')
854    secs = et.findall('section')
855
856    # regression test for fix #1363
857    para0 = secs[0].findall('paragraph')
858    assert_elem(
859        para0[0],
860        ['SEE THIS DECORATOR:', 'sensitive_variables()', '.'],
861        ['sensitive.sensitive_variables'])
862
863
864@sphinx_intl
865@pytest.mark.sphinx('xml')
866@pytest.mark.test_params(shared_result='test_intl_basic')
867def test_xml_keep_external_links(app):
868    app.build()
869    # --- keep external links: regression test for #1044
870    et = etree_parse(app.outdir / 'external_links.xml')
871    secs = et.findall('section')
872
873    para0 = secs[0].findall('paragraph')
874    # external link check
875    assert_elem(
876        para0[0],
877        ['EXTERNAL LINK TO', 'Python', '.'],
878        ['http://python.org/index.html'])
879
880    # internal link check
881    assert_elem(
882        para0[1],
883        ['EXTERNAL LINKS', 'IS INTERNAL LINK.'],
884        ['i18n-with-external-links'])
885
886    # inline link check
887    assert_elem(
888        para0[2],
889        ['INLINE LINK BY', 'THE SPHINX SITE', '.'],
890        ['http://sphinx-doc.org'])
891
892    # unnamed link check
893    assert_elem(
894        para0[3],
895        ['UNNAMED', 'LINK', '.'],
896        ['http://google.com'])
897
898    # link target swapped translation
899    para1 = secs[1].findall('paragraph')
900    assert_elem(
901        para1[0],
902        ['LINK TO', 'external2', 'AND', 'external1', '.'],
903        ['https://www.google.com/external2',
904         'https://www.google.com/external1'])
905    assert_elem(
906        para1[1],
907        ['LINK TO', 'THE PYTHON SITE', 'AND', 'THE SPHINX SITE', '.'],
908        ['http://python.org', 'http://sphinx-doc.org'])
909
910    # multiple references in the same line
911    para2 = secs[2].findall('paragraph')
912    assert_elem(
913        para2[0],
914        ['LINK TO', 'EXTERNAL LINKS', ',', 'Python', ',',
915         'THE SPHINX SITE', ',', 'UNNAMED', 'AND',
916         'THE PYTHON SITE', '.'],
917        ['i18n-with-external-links', 'http://python.org/index.html',
918         'http://sphinx-doc.org', 'http://google.com',
919         'http://python.org'])
920
921
922@sphinx_intl
923@pytest.mark.sphinx('xml')
924@pytest.mark.test_params(shared_result='test_intl_basic')
925def test_xml_role_xref(app):
926    app.build()
927    # --- role xref: regression test for #1090, #1193
928    et = etree_parse(app.outdir / 'role_xref.xml')
929    sec1, sec2 = et.findall('section')
930
931    para1, = sec1.findall('paragraph')
932    assert_elem(
933        para1,
934        ['LINK TO', "I18N ROCK'N ROLE XREF", ',', 'CONTENTS', ',',
935         'SOME NEW TERM', '.'],
936        ['i18n-role-xref', 'index',
937         'glossary_terms#term-Some-term'])
938
939    para2 = sec2.findall('paragraph')
940    assert_elem(
941        para2[0],
942        ['LINK TO', 'SOME OTHER NEW TERM', 'AND', 'SOME NEW TERM', '.'],
943        ['glossary_terms#term-Some-other-term',
944         'glossary_terms#term-Some-term'])
945    assert_elem(
946        para2[1],
947        ['LINK TO', 'LABEL', 'AND',
948         'SAME TYPE LINKS', 'AND', 'SAME TYPE LINKS', '.'],
949        ['i18n-role-xref', 'same-type-links', 'same-type-links'])
950    assert_elem(
951        para2[2],
952        ['LINK TO', 'I18N WITH GLOSSARY TERMS', 'AND', 'CONTENTS', '.'],
953        ['glossary_terms', 'index'])
954    assert_elem(
955        para2[3],
956        ['LINK TO', '--module', 'AND', '-m', '.'],
957        ['cmdoption-module', 'cmdoption-m'])
958    assert_elem(
959        para2[4],
960        ['LINK TO', 'env2', 'AND', 'env1', '.'],
961        ['envvar-env2', 'envvar-env1'])
962    assert_elem(
963        para2[5],
964        ['LINK TO', 'token2', 'AND', 'token1', '.'],
965        [])  # TODO: how do I link token role to productionlist?
966    assert_elem(
967        para2[6],
968        ['LINK TO', 'same-type-links', 'AND', "i18n-role-xref", '.'],
969        ['same-type-links', 'i18n-role-xref'])
970
971
972@sphinx_intl
973@pytest.mark.sphinx('xml')
974@pytest.mark.test_params(shared_result='test_intl_basic')
975def test_xml_warnings(app, warning):
976    app.build()
977    # warnings
978    warnings = getwarning(warning)
979    assert 'term not in glossary' not in warnings
980    assert 'undefined label' not in warnings
981    assert 'unknown document' not in warnings
982
983
984@sphinx_intl
985@pytest.mark.sphinx('xml')
986@pytest.mark.test_params(shared_result='test_intl_basic')
987def test_xml_label_targets(app):
988    app.build()
989    # --- label targets: regression test for #1193, #1265
990    et = etree_parse(app.outdir / 'label_target.xml')
991    secs = et.findall('section')
992
993    para0 = secs[0].findall('paragraph')
994    assert_elem(
995        para0[0],
996        ['X SECTION AND LABEL', 'POINT TO', 'implicit-target', 'AND',
997         'X SECTION AND LABEL', 'POINT TO', 'section-and-label', '.'],
998        ['implicit-target', 'section-and-label'])
999
1000    para1 = secs[1].findall('paragraph')
1001    assert_elem(
1002        para1[0],
1003        ['X EXPLICIT-TARGET', 'POINT TO', 'explicit-target', 'AND',
1004         'X EXPLICIT-TARGET', 'POINT TO DUPLICATED ID LIKE', 'id1',
1005         '.'],
1006        ['explicit-target', 'id1'])
1007
1008    para2 = secs[2].findall('paragraph')
1009    assert_elem(
1010        para2[0],
1011        ['X IMPLICIT SECTION NAME', 'POINT TO',
1012         'implicit-section-name', '.'],
1013        ['implicit-section-name'])
1014
1015    sec2 = secs[2].findall('section')
1016
1017    para2_0 = sec2[0].findall('paragraph')
1018    assert_elem(
1019        para2_0[0],
1020        ['`X DUPLICATED SUB SECTION`_', 'IS BROKEN LINK.'],
1021        [])
1022
1023    para3 = secs[3].findall('paragraph')
1024    assert_elem(
1025        para3[0],
1026        ['X', 'bridge label',
1027         'IS NOT TRANSLATABLE BUT LINKED TO TRANSLATED ' +
1028         'SECTION TITLE.'],
1029        ['label-bridged-target-section'])
1030    assert_elem(
1031        para3[1],
1032        ['X', 'bridge label', 'POINT TO',
1033         'LABEL BRIDGED TARGET SECTION', 'AND', 'bridge label2',
1034         'POINT TO', 'SECTION AND LABEL', '. THE SECOND APPEARED',
1035         'bridge label2', 'POINT TO CORRECT TARGET.'],
1036        ['label-bridged-target-section',
1037         'section-and-label',
1038         'section-and-label'])
1039
1040
1041@sphinx_intl
1042@pytest.mark.sphinx('html')
1043@pytest.mark.test_params(shared_result='test_intl_basic')
1044def test_additional_targets_should_not_be_translated(app):
1045    app.build()
1046    # [literalblock.txt]
1047    result = (app.outdir / 'literalblock.html').read_text()
1048
1049    # title should be translated
1050    expected_expr = 'CODE-BLOCKS'
1051    assert_count(expected_expr, result, 2)
1052
1053    # ruby code block should not be translated but be highlighted
1054    expected_expr = """<span class="s1">&#39;result&#39;</span>"""
1055    assert_count(expected_expr, result, 1)
1056
1057    # C code block without lang should not be translated and *ruby* highlighted
1058    expected_expr = """<span class="c1">#include &lt;stdlib.h&gt;</span>"""
1059    assert_count(expected_expr, result, 1)
1060
1061    # C code block with lang should not be translated but be *C* highlighted
1062    expected_expr = ("""<span class="cp">#include</span> """
1063                     """<span class="cpf">&lt;stdio.h&gt;</span>""")
1064    assert_count(expected_expr, result, 1)
1065
1066    # literal block in list item should not be translated
1067    expected_expr = ("""<span class="n">literal</span>"""
1068                     """<span class="o">-</span>"""
1069                     """<span class="n">block</span>\n"""
1070                     """<span class="k">in</span> """
1071                     """<span class="n">list</span>""")
1072    assert_count(expected_expr, result, 1)
1073
1074    # doctest block should not be translated but be highlighted
1075    expected_expr = (
1076        """<span class="gp">&gt;&gt;&gt; </span>"""
1077        """<span class="kn">import</span> <span class="nn">sys</span>  """
1078        """<span class="c1"># sys importing</span>""")
1079    assert_count(expected_expr, result, 1)
1080
1081    # [raw.txt]
1082
1083    result = (app.outdir / 'raw.html').read_text()
1084
1085    # raw block should not be translated
1086    expected_expr = """<iframe src="http://sphinx-doc.org"></iframe></div>"""
1087    assert_count(expected_expr, result, 1)
1088
1089    # [figure.txt]
1090
1091    result = (app.outdir / 'figure.html').read_text()
1092
1093    # alt and src for image block should not be translated
1094    expected_expr = """<img alt="i18n" src="_images/i18n.png" />"""
1095    assert_count(expected_expr, result, 1)
1096
1097    # alt and src for figure block should not be translated
1098    expected_expr = """<img alt="img" src="_images/img.png" />"""
1099    assert_count(expected_expr, result, 1)
1100
1101
1102@sphinx_intl
1103@pytest.mark.sphinx(
1104    'html',
1105    srcdir='test_additional_targets_should_be_translated',
1106    confoverrides={
1107        'language': 'xx', 'locale_dirs': ['.'],
1108        'gettext_compact': False,
1109        'gettext_additional_targets': [
1110            'index',
1111            'literal-block',
1112            'doctest-block',
1113            'raw',
1114            'image',
1115        ],
1116    }
1117)
1118def test_additional_targets_should_be_translated(app):
1119    app.build()
1120    # [literalblock.txt]
1121    result = (app.outdir / 'literalblock.html').read_text()
1122
1123    # title should be translated
1124    expected_expr = 'CODE-BLOCKS'
1125    assert_count(expected_expr, result, 2)
1126
1127    # ruby code block should be translated and be highlighted
1128    expected_expr = """<span class="s1">&#39;RESULT&#39;</span>"""
1129    assert_count(expected_expr, result, 1)
1130
1131    # C code block without lang should be translated and *ruby* highlighted
1132    expected_expr = """<span class="c1">#include &lt;STDLIB.H&gt;</span>"""
1133    assert_count(expected_expr, result, 1)
1134
1135    # C code block with lang should be translated and be *C* highlighted
1136    expected_expr = ("""<span class="cp">#include</span> """
1137                     """<span class="cpf">&lt;STDIO.H&gt;</span>""")
1138    assert_count(expected_expr, result, 1)
1139
1140    # literal block in list item should be translated
1141    expected_expr = ("""<span class="no">LITERAL</span>"""
1142                     """<span class="o">-</span>"""
1143                     """<span class="no">BLOCK</span>\n"""
1144                     """<span class="no">IN</span> """
1145                     """<span class="no">LIST</span>""")
1146    assert_count(expected_expr, result, 1)
1147
1148    # doctest block should not be translated but be highlighted
1149    expected_expr = (
1150        """<span class="gp">&gt;&gt;&gt; </span>"""
1151        """<span class="kn">import</span> <span class="nn">sys</span>  """
1152        """<span class="c1"># SYS IMPORTING</span>""")
1153    assert_count(expected_expr, result, 1)
1154
1155    # [raw.txt]
1156
1157    result = (app.outdir / 'raw.html').read_text()
1158
1159    # raw block should be translated
1160    expected_expr = """<iframe src="HTTP://SPHINX-DOC.ORG"></iframe></div>"""
1161    assert_count(expected_expr, result, 1)
1162
1163    # [figure.txt]
1164
1165    result = (app.outdir / 'figure.html').read_text()
1166
1167    # alt and src for image block should be translated
1168    expected_expr = """<img alt="I18N -&gt; IMG" src="_images/img.png" />"""
1169    assert_count(expected_expr, result, 1)
1170
1171    # alt and src for figure block should be translated
1172    expected_expr = """<img alt="IMG -&gt; I18N" src="_images/i18n.png" />"""
1173    assert_count(expected_expr, result, 1)
1174
1175
1176@sphinx_intl
1177@pytest.mark.sphinx('text')
1178@pytest.mark.test_params(shared_result='test_intl_basic')
1179def test_text_references(app, warning):
1180    app.builder.build_specific([app.srcdir / 'refs.txt'])
1181
1182    warnings = warning.getvalue().replace(os.sep, '/')
1183    warning_expr = 'refs.txt:\\d+: ERROR: Unknown target name:'
1184    assert_count(warning_expr, warnings, 0)
1185
1186
1187@pytest.mark.sphinx(
1188    'dummy', testroot='images',
1189    srcdir='test_intl_images',
1190    confoverrides={'language': 'xx'}
1191)
1192@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows")
1193def test_image_glob_intl(app):
1194    app.build()
1195
1196    # index.rst
1197    doctree = app.env.get_doctree('index')
1198    assert_node(doctree[0][1], nodes.image, uri='rimg.xx.png',
1199                candidates={'*': 'rimg.xx.png'})
1200
1201    assert isinstance(doctree[0][2], nodes.figure)
1202    assert_node(doctree[0][2][0], nodes.image, uri='rimg.xx.png',
1203                candidates={'*': 'rimg.xx.png'})
1204
1205    assert_node(doctree[0][3], nodes.image, uri='img.*',
1206                candidates={'application/pdf': 'img.pdf',
1207                            'image/gif': 'img.gif',
1208                            'image/png': 'img.png'})
1209
1210    assert isinstance(doctree[0][4], nodes.figure)
1211    assert_node(doctree[0][4][0], nodes.image, uri='img.*',
1212                candidates={'application/pdf': 'img.pdf',
1213                            'image/gif': 'img.gif',
1214                            'image/png': 'img.png'})
1215
1216    # subdir/index.rst
1217    doctree = app.env.get_doctree('subdir/index')
1218    assert_node(doctree[0][1], nodes.image, uri='subdir/rimg.xx.png',
1219                candidates={'*': 'subdir/rimg.xx.png'})
1220
1221    assert_node(doctree[0][2], nodes.image, uri='subdir/svgimg.*',
1222                candidates={'application/pdf': 'subdir/svgimg.pdf',
1223                            'image/svg+xml': 'subdir/svgimg.xx.svg'})
1224
1225    assert isinstance(doctree[0][3], nodes.figure)
1226    assert_node(doctree[0][3][0], nodes.image, uri='subdir/svgimg.*',
1227                candidates={'application/pdf': 'subdir/svgimg.pdf',
1228                            'image/svg+xml': 'subdir/svgimg.xx.svg'})
1229
1230
1231@pytest.mark.sphinx(
1232    'dummy', testroot='images',
1233    srcdir='test_intl_images',
1234    confoverrides={
1235        'language': 'xx',
1236        'figure_language_filename': '{root}{ext}.{language}',
1237    }
1238)
1239@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows")
1240def test_image_glob_intl_using_figure_language_filename(app):
1241    app.build()
1242
1243    # index.rst
1244    doctree = app.env.get_doctree('index')
1245    assert_node(doctree[0][1], nodes.image, uri='rimg.png.xx',
1246                candidates={'*': 'rimg.png.xx'})
1247
1248    assert isinstance(doctree[0][2], nodes.figure)
1249    assert_node(doctree[0][2][0], nodes.image, uri='rimg.png.xx',
1250                candidates={'*': 'rimg.png.xx'})
1251
1252    assert_node(doctree[0][3], nodes.image, uri='img.*',
1253                candidates={'application/pdf': 'img.pdf',
1254                            'image/gif': 'img.gif',
1255                            'image/png': 'img.png'})
1256
1257    assert isinstance(doctree[0][4], nodes.figure)
1258    assert_node(doctree[0][4][0], nodes.image, uri='img.*',
1259                candidates={'application/pdf': 'img.pdf',
1260                            'image/gif': 'img.gif',
1261                            'image/png': 'img.png'})
1262
1263    # subdir/index.rst
1264    doctree = app.env.get_doctree('subdir/index')
1265    assert_node(doctree[0][1], nodes.image, uri='subdir/rimg.png',
1266                candidates={'*': 'subdir/rimg.png'})
1267
1268    assert_node(doctree[0][2], nodes.image, uri='subdir/svgimg.*',
1269                candidates={'application/pdf': 'subdir/svgimg.pdf',
1270                            'image/svg+xml': 'subdir/svgimg.svg'})
1271
1272    assert isinstance(doctree[0][3], nodes.figure)
1273    assert_node(doctree[0][3][0], nodes.image, uri='subdir/svgimg.*',
1274                candidates={'application/pdf': 'subdir/svgimg.pdf',
1275                            'image/svg+xml': 'subdir/svgimg.svg'})
1276
1277
1278def getwarning(warnings):
1279    return strip_escseq(warnings.getvalue().replace(os.sep, '/'))
1280
1281
1282@pytest.mark.sphinx('html', testroot='basic', confoverrides={'language': 'de'})
1283def test_customize_system_message(make_app, app_params, sphinx_test_tempdir):
1284    try:
1285        # clear translators cache
1286        locale.translators.clear()
1287
1288        # prepare message catalog (.po)
1289        locale_dir = sphinx_test_tempdir / 'basic' / 'locales' / 'de' / 'LC_MESSAGES'
1290        locale_dir.makedirs()
1291        with (locale_dir / 'sphinx.po').open('wb') as f:
1292            catalog = Catalog()
1293            catalog.add('Quick search', 'QUICK SEARCH')
1294            pofile.write_po(f, catalog)
1295
1296        # construct application and convert po file to .mo
1297        args, kwargs = app_params
1298        app = make_app(*args, **kwargs)
1299        assert (locale_dir / 'sphinx.mo').exists()
1300        assert app.translator.gettext('Quick search') == 'QUICK SEARCH'
1301
1302        app.build()
1303        content = (app.outdir / 'index.html').read_text()
1304        assert 'QUICK SEARCH' in content
1305    finally:
1306        locale.translators.clear()
1307