tkmsgcat
Create multilingual interfaces for your tkinter applications.
tkinter-msgcat leverages Tk's msgcat to provide a per-instance message catalog which holds all the translations, while allowing them to be kept in separate files away from code.
Example use:
>>> from tkmsgcat import load, locale, translate
>>> load("msgs")
>>> locale = "hi"
>>> translate("Hello")
"नमस्ते"
Complete docs available on https://tkmsgcat.rtfd.io.
MessageCatalog
Override this to create custom msgcat functionality.
Most applications should suffice with the package-level functions.
Scope
Tkinter's message catalog is scoped to a tkinter.Tk
instance and
not the Python interpreter! In practice, this means that you need to
have a single tkinter.Tk
instance to share the loaded translations
and locales.
Source code in tkmsgcat/__init__.py
class MessageCatalog:
"""Override this to create custom msgcat functionality.
Most applications should suffice with the package-level functions.
Caution: Scope
Tkinter's message catalog is scoped to a `tkinter.Tk` instance and
not the Python interpreter! In practice, this means that you need to
have a single `tkinter.Tk` instance to share the loaded translations
and locales.
"""
@property
def root(self) -> tk.Tk:
return cast(tk.Tk, _get_default_root(what="use msgcat"))
def eval_(self, cmd: str) -> str:
log.debug("Evaluating %s", cmd)
return self.root.eval(cmd)
def _splitlist(self, __s: str) -> tuple[str]:
# pylint: disable=deprecated-typing-alias
return cast(Tuple[str], self.root.splitlist(__s))
@staticmethod
def _join(*__l: str) -> str:
new_l = []
for i in __l:
stripped = i.strip('"')
new_l.append(f'"{stripped}"')
return " ".join(new_l)
@staticmethod
def _dict2str(__d: dict[str, str]) -> str:
lst = []
for key, val in __d.items():
# Append quotes to support strings with spaces
s_key = key.strip('"')
s_val = val.strip('"')
key = f'"{s_key}"'
val = f'"{s_val}"'
lst.extend([key, val])
return " ".join(lst)
@contextlib.contextmanager
def _locale_ctx(self, newlocale: str) -> Iterator[None]:
oldlc = self.locale
self.locale = newlocale
try:
yield None
finally:
self.locale = oldlc
def is_init(self) -> bool:
tkbool = self.eval_("::msgcat::mcpackageconfig isset mcfolder")
return cast(bool, self.root.getboolean(tkbool))
def is_loaded(self, locale_: str) -> bool:
tklist = self.eval_("::msgcat::mcloadedlocales loaded")
return locale_ in self._splitlist(tklist)
@overload
def load(self, dir_: str) -> None:
... # pragma: no cover
@overload
def load(self, dir_: pathlib.Path) -> None:
... # pragma: no cover
def load(self, dir_: str | pathlib.Path) -> None:
_path = dir_ if isinstance(dir_, pathlib.Path) else pathlib.Path(dir_)
_resolvedpath = _path.resolve()
# ! Tk bug: All backslashes need to be replaced by formward slashes
msgsdir = str(_resolvedpath).replace("\\", "/")
log.debug("Loading translations from %s", msgsdir)
self.eval_(
f'::msgcat::mcload [file join [file dirname [info script]] "{msgsdir}"]'
)
@property
def locale(self) -> str:
return self.eval_("::msgcat::mclocale")
@locale.setter
def locale(self, newlocale: str) -> None:
self.eval_(f"::msgcat::mclocale {newlocale}")
@property
def loaded_locales(self) -> tuple[str]:
tklist = self.eval_("::msgcat::mcloadedlocales loaded")
return self._splitlist(tklist)
loaded_from = _PackageOption("mcfolder")
def longest(self, strings: tuple[str]) -> int:
return int(self.eval_(f"::msgcat::mcmax {self._join(*strings)}"))
def longest_in(self, locale_: str, strings: tuple[str]) -> int:
with self._locale_ctx(locale_):
return self.longest(strings)
@property
def preferences(self) -> tuple[str]:
tklist = self.eval_("::msgcat::mcpreferences")
return self._splitlist(tklist)
def has(self, what: str, search_all: bool = True) -> bool:
command = "::msgcat::mcexists"
if not search_all:
command += " -exactlocale"
command += f" {what}"
return cast(bool, self.root.getboolean(self.eval_(command)))
def add(self, what: str, translation: str) -> None:
self.eval_(f'::msgcat::mcset {self.locale} "{what}" "{translation}"')
def add_to(self, locale_: str, what: str, translation: str) -> None:
self.eval_(f'::msgcat::mcset {locale_} "{what}" "{translation}"')
def update(self, translations: dict[str, str]) -> None:
self.eval_(
f"::msgcat::mcmset {self.locale} {{{self._dict2str(translations)}}}",
)
def update_to(self, locale_: str, translations: dict[str, str]) -> None:
self.eval_(f"::msgcat::mcmset {locale_} {{{self._dict2str(translations)}}}")
def get(self, what: str, *fmtargs: str) -> str:
command = f'::msgcat::mc "{what}"'
if fmtargs:
command = command + " " + self._join(*fmtargs)
return self.eval_(command)
def get_from(self, locale_: str, what: str, *fmtargs: str) -> str:
with self._locale_ctx(locale_):
return self.get(what, *fmtargs)
# TODO Doesn't work
# locale_handler = _Handler("changecmd")
missing_handler = _PackageOption("unknowncmd")
# TODO Doesn't work
# preload_handler = _Handler("loadcmd")
def unload(self) -> None:
self.eval_("::msgcat::mcforgetpackage")
add(what: str, translation: str) -> None
Set/update a translation for the current locale.
Parameters: |
|
---|
Source code in tkmsgcat/__init__.py
def add(what: str, translation: str) -> None:
"""Set/update a translation for the current locale.
Args:
what (str): The string to be translated.
translation (str): The translated string.
"""
_default_msgcat.add(what, translation)
add_to(locale_: str, what: str, translation: str) -> None
Set/update a translation in a specific locale.
Parameters: |
|
---|
Source code in tkmsgcat/__init__.py
def add_to(locale_: str, what: str, translation: str) -> None:
"""Set/update a translation in a specific locale.
Args:
locale_ (str): The locale in which this operation will take place.
what (str): The string to be translated.
translation (str): The translated string.
"""
_default_msgcat.add_to(locale_, what, translation)
get(what: str, *fmtargs: str) -> str
Translate a string according to a user's current locale.
Parameters: |
|
---|
Returns: |
|
---|
Source code in tkmsgcat/__init__.py
def get(what: str, *fmtargs: str) -> str:
"""Translate a string according to a user's current locale.
Args:
what (str): The string to be translated. It should generally be in
English as that is the language used by code itself.
*fmtargs (tuple[str], optional): Extra arguments passed internally
to the [format](https://www.tcl.tk/man/tcl8.6/TclCmd/format.html)
package.
Returns:
str: The translated string.
"""
return _default_msgcat.get(what, *fmtargs)
get_from(locale_: str, what: str, *fmtargs: str) -> str
Get the translation of a string from a specific locale.
Parameters: |
|
---|
Returns: |
|
---|
Source code in tkmsgcat/__init__.py
def get_from(locale_: str, what: str, *fmtargs: str) -> str:
"""Get the translation of a string from a specific locale.
Args:
locale_ (str): The locale to be used for looking up `__what`.
what (str): The string to be translated. It should generally be in
English as that is the language used by code itself.
*fmtargs (tuple[str], optional): Extra arguments passed internally
to the [format](https://www.tcl.tk/man/tcl8.6/TclCmd/format.html)
package.
Returns:
str: The translated string.
"""
return _default_msgcat.get_from(locale_, what, *fmtargs)
has(what: str, search_all: bool = True) -> bool
Check if a string has a translation in the current/all locale(s).
Parameters: |
|
---|
Returns: |
|
---|
Source code in tkmsgcat/__init__.py
def has(what: str, search_all: bool = True) -> bool:
"""Check if a string has a translation in the current/all locale(s).
Args:
what (str): The string to lookup for a translation.
search_all (bool, optional): Whether to search in all of the loaded
locales or just the current locale. If a locale is not set, the
value returned by `preferences` is used. Defaults to True.
Returns:
bool: Whether the given string has a translation.
"""
return _default_msgcat.has(what, search_all)
is_init() -> bool
Whether any translation file has been loaded.
Source code in tkmsgcat/__init__.py
def is_init() -> bool:
"""Whether any translation file has been loaded."""
return _default_msgcat.is_init()
is_loaded(locale_: str) -> bool
Whether a translation file for a particular locale is loaded.
Parameters: |
|
---|
Source code in tkmsgcat/__init__.py
def is_loaded(locale_: str) -> bool:
"""Whether a translation file for a particular locale is loaded.
Args:
locale_ (str): The locale to be checked if it is loaded.
"""
return _default_msgcat.is_loaded(locale_)
load(dir_: str | pathlib.Path) -> None
Loads all translation files from the specified directory.
Parameters: |
|
---|
Source code in tkmsgcat/__init__.py
def load(dir_: str | pathlib.Path) -> None:
"""Loads all translation files from the specified directory.
Args:
dir_ (str | pathlib.Path): The path/name of the directory where
all the translation files (.msg extension) are stored. Tk
recommends you to store them all in a separate `msgs` directory at
the package level and name them according to their locale.
"""
_default_msgcat.load(dir_)
loaded_from() -> str
Returns the path of the directory from which translations were loaded.
Exceptions: |
|
---|
Source code in tkmsgcat/__init__.py
def loaded_from() -> str:
"""Returns the path of the directory from which translations were loaded.
Raises:
AttributeError: When the directory is not set.
"""
return _default_msgcat.loaded_from
loaded_locales() -> tuple[str]
Returns a list of all the currently loaded locales.
A locale is loaded only when it is requested i.e. set via locale
.
Source code in tkmsgcat/__init__.py
def loaded_locales() -> tuple[str]:
"""Returns a list of all the currently loaded locales.
A locale is loaded only when it is requested i.e. set via `locale`.
"""
return _default_msgcat.loaded_locales
locale(newlocale: str = '') -> str
The locale used to translate strings.
Tip
See msgcat manual for details on how to specify a locale.
Parameters: |
|
---|
Returns: |
|
---|
Source code in tkmsgcat/__init__.py
def locale(newlocale: str = "") -> str:
"""The locale used to translate strings.
Tip:
See [msgcat manual](https://www.tcl-lang.org/man/tcl/TclCmd/msgcat.htm#M19)
for details on how to specify a locale.
Args:
newlocale (str): Use this to change the locale.
Returns:
str: The currently used locale.
"""
if newlocale:
_default_msgcat.locale = newlocale
return _default_msgcat.locale
longest(what: tuple[str]) -> int
Find the length of the longest translated string in the current locale.
This is useful in deciding the maximum size of a label or a button when
using the place
geometry manager, for exmaple.
Parameters: |
|
---|
Returns: |
|
---|
Source code in tkmsgcat/__init__.py
def longest(what: tuple[str]) -> int:
"""Find the length of the longest translated string in the current locale.
This is useful in deciding the maximum size of a label or a button when
using the `place` geometry manager, for exmaple.
Args:
what (str): The strings whose translations are to be compared.
Returns:
int: Length of the longest translated string with respect to all the
strings passed .
"""
return _default_msgcat.longest(what)
longest_in(locale_: str, what: tuple[str]) -> int
Find the length of the longest translated string in a specific locale.
This is useful in deciding the maximum size of a label or a button when
using the place
geometry manager, for exmaple.
Parameters: |
|
---|
Returns: |
|
---|
Source code in tkmsgcat/__init__.py
def longest_in(locale_: str, what: tuple[str]) -> int:
"""Find the length of the longest translated string in a specific locale.
This is useful in deciding the maximum size of a label or a button when
using the `place` geometry manager, for exmaple.
Args:
locale_ (str): The locale to use for finding the length.
what (str): The strings whose translations are to be compared.
Returns:
int: Length of the longest translated string with respect to all the
strings passed .
"""
return _default_msgcat.longest_in(locale_, what)
missing_handler(func: Callable[..., str] | None = None) -> None
Register the callback invoked when a translation is not found.
It is invoked with the same arguments passed to translate
. It must
return a formatted message as translate
would do normally.
Parameters: |
|
---|
Source code in tkmsgcat/__init__.py
def missing_handler(func: Callable[..., str] | None = None) -> None:
"""Register the callback invoked when a translation is not found.
It is invoked with the same arguments passed to `translate`. It must
return a formatted message as `translate` would do normally.
Args:
func (Callable, optional): The handler is set when this has a value
and unset when it is None. Defaults to None.
"""
if func:
_default_msgcat.missing_handler = func
else:
del _default_msgcat.missing_handler
preferences() -> tuple[str]
Returns a list of the preferred locales based on the current locale.
Source code in tkmsgcat/__init__.py
def preferences() -> tuple[str]:
"""Returns a list of the preferred locales based on the current locale."""
return _default_msgcat.preferences
unload() -> None
Unloads all translations and forgets all callbacks and settings.
You can reinitialise the message catalog by a calling load
with the
appropriate arguments.
Source code in tkmsgcat/__init__.py
def unload() -> None:
"""Unloads all translations and forgets all callbacks and settings.
You can reinitialise the message catalog by a calling `load` with the
appropriate arguments.
"""
_default_msgcat.unload()
update(translations: dict[str, str]) -> None
Set/update translations of the current locale.
Parameters: |
|
---|
Source code in tkmsgcat/__init__.py
def update(translations: dict[str, str]) -> None:
"""Set/update translations of the current locale.
Args:
translations (dict[str, str]): A mapping of source strings to
translated strings.
"""
_default_msgcat.update(translations)
update_to(locale_: str, translations: dict[str, str]) -> None
Set/update translations in a specific locale.
Parameters: |
|
---|
Source code in tkmsgcat/__init__.py
def update_to(locale_: str, translations: dict[str, str]) -> None:
"""Set/update translations in a specific locale.
Args:
locale_ (str): The locale in which this operation will take place.
translations (dict[str, str]): A mapping of source strings to
translated strings.
"""
_default_msgcat.update_to(locale_, translations)