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 True

Get 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 True

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