Package: i18nice
i18niceThis library provides i18n functionality for Python 3 out of the box. The usage is mostly based on Rails i18n library.CHANGELOGCONTRIBUTINGInstallationJust runpipinstalli18niceIf you want to use YAML to store your translations, usepipinstalli18nice[YAML]UsageBasic usageThe simplest, though not very useful usage would beimporti18ni18n.add_translation('foo','bar')i18n.t('foo')# barUsing translation filesYAML and JSON formats are supported to store translations. With the default configuration, if you have the followingfoo.en.ymlfileen:hi:Hello world !Or a JSON filefoo.en.json{"en":{"hi":"Hello world !"}}in/path/to/translationsfolder, you simply need to add the folder to the translations path.importi18ni18n.load_path.append('/path/to/translations')i18n.t('foo.hi')# Hello world !Please note that YAML format is used as default file format if you haveyamlmodule installed.
If bothyamlandjsonmodules available and you want to use JSON to store translations, explicitly specify that:i18n.set('file_format', 'json')!WARNING!yaml.FullLoaderis no longer used by default.
If you need full yaml functionalities, override it with a custom loader:classMyLoader(i18n.loaders.YamlLoader):loader=yaml.FullLoaderi18n.register_loader(MyLoader,["yml","yaml"])MemoizationThe configuration valueenable_memoization(Trueby default) disables reloading of files every time when searching for missing translation.
When translations are loaded, they're always stored in memory, hence it does not affect how existing translations are accessed.Load everythingi18n.load_everything()will load every file inload_pathand subdirectories that matchesfilename_formatandfile_format.
You can call it with locale argument to load only one locale.i18n.unload_everything()will clear all caches.i18n.reload_everything()is just a shortcut forunload_everything()followed byload_everything().For the best performance, you can passlock=Truetoload_everything()to disable searching for missing translations completely.
It'll prevent slowdowns caused by missing translations, but you'll need to useunload_everything()to be able to load files again.NamespacesFile namespacesIn the above example, the translation key isfoo.hiand not justhi. This is because the translation filename format is by default{namespace}.{locale}.{format}, so the {namespace} part of the file is used as translation.To remove{namespace}from filename format please change thefilename_formatconfiguration.i18n.set('filename_format','{locale}.{format}')Directory namespacesIf your files are in subfolders, the foldernames are also used as namespaces, so for example if your translation root path is/path/to/translationsand you have the file/path/to/translations/my/app/name/foo.en.yml, the translation namespace for the file will bemy.app.nameand the file keys will therefore be accessible frommy.app.name.foo.my_key.FunctionalitiesPlaceholderYou can of course use placeholders in your translations. With the default configuration, the placeholders are used by inserting%{placeholder_name}in the translation string. Here is a sample usage.i18n.add_translation('hi','Hello %{name}!')i18n.t('hi',name='Bob')# Hello Bob !Braces are optional if the identifier is separated from following words.To escape the delimiter you need to put it twice (like%%).PluralizationPluralization is based on Rail i18n module. By passing acountvariable to your translation, it will be pluralized. The translation value should be a dictionary with at least the keysoneandmany. You can add azeroorfewkey when needed, if it is not presentmanywill be used instead. Here is a sample usage.i18n.add_translation('mail_number',{'zero':'You do not have any mail.','one':'You have a new mail.','few':'You only have %{count}mails.','many':'You have %{count}new mails.'})i18n.t('mail_number',count=0)# You do not have any mail.i18n.t('mail_number',count=1)# You have a new mail.i18n.t('mail_number',count=3)# You only have 3 new mails.i18n.t('mail_number',count=12)# You have 12 new mails.FallbackYou can set a fallback which will be used when the key is not found in the default locale.i18n.set('locale','jp')i18n.set('fallback','en')i18n.add_translation('foo','bar',locale='en')i18n.t('foo')# barNote that settinglocaleandfallbackto the same value will result infallbackbeingNone.Skip locale from rootSometimes i18n structure file came from another project or not contains root element with locale eg.enname.{"foo":"FooBar"}However, we would like to use this i18n .json file in our Python sub-project or micro service as base file for translations.i18nicehas special configuration that is skipping locale eg.enroot data element from the file.i18n.set('skip_locale_root_data',True)Different directory structureIf your tree of translation files looks similar to this, you can enableuse_locale_dirsto get the files properly loaded:locales
├── en-US
│   ├── common.yml
│   └── gui
│       ├── page1.yml
│       └── page2.yml
└── fr-FR
    ├── common.yml
    └── gui
        ├── page1.yml
        └── page2.ymlExample code:importi18ni18n.load_path.append("locales")i18n.set("file_format","yml")i18n.set("filename_format","{namespace}.{format}")i18n.set("skip_locale_root_data",True)i18n.set("use_locale_dirs",True)print(i18n.t("common.text1",locale="en-US"))print(i18n.t("gui.page1.title",locale="en-US"))ListsIt's possible to use lists of translations, for example:# translations.en.ymlen:days:-Monday-Tuesday-Wednesday...# translate.pyfromdatetimeimportdateimporti18ni18n.load_path.append(".")# will print current dayprint(i18n.t("translations.days")[date.today().weekday()])It's also possible to use pluralization in lists:days:
  - one: Monday
    many: Mondays
  ...Note 1:
The function actually returns aLazyTranslationTupleinstead oflist.Note 2:
Because the tuple is lazy, it'll only process elements when they're requested.
If you need to get fully processed translation, you can force it with[:]:# ({'one': 'Monday', 'many': 'Mondays'}, {'one': 'Tuesday', 'many': 'Tuesdays'}, ...)print(i18n.t("translations.days",count=3))# ('Mondays', 'Tuesdays', 'Wednesdays')print(i18n.t("translations.days",count=3)[:])Note 3 (for type checking):tdeclares its return type asstrby default. To simplify type checking in situations where lists are used, you can pass_list=Trueto it, which should have less overhead than callingcastand be less intrusive thantype: ignorecomment.This will NOT affect actual return type and is purely for type checkers.Static referencesStatic references allow you to refer to other translation values. This can be useful to avoid repetition. To create a static reference, simply put a key prefixed with namespace delimiter to a placeholder. For example:{"en":{"progname":"Program Name","welcome":"Welcome to %{.progname}!"}}Note that you don't need to specify the absolute key:{"en":{"interface":{"progname":"Program Name","ref":"%{.progname} and %{.interface.progname} refer to the same value"}}}To be exact, keys are searched from top to bottom. For example, if you referred to.c.my_keyina.b.c.d, the library will first check forc.my_key, thena.c.my_key, and finally finda.b.c.my_keyif it's present. If not, it'll try to searchc.my_keyin other files and throw an exception if that also fails.Error handlingThere are three config options for handling different situations.
Setting it toNonedisables handling (default),"error"enables error throwing.
You can also set your custom handlers:on_missing_translation(key, locale, **kwargs)on_missing_plural(key, locale, translation, count)on_missing_placeholder(key, locale, translation, placeholder)Example:importlogging,i18ndefhandler(key,locale,text,name):logging.warning(f"Missing placeholder{name!r}while translating{key!r}to{locale!r}(in{text!r})")return"undefined"i18n.set("on_missing_placeholder",handler)i18n.add_translation("am","Amount is %{amount}")print(i18n.t("am"))# output:# WARNING:root:Missing placeholder 'amount' while translating 'am' to 'en' (in 'Amount is %{amount}')# Amount is undefinedCustom functionsAdd your custom functions and choose translation variants during runtime.
All arguments given totwould be passed to the function as keyword arguments.
This may be an alternative for pluralization, especially if a language has more than one plural form.Example (correct plural form of days in Ukrainian):i18n.set("locale","uk")i18n.add_translation("days","%{count}%{p(день|дні|днів)}")defdetermine_plural_form(*,count):count=abs(count)ifcount%10>=5orcount%10==0or(count%100)inrange(11,20):return2elifcount%10==1:return0return1i18n.add_function("p",determine_plural_form,"uk")i18n.t("days",count=1)# 1 деньi18n.t("days",count=2)# 2 дні# i18n.t("days", count=5) # 5 днівTestingYou can run tests withpython dev-helper.py run-tests.