Package: verboselib
A library with CLI tools allowing to add i18n and l10n to Python apps and libs with ease.ContentsSynopsisDomains of UseInstallationQuickstartBrief ExampleLazy Translations ExampleAPIActive LanguageCurrent LanguageDefault LanguageDisabling TranslationsLocale-to-language ConversionsTranslations Catalogs RegistryInstance CreationInstance LocationMethodsTranslations Catalogs DirectoryWorkflow OverviewDiscovery of Translatable Messages.pofilesMessage ContextsPlural FormsUtilitiesextractorxcompileorcThread-safetyChangelogSynopsisverboselibis a thin abstraction layer on top ofGNU gettexttoolset andPython gettextmodule.In contrast to the rawgettextAPI,verboselibprovides a unified and thread-safe way to access translation catalogs and an easier way to set active language dynamically.Additionally, it allows definitions of lazy translations, which can be useful for module-level variables or class-level attributes.Besides,verboselibcomes with a bunch of CLI tools for extracting translatable messages from sources and compiling them. Those tools do not impose code dependencies and can be used as stand-alone utilities.Domains of UseThe primary domain of use is applications & services. However, it can also be used in libraries.In such case users of a target library will have to be aware of usingverboselibas well. Hence,verboselibcan be viewed as an i18n & l10n framework.Generally, reliance on frameworks is a thing to refrain from in stand-alone public libraries, but it can also be a totally valid design decision for auxiliary libraries at a product-level scale.InstallationAvailable as aPyPIpackage:pipinstallverboselibQuickstartThe following examples provide a quick overview of what usage ofverboseliblooks like.Brief ExampleThe briefest usage example shows how to get immediate translations:fromverboselibimportTranslations# (1)fromverboselibimportset_language# (2)translations=Translations(# (3)domain="the_app",locale_dir_path="locale",)_=translations.gettext# (4)set_language("en")# (5)print(_("Hi there!"))# (6) 'Hi there!'set_language("sv")# (7)print(_("Hi there!"))# (8) 'Hej där!'And here is the explanation for the noted lines:ImportTranslationsclass, which is a translations registry.Importset_language()function, which allows switching between languages.Create an instance ofTranslationsclass, specifying the messages domain and location of the translations catalogs directory.Define_as a shortcut fortranslations.gettext()function.Set the current language to English.Print aHi there!there message passed as an argument to the_function. This givesHi there!as the output.Set the current language to Swedish.Print the same message again and getHej där!as the output.The example is naïve, but calls togettext()via the_shortcut are very common inside functions, where messages are translated when functions are called. For example:defprint_message():print(_("Hi there!"))set_language("en")print_message()# 'Hi there!'set_language("sv")print(_("Hi there!"))# 'Hej där!'Lazy Translations ExampleOftentimes there’s a need to have a placeholder or just a message, the definition of which must be separated from its evaluation. This is achieved via lazy translations:fromverboselibimportTranslationsfromverboselibimportset_languagetranslations=Translations(domain="the_app",locale_dir_path="locale",)L_=translations.gettext_lazy# (1)classGreeter:greeting_fmt=L_("Hi there,{name}!")# (2)@classmethoddefmake_greeting(cls,name):returncls.greeting_fmt.format(# (3)name=name,)set_language("en")print(Greeter.make_greeting("user"))# (4) 'Hi there, user!'set_language("sv")print(Greeter.make_greeting("user"))# 'Hej där, user!'Comments for the noted lines:gettext_lazyis used instead ofgettextandL_shortcut is used instead of_.A translatable string is defined as a class-level attribute usingL_shortcut.The translatable string is accessed as a normal string.The method is called and a parameterized translated string is returned.This example is also naïve, but here the value ofGreeter.greeting_fmtis not translated into a solid string during construction of theGreeterclass. This is important, as the class is constructed only once. The actual type ofgreeting_fmtis not a string, butlazy_string.LazyString, which is a string’s proxy:>>>type(Greeter.greeting_fmt)<class'lazy_string.LazyString'>APIThere are several aspects to consider when usingverboselib:Active language.Translations catalogs registry — an instance ofverboselib.Translationsclass.Translations catalogs directory — a directory where.poand.mofiles are located.Translatable messages themselves.Tools for extracting messages and compiling translations.The sections below describe those aspects separately.Active LanguageActive language is the language which will be used for getting final values of translatable strings.Itscurrent valueis a string defined by a user, e.g. “en”. The value can be missing, i.e. not set.In addition to the current value, it is possible to define adefault value. So, if the current value is not set, it will fallback to the default value.By default, both “current value” and “default value” are not set, i.e. they areNone.Finally, it’s possible toturntranslationsoff, so that translations will be equal to original messages.Current LanguageThe current language inverboselibis controlled and queried via the following functions:set_language(language)Sets the current language for the current thread.fromverboselibimportset_languageset_language("en")get_language()Queries name of the current language in the current thread as a string.fromverboselibimportget_languagefromverboselibimportset_languageget_language()# Noneset_language("en")get_language()# 'en'drop_language()Removes the value of the current language for the current thread. The value will fallback to the default value.fromverboselibimportdrop_languagefromverboselibimportget_languagefromverboselibimportset_languageset_language("en")get_language()# 'en'drop_language()get_language()# NoneDefault LanguageThe default language is controlled by functions which are similar to functions used to control the current language:set_default_language(language)Sets the default language for the current thread.fromverboselibimportset_default_languageset_default_language("en")get_default_languageQueries value of the default language for the current thread as a string.fromverboselibimportget_default_languagefromverboselibimportset_default_languageget_default_language()# Noneset_default_language("en")get_default_language()# 'en'drop_default_languageRemoves the value of the default language for the current thread.fromverboselibimportdrop_default_languagefromverboselibimportget_default_languagefromverboselibimportset_default_languageset_default_language("en")get_default_language()# 'en'drop_default_language()get_default_language()# NoneUsually, only theset_default_language(...)is used. This can be helpful ifNoneis a possible value for the current language. In such a case at least a default language will be used:defgreet_user(user):set_language(user.language)# can be Noneprint(_("Hi there,{name}!").format(name=user.name))drop_language()set_default_language("en")...user=get_user()greet_user(user)Disabling TranslationsAt certain times it can be useful to disable translations, for example, during debugging.This can be done viaset_language_bypass()function. It disables the current language and prevents it from falling back to the default language.fromverboselibimportdrop_languagefromverboselibimportget_languagefromverboselibimportset_languagefromverboselibimportset_default_languagefromverboselibimportset_language_bypassset_default_language("en")set_language("fr")get_language()# 'fr'set_language_bypass()get_language()# Nonedrop_language()get_language()# 'en'Note that the 2nd call toget_language()returnedNone.Locale-to-language Conversionsverboselibcomes up with a couple of helper functions for converting languages to locales:fromverboselibimportto_localeto_locale("en-us")# 'en_US'and vice versa, for converting locales to languages:fromverboselibimportto_languageto_language("en_US")# 'en-us'Translations Catalogs RegistryTranslations catalogs registry (verboselib.Translations) is a facade in front ofgettext.GNUTranslations.Instance CreationTheverboselib.Translationsclass requires the following arguments to be provided:domainA name (string) of the domain of translations. Usually, it’s the name of the application, of the library, or it can be just"messages".locale_dir_pathA path (stringorpathlib.Path) to the translations catalogs directory, which is a place where actual translations are stored. Usually, such directory is calledlocaleand is located inside the top-level directory of the application or library. The path is strongly recommended to be absolute.Example:frompathlibimportPathfromverboselibimportTranslations__here__=Path(__file__).absolute().parenttranslations=Translations(domain="messages",locale_dir_path=(__here__/"locale"),)Instance LocationAlthough instances ofTranslationsare just objects which can be passed to functions, it is recommended to create a single instance ofTranslationsas a global variable in a separate module, saytranslations.py. Those instances are thread-safe.Additionally, it can be handy to make module-level aliases for the methods of aTranslationsinstance:# foo_package/translations.pyfrompathlibimportPathfromverboselibimportTranslationstranslations=Translations(domain="foo_package",locale_dir_path=(Path(__file__).absolute().parent/"locale"),)gettext=translations.gettextgettext_lazy=translations.gettext_lazyThis can look a bit ugly, but in such a case it’s convenient to access those methods as functions from other modules, e.g.:# foo_package/logic.pyfrom.translationsimportgettextas_from.translationsimportgettext_lazyasL_print(_("Hello"))greeting_fmt=L_("Hello,{name}")MethodsThe API ofverboselib.Translationsis compatible withGNUTranslationsand includes the following methods:gettext(message)ngettext(singular, plural, n)pgettext(context, message)npgettext(context, singular, plural, n)Additionally,verboselib.Translationsprovides their lazy versions:gettext_lazy(message)ngettext_lazy(singular, plural, n)pgettext_lazy(context, message)npgettext_lazy(context, singular, plural, n)Those lazy methods return an instance oflazy_string.LazyStringwhich is a string’s proxy.As forngettextandnpgettextmethods and their lazy counterparts, not only anintcan be passed as thenargument, but also a callable accepting no arguments and returning anint. For example, both the following calls are valid and conceptually identical:translations.ngettext("window","windows",1)translations.ngettext("window","windows",lambda:1)Translations Catalogs DirectoryAll translations are stored in a catalogs directory, where each language has its own subdirectory.This section describes how to build such a catalog.Workflow OverviewFirstly, translatable messages are extracted from source files into.pofiles. Those files contain IDs of messages and file locations where those messages are observed, e.g.:#: foo.py:105 foo.py:203
msgid "Hi there, {name}!"
msgstr ""Translators fill in, well, translations for IDs inside.pofiles:#: foo.py:105 foo.py:203
msgid "Hi there, {name}!"
msgstr "Hej där, {name}!"Finally, those.pofiles are compiled into.mofiles.Discovery of Translatable MessagesIn order to be discovered and extracted, messages in source files have to be marked in a certain way.This is achieved by wrapping a message by a pair of parentheses()prefixed by akeyword. This makes it look like a function call, which it really is:gettext("a message")Here,gettextis used as a keyword. It’s also possible to use its shortcut which is_:_("a message")Both of those variants are equal, but the latter is more concise.By defaultverboselibrecognizes the following keywords:KeywordShortcutExamplegettext__("message")gettext_lazyL_L_("message")ngettextN_N_("single","plural", 123)ngettext_lazyLN_LN_("single","plural", 123)pgettextP_P_("messagecontext", "message")pgettext_lazyLP_LP_("messagecontext", "message")npgettextNP_LP_("messagecontext", "single", "plural", 123)npgettext_lazyLNP_LP_("messagecontext", "single", "plural", 123)Technically, any literal can be used as a keyword. But if a non-default keyword is used, it must be specified during extraction, which is described later..pofilesEvery.pofile includes a header at the beginning.It consists of key-value metadata separated from file’s body via a blank line. Example:msgid ""
msgstr ""
"Project-Id-Version: foo 1.0.0\n"
"PO-Revision-Date: 2020-10-09 21:24+0300\n"
"Report-Msgid-Bugs-To: support@foo.com\n"
"Last-Translator: Mr Translator <translation-team@foo.com>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

