diff --git a/CHANGES b/CHANGES index ec7707a5ef0..ab49ff15503 100644 --- a/CHANGES +++ b/CHANGES @@ -21,12 +21,18 @@ Incompatible changes intersphinx should resolve, or explicitly set the value of this configuration variable to an empty list. * #9999: LaTeX: separate terms from their definitions by a CR (refs: #9985) +* #10062: Change the default language to ``'en'`` if any language is not set in + ``conf.py`` Deprecated ---------- * setuptools integration. The ``build_sphinx`` sub-command for setup.py is marked as deprecated to follow the policy of setuptools team. +* The ``locale`` argument of ``sphinx.util.i18n:babel_format_date()`` becomes + required +* The ``language`` argument of ``sphinx.util.i18n:format_date()`` becomes + required * ``sphinx.writers.latex.LaTeXWriter.docclasses`` Features added diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 159c6d8b68a..1d3ad693087 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -27,6 +27,16 @@ The following is a list of deprecated interfaces. - 7.0 - N/A + * - The ``locale`` argument of ``sphinx.util.i18n:babel_format_date()`` + - 5.0 + - 7.0 + - N/A + + * - The ``language`` argument of ``sphinx.util.i18n:format_date()`` + - 5.0 + - 7.0 + - N/A + * - ``sphinx.writers.latex.LaTeXWriter.docclasses`` - 5.0 - 7.0 diff --git a/sphinx/application.py b/sphinx/application.py index fd8b65b6ef6..103ebb6340c 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -266,7 +266,7 @@ def _init_i18n(self) -> None: """Load translated strings from the configured localedirs if enabled in the configuration. """ - if self.config.language is None: + if self.config.language == 'en': self.translator, has_translation = locale.init([], None) else: logger.info(bold(__('loading translations [%s]... ') % self.config.language), @@ -285,8 +285,7 @@ def _init_i18n(self) -> None: locale_dirs += [path.join(package_dir, 'locale')] self.translator, has_translation = locale.init(locale_dirs, self.config.language) - if has_translation or self.config.language == 'en': - # "en" never needs to be translated + if has_translation: logger.info(__('done')) else: logger.info(__('not available for built-in messages')) diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index ac1fbbaec9d..674b5147f0e 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -326,7 +326,7 @@ def init_js_files(self) -> None: attrs.setdefault('priority', 800) # User's JSs are loaded after extensions' self.add_js_file(filename, **attrs) - if self.config.language and self._get_translations_js(): + if self._get_translations_js(): self.add_js_file('translations.js') def add_js_file(self, filename: str, **kwargs: Any) -> None: @@ -431,8 +431,6 @@ def prepare_writing(self, docnames: Set[str]) -> None: if self.search: from sphinx.search import IndexBuilder lang = self.config.html_search_language or self.config.language - if not lang: - lang = 'en' self.indexer = IndexBuilder(self.env, lang, self.config.html_search_options, self.config.html_search_scorer) @@ -767,10 +765,9 @@ def create_pygments_style_file(self) -> None: def copy_translation_js(self) -> None: """Copy a JavaScript file for translations.""" - if self.config.language is not None: - jsfile = self._get_translations_js() - if jsfile: - copyfile(jsfile, path.join(self.outdir, '_static', 'translations.js')) + jsfile = self._get_translations_js() + if jsfile: + copyfile(jsfile, path.join(self.outdir, '_static', 'translations.js')) def copy_stemmer_js(self) -> None: """Copy a JavaScript file for stemmer.""" diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 139973c3241..ccb92f47e6a 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -170,9 +170,8 @@ def init_context(self) -> None: self.context.update(ADDITIONAL_SETTINGS.get(self.config.latex_engine, {})) # 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, {})) + key = (self.config.latex_engine, self.config.language[:2]) + self.context.update(ADDITIONAL_SETTINGS.get(key, {})) # Apply user settings to context self.context.update(self.config.latex_elements) @@ -203,7 +202,7 @@ def update_context(self) -> None: def init_babel(self) -> None: self.babel = ExtBabel(self.config.language, not self.context['babel']) - if self.config.language and not self.babel.is_supported_language(): + if not self.babel.is_supported_language(): # emit warning if specified language is invalid # (only emitting, nothing changed to processing) logger.warning(__('no Babel option known for language %r'), @@ -232,12 +231,11 @@ def init_multilingual(self) -> None: self.context['classoptions'] += ',' + self.babel.get_language() # this branch is not taken for xelatex/lualatex if default settings self.context['multilingual'] = self.context['babel'] - if self.config.language: - self.context['shorthandoff'] = SHORTHANDOFF + self.context['shorthandoff'] = SHORTHANDOFF - # Times fonts don't work with Cyrillic languages - if self.babel.uses_cyrillic() and 'fontpkg' not in self.config.latex_elements: - self.context['fontpkg'] = '' + # Times fonts don't work with Cyrillic languages + if self.babel.uses_cyrillic() and 'fontpkg' not in self.config.latex_elements: + self.context['fontpkg'] = '' elif self.context['polyglossia']: self.context['classoptions'] += ',' + self.babel.get_language() options = self.babel.get_mainlanguage_options() @@ -380,14 +378,10 @@ def copy_support_files(self) -> None: # configure usage of xindy (impacts Makefile and latexmkrc) # FIXME: convert this rather to a confval with suitable default # according to language ? but would require extra documentation - if self.config.language: - xindy_lang_option = \ - XINDY_LANG_OPTIONS.get(self.config.language[:2], - '-L general -C utf8 ') - xindy_cyrillic = self.config.language[:2] in XINDY_CYRILLIC_SCRIPTS - else: - xindy_lang_option = '-L english -C utf8 ' - xindy_cyrillic = False + xindy_lang_option = XINDY_LANG_OPTIONS.get(self.config.language[:2], + '-L general -C utf8 ') + xindy_cyrillic = self.config.language[:2] in XINDY_CYRILLIC_SCRIPTS + context = { 'latex_engine': self.config.latex_engine, 'xindy_use': self.config.latex_use_xindy, @@ -474,7 +468,7 @@ def default_latex_engine(config: Config) -> str: """ Better default latex_engine settings for specific languages. """ if config.language == 'ja': return 'uplatex' - elif (config.language or '').startswith('zh'): + elif config.language.startswith('zh'): return 'xelatex' elif config.language == 'el': return 'xelatex' diff --git a/sphinx/builders/latex/util.py b/sphinx/builders/latex/util.py index 6b797c9a248..4a5c390c3bf 100644 --- a/sphinx/builders/latex/util.py +++ b/sphinx/builders/latex/util.py @@ -20,7 +20,7 @@ def __init__(self, language_code: str, use_polyglossia: bool = False) -> None: self.language_code = language_code self.use_polyglossia = use_polyglossia self.supported = True - super().__init__(language_code or '') + super().__init__(language_code) def uses_cyrillic(self) -> bool: return self.language in self.cyrillic_languages diff --git a/sphinx/config.py b/sphinx/config.py index 38ed1d38882..6ee036ebb30 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -100,7 +100,7 @@ class Config: # the real default is locale-dependent 'today_fmt': (None, 'env', [str]), - 'language': (None, 'env', [str]), + 'language': ('en', 'env', [str]), 'locale_dirs': (['locales'], 'env', []), 'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]), 'gettext_allow_fuzzy_translations': (False, 'gettext', []), diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 9dde7b407d8..fa1b6e3689d 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -261,7 +261,7 @@ def _update_settings(self, config: Config) -> None: """Update settings by new config.""" self.settings['input_encoding'] = config.source_encoding self.settings['trim_footnote_reference_space'] = config.trim_footnote_reference_space - self.settings['language_code'] = config.language or 'en' + self.settings['language_code'] = config.language # Allow to disable by 3rd party extension (workaround) self.settings.setdefault('smart_quotes', True) diff --git a/sphinx/environment/collectors/asset.py b/sphinx/environment/collectors/asset.py index 9530b3bd94d..74a183b8782 100644 --- a/sphinx/environment/collectors/asset.py +++ b/sphinx/environment/collectors/asset.py @@ -64,18 +64,16 @@ def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: rel_imgpath, full_imgpath = app.env.relfn2path(imguri, docname) node['uri'] = rel_imgpath - if app.config.language: - # Search language-specific figures at first - i18n_imguri = get_image_filename_for_language(imguri, app.env) - _, full_i18n_imgpath = app.env.relfn2path(i18n_imguri, docname) - self.collect_candidates(app.env, full_i18n_imgpath, candidates, node) + # Search language-specific figures at first + i18n_imguri = get_image_filename_for_language(imguri, app.env) + _, full_i18n_imgpath = app.env.relfn2path(i18n_imguri, docname) + self.collect_candidates(app.env, full_i18n_imgpath, candidates, node) self.collect_candidates(app.env, full_imgpath, candidates, node) else: - if app.config.language: - # substitute imguri by figure_language_filename - # (ex. foo.png -> foo.en.png) - imguri = search_image_for_language(imguri, app.env) + # substitute imguri by figure_language_filename + # (ex. foo.png -> foo.en.png) + imguri = search_image_for_language(imguri, app.env) # Update `node['uri']` to a relative path from srcdir # from a relative path from current document. diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 511a9abee97..db94786c239 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -10,14 +10,16 @@ import os import re +import warnings from datetime import datetime, timezone from os import path -from typing import TYPE_CHECKING, Callable, Generator, List, NamedTuple, Optional, Tuple, Union +from typing import TYPE_CHECKING, Callable, Generator, List, NamedTuple, Tuple, Union import babel.dates from babel.messages.mofile import write_mo from babel.messages.pofile import read_po +from sphinx.deprecation import RemovedInSphinx70Warning from sphinx.errors import SphinxError from sphinx.locale import __ from sphinx.util import logging @@ -173,9 +175,11 @@ def docname_to_domain(docname: str, compaction: Union[bool, str]) -> str: date_format_re = re.compile('(%s)' % '|'.join(date_format_mappings)) -def babel_format_date(date: datetime, format: str, locale: Optional[str], +def babel_format_date(date: datetime, format: str, locale: str, formatter: Callable = babel.dates.format_date) -> str: if locale is None: + warnings.warn('The locale argument for babel_format_date() becomes required.', + RemovedInSphinx70Warning) locale = 'en' # Check if we have the tzinfo attribute. If not we cannot do any time @@ -194,7 +198,7 @@ def babel_format_date(date: datetime, format: str, locale: Optional[str], return format -def format_date(format: str, date: datetime = None, language: Optional[str] = None) -> str: +def format_date(format: str, date: datetime = None, language: str = None) -> str: if date is None: # If time is not specified, try to use $SOURCE_DATE_EPOCH variable # See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal @@ -204,6 +208,11 @@ def format_date(format: str, date: datetime = None, language: Optional[str] = No else: date = datetime.now(timezone.utc).astimezone() + if language is None: + warnings.warn('The language argument for format_date() becomes required.', + RemovedInSphinx70Warning) + language = 'en' + result = [] tokens = date_format_re.split(format) for token in tokens: @@ -229,9 +238,6 @@ def format_date(format: str, date: datetime = None, language: Optional[str] = No def get_image_filename_for_language(filename: str, env: "BuildEnvironment") -> str: - if not env.config.language: - return filename - filename_format = env.config.figure_language_filename d = dict() d['root'], d['ext'] = path.splitext(filename) @@ -252,9 +258,6 @@ def get_image_filename_for_language(filename: str, env: "BuildEnvironment") -> s def search_image_for_language(filename: str, env: "BuildEnvironment") -> str: - if not env.config.language: - return filename - translated = get_image_filename_for_language(filename, env) _, abspath = env.relfn2path(translated) if path.exists(abspath): diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 1063392dd42..3037c8fa4a8 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -333,7 +333,7 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder", if self.config.numfig and self.config.math_numfig: sphinxpkgoptions.append('mathnumfig') - if (self.config.language not in {None, 'en', 'ja'} and + if (self.config.language not in {'en', 'ja'} and 'fncychap' not in self.config.latex_elements): # use Sonny style if any language specified (except English) self.elements['fncychap'] = (r'\usepackage[Sonny]{fncychap}' + CR + @@ -341,7 +341,7 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder", r'\ChTitleVar{\Large\normalfont\sffamily}') self.babel = self.builder.babel - if self.config.language and not self.babel.is_supported_language(): + if not self.babel.is_supported_language(): # emit warning if specified language is invalid # (only emitting, nothing changed to processing) logger.warning(__('no Babel option known for language %r'), diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 2c686cda731..1c3a8afeb10 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -528,7 +528,7 @@ def test_babel_with_no_language_settings(app, status, warning): assert '\\usepackage[Bjarne]{fncychap}' in result assert ('\\addto\\captionsenglish{\\renewcommand{\\contentsname}{Table of content}}\n' in result) - assert '\\shorthandoff' not in result + assert '\\shorthandoff{"}' in result # sphinxmessages.sty result = (app.outdir / 'sphinxmessages.sty').read_text() diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py index 3f4e8d52a80..49a7067f534 100644 --- a/tests/test_util_i18n.py +++ b/tests/test_util_i18n.py @@ -98,15 +98,6 @@ def test_format_date(): def test_get_filename_for_language(app): app.env.temp_data['docname'] = 'index' - # language is None - app.env.config.language = None - assert app.env.config.language is None - assert i18n.get_image_filename_for_language('foo.png', app.env) == 'foo.png' - assert i18n.get_image_filename_for_language('foo.bar.png', app.env) == 'foo.bar.png' - assert i18n.get_image_filename_for_language('subdir/foo.png', app.env) == 'subdir/foo.png' - assert i18n.get_image_filename_for_language('../foo.png', app.env) == '../foo.png' - assert i18n.get_image_filename_for_language('foo', app.env) == 'foo' - # language is en app.env.config.language = 'en' assert i18n.get_image_filename_for_language('foo.png', app.env) == 'foo.en.png' @@ -115,15 +106,6 @@ def test_get_filename_for_language(app): assert i18n.get_image_filename_for_language('../foo.png', app.env) == '../foo.en.png' assert i18n.get_image_filename_for_language('foo', app.env) == 'foo.en' - # modify figure_language_filename and language is None - app.env.config.language = None - app.env.config.figure_language_filename = 'images/{language}/{root}{ext}' - assert i18n.get_image_filename_for_language('foo.png', app.env) == 'foo.png' - assert i18n.get_image_filename_for_language('foo.bar.png', app.env) == 'foo.bar.png' - assert i18n.get_image_filename_for_language('subdir/foo.png', app.env) == 'subdir/foo.png' - assert i18n.get_image_filename_for_language('../foo.png', app.env) == '../foo.png' - assert i18n.get_image_filename_for_language('foo', app.env) == 'foo' - # modify figure_language_filename and language is 'en' app.env.config.language = 'en' app.env.config.figure_language_filename = 'images/{language}/{root}{ext}'