diff --git a/CHANGES.rst b/CHANGES.rst index 77f4b4b4b1..7adf27d35a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,10 @@ Version 8.4.0 Unreleased +- Allow customizing locales via ``click.set_click_locale`` and ``click.reset_click_locale`` + and querying the active locale via ``click.get_click_locale``. + Click now uses its own gettext domain. :issue:`2706` + - :class:`ParamType` typing improvements. :pr:`3371` - :class:`ParamType` is now a generic abstract base class, diff --git a/docs/contributing.md b/docs/contributing.md index 6ce30ef90b..71dac0b01f 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -42,3 +42,14 @@ external package for it. ## Formatting Wrap lines in Markdown files at 80 characters. + +## Translations + +If you want to work on translations: + +* Install gettext. Windows requires a unix-like environment - see [MinGW gettext](https://gnuwin32.sourceforge.net/packages/gettext.htm). + +* Compile the translations. + ```shell-session + $ bash ./locales/compile.sh + ``` diff --git a/locales/bg_BG/LC_MESSAGES/click.po b/locales/bg_BG/LC_MESSAGES/click.po new file mode 100644 index 0000000000..b37d4d3f22 --- /dev/null +++ b/locales/bg_BG/LC_MESSAGES/click.po @@ -0,0 +1,180 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: bg_BG" +"Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1;" + +msgid "{editor}: Editing failed" +msgstr "{editor}: Редактирането се провалили" + +msgid "{editor}: Editing failed: {e}" +msgstr "{editor}: Редактирането се провалили: {e}" + +msgid "Aborted!" +msgstr "Авария!" + +msgid "Show this message and exit." +msgstr "Покажи това съобщение и излез." + +msgid "Usage:" +msgstr "Използване:" + +msgid "(Deprecated) {text}" +msgstr "(Депрекиран) {text}" + +msgid "Options" +msgstr "Опции" + +msgid "Got unexpected extra argument ({args})" +msgid_plural "Got unexpected extra arguments ({args})" +msgstr[0] "Получен е неочакван допълнителен аргумент ({args})" +msgstr[1] "Получени са неочаквани допълнителни аргументи ({args})" + +msgid "DeprecationWarning: The command {name!r} is deprecated." +msgstr "DeprecationWarning: Командата {name!r} е депрекирана." + +msgid "Commands" +msgstr "Команди" + +msgid "Missing command." +msgstr "Липсва команда." + +msgid "No such command {name!r}." +msgstr "Няма команда {name!r}." + +msgid "Argument {name!r} takes {nargs} values but 1 was given." +msgid_plural "Argument {name!r} takes {nargs} values but {len} were given." +msgstr[0] "Аргументът {name!r} приема {nargs} стойности, но е дадена 1." +msgstr[1] "Аргументът {name!r} приема {nargs} стойности, но са дадени {len}." + +msgid "env var: {var}" +msgstr "променлива на средата: {var}" + +msgid "default: {default}" +msgstr "по подразбиране: {default}" + +msgid "required" +msgstr "задължително" + +msgid "%(prog)s, version %(version)s" +msgstr "%(prog)s, версия %(version)s" + +msgid "Show the version and exit." +msgstr "Покажи версията и излез." + +msgid "Error: {message}" +msgstr "Грешка: {message}" + +msgid "Try '{command} {option}' for help." +msgstr "Опитайте '{command} {option}' за помощ." + +msgid "Invalid value: {message}" +msgstr "Невалидна стойност." + +msgid "Invalid value for {param_hint}: {message}" +msgstr "Невалидна стойност за {param_hint}: {message}" + +msgid "Missing argument." +msgstr "Липсващ аргумент." + +msgid "Missing option." +msgstr "Липсваща опция." + +msgid "Missing parameter." +msgstr "Липсващ параметър." + +msgid "Missing {param_type}." +msgstr "Липсва {param_type}." + +msgid "Missing parameter: {param_name}" +msgstr "Липсващ параметър: {param_name}" + +msgid "No such option: {name}" +msgstr "Няма такава опция: {name}." + +msgid "Did you mean {possibility}?" +msgid_plural "(Possible options: {possibilities})" +msgstr[0] "Имали сте предвид {possibility}?" +msgstr[1] "(Възможни варианти {possibilities})" + +msgid "unknown error" +msgstr "неизвестна грешка" + +msgid "Could not open file {filename!r}: {message}" +msgstr "Не можах да отворя файла {filename!r}: {message}" + +msgid "Argument {name!r} takes {nargs} values." +msgstr "Аргументът {name!r} приема {nargs} стойности." + +msgid "Option {name!r} does not take a value." +msgstr "Опцията {name!r} не приема стойности." + +msgid "Option {name!r} requires an argument." +msgid_plural "Option {name!r} requires {nargs} arguments." +msgstr[0] "Опцията {name!r} изисква аргумент." +msgstr[1] "Опцията {name!r} изисква {nargs} аргумента." + +msgid "Repeat for confirmation" +msgstr "Повторете за потвърждение" + +msgid "Error: The value you entered was invalid." +msgstr "Грешка: Въведената стойност е невалидна." + +msgid "Error: The two entered values do not match." +msgstr "Грешка: Двете въведени стойности не съвпадат." + +msgid "Error: invalid input." +msgstr "Грешка: невалидна въведена стойност." + +msgid "Press any key to continue..." +msgstr "Натиснете произволен клавиш, за да продължите..." + +msgid "Choose from:\n\t{choices}" +msgstr "Изберете сред:\n\t{choices}" + +msgid "{value!r} is not {choice}." +msgid_plural "{value!r} is not one of {choices}." +msgstr[0] "{value!r} не съвпада с {choice}." +msgstr[1] "{value!r} не е сред {choices}." + +msgid "{value!r} does not match the format {format}." +msgid_plural "{value!r} is not one of {choices}." +msgstr[0] "{value!r} няма формат {format}." +msgstr[1] "{value!r} не е сред форматите {formats}." + +msgid "{value!r} is not a valid {number_type}." +msgstr "{value!r} не е валидно {number_type}." + +msgid "{value!r} is not a valid {range}." +msgstr "{value!r} не в диапазона {range}." + +msgid "{value!r} is not a valid boolean." +msgstr "{value!r} не е валидна булева стойност." + +msgid "{value!r} is not a valid UUID." +msgstr "{value!r} не е валидно UUID." + +msgid "file" +msgstr "файл" + +msgid "directory" +msgstr "директория" + +msgid "path" +msgstr "път" + +msgid "{name} {filename!r} does not exist." +msgstr "{name} {filename!r} не съществува." + +msgid "{name} {filename!r} is a file." +msgstr "{name} {filename!r} е файл." + +msgid "{name} {filename!r} is a directory." +msgstr "{name} {filename!r} е директория." + +msgid "{name} {filename!r} is not writable." +msgstr "не е позволено записването в {name} {filename!r}." + +msgid "{name} {filename!r} is not readable." +msgstr "не е позволено четенето от {name} {filename!r}." diff --git a/locales/compile.sh b/locales/compile.sh new file mode 100755 index 0000000000..4f17aa29dd --- /dev/null +++ b/locales/compile.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +for file in locales/*/LC_MESSAGES/click.po; do + locale=$(basename $(dirname $(dirname $file))) + output_dir=src/click/$(dirname $file) + mkdir --parents $output_dir + echo "Building .mo file for ${locale}" + msgfmt --statistics --check-format $file -o $output_dir/click.mo +done diff --git a/locales/en_US/LC_MESSAGES/click.po b/locales/en_US/LC_MESSAGES/click.po new file mode 100644 index 0000000000..b1e9468e20 --- /dev/null +++ b/locales/en_US/LC_MESSAGES/click.po @@ -0,0 +1,6 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en_US" +"Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1;" diff --git a/locales/ru_RU/LC_MESSAGES/click.po b/locales/ru_RU/LC_MESSAGES/click.po new file mode 100644 index 0000000000..5522ab872b --- /dev/null +++ b/locales/ru_RU/LC_MESSAGES/click.po @@ -0,0 +1,180 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ru_RU" +"Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1;" + +msgid "{editor}: Editing failed" +msgstr "{editor}: Редактирование провалилось" + +msgid "{editor}: Editing failed: {e}" +msgstr "{editor}: Редактирование провалилось: {e}" + +msgid "Aborted!" +msgstr "Авария!" + +msgid "Show this message and exit." +msgstr "Показать это сообщение и выйти." + +msgid "Usage:" +msgstr "Использование:" + +msgid "(Deprecated) {text}" +msgstr "(Депрекированый) {text}" + +msgid "Options" +msgstr "Опции" + +msgid "Got unexpected extra argument ({args})" +msgid_plural "Got unexpected extra arguments ({args})" +msgstr[0] "Получен неожиданный дополнительный аргумент ({args})" +msgstr[1] "Получены неожиданные дополнительные аргументы ({args})" + +msgid "DeprecationWarning: The command {name!r} is deprecated." +msgstr "DeprecationWarning: Команда {name!r} является депрекированой." + +msgid "Commands" +msgstr "Команды" + +msgid "Missing command." +msgstr "Отсутствует команда." + +msgid "No such command {name!r}." +msgstr "Нет команды {name!r}." + +msgid "Argument {name!r} takes {nargs} values but 1 was given." +msgid_plural "Argument {name!r} takes {nargs} values but {len} were given." +msgstr[0] "Аргумент {name!r} принимает {nargs} значений, но дано 1." +msgstr[1] "Аргумент {name!r} принимает {nargs} значений, но даны {len}." + +msgid "env var: {var}" +msgstr "переменная среды: {var}" + +msgid "default: {default}" +msgstr "по умолчанию: {default}" + +msgid "required" +msgstr "обязательно" + +msgid "%(prog)s, version %(version)s" +msgstr "%(prog)s, версия %(version)s" + +msgid "Show the version and exit." +msgstr "Показать версию и выйти." + +msgid "Error: {message}" +msgstr "Ошибка: {message}" + +msgid "Try '{command} {option}' for help." +msgstr "Попробуйте '{command} {option}' для помощи." + +msgid "Invalid value: {message}" +msgstr "Невалидное значение." + +msgid "Invalid value for {param_hint}: {message}" +msgstr "Невалидное значение для {param_hint}: {message}" + +msgid "Missing argument." +msgstr "Отсутствующий аргумент." + +msgid "Missing option." +msgstr "Отсутствующая опция." + +msgid "Missing parameter." +msgstr "Отсутствующий параметр." + +msgid "Missing {param_type}." +msgstr "Отсутствует {param_type}." + +msgid "Missing parameter: {param_name}" +msgstr "Отсутствующий параметр: {param_name}" + +msgid "No such option: {name}" +msgstr "Нет такой опции: {name}." + +msgid "Did you mean {possibility}?" +msgid_plural "(Possible options: {possibilities})" +msgstr[0] "Вы имели ввиду {possibility}?" +msgstr[1] "(Возможные варианты {possibilities})" + +msgid "unknown error" +msgstr "неизвестная ошибка" + +msgid "Could not open file {filename!r}: {message}" +msgstr "Не смог открыть файл {filename!r}: {message}" + +msgid "Argument {name!r} takes {nargs} values." +msgstr "Аргумент {name!r} принимает {nargs} значений." + +msgid "Option {name!r} does not take a value." +msgstr "Опция {name!r} не принимает значений." + +msgid "Option {name!r} requires an argument." +msgid_plural "Option {name!r} requires {nargs} arguments." +msgstr[0] "Опция {name!r} требует аргумент." +msgstr[1] "Опция {name!r} требует {nargs} аргументов." + +msgid "Repeat for confirmation" +msgstr "Повторите для подтверждения" + +msgid "Error: The value you entered was invalid." +msgstr "Ошибка: Введённое значение является невалидным." + +msgid "Error: The two entered values do not match." +msgstr "Ошибка: Два введённых значения не совпадают." + +msgid "Error: invalid input." +msgstr "Ошибка: невалидный ввод." + +msgid "Press any key to continue..." +msgstr "Для продолжения нажмите любую кнопку..." + +msgid "Choose from:\n\t{choices}" +msgstr "Выберите среди:\n\t{choices}" + +msgid "{value!r} is not {choice}." +msgid_plural "{value!r} is not one of {choices}." +msgstr[0] "{value!r} не совпадает с {choice}." +msgstr[1] "{value!r} не среди {choices}." + +msgid "{value!r} does not match the format {format}." +msgid_plural "{value!r} is not one of {choices}." +msgstr[0] "{value!r} не в формате {format}." +msgstr[1] "{value!r} ни в одном из форматов {formats}." + +msgid "{value!r} is not a valid {number_type}." +msgstr "{value!r} является невалидным {number_type}." + +msgid "{value!r} is not a valid {range}." +msgstr "{value!r} не в диапазоне {range}." + +msgid "{value!r} is not a valid boolean." +msgstr "{value!r} является невалидным булевым значением." + +msgid "{value!r} is not a valid UUID." +msgstr "{value!r} является невалидным UUID." + +msgid "file" +msgstr "файл" + +msgid "directory" +msgstr "директория" + +msgid "path" +msgstr "путь" + +msgid "{name} {filename!r} does not exist." +msgstr "{name} {filename!r} не существует." + +msgid "{name} {filename!r} is a file." +msgstr "{name} {filename!r} файл." + +msgid "{name} {filename!r} is a directory." +msgstr "{name} {filename!r} директория." + +msgid "{name} {filename!r} is not writable." +msgstr "не позволено записывать в {name} {filename!r}." + +msgid "{name} {filename!r} is not readable." +msgstr "не позволено читать {name} {filename!r}." diff --git a/pyproject.toml b/pyproject.toml index 5a0e37d048..56da00ef1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,7 @@ name = "click" include = [ "docs/", "tests/", + "locales/", "CHANGES.rst", "uv.lock" ] diff --git a/src/click/__init__.py b/src/click/__init__.py index 64be7e0c3c..a476d5474a 100644 --- a/src/click/__init__.py +++ b/src/click/__init__.py @@ -39,6 +39,9 @@ from .formatting import HelpFormatter as HelpFormatter from .formatting import wrap_text as wrap_text from .globals import get_current_context as get_current_context +from .locales import get_click_locale as get_click_locale +from .locales import reset_click_locale as reset_click_locale +from .locales import set_click_locale as set_click_locale from .termui import clear as clear from .termui import confirm as confirm from .termui import echo_via_pager as echo_via_pager diff --git a/src/click/_termui_impl.py b/src/click/_termui_impl.py index 219fbaa1f7..ca215b2e81 100644 --- a/src/click/_termui_impl.py +++ b/src/click/_termui_impl.py @@ -15,7 +15,6 @@ import sys import time import typing as t -from gettext import gettext as _ from io import StringIO from pathlib import Path from types import TracebackType @@ -28,6 +27,7 @@ from ._compat import term_len from ._compat import WIN from .exceptions import ClickException +from .locales import gettext as _ from .utils import echo V = t.TypeVar("V") diff --git a/src/click/core.py b/src/click/core.py index c5cab15c07..a66ff95418 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -15,8 +15,6 @@ from contextlib import contextmanager from contextlib import ExitStack from functools import update_wrapper -from gettext import gettext as _ -from gettext import ngettext from itertools import repeat from types import TracebackType @@ -35,6 +33,8 @@ from .formatting import join_options from .globals import pop_context from .globals import push_context +from .locales import gettext as _ +from .locales import ngettext from .parser import _OptionParser from .parser import _split_opt from .termui import confirm diff --git a/src/click/decorators.py b/src/click/decorators.py index 14aee42ea4..3737f98656 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -3,7 +3,6 @@ import inspect import typing as t from functools import update_wrapper -from gettext import gettext as _ from .core import Argument from .core import Command @@ -12,6 +11,7 @@ from .core import Option from .core import Parameter from .globals import get_current_context +from .locales import gettext as _ from .utils import echo if t.TYPE_CHECKING: diff --git a/src/click/exceptions.py b/src/click/exceptions.py index 4914d9cfe2..6436eb5aee 100644 --- a/src/click/exceptions.py +++ b/src/click/exceptions.py @@ -2,11 +2,11 @@ import collections.abc as cabc import typing as t -from gettext import gettext as _ -from gettext import ngettext from ._compat import get_text_stderr from .globals import resolve_color_default +from .locales import gettext as _ +from .locales import ngettext from .utils import echo from .utils import format_filename diff --git a/src/click/formatting.py b/src/click/formatting.py index de2ca47117..b9ba767fe3 100644 --- a/src/click/formatting.py +++ b/src/click/formatting.py @@ -2,9 +2,9 @@ import collections.abc as cabc from contextlib import contextmanager -from gettext import gettext as _ from ._compat import term_len +from .locales import gettext as _ from .parser import _split_opt # Can force a width. This is used by the test system diff --git a/src/click/locales.py b/src/click/locales.py new file mode 100644 index 0000000000..9c65ecbff1 --- /dev/null +++ b/src/click/locales.py @@ -0,0 +1,59 @@ +import os +from gettext import NullTranslations +from gettext import translation as build_translations_object + +_translations: NullTranslations = NullTranslations() +_active_locale: str | None = None + + +LOCALE_ROOT_PATH = os.path.join(os.path.dirname(__file__), "locales") + + +def reset_click_locale() -> None: + """Reset the active locale.""" + + global _active_locale + global _translations + _active_locale = None + _translations = NullTranslations() + + +def set_click_locale(target_locale: str) -> None: + """Set the locale. If the locale is unrecognized, a NotImplementedError is raised. + + :param target_locale: The name of the locale, e.g. en_US + """ + global _active_locale + global _translations + + try: + _translations = build_translations_object( + "click", LOCALE_ROOT_PATH, [target_locale] + ) + except FileNotFoundError: + raise NotImplementedError(f"Unrecognized locale {target_locale}") from None + else: + _active_locale = target_locale + + +def get_click_locale() -> str | None: + """Get the active locale.""" + return _active_locale + + +def gettext(message: str) -> str: + """Wrapper around the gettext.gettext function that respects click's active locale. + + :param message: The message id to translate. + """ + return _translations.gettext(message) + + +def ngettext(msgid1: str, msgid2: str, n: int) -> str: + """Wrapper around the gettext.ngettext function that respects click's active locale. + + :param msgid1: Singular form of the message id. + :param msgid2: Plural form of the message id. + :n: The number used to determine the form. + """ + return _translations.ngettext(msgid1, msgid2, n) diff --git a/src/click/locales/bg_BG/LC_MESSAGES/click.mo b/src/click/locales/bg_BG/LC_MESSAGES/click.mo new file mode 100644 index 0000000000..6b8baab224 Binary files /dev/null and b/src/click/locales/bg_BG/LC_MESSAGES/click.mo differ diff --git a/src/click/locales/en_US/LC_MESSAGES/click.mo b/src/click/locales/en_US/LC_MESSAGES/click.mo new file mode 100644 index 0000000000..5837205c0c Binary files /dev/null and b/src/click/locales/en_US/LC_MESSAGES/click.mo differ diff --git a/src/click/locales/ru_RU/LC_MESSAGES/click.mo b/src/click/locales/ru_RU/LC_MESSAGES/click.mo new file mode 100644 index 0000000000..09820c20cf Binary files /dev/null and b/src/click/locales/ru_RU/LC_MESSAGES/click.mo differ diff --git a/src/click/parser.py b/src/click/parser.py index 4fcbf7caa8..33eb6a2c4b 100644 --- a/src/click/parser.py +++ b/src/click/parser.py @@ -27,8 +27,6 @@ import collections.abc as cabc import typing as t from collections import deque -from gettext import gettext as _ -from gettext import ngettext from ._utils import FLAG_NEEDS_VALUE from ._utils import UNSET @@ -36,6 +34,8 @@ from .exceptions import BadOptionUsage from .exceptions import NoSuchOption from .exceptions import UsageError +from .locales import gettext as _ +from .locales import ngettext if t.TYPE_CHECKING: from ._utils import T_FLAG_NEEDS_VALUE diff --git a/src/click/shell_completion.py b/src/click/shell_completion.py index 5a89b64ec8..fe7af80da7 100644 --- a/src/click/shell_completion.py +++ b/src/click/shell_completion.py @@ -4,7 +4,6 @@ import os import re import typing as t -from gettext import gettext as _ from .core import Argument from .core import Command @@ -13,6 +12,7 @@ from .core import Option from .core import Parameter from .core import ParameterSource +from .locales import gettext as _ from .utils import echo diff --git a/src/click/termui.py b/src/click/termui.py index 08d732895e..47b7b727f7 100644 --- a/src/click/termui.py +++ b/src/click/termui.py @@ -8,7 +8,6 @@ import typing as t from contextlib import AbstractContextManager from contextlib import redirect_stdout -from gettext import gettext as _ from ._compat import isatty from ._compat import strip_ansi @@ -16,6 +15,7 @@ from .exceptions import Abort from .exceptions import UsageError from .globals import resolve_color_default +from .locales import gettext as _ from .types import Choice from .types import convert_type from .types import ParamType @@ -205,7 +205,7 @@ def prompt_func(text: str) -> str: if hide_input: echo(_("Error: The value you entered was invalid."), err=err) else: - echo(_("Error: {e.message}").format(e=e), err=err) + echo(_("Error: {message}").format(message=e.message), err=err) continue if not confirmation_prompt: return result diff --git a/src/click/testing.py b/src/click/testing.py index 04e7f1d925..f0eb334e02 100644 --- a/src/click/testing.py +++ b/src/click/testing.py @@ -13,6 +13,7 @@ from . import _compat from . import formatting +from . import locales from . import termui from . import utils from ._compat import _find_binary_reader @@ -667,3 +668,16 @@ def isolated_filesystem( shutil.rmtree(dt) except OSError: pass + + +@contextlib.contextmanager +def ContextualizedLocale(target_locale: str) -> cabc.Iterator[None]: + old_locale = locales.get_click_locale() + locales.set_click_locale(target_locale) + + yield + + if old_locale is None: + locales.reset_click_locale() + else: + locales.set_click_locale(old_locale) diff --git a/src/click/types.py b/src/click/types.py index 556f20f2b8..7b49f5972a 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -9,12 +9,12 @@ import typing as t import uuid from datetime import datetime -from gettext import gettext as _ -from gettext import ngettext from ._compat import _get_argv_encoding from ._compat import open_stream from .exceptions import BadParameter +from .locales import gettext as _ +from .locales import ngettext from .utils import format_filename from .utils import LazyFile from .utils import safecall diff --git a/tests/test_locales.py b/tests/test_locales.py new file mode 100644 index 0000000000..b891bfc46b --- /dev/null +++ b/tests/test_locales.py @@ -0,0 +1,75 @@ +from textwrap import dedent + +import pytest + +import click +from click.locales import set_click_locale +from click.testing import CliRunner +from click.testing import ContextualizedLocale + + +def test_test_unrecognized_locale(runner: CliRunner) -> None: + @click.command() + def cli() -> None: + pass + + with pytest.raises(NotImplementedError): + set_click_locale("unrecognized") + + +def test_help_translation_to_russian(runner: CliRunner) -> None: + @click.command() + def cli() -> None: + pass + + with ContextualizedLocale("ru_RU"): + result = runner.invoke(cli, ["--help"]) + + assert result.stdout == dedent("""\ + Использование: cli [OPTIONS] + + Опции: + --help Показать это сообщение и выйти. + """) + + +def test_help_translation_to_bulgarian(runner: CliRunner) -> None: + @click.command() + def cli() -> None: + pass + + with ContextualizedLocale("bg_BG"): + result = runner.invoke(cli, ["--help"]) + + assert result.stdout == dedent("""\ + Използване: cli [OPTIONS] + + Опции: + --help Покажи това съобщение и излез. + """) + + +def test_dynamic_change_of_language(runner: CliRunner) -> None: + @click.command() + def cli() -> None: + pass + + with ContextualizedLocale("en_US"): + result = runner.invoke(cli, ["--help"]) + + assert result.stdout == dedent("""\ + Usage: cli [OPTIONS] + + Options: + --help Show this message and exit. + """) + + with ContextualizedLocale("ru_RU"): + result = runner.invoke(cli, ["--help"]) + + assert result.stdout == dedent("""\ + Использование: cli [OPTIONS] + + Опции: + --help Показать это сообщение и выйти. + """)