msgid "Log in"
msgstr ""Refer togettextdocs for more details on.po filesand on.po headers.Message ContextsFunctions aspgettext(),npgettext(), and their lazy fellows allow to provide a message context.This is just a string which will appear in.pofiles to give a hint for translators about the meaning of the message.For example, the following call topgettext:P_("abbrev. month","Jan")will add amsgctxtattribute into.pofiles:#: foo.py:90
msgctxt "abbrev. month"
msgid "Jan"
msgstr ""Plural FormsFunctions asngettext(),npgettext(), and their lazy counterparts allow to get different translations depending on the integer numbernprovided to them, e.g.:N_("window","windows",1)In this trivial examplenis1. However, it can be a variable or a parameterless callable returning anint:defget_users_online()->int:n=...returnnN_("user online","users online",get_users_online)This looks pretty simple, but that is not the end of the story.Different languages can have different number of plural forms and each form can have their own calculation rules.So, in order to make plural forms actually work, each.pofile must include aPlural-Formsmetadata in its header.For example, languages of the Germanic family, like English, have 2 plural forms defined as:"Plural-Forms: nplurals=2; plural=n != 1\n"Examples of rules for other languages can be found atPlural-Forms documentation page.Finally, every message having plural forms must have as many translations as there are plural forms specified byPlural-Forms. For example:#: foo.py:74
msgid "window"
msgid_plural "windows"
msgstr[0] "вікно"
msgstr[1] "вікна"
msgstr[2] "вікон"Refer togettextdocs for more info ontranslating plural forms.Utilitiesverboselibcomes with a couple of stand-alone CLI utilities for extracting and compiling translatable messages.These utilities are implemented as subcommands of the main command namedverboselib.Runverboselibwith-hflag to get generic help:verboselib -h

