Repository: sphinx-doc/sphinx

Issue:
Use xeCJK for fontenc as default with XeLaTex and Chinese docs
When [building Chinese translation of CPython docs](https://github.com/python/docsbuild-scripts/pull/70). The only valid option I tried out is using xeCJK as the fontenc and using xelatex.

A simple project created by sphinx-quickstart, with some Chinese words(like `测试`) in `source/index.rst`, will fail the `make latexpdf` command.

Howerver the following command works,

```
make latexpdf SPHINXOPTS='-D latex_engine=xelatex -D latex_elements.fontenc=\\usepackage{xeCJK} -D latex_elements.inputenc='
```

Although I still get warning like `no Babel option known for language 'zh-cn'`, but the PDF looks good.

Could you set these as default for Chinese documents?

xeCJK will use Fandol font as default, both are Tex packages

https://ctan.org/pkg/xecjk
https://ctan.org/pkg/fandol


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398?src=pr&el=h1) Report
> Merging [#6398](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398?src=pr&el=desc) into [2.0](https://codecov.io/gh/sphinx-doc/sphinx/commit/6e795a05beb79a6bc8fa8453f37512b4887c13af?src=pr&el=desc) will **increase** coverage by `0.02%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398/graphs/tree.svg?width=650&token=VFlFSELbkw&height=150&src=pr)](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##              2.0    #6398      +/-   ##
==========================================
+ Coverage   83.62%   83.65%   +0.02%     
==========================================
  Files         282      282              
  Lines       40393    40402       +9     
  Branches     5945     5945              
==========================================
+ Hits        33779    33797      +18     
+ Misses       5282     5276       -6     
+ Partials     1332     1329       -3
```


| [Impacted Files](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [sphinx/writers/latex.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398/diff?src=pr&el=tree#diff-c3BoaW54L3dyaXRlcnMvbGF0ZXgucHk=) | `81.78% <ø> (+0.13%)` | :arrow_up: |
| [tests/test\_build\_latex.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9idWlsZF9sYXRleC5weQ==) | `95.31% <100%> (+0.04%)` | :arrow_up: |
| [sphinx/builders/latex/util.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398/diff?src=pr&el=tree#diff-c3BoaW54L2J1aWxkZXJzL2xhdGV4L3V0aWwucHk=) | `86.48% <100%> (+6.48%)` | :arrow_up: |
| [sphinx/builders/latex/\_\_init\_\_.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398/diff?src=pr&el=tree#diff-c3BoaW54L2J1aWxkZXJzL2xhdGV4L19faW5pdF9fLnB5) | `80.85% <100%> (+1.95%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398?src=pr&el=footer). Last update [6e795a0...7aab14a](https://codecov.io/gh/sphinx-doc/sphinx/pull/6398?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGES b/CHANGES
index 1a6a22341..b95c20fb2 100644
--- a/CHANGES
+++ b/CHANGES
@@ -12,6 +12,7 @@ Incompatible changes
 * #6230: The anchor of term in glossary directive is changed if it is consisted
   by non-ASCII characters
 * #4550: html: Centering tables by default using CSS
+* #6239: latex: xelatex and xeCJK are used for Chinese documents by default
 
 Deprecated
 ----------
@@ -105,6 +106,7 @@ Features added
   full production rule
 * #6373: autosectionlabel: Allow suppression of warnings
 * coverage: Support a new ``coverage_ignore_pyobjects`` option
+* #6239: latex: Support to build Chinese documents
 
 Bugs fixed
 ----------
diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py
index 973e7c67d..e5754ec9b 100644
--- a/sphinx/builders/latex/__init__.py
+++ b/sphinx/builders/latex/__init__.py
@@ -180,12 +180,10 @@ def init_context(self):
         # Add special settings for latex_engine
         self.context.update(ADDITIONAL_SETTINGS.get(self.config.latex_engine, {}))
 
-        # for xelatex+French, don't use polyglossia by default
-        if self.config.latex_engine == 'xelatex':
-            if self.config.language:
-                if self.config.language[:2] == 'fr':
-                    self.context['polyglossia'] = ''
-                    self.context['babel'] = r'\usepackage{babel}'
+        # Add special settings for (latex_engine, language_code)
+        if self.config.language:
+            key = (self.config.latex_engine, self.config.language[:2])
+            self.context.update(ADDITIONAL_SETTINGS.get(key, {}))
 
         # Apply extension settings to context
         self.context['packages'] = self.usepackages
@@ -441,6 +439,8 @@ def default_latex_engine(config):
     """ Better default latex_engine settings for specific languages. """
     if config.language == 'ja':
         return 'platex'
+    elif (config.language or '').startswith('zh'):
+        return 'xelatex'
     else:
         return 'pdflatex'
 
diff --git a/sphinx/builders/latex/util.py b/sphinx/builders/latex/util.py
index 156ea89ad..3aa278496 100644
--- a/sphinx/builders/latex/util.py
+++ b/sphinx/builders/latex/util.py
@@ -47,11 +47,13 @@ def language_name(self, language_code):
             # polyglossia calls new orthography (Neue Rechtschreibung) as
             # german (with new spelling option).
             return 'german'
-        elif not language:
+        elif language:
+            return language
+        elif language_code.startswith('zh'):
+            return 'english'  # fallback to english (behaves like supported)
+        else:
             self.supported = False
             return 'english'  # fallback to english
-        else:
-            return language
 
     def get_mainlanguage_options(self):
         # type: () -> str
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index 820d41b9f..899df04c9 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -216,7 +216,17 @@
         'fncychap':     '',
         'geometry':     '\\usepackage[dvipdfm]{geometry}',
     },
-}  # type: Dict[str, Dict[str, Any]]
+
+    # special settings for latex_engine + language_code
+    ('xelatex', 'fr'): {
+        # use babel instead of polyglossia by default
+        'polyglossia':  '',
+        'babel':        '\\usepackage{babel}',
+    },
+    ('xelatex', 'zh'): {
+        'fontenc':      '\\usepackage{xeCJK}',
+    },
+}  # type: Dict[Any, Dict[str, Any]]
 
 EXTRA_RE = re.compile(r'^(.*\S)\s+\(([^()]*)\)\s*$')
 


Test patch:
diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py
index f02394cf1..45e993501 100644
--- a/tests/test_build_latex.py
+++ b/tests/test_build_latex.py
@@ -165,6 +165,16 @@ def test_latex_basic(app, status, warning):
     assert r'\renewcommand{\releasename}{}' in result
 
 
+@pytest.mark.sphinx('latex', testroot='basic', confoverrides={'language': 'zh'})
+def test_latex_additional_settings_for_language_code(app, status, warning):
+    app.builder.build_all()
+    result = (app.outdir / 'test.tex').text(encoding='utf8')
+    print(result)
+    print(status.getvalue())
+    print(warning.getvalue())
+    assert r'\usepackage{xeCJK}' in result
+
+
 @pytest.mark.sphinx('latex', testroot='latex-title')
 def test_latex_title_after_admonitions(app, status, warning):
     app.builder.build_all()