1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
"""ft_gettext.py
The usual pattern when using gettext is to surround strings to be translated
by a call to a function named _, as in
_("This string should be translated.")
This is done when having gettext "install" a given language: it adds a function
named _ to the global builtins. However, this can fail if some other program,
such as the Python REPL, also modifies the builtins and define _ in its own
ways.
To avoid such problems, we use a custom class that keep track of the language
preferred for the translation. Inside any namespace, when we need to
provide a translation, we define locally _ to be
_ = current_lang.translate
where current_lang.translate means gettext.translation().gettext where
gettext.translation() is the class-based API for gettext.
"""
import gettext
import os
from typing import Optional
from . import debug_helper
from .typing_info import Translator
class LangState:
def __init__(self) -> None:
self._translate: Translator = lambda text: text
self.lang = "en"
def install(self, lang: Optional[str] = None) -> None:
"""Sets the language to be used for translations"""
if lang is None:
lang = "en"
try:
# We first look for the exact language requested.
_lang = gettext.translation(
f"friendly_tb_{lang}",
localedir=os.path.normpath(
os.path.join(os.path.dirname(__file__), "locales")
),
languages=[lang],
fallback=False,
)
except FileNotFoundError:
# If it is not available, we make it possible to replace a
# language specific to a region, as in fr_CA, by a more
# generic version, such as fr, defined by a two-letter code.
lang = lang[:2]
_lang = gettext.translation(
f"friendly_tb_{lang}",
localedir=os.path.normpath(
os.path.join(os.path.dirname(__file__), "locales")
),
languages=[lang],
fallback=True, # This means that the hard-coded strings in
# the source file will be used if the requested language
# is not available.
)
self.lang = lang
self._translate = _lang.gettext
def translate(self, text: str) -> str:
translation = self._translate(text)
if translation == text and self.lang == "fr": # pragma: no cover
debug_helper.log(f"Potentially untranslated text for {self.lang}:")
debug_helper.log(text)
return translation
current_lang = LangState() # noqa
_ = current_lang.translate
def please_report() -> str:
return _(
"Please report this example to\n"
"https://github.com/friendly-traceback/friendly-traceback/issues/new\n"
"If you are using a REPL, use `www('bug')` to do so.\n\n"
)
def unknown_case() -> str:
return _("Friendly-traceback does not know the cause of this error.\n")
def no_information() -> str:
debug_helper.log("New case to consider.")
return (
_("No information is known about this exception.\n")
+ please_report()
+ _(
"If you are using the Friendly console, use `www()` to\n"
"do an Internet search for this particular case.\n"
)
)
def internal_error(e: Optional[BaseException]) -> str:
debug_helper.log(f"--> Internal error: {repr(e)}")
return _("Internal error for Friendly.\n") + please_report()
|