usage: verboselib [-h] [-V] {extract,x,compile,c} ...

run a verboselib command

optional arguments:
  -h, --help            show this help message and exit
  -V, --version         show version of verboselib and exit

subcommands:
  {extract,x,compile,c}
    extract (x)         extract translatable strings from sources into '.po' files
    compile (c)         compile '.po' text files into '.mo' binariesextractorxUsed to extract translatable messages from sources. Creates or updates the directory with translations catalogs. Run with-hflag for help:verboselib x -h

usage: extract [-h] [-d DOMAIN] [-l LOCALE] [-a] [-o OUTPUT_DIR] [-k KEYWORD] [--no-default-keywords] [-e EXTENSIONS] [-s] [-i PATTERN] [--no-default-ignore] [--no-wrap]
              [--no-location] [--no-obsolete] [--keep-pot] [--xgettext-extra-args XGETTEXT_EXTRA_ARGS] [--msguniq-extra-args MSGUNIQ_EXTRA_ARGS]
              [--msgmerge-extra-args MSGMERGE_EXTRA_ARGS] [--msgattrib-extra-args MSGATTRIB_EXTRA_ARGS] [-v]

extract translatable strings from sources into '.po' files

optional arguments:
  -h, --help            show this help message and exit
  -d DOMAIN, --domain DOMAIN
                        domain of message files (default: messages)
  -l LOCALE, --locale LOCALE
                        create or update '.po' message files for the given locale(s), ex: 'en_US'; can be specified multiple times (default: None)
  -a, --all             update all '.po' message files for all existing locales (default: False)
  -o OUTPUT_DIR, --output-dir OUTPUT_DIR
                        path to the directory where locales will be stored, a.k.a. 'locale dir' (default: locale)
  -k KEYWORD, --keyword KEYWORD
                        extra keyword to look for, ex: 'L_'; can be specified multiple times (default: None)
  --no-default-keywords
                        do not use default keywords as {'_', 'gettext', 'L_', 'gettext_lazy', 'N_:1,2', 'ngettext:1,2', 'LN_:1,2', 'ngettext_lazy:1,2', 'P_:1c,2',
                        'pgettext:1c,2', 'LP_:1c,2', 'pgettext_lazy:1c,2', 'NP_:1c,2,3', 'npgettext:1c,2,3', 'LNP_:1c,2,3', 'npgettext_lazy:1c,2,3'} (default: False)
  -e EXTENSIONS, --extension EXTENSIONS
                        extra file extension(s) to scan in addition to '.py'; separate multiple values with commas or specify the parameter multiple times (default: None)
  -s, --links           follow links to files and directories when scanning sources for translation strings (default: False)
  -i PATTERN, --ignore PATTERN
                        extra glob-style patterns for ignoring files or directories; can be specified multiple times (default: None)
  --no-default-ignore   do not ignore the common glob-style patterns as {'.*', '*~', 'CVS', '__pycache__', '*.pyc'} (default: False)
  --no-wrap             do not break long message lines into several lines (default: False)
  --no-location         do not write location lines, ex: '#: filename:lineno' (default: False)
  --no-obsolete         remove obsolete message strings (default: False)
  --keep-pot            keep '.pot' file after creating '.po' files (useful for debugging) (default: False)
  --xgettext-extra-args XGETTEXT_EXTRA_ARGS
                        extra arguments for 'xgettext' utility; can be comma-separated or specified multiple times (default: None)
  --msguniq-extra-args MSGUNIQ_EXTRA_ARGS
                        extra arguments for 'msguniq' utility; can be comma-separated or specified multiple times (default: None)
  --msgmerge-extra-args MSGMERGE_EXTRA_ARGS
                        extra arguments for 'msgmerge' utility; can be comma-separated or specified multiple times (default: None)
  --msgattrib-extra-args MSGATTRIB_EXTRA_ARGS
                        extra arguments for 'msgattrib' utility; can be comma-separated or specified multiple times (default: None)
  -v, --verbose         use verbose output (default: False)The basic usage example:verboselibx-l'uk'-l'en'-l'it'Use-aflag to update all existing.pofiles:verboselibx-aUse--keyword(-k) argument to specify additional keywords to look for, e.g.:verboselibx-a-k'FOO_'-k'BAR_'compileorcCompiles all.pofiles into.mofiles. Basic usage has no arguments:verboselibcUse-hflag for help:verboselib c -h

