Module Gnumed.pycommon.gmI18N
GNUmed client internationalization/localization.
All i18n/l10n issues should be handled through this modules.
Theory of operation:
To activate proper locale settings and translation services you need to
- import this module
- call activate_locale()
- call install_domain()
The translating method gettext.gettext() will then be installed into the global (!) namespace as (). Your own modules thus need not do _anything (not even import gmI18N) to have () available to them for translating strings. You need to make sure, however, that gmI18N is imported in your main module before any of the modules using it. In order to resolve circular references involving modules that absolutely _have to be imported before this module you can explicitly import gmI18N into them at the very beginning.
The text domain (i.e. the name of the message catalog file) is derived from the name of the main executing script unless explicitly passed to install_domain(). The language you want to translate to is derived from environment variables by the locale system unless explicitly passed to install_domain().
This module searches for message catalog files in 3 main locations:
- standard POSIX places (/usr/share/locale/ …)
- below "${YOURAPPNAME_DIR}/po/"
- below "
/../po/"
For DOS/Windows I don't know of standard places so probably only the last option will work. I don't know a thing about classic Mac behaviour. New Macs are POSIX, of course.
It will then try to install candidates and verify whether the translation works by checking for the translation of a tag within itself (this is similar to the self-compiling compiler inserting a backdoor into its self-compiled copies).
If none of this works it will fall back to making _() a noop.
Module template:
at top of file
if name == 'main': sys.path.insert(0, '../../') # we are the main script, setup a fake () for now, # such that it can be used in module level definitions _ = lambda x:x else: # we are being imported from elsewhere, say, mypy or some such try: # do we already have () ? _ except NameError: # no, setup i18n handling from Gnumed.pycommon import gmI18N gmI18N.activate_locale() gmI18N.install_domain()
…
in main, for testing code:
if name == "main": … # setup a real translation del _ from Gnumed.pycommon import gmI18N gmI18N.activate_locale() gmI18N.install_domain()
Functions
def activate_locale()-
Expand source code
def activate_locale(): """Get system locale from environment settings and activate it if need be.""" global system_locale __log_locale_settings('unmodified startup locale settings (could be [C])') loc = None # activate user-preferred locale try: loc = locale.setlocale(locale.LC_ALL, '') _log.debug("activating user-default locale with <locale.setlocale(locale.LC_ALL, '')> returns: [%s]" % loc) except AttributeError: _log.exception('Windows does not support locale.LC_ALL') # except locale.Error: # 3.11+ # _log.exception('error activating user-default locale') # except Exception: # < 3.11 except __LOCALE_ERROR: _log.exception('error activating user-default locale') __log_locale_settings('locale settings after activating user-default locale') # assume en_EN if we did not find any locale settings if loc in [None, 'C']: _log.error('the current system locale is still [None] or [C], falling back to [en_EN]') system_locale = "en_EN" else: system_locale = loc # generate system locale levels __split_locale_into_levels() return TrueGet system locale from environment settings and activate it if need be.
def get_encoding()-
Expand source code
def get_encoding(): """Try to get a sane encoding. On MaxOSX locale.setlocale(locale.LC_ALL, '') does not have the desired effect, so that locale.getlocale()[1] still returns None. So in that case try to fallback to locale.getpreferredencoding(). *sys.getdefaultencoding()* - what Python itself uses to convert string <-> unicode when no other encoding was specified - ascii by default - can be set in site.py and sitecustomize.py *locale.getlocale()[1]* - what the current locale is *actually* using as the encoding for text conversion *locale.getpreferredencoding()* - what the current locale would *recommend* using as the encoding for text conversion """ global _current_encoding if _current_encoding is not None: return _current_encoding enc = sys.getdefaultencoding() if enc != 'ascii': _current_encoding = enc return _current_encoding enc = locale.getlocale()[1] if enc is not None: _current_encoding = enc return _current_encoding global _encoding_mismatch_already_logged if not _encoding_mismatch_already_logged: _log.debug('*actual* encoding of locale is None, using encoding *recommended* by locale') _encoding_mismatch_already_logged = True return locale.getpreferredencoding(do_setlocale = False)Try to get a sane encoding.
On MaxOSX locale.setlocale(locale.LC_ALL, '') does not have the desired effect, so that locale.getlocale()[1] still returns None. So in that case try to fallback to locale.getpreferredencoding().
sys.getdefaultencoding() - what Python itself uses to convert string <-> unicode when no other encoding was specified - ascii by default - can be set in site.py and sitecustomize.py locale.getlocale()[1] - what the current locale is actually using as the encoding for text conversion locale.getpreferredencoding() - what the current locale would recommend using as the encoding for text conversion
def install_domain(domain: str = None, language: str = None, prefer_local_catalog: bool = False) ‑> bool-
Expand source code
def install_domain(domain:str=None, language:str=None, prefer_local_catalog:bool=False) -> bool: """Install a text domain suitable for the main script. Args: domain: a named translation domain (typically the base name of the translation file), defaults to the python script's name language: a language code, as in the first part of a locale name, say, en_EN or de_DE, defaults to user locale language """ # text domain directly specified ? if domain is None: _log.info('domain not specified, deriving from script name') # get text domain from name of script domain = os.path.splitext(os.path.basename(sys.argv[0]))[0] _log.info('text domain is [%s]' % domain) # http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html _log.debug('searching message catalog file for system locale [%s]' % system_locale) _log.debug('checking process environment:') for env_var in ['LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG']: tmp = os.getenv(env_var) if env_var is None: _log.debug(' ${%s} not set' % env_var) else: _log.debug(' ${%s} = [%s]' % (env_var, tmp)) # language codes to try lang_candidates = [] # 1) explicit language or default system language if language: _log.info('explicit request for target language [%s]' % language) lang_candidates.append(language) # also try default language for user in case explicit language fails lang_candidates.append(None) else: # default language for user (locale.getlocale()[0] value) lang_candidates.append(None) # 2) try locale.getlocale()[0], if not yet in list # (this can be strange on, say, Windows: Hungarian_Hungary) if locale.getlocale()[0] not in lang_candidates: lang_candidates.append(locale.getlocale()[0]) # 3) add variants lang_variants = [] for lang in lang_candidates: if lang is None: continue cand = lang.split('.')[0] if cand not in lang_candidates: _log.debug('new language candidate: %s -> %s', lang, cand) lang_variants.append(cand) cand = lang.split('@')[0] if cand not in lang_candidates: _log.debug('new language candidate: %s -> %s', lang, cand) lang_variants.append(cand) cand = lang.split('_')[0] if cand not in lang_candidates: _log.debug('new language candidate: %s -> %s', lang, cand) lang_variants.append(cand) for lang in lang_variants: if lang in lang_candidates: continue lang_candidates.append(lang) _log.debug('languages to try for translation: %s (None: implicit system default)', lang_candidates) initial_lang = os.getenv('LANG') _log.info('initial ${LANG} setting: %s', initial_lang) # loop over language candidates for lang_candidate in lang_candidates: # setup baseline _log.debug('resetting ${LANG} to initial user default [%s]', initial_lang) if initial_lang is None: del os.environ['LANG'] lang2log = '$LANG=<>' else: os.environ['LANG'] = initial_lang lang2log = '$LANG(default)=%s' % initial_lang # setup candidate language if lang_candidate is not None: _log.info('explicitly overriding system locale language [%s] by setting ${LANG} to [%s]', initial_lang, lang_candidate) os.environ['LANG'] = lang_candidate lang2log = '$LANG(explicit)=%s' % lang_candidate if __install_domain(domain = domain, prefer_local_catalog = prefer_local_catalog, language = lang2log): return True # install a dummy translation class _log.warning("falling back to NullTranslations() class") # this shouldn't fail dummy = gettext.NullTranslations() dummy.install() return TrueInstall a text domain suitable for the main script.
Args
domain- a named translation domain (typically the base name of the translation file), defaults to the python script's name
language- a language code, as in the first part of a locale name, say, en_EN or de_DE, defaults to user locale language