usage: compile [-h] [-d LOCALES_DIR] [-l LOCALE] [-e EXCLUDE] [-f] [--msgfmt-extra-args MSGFMT_EXTRA_ARGS] [-v]

compile '.po' text files into '.mo' binaries

optional arguments:
  -h, --help            show this help message and exit
  -d LOCALES_DIR, --locale-dir LOCALES_DIR
                        path to the directory where locales are stored (default: locale)
  -l LOCALE, --locale LOCALE
                        locale(s) to process, ex: 'en_US'; can be specified multiple times; all locales are processed if not specified (default: None)
  -e EXCLUDE, --exclude EXCLUDE
                        locale(s) to exclude, ex: 'en_US'; can be specified multiple times (default: None)
  -f, --use-fuzzy       use fuzzy translations (default: False)
  --msgfmt-extra-args MSGFMT_EXTRA_ARGS
                        extra arguments for 'msgfmt' utility; can be comma-separated or specified multiple times (default: None)
  -v, --verbose         use verbose output (default: False)Thread-safetyThe current and the default languages arethread-local. Hence, the functions for manipulating and querying them, likeset_language(), are thread-safe. However, the values have to be set in each thread separately.As for the translations catalog registry,verboselib.Translations, it is also thread-safe, as it relies onRLocks. It’s recommended to be used in libraries. However, if the target is an application and it is guaranteed to be single-threaded, it’s possible to use a not-thread-safe version:fromverboselibimportNotThreadSafeTranslationsChangelog1.1.0(Nov 18, 2020)API changes:verboselib.lazyis extracted into a standalone liblazy-string.Misc:Annotations using classes fromtypingare tidied up and deprecations starting from Python 3.9 are handled.1.0.1(Oct 30, 2020)Fixverboselib.utils.export()helper which adds objects to__all__variable of their own modules.1.0.0(Oct 11, 2020)API changes:verboselib.factory.TranslationsFactoryis nowverboselib.translations.Translations.locale_dirargument ofTranslationsislocale_dir_pathnow and instances ofpathlib.Pathcan be used in addition to strings.verboselib.translations.Translationsis now thread-safe.verboselib.translations.NotThreadSafeTranslationsis added.Methodsugettext()andugettext_lazy()are removed fromTranslations.Methodsngettext(),ngettext_lazy(),pgettext(),pgettext_lazy(),npgettext(), andnpgettext_lazy()are added toTranslations.Functionget_default_language()is added.Functionverboselib.heplers.to_locale()no longer hasto_lowerargument.verboselib-manageCLI utility is now justverboselib.compileandextractsubcommands ofverboselibhavecandxaliases respectively.Flags--no-default-keywords,--xgettext-extra-args,--msguniq-extra-args,--msgmerge-extra-args, and--msgattrib-extra-argsare added to theextractcommand.Flags--exclude,--use-fuzzy, and--msgfmt-extra-argsare added to thecompilecommand.Python support:Support of allPythonversions below3.7is dropped.Other:All external dependencies are removed.The license is switched fromLGPLv3toMIT.0.2.1(Jul 16, 2017)Fixversioncommand.Renameverboselib-manage.pyexecutable to simplyverboselib-manage.0.2.0(Dec 31, 2014)Addget_default_language()method.Use default translation classes fromgettextmodule.0.1.0(Jul 17, 2014)Initial version.