diff --git a/gramps/gen/const.py.in b/gramps/gen/const.py.in index a10460c04..487c22fcf 100644 --- a/gramps/gen/const.py.in +++ b/gramps/gen/const.py.in @@ -42,8 +42,6 @@ import uuid # Gramps modules # #------------------------------------------------------------------------- -from .ggettext import sgettext as _ -from .svn_revision import get_svn_revision #------------------------------------------------------------------------- # @@ -94,6 +92,12 @@ APP_GRAMPS_PKG = "application/x-gramps-package" APP_GENEWEB = "application/x-geneweb" APP_VCARD = ["text/x-vcard", "text/x-vcalendar"] +#------------------------------------------------------------------------- +# +# system paths +# +#------------------------------------------------------------------------- +LOCALE_DIR = "@LOCALE_DIR@" #------------------------------------------------------------------------- # # Platforms @@ -213,6 +217,15 @@ LOGO = os.path.join(IMAGE_DIR, "logo.png") SPLASH = os.path.join(IMAGE_DIR, "splash.jpg") LICENSE_FILE = os.path.join(DOC_DIR, 'COPYING') +#------------------------------------------------------------------------- +# +# Init Localization +# +#------------------------------------------------------------------------- +from .utils.grampslocale import GrampsLocale +GRAMPS_LOCALE = GrampsLocale() +from .ggettext import sgettext as _ +>>>>>>> GrampsLocale: Replace use of the GNU Gettext API with the Gettext Class API #------------------------------------------------------------------------- # @@ -326,6 +339,7 @@ SHORTOPTS = "O:C:i:e:f:a:p:d:c:lLhuv?s" GRAMPS_UUID = uuid.UUID('516cd010-5a41-470f-99f8-eb22f1098ad6') +<<<<<<< HEAD def need_to_update_const(): """ Check to see if this file is older than setup.py or const.py.in """ @@ -353,4 +367,9 @@ if need_to_update_const(): print("Outdated gramps.gen.const; please run 'python setup.py build'") GRAMPS_LOCALE = 0 +from .utils.grampslocale import GrampsLocale +GRAMPS_LOCALE = GrampsLocale() + +======= +>>>>>>> GrampsLocale: Replace use of the GNU Gettext API with the Gettext Class API diff --git a/gramps/gen/ggettext.py b/gramps/gen/ggettext.py index 2bf887300..cdfb0618b 100644 --- a/gramps/gen/ggettext.py +++ b/gramps/gen/ggettext.py @@ -30,65 +30,17 @@ This module ("Gramps Gettext") is an extension to the Python gettext module. # python modules # #------------------------------------------------------------------------ -import gettext as pgettext - -import sys -if sys.version_info[0] < 3: - cuni = unicode -else: - cuni = str - -def gettext(msgid): - """ - Obtain translation of gettext, return a unicode object - :param msgid: The string to translated. - :type msgid: unicode - :returns: Translation or the original. - :rtype: unicode - """ - # If msgid =="" then gettext will return po file header - # and that's not what we want. - if len(msgid.strip()) == 0: - return msgid - return cuni(pgettext.gettext(msgid)) - -def ngettext(singular, plural, n): - """ - The translation of singular/plural is returned unless the translation is - not available and the singular contains the separator. In that case, - the returned value is the singular. - - :param singular: The singular form of the string to be translated. - may contain a context seperator - :type singular: unicode - :param plural: The plural form of the string to be translated. - :type plural: unicode - :param n: the amount for which to decide the translation - :type n: int - :returns: Translation or the original. - :rtype: unicode - """ - return cuni(pgettext.ngettext(singular, plural, n)) - -def sgettext(msgid, sep='|'): - """ - Strip the context used for resolving translation ambiguities. - - The translation of msgid is returned unless the translation is - not available and the msgid contains the separator. In that case, - the returned value is the portion of msgid following the last - separator. Default separator is '|'. - - :param msgid: The string to translated. - :type msgid: unicode - :param sep: The separator marking the context. - :type sep: unicode - :returns: Translation or the original with context stripped. - :rtype: unicode - - """ - msgval = pgettext.gettext(msgid) - if msgval == msgid: - sep_idx = msgid.rfind(sep) - msgval = msgid[sep_idx+1:] - return cuni(msgval) +from gramps.gen.const import GRAMPS_LOCALE as _gl +_tl = _gl.get_translation() +gettext = _tl.gettext +# When in the 'C' locale, get_translation returns a NULLTranslation +# which doesn't provide sgettext. This traps that case and uses +# gettext instead -- which is fine, because there's no translation +# file involved and it's just going to return the msgid anyeay. +sgettext = None +try: + _tl.__getattr__(sgettext) + sgettext = _tl.sgettext +except AttributeError: + sgettext = _tl.gettext +ngettext = _tl.ngettext diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py index 7d6926277..25bf4ec57 100644 --- a/gramps/gen/plug/_pluginreg.py +++ b/gramps/gen/plug/_pluginreg.py @@ -42,9 +42,8 @@ import traceback # GRAMPS modules # #------------------------------------------------------------------------- -from ..const import VERSION as GRAMPSVERSION, VERSION_TUPLE +from ..const import VERSION as GRAMPSVERSION, VERSION_TUPLE, GRAMPS_LOCALE as glocale from ..const import IMAGE_DIR -from ..utils.grampslocale import get_addon_translator from ..ggettext import gettext as _ from ..constfunc import STRTYPE @@ -836,8 +835,9 @@ class PluginData(object): def _set_gramplet_title(self, gramplet_title): if not self._ptype == GRAMPLET: raise ValueError('gramplet_title may only be set for GRAMPLET plugins') - if not isinstance(gramplet_title, str): - raise ValueError('Plugin must have a string as gramplet_title') + if not (sys.version_info[0] < 3 and isinstance(gramplet_title, unicode) + or isinstance(gramplet_title, str)): + raise ValueError('gramplet_title is type %s, string or unicode required' % type(gramplet_title)) self._gramplet_title = gramplet_title def _get_gramplet_title(self): @@ -1091,7 +1091,7 @@ class PluginRegister(object): full_filename = os.path.join(dir, filename) if sys.version_info[0] < 3: full_filename = full_filename.encode(sys.getfilesystemencoding()) - local_gettext = get_addon_translator(full_filename).gettext + local_gettext = glocale.get_addon_translator(full_filename).gettext try: #execfile(full_filename, exec(compile(open(full_filename).read(), full_filename, 'exec'), diff --git a/gramps/gen/utils/file.py b/gramps/gen/utils/file.py index 19074c080..ba2c711c1 100644 --- a/gramps/gen/utils/file.py +++ b/gramps/gen/utils/file.py @@ -170,7 +170,7 @@ def get_unicode_path_from_env_var(path): """ # make only unicode of path of type 'str' if not (isinstance(path, str)): - return path + raise TypeError("path %s isn't a str" % str(path)) if win(): # In Windows path/filename returned from a environment variable is in filesystemencoding diff --git a/gramps/gen/utils/grampslocale.py b/gramps/gen/utils/grampslocale.py index 8b707542d..1961f822e 100644 --- a/gramps/gen/utils/grampslocale.py +++ b/gramps/gen/utils/grampslocale.py @@ -37,109 +37,45 @@ import logging # gramps modules # #------------------------------------------------------------------------- -from ..const import ROOT_DIR -from ..constfunc import mac, UNITYPE +from ..const import LOCALE_DIR +from ..constfunc import mac, win, UNITYPE -class GrampsLocale(locale): -""" -Encapsulate a locale -""" - def __init__(self): - def _get_prefix(self): - """ - Find the root path for share/locale - """ - if sys.platform == "win32": - if sys.prefix == os.path.dirname(os.getcwd()): - return sys.prefix - else: - return os.path.join(os.path.dirname(__file__), os.pardir) - elif sys.platform == "darwin" and sys.prefix != sys.exec_prefix: - return sys.prefix - else: - return os.path.join(os.path.dirname(__file__), os.pardir) - - def _init_gettext(self): - """ - Set up the gettext domain - """ -#the order in which bindtextdomain on gettext and on locale is called -#appears important, so we refrain from doing first all gettext. +#------------------------------------------------------------------------ # -#setup_gettext() - gettext.bindtextdomain(self.localedomain, self.localedir) - try: - locale.setlocale(locale.LC_ALL,'') - except: - logging.warning(_("WARNING: Setting locale failed. Please fix" - " the LC_* and/or the LANG environment " - "variables to prevent this error")) - try: - # It is probably not necessary to set the locale to 'C' - # because the locale will just stay at whatever it was, - # which at startup is "C". - # however this is done here just to make sure that the locale - # functions are working - locale.setlocale(locale.LC_ALL,'C') - except: - logging.warning(_("ERROR: Setting the 'C' locale didn't " - "work either")) - # FIXME: This should propagate the exception, - # if that doesn't break Gramps under Windows - raise - - gettext.textdomain(slef.localedomain) - if sys.version_info[0] < 3: - gettext.install(self.localedomain, localedir=None, unicode=1) #None is sys default locale - else: - gettext.install(self.localedomain, localedir=None) #None is sys default locale - - if hasattr(os, "uname"): - operating_system = os.uname()[0] - else: - operating_system = sys.platform - - if win(): # Windows - setup_windows_gettext() - elif operating_system == 'FreeBSD': - try: - gettext.bindtextdomain(self.localedomain, self.localedir) - except locale.Error: - logging.warning('No translation in some Gtk.Builder strings, ') - elif operating_system == 'OpenBSD': - pass - else: # normal case - try: - locale.bindtextdomain(self.localedomain, self.localedir) - #locale.textdomain(self.localedomain) - except locale.Error: - logging.warning('No translation in some Gtk.Builder strings, ') - - prefixdir = self._get_prefix() - if "GRAMPSI18N" in os.environ: - if os.path.exists(os.environ["GRAMPSI18N"]): - self.localedir = os.environ["GRAMPSI18N"] - else: - self.localedir = None - elif os.path.exists( os.path.join(ROOT_DIR, "lang") ): - self.localedir = os.path.join(ROOT_DIR, "lang") - elif os.path.exists(os.path.join(prefixdir, "share/locale")): - self.localedir = os.path.join(prefixdir, "share/locale") - else: - self.lang = os.environ.get('LANG', 'en') - if self.lang and self.lang[:2] == 'en': - pass # No need to display warning, we're in English - else: - logging.warning('Locale dir does not exist at ' + - os.path.join(prefixdir, "share/locale")) - logging.warning('Running python setup.py install --prefix=YourPrefixDir might fix the problem') +# GrampsLocale Class +# +#------------------------------------------------------------------------ +class GrampsLocale(object): + """ + Encapsulate a locale + """ + def __init__(self): self.localedir = None + self.lang = None + self.language = [] + if ("GRAMPSI18N" in os.environ + and os.path.exists(os.environ["GRAMPSI18N"])): + self.localedir = os.environ["GRAMPSI18N"] + elif os.path.exists(LOCALE_DIR): + self.localedir = LOCALE_DIR + elif os.path.exists(os.path.join(sys.prefix, "share", "locale")): + self.localedir = os.path.join(sys.prefix, "share", "locale") + else: + lang = os.environ.get('LANG', 'en') + if lang and lang[:2] == 'en': + pass # No need to display warning, we're in English + else: + logging.warning('Locale dir does not exist at %s', LOCALE_DIR) + logging.warning('Running python setup.py install --prefix=YourPrefixDir might fix the problem') + if not self.localedir: +#No localization files, no point in continuing + return self.localedomain = 'gramps' if mac(): from . import maclocale - maclocale.mac_setup_localization(self.localedir, self.localedomain) + (self.lang, self.language) = maclocale.mac_setup_localization(self) else: self.lang = ' ' try: @@ -151,25 +87,142 @@ Encapsulate a locale self.lang = locale.getdefaultlocale()[0] + '.UTF-8' except TypeError: logging.warning('Unable to determine your Locale, using English') - self.lang = 'en.UTF-8' + self.lang = 'C.UTF-8' - os.environ["LANG"] = self.lang - os.environ["LANGUAGE"] = self.lang - self._init_gettext() + if "LANGUAGE" in os.environ: + language = [l for l in os.environ["LANGUAGE"].split(":") + if l in self.get_available_translations()] + self.language = language + else: + self.language = [self.lang[0:2]] + +#GtkBuilder depends on reading Glade files as UTF-8 and crashes if it +#doesn't, so set $LANG to have a UTF-8 locale. NB: This does *not* +#affect locale.getpreferredencoding() or sys.getfilesystemencoding() +#which are set by python long before we get here. + check_lang = self.lang.split('.') + if len(check_lang) < 2 or check_lang[1] not in ["utf-8", "UTF-8"]: + self.lang = '.'.join((check_lang[0], 'UTF-8')) + os.environ["LANG"] = self.lang + # Set Gramps's translations + self.translation = self._get_translation(self.localedomain, self.localedir, self.language) + # Now set the locale for everything besides translations. + + try: + # First try the environment to preserve individual variables + locale.setlocale(locale.LC_ALL, '') + try: + #Then set LC_MESSAGES to self.lang + locale.setlocale(locale.LC_MESSAGES, self.lang) + except locale.Error: + logging.warning("Unable to set translations to %s, locale not found.", self.lang) + except locale.Error: + # That's not a valid locale -- on Linux, probably not installed. + try: + # First fallback is self.lang + locale.setlocale(locale.LC_ALL, self.lang) + logging.warning("Setting locale to individual LC_ variables failed, falling back to %s.", self.lang) + + except locale.Error: + # No good, set the default encoding to C.UTF-8. Don't + # mess with anything else. + locale.setlocale(locale.LC_ALL, 'C.UTF-8') + logging.error("Failed to set locale %s, falling back to English", self.lang) + # $LANGUAGE is what sets the Gtk+ translations + os.environ["LANGUAGE"] = ':'.join(self.language) + # GtkBuilder uses GLib's g_dgettext wrapper, which oddly is bound + # with locale instead of gettext. + locale.bindtextdomain(self.localedomain, self.localedir) #------------------------------------------------------------------------- # # Public Functions # #------------------------------------------------------------------------- - + def get_localedomain(self): """ Get the LOCALEDOMAIN used for the Gramps application. + Required by gui/glade.py to pass to Gtk.Builder """ return self.localedomain - def get_addon_translator(self, filename=None, domain="addon", + def _get_translation(self, domain = None, + localedir = None, + languages=None): + """ + Get a translation of one of our classes. Doesn't return the + singleton so that it can be used by get_addon_translation() + """ + if not domain: + domain = self.localedomain + if not languages: + languages = self.language + if not localedir: + localedir = self.localedir + + if gettext.find(domain, localedir, languages): + return gettext.translation(domain, localedir, + languages, + class_ = GrampsTranslations) + else: + logging.debug("Unable to find translations for %s and %s in %s" + , domain, languages, localedir) + return GrampsNullTranslations() + +#------------------------------------------------------------------------- +# +# Public Functions +# +#------------------------------------------------------------------------- + + def get_localedomain(self): + """ + Get the LOCALEDOMAIN used for the Gramps application. + Required by gui/glade.py to pass to Gtk.Builder + """ + return self.localedomain + + def get_language_list(self): + """ + Return the list of configured languages. Used by + ViewManager.check_for_updates to select the language for the + addons descriptions. + """ + return self.language + + def get_translation(self, domain = None, languages = None): + """ + Get a translation object for a particular language. + See the gettext documentation for the available functions + >>> glocale = GrampsLocale() + >>> _ = glocale.get_translation('foo', 'French') + >>> _ = tr.gettext + """ + + if ((domain and not domain == self.localedomain) + or (languages and not languages == self.language)): + if not domain: + domain = self.localedomain + if not languages: + languages = self.language + fallback = False + if "en" in languages: + fallback = True + try: + # Don't use _get_translation because we want to fall + # back on the singleton rather than a NullTranslation + return gettext.translation(domain, self.localedir, + languages, + class_ = GrampsTranslations, + fallback = fallback) + except IOError: + logging.warning("None of the requested languages (%s) were available, using %s instead", ', '.join(languages), self.lang) + return self.translation + else: + return self.translation + + def get_addon_translator(self, filename, domain="addon", languages=None): """ Get a translator for an addon. @@ -181,43 +234,32 @@ Encapsulate a locale returns - a gettext.translation object Example: - _ = get_addon_translator(languages=["fr_BE.utf8"]).gettext - - The return object has the following properties and methods: - .gettext - .info - .lgettext - .lngettext - .ngettext - .output_charset - .plural - .set_output_charset - .ugettext - .ungettext + _ = glocale.get_addon_translator(languages=["fr_BE.utf8"]).gettext + See the python gettext documentation. Assumes path/filename path/locale/LANG/LC_MESSAGES/addon.mo. """ - if filename is None: - filename = sys._getframe(1).f_code.co_filename - gramps_translator = gettext.translation(LOCALEDOMAIN, LOCALEDIR, - fallback=True) - path = os.path.dirname(os.path.abspath(filename)) + path = self.localedir + # If get the path of the calling module's uncompiled file. This seems a remarkably bad idea. +# if filename is None: +# filename = sys._getframe(1).f_code.co_filename + + gramps_translator = self._get_translation() + + path = os.path.dirname(os.path.abspath(filename)) # Check if path is of type str. Do import and conversion if so. # The import cannot be done at the top as that will conflict with the translation system. if not isinstance(path, UNITYPE) == str: from .file import get_unicode_path_from_env_var - path = get_unicode_path_from_env_var(path) - if languages: - addon_translator = gettext.translation(domain, - os.path.join(path, "locale"), - languages=languages, - fallback=True) - else: - addon_translator = gettext.translation(domain, - os.path.join(path, "locale"), - fallback=True) + path = get_unicode_path_from_env_var(path) + if languages: + addon_translator = self._get_translation(domain, + path, + languages=languages) + else: + addon_translator = self._get_translation(domain, path) gramps_translator.add_fallback(addon_translator) return gramps_translator # with a language fallback @@ -231,12 +273,13 @@ Encapsulate a locale """ languages = ["en"] - if slef.localedir is None: + if self.localedir is None: return languages for langdir in os.listdir(self.localedir): - mofilename = os.path.join(self.localedir, langdir, - "LC_MESSAGES", "%s.mo" % self.localedomain ) + mofilename = os.path.join(self.localedir, langdir, + "LC_MESSAGES", + "%s.mo" % self.localedomain ) if os.path.exists(mofilename): languages.append(langdir) @@ -249,7 +292,7 @@ Encapsulate a locale Translates objclass_str into "... %s", where objclass_str is 'Person', 'person', 'Family', 'family', etc. """ - from ..ggettext import gettext as _ + _ = self.translation.gettext objclass = objclass_str.lower() if objclass == "person": return _("the person") @@ -271,3 +314,184 @@ Encapsulate a locale return _("the filter") else: return _("See details") + + def getfilesystemencoding(self): + """ + If the locale isn't configured correctly, this will return + 'ascii' or 'ANSI_X3.4-1968' or some other unfortunate + result. Current unix systems all encode filenames in utf-8, + and Microsoft Windows uses utf-16 (which they call mbcs). Make + sure we return the right value. + """ + encoding = sys.getfilesystemencoding() + + if encoding in ("utf-8", "UTF-8", "utf8", "UTF8", "mbcs", "MBCS"): + return encoding + + return "utf-8" + +#------------------------------------------------------------------------- +# +# GrampsTranslation Class +# +#------------------------------------------------------------------------- +class GrampsTranslations(gettext.GNUTranslations): + """ + Overrides and extends gettext.GNUTranslations. See the Python gettext + "Class API" documentation for how to use this. + """ + def language(self): + """ + Return the target languge of this translations object. + """ + return self.info()["language"] + + def gettext(self, msgid): + """ + Obtain translation of gettext, return a unicode object + :param msgid: The string to translated. + :type msgid: unicode + :returns: Translation or the original. + :rtype: unicode + """ + # If msgid =="" then gettext will return po file header + # and that's not what we want. + if len(msgid.strip()) == 0: + return msgid + if sys.version_info[0] < 3: + return gettext.GNUTranslations.ugettext(self, msgid) + else: + return gettext.GNUTranslations.gettext(self, msgid) + + def ngettext(self, singular, plural, num): + """ + The translation of singular/plural is returned unless the translation is + not available and the singular contains the separator. In that case, + the returned value is the singular. + + :param singular: The singular form of the string to be translated. + may contain a context seperator + :type singular: unicode + :param plural: The plural form of the string to be translated. + :type plural: unicode + :param num: the amount for which to decide the translation + :type num: int + :returns: Translation or the original. + :rtype: unicode + """ + if sys.version_info[0] < 3: + return gettext.GNUTranslations.ungettext(self, singular, + plural, num) + else: + return gettext.GNUTranslations.ngettext(self, singular, + plural, num) + + def sgettext(self, msgid, sep='|'): + """ + Even with a null translator we need to filter out the translator hint. + """ + msgval = self.gettext(msgid) + if msgval == msgid: + sep_idx = msgid.rfind(sep) + msgval = msgid[sep_idx+1:] + return msgval + + +#------------------------------------------------------------------------- +# +# Translations Classes +# +#------------------------------------------------------------------------- +class GrampsTranslations(gettext.GNUTranslations): + """ + Overrides and extends gettext.GNUTranslations. See the Python gettext + "Class API" documentation for how to use this. + """ + def language(self): + """ + Return the target languge of this translations object. + """ + return self.info()["language"] + + def gettext(self, msgid): + """ + Obtain translation of gettext, return a unicode object + :param msgid: The string to translated. + :type msgid: unicode + :returns: Translation or the original. + :rtype: unicode + """ + # If msgid =="" then gettext will return po file header + # and that's not what we want. + if len(msgid.strip()) == 0: + return msgid + if sys.version_info[0] < 3: + return gettext.GNUTranslations.ugettext(self, msgid) + else: + return gettext.GNUTranslations.gettext(self, msgid) + + def ngettext(self, singular, plural, num): + """ + The translation of singular/plural is returned unless the translation is + not available and the singular contains the separator. In that case, + the returned value is the singular. + + :param singular: The singular form of the string to be translated. + may contain a context seperator + :type singular: unicode + :param plural: The plural form of the string to be translated. + :type plural: unicode + :param num: the amount for which to decide the translation + :type num: int + :returns: Translation or the original. + :rtype: unicode + """ + if sys.version_info[0] < 3: + return gettext.GNUTranslations.ungettext(self, singular, + plural, num) + else: + return gettext.GNUTranslations.ngettext(self, singular, + plural, num) + + def sgettext(self, msgid, sep='|'): + """ + Strip the context used for resolving translation ambiguities. + + The translation of msgid is returned unless the translation is + not available and the msgid contains the separator. In that case, + the returned value is the portion of msgid following the last + separator. Default separator is '|'. + + :param msgid: The string to translated. + :type msgid: unicode + :param sep: The separator marking the context. + :type sep: unicode + :returns: Translation or the original with context stripped. + :rtype: unicode + """ + msgval = self.gettext(msgid) + if msgval == msgid: + sep_idx = msgid.rfind(sep) + msgval = msgid[sep_idx+1:] + return msgval + + +class GrampsNullTranslations(gettext.NullTranslations): + """ + Extends gettext.NullTranslations to provide the sgettext method. + + Note that it's necessary for msgid to be unicode. If it's not, + neither will be the returned string. + """ + def sgettext(self, msgid): + msgval = self.gettext(msgid) + if msgval == msgid: + sep_idx = msgid.rfind(sep) + msgval = msgid[sep_idx+1:] + return msgval + + def language(self): + """ + The null translation returns the raw msgids, which are in English + """ + return "en" diff --git a/gramps/gen/utils/keyword.py b/gramps/gen/utils/keyword.py index 696d89dfd..27681920b 100644 --- a/gramps/gen/utils/keyword.py +++ b/gramps/gen/utils/keyword.py @@ -50,8 +50,8 @@ Keyword translation interface # 'n' : nickname = nick name # 'g' : familynick = family nick name -import gettext -_ = gettext.gettext +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.get_translation().gettext KEYWORDS = [("title", "t", _("Person|Title"), _("Person|TITLE")), ("given", "f", _("Given"), _("GIVEN")), diff --git a/gramps/gen/utils/maclocale.py b/gramps/gen/utils/maclocale.py index ffaf3c1a0..00d49bd65 100644 --- a/gramps/gen/utils/maclocale.py +++ b/gramps/gen/utils/maclocale.py @@ -73,36 +73,23 @@ locale, leaving $LANGUAGE unset (which is the same as setting it to import sys, os, subprocess -def get_available_translations(dir, domain): +def mac_setup_localization(glocale): """ - Get a list of available translations. - - :returns: A list of translation languages. - :rtype: unicode[] - + Set up the localization parameters from OSX's "defaults" system, + permitting environment variables to override the settings. """ - languages = ["en"] - - if dir is None: - return languages - - for langdir in os.listdir(dir): - mofilename = os.path.join( dir, langdir, - "LC_MESSAGES", "%s.mo" % domain ) - if os.path.exists(mofilename): - languages.append(langdir) - - languages.sort() - - return languages - -def mac_setup_localization(dir, domain): defaults = "/usr/bin/defaults" find = "/usr/bin/find" locale_dir = "/usr/share/locale" - available = get_available_translations(dir, domain) + if glocale: + available = glocale.get_available_translations() + else: + available = ['en'] def mac_language_list(): + """ + Extract the languages list from defaults. + """ languages = [] try: languages = subprocess.Popen( @@ -140,6 +127,9 @@ def mac_setup_localization(dir, domain): return usable def mac_get_locale(): + """ + Get the locale and specifiers from defaults. + """ locale = "" calendar = "" currency = "" @@ -177,6 +167,9 @@ def mac_setup_localization(dir, domain): return (locale, calendar, currency) def mac_get_collation(): + """ + Extract the collation (sort order) locale from the defaults string. + """ collation = "" try: collation = subprocess.Popen( @@ -196,11 +189,13 @@ def mac_setup_localization(dir, domain): return collation -# Locale.setlocale() will throw if any LC_* environment variable isn't -# a fully qualified one present in -# /usr/share/locale. mac_resolve_locale ensures that a locale meets -# that requirement. def mac_resolve_locale(loc): + """ + Locale.setlocale() will throw if any LC_* environment variable + isn't a fully qualified one present in + /usr/share/locale. mac_resolve_locale ensures that a locale + meets that requirement. + """ if len(loc) < 2: return None if len(loc) >= 5 and os.path.exists(os.path.join(locale_dir, loc[:5])): @@ -214,11 +209,10 @@ def mac_setup_localization(dir, domain): else: # OK, no, look through the translation list, but that's not likely # to be 5 letters long either - for l in translations: - if (l.startswith(loc) and len(l) >= 5 - and os.path.exists(os.path.join(locale_dir, l[:5]))): - return l[:5] - break + for _la in translations: + if (_la.startswith(loc) and len(_la) >= 5 + and os.path.exists(os.path.join(locale_dir, _la[:5]))): + return _la[:5] else: # so as a last resort, pick the first one for that language. @@ -237,20 +231,22 @@ def mac_setup_localization(dir, domain): collation = mac_get_collation() translations = mac_language_list() - if "LANGUAGE" not in os.environ: - if len(translations) > 0: - if "MULTI_TRANSLATION" in os.environ: - os.environ["LANGUAGE"] = ":".join(translations) - else: - os.environ["LANGUAGE"] = translations[0] - elif (len(loc) > 0 and loc in available - and not locale.starts_with("en")): - os.environ["LANGUAGE"] = locale - elif (len(collation) > 0 and collation in available - and not collation.starts_with("en")): - os.environ["LANGUAGE"] = collation + if currency and "LC_MONETARY" not in os.environ: + os.environ["LC_MONETARY"] = currency - if "LANG" not in os.environ: + if calendar and "LC_TIME" not in os.environ: + os.environ["LC_TIME"] = calendar + + if currency and "LC_MONETARY" not in os.environ: + os.environ["LC_MONETARY"] = currency + + + if calendar and "LC_TIME" not in os.environ: + os.environ["LC_TIME"] = calendar + + if "LANG" in os.environ: + lang = os.environ["LANG"] + else: lang = "en_US" loc = mac_resolve_locale(loc) if loc != None: @@ -261,6 +257,20 @@ def mac_setup_localization(dir, domain): elif len(collation) > 0: lang = mac_resolve_locale(collation) - if lang != None: - os.environ["LANG"] = lang - os.environ["LC_CTYPE"] = lang + ".UTF-8" + + if "LANGUAGE" in os.environ: + language = [l for l in os.environ["LANGUAGE"].split(":") + if l in available] + elif "LANG" in os.environ: + language = [lang[0:2]] + else: + if len(translations) > 0: + language = translations + elif (len(loc) > 0 and loc in available + and not loc.startswith("en")): + language = [loc] + elif (len(collation) > 0 and collation in available + and not collation.startswith("en")): + language = [collation] + + return (lang, language) diff --git a/gramps/grampsapp.py b/gramps/grampsapp.py index e86544ea5..31ee13fca 100644 --- a/gramps/grampsapp.py +++ b/gramps/grampsapp.py @@ -39,8 +39,7 @@ if sys.version_info[0] < 3: ## import os import signal -import gettext -_ = gettext.gettext + import locale import logging @@ -53,8 +52,9 @@ from subprocess import Popen, PIPE # GRAMPS modules # #------------------------------------------------------------------------- -from .gen.const import APP_GRAMPS, USER_DIRLIST, HOME_DIR, VERSION_TUPLE, GRAMPS_LOCALE +from .gen.const import APP_GRAMPS, USER_DIRLIST, HOME_DIR, VERSION_TUPLE from .gen.constfunc import win + #------------------------------------------------------------------------- # # Setup logging @@ -113,14 +113,15 @@ def exc_hook(type, value, tb): sys.excepthook = exc_hook from .gen.mime import mime_type_is_defined -from .gen.utils.grampslocale import GrampsLocale + #------------------------------------------------------------------------- # -# Load internationalization setup +# Instantiate Localization # #------------------------------------------------------------------------- -const.GRAMPS_LOCALE = GrampsLocale() +from .gen.const import GRAMPS_LOCALE as glocale +_ = glocale.get_translation().gettext #------------------------------------------------------------------------- # diff --git a/gramps/gui/clipboard.py b/gramps/gui/clipboard.py index 556f34315..73582a81e 100644 --- a/gramps/gui/clipboard.py +++ b/gramps/gui/clipboard.py @@ -50,14 +50,13 @@ from gi.repository import GdkPixbuf # gramps modules # #------------------------------------------------------------------------- -from gramps.gen.const import IMAGE_DIR, URL_MANUAL_PAGE +from gramps.gen.const import IMAGE_DIR, URL_MANUAL_PAGE, GRAMPS_LOCALE as glocale from gramps.gen.config import config from gramps.gen.lib import NoteType from gramps.gen.datehandler import get_date from .display import display_help from .managedwindow import ManagedWindow from gramps.gen.ggettext import sgettext as _ -from gramps.gen.utils.grampslocale import trans_objclass from gramps.gen.constfunc import mac from .glade import Glade from .ddtargets import DdTargets @@ -1470,13 +1469,13 @@ class MultiTreeView(Gtk.TreeView): objclass, handle = None, None if objclass in ['Person', 'Event', 'Media', 'Source', 'Repository', 'Family', 'Note', 'Place']: - menu_item = Gtk.MenuItem(label=_("the object|See %s details") % trans_objclass(objclass)) + menu_item = Gtk.MenuItem(label=_("the object|See %s details") % glocale.trans_objclass(objclass)) menu_item.connect("activate", lambda widget: self.edit_obj(objclass, handle)) popup.append(menu_item) menu_item.show() # --------------------------- - menu_item = Gtk.MenuItem(label=_("the object|Make %s active") % trans_objclass(objclass)) + menu_item = Gtk.MenuItem(label=_("the object|Make %s active") % glocale.trans_objclass(objclass)) menu_item.connect("activate", lambda widget: self.uistate.set_active(handle, objclass)) popup.append(menu_item) @@ -1492,7 +1491,7 @@ class MultiTreeView(Gtk.TreeView): obj = self.dbstate.db.get_table_metadata(objclass)["handle_func"](my_handle) if obj: gids.add(obj.gramps_id) - menu_item = Gtk.MenuItem(label=_("the object|Create Filter from %s selected...") % trans_objclass(objclass)) + menu_item = Gtk.MenuItem(label=_("the object|Create Filter from %s selected...") % glocale.trans_objclass(objclass)) menu_item.connect("activate", lambda widget: make_filter(self.dbstate, self.uistate, objclass, gids, title=self.title)) diff --git a/gramps/gui/glade.py b/gramps/gui/glade.py index aeb54a473..bcb1b2bb8 100644 --- a/gramps/gui/glade.py +++ b/gramps/gui/glade.py @@ -48,8 +48,7 @@ from gi.repository import Gtk # gramps modules # #------------------------------------------------------------------------ -from gramps.gen.const import GLADE_DIR -from gramps.gen.utils.grampslocale import LOCALEDOMAIN +from gramps.gen.const import GLADE_DIR, GRAMPS_LOCALE as glocale from gramps.gen.constfunc import STRTYPE #------------------------------------------------------------------------ @@ -82,7 +81,7 @@ class Glade(Gtk.Builder): :returns: reference to the newly-created Glade instance """ GObject.GObject.__init__(self) - self.set_translation_domain(LOCALEDOMAIN) + self.set_translation_domain(glocale.get_localedomain()) filename_given = filename is not None dirname_given = dirname is not None diff --git a/gramps/gui/plug/quick/_quicktable.py b/gramps/gui/plug/quick/_quicktable.py index e22c12cfd..10c868097 100644 --- a/gramps/gui/plug/quick/_quicktable.py +++ b/gramps/gui/plug/quick/_quicktable.py @@ -52,9 +52,9 @@ from gi.repository import Gtk # Gramps modules # #------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.ggettext import sgettext as _ from gramps.gen.simple import SimpleTable -from gramps.gen.utils.grampslocale import trans_objclass from gramps.gen.errors import WindowActiveError from ...widgets.multitreeview import MultiTreeView from ...ddtargets import DdTargets @@ -127,7 +127,7 @@ class QuickTable(SimpleTable): if (index is not None and self._link[index]): # See details (edit, etc): objclass, handle = self._link[index] - menu_item = Gtk.MenuItem(label=_("the object|See %s details") % trans_objclass(objclass)) + menu_item = Gtk.MenuItem(label=_("the object|See %s details") % glocale.trans_objclass(objclass)) menu_item.connect("activate", lambda widget: self.on_table_doubleclick(treeview)) popup.append(menu_item) @@ -137,7 +137,7 @@ class QuickTable(SimpleTable): (index is not None and self._link[index])): objclass, handle = self._link[index] if objclass == 'Person': - menu_item = Gtk.MenuItem(label=_("the object|Make %s active") % trans_objclass('Person')) + menu_item = Gtk.MenuItem(label=_("the object|Make %s active") % glocale.trans_objclass('Person')) menu_item.connect("activate", lambda widget: self.on_table_click(treeview)) popup.append(menu_item) diff --git a/gramps/plugins/lib/libtranslate.py b/gramps/plugins/lib/libtranslate.py index 7ae412726..9c582267d 100644 --- a/gramps/plugins/lib/libtranslate.py +++ b/gramps/plugins/lib/libtranslate.py @@ -8,7 +8,7 @@ # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # -# This program is distributed in the hope that it will be useful, +# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. @@ -29,15 +29,15 @@ Translator class for use by plugins. # python modules # #------------------------------------------------------------------------ -import gettext -_ = gettext.gettext +from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.ggettext import gettext as _ #------------------------------------------------------------------------ # # GRAMPS modules # #------------------------------------------------------------------------ -from gramps.gen.utils.grampslocale import get_localedomain +from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.datehandler import displayer, LANG_TO_DISPLAY from gramps.gen.config import config from gramps.gen.lib.grampstype import GrampsType @@ -97,15 +97,15 @@ _COUNTRY_MAP = { #------------------------------------------------------------------------ def get_language_string(lang_code): """ - Given a language code of the form "lang_region", return a text string + Given a language code of the form "lang_region", return a text string representing that language. """ code_parts = lang_code.rsplit("_") - + lang = code_parts[0] if lang in _LANG_MAP: lang = _LANG_MAP[lang] - + if len(code_parts) > 1: country = code_parts[1] if country in _COUNTRY_MAP: @@ -114,7 +114,7 @@ def get_language_string(lang_code): { 'language' : lang, 'country' : country } else: retstr = lang - + return retstr #------------------------------------------------------------------------- @@ -122,62 +122,57 @@ def get_language_string(lang_code): # Translator # #------------------------------------------------------------------------- -class Translator: +class Translator(object): """ This class provides translated strings for the configured language. """ DEFAULT_TRANSLATION_STR = "default" - + def __init__(self, lang=DEFAULT_TRANSLATION_STR): """ - :param lang: The language to translate to. + :param lang: The language to translate to. The language can be: * The name of any installed .mo file * "en" to use the message strings in the code * "default" to use the default translation being used by gettext. :type lang: string :return: nothing - + """ if lang == Translator.DEFAULT_TRANSLATION_STR: - self.__trans = None + self.__trans = glocale.get_translation() self.__dd = displayer else: - # fallback=True will cause the translator to use English if - # lang = "en" or if something goes wrong. - self.__trans = gettext.translation(get_localedomain(), - languages=[lang], - fallback=True) + # If lang isn't supported, this will fallback to the + # current global language + self.__trans = glocale.get_translation(languages=[lang]) val = config.get('preferences.date-format') if lang in LANG_TO_DISPLAY: self.__dd = LANG_TO_DISPLAY[lang](val) else: self.__dd = displayer - + def gettext(self, message): """ Return the unicode translated string. - + :param message: The message to be translated. :type message: string :returns: The translated message :rtype: unicode - + """ - if self.__trans is None: - return cuni(gettext.gettext(message)) - else: - return self.__trans.ugettext(message) - + return self.__trans.gettext(message) + def ngettext(self, singular, plural, n): """ Return the unicode translated singular/plural string. - + The translation of singular/plural is returned unless the translation is not available and the singular contains the separator. In that case, the returned value is the portion of singular following the last separator. Default separator is '|'. - + :param singular: The singular form of the string to be translated. may contain a context separator :type singular: unicode @@ -187,53 +182,49 @@ class Translator: :type n: int :returns: The translated singular/plural message :rtype: unicode - + """ - if self.__trans is None: - return cuni(gettext.ngettext(singular, plural, n)) - else: - return self.__trans.ungettext(singular, plural, n) - + return self.__trans.ngettext(singular, plural, n) + def sgettext(self, msgid, sep='|'): """ Strip the context used for resolving translation ambiguities. - + The translation of msgid is returned unless the translation is not available and the msgid contains the separator. In that case, the returned value is the portion of msgid following the last separator. Default separator is '|'. - + :param msgid: The string to translated. :type msgid: unicode :param sep: The separator marking the context. :type sep: unicode :returns: Translation or the original with context stripped. :rtype: unicode - + """ - msgval = self.gettext(msgid) - if msgval == msgid: - sep_idx = msgid.rfind(sep) - msgval = msgid[sep_idx+1:] - return cuni(msgval) - + try: + return self.__trans.sgettext(msgid) + except AttributeError: + return self.__trans.gettext(msgid) + def get_date(self, date): """ Return a string representing the date appropriate for the language being translated. - + :param date: The date to be represented. :type date: :class:`~gen.lib.date.Date` :returns: The date as text in the proper language. :rtype: unicode """ return self.__dd.display(date) - + def get_type(self, name): """ Return a string representing the name appropriate for the language being translated. - + :param name: The name type to be represented. :returns: The name as text in the proper language. :rtype: unicode diff --git a/gramps/plugins/textreport/ancestorreport.py b/gramps/plugins/textreport/ancestorreport.py index b109b9ddf..68d74138a 100644 --- a/gramps/plugins/textreport/ancestorreport.py +++ b/gramps/plugins/textreport/ancestorreport.py @@ -39,6 +39,7 @@ from gramps.gen.ggettext import gettext as _ # gramps modules # #------------------------------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.display.name import displayer as global_name_display from gramps.gen.errors import ReportError from gramps.gen.lib import ChildRefType @@ -50,7 +51,6 @@ from gramps.gen.plug.docgen import (IndexMark, FontStyle, ParagraphStyle, from gramps.gen.plug.report import Report from gramps.gen.plug.report import utils as ReportUtils from gramps.gen.plug.report import MenuReportOptions -from gramps.gen.utils.grampslocale import get_available_translations from gramps.plugins.lib.libnarrate import Narrator from gramps.plugins.lib.libtranslate import Translator, get_language_string @@ -299,7 +299,7 @@ class AncestorOptions(MenuReportOptions): trans = EnumeratedListOption(_("Translation"), Translator.DEFAULT_TRANSLATION_STR) trans.add_item(Translator.DEFAULT_TRANSLATION_STR, _("Default")) - for language in get_available_translations(): + for language in glocale.get_available_translations(): trans.add_item(language, get_language_string(language)) trans.set_help(_("The translation to be used for the report.")) menu.add_option(category_name, "trans", trans) diff --git a/gramps/plugins/textreport/detancestralreport.py b/gramps/plugins/textreport/detancestralreport.py index 575a7f5db..d12195c84 100644 --- a/gramps/plugins/textreport/detancestralreport.py +++ b/gramps/plugins/textreport/detancestralreport.py @@ -42,6 +42,7 @@ from gramps.gen.ggettext import gettext as _ # GRAMPS modules # #------------------------------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.display.name import displayer as global_name_display from gramps.gen.errors import ReportError from gramps.gen.lib import EventType, FamilyRelType, Person, NoteType @@ -54,7 +55,6 @@ from gramps.gen.plug.report import endnotes from gramps.gen.plug.report import utils as ReportUtils from gramps.gen.plug.report import MenuReportOptions from gramps.plugins.lib.libnarrate import Narrator -from gramps.gen.utils.grampslocale import get_available_translations from gramps.plugins.lib.libtranslate import Translator, get_language_string #------------------------------------------------------------------------ @@ -753,7 +753,7 @@ class DetAncestorOptions(MenuReportOptions): trans = EnumeratedListOption(_("Translation"), Translator.DEFAULT_TRANSLATION_STR) trans.add_item(Translator.DEFAULT_TRANSLATION_STR, _("Default")) - for language in get_available_translations(): + for language in glocale.get_available_translations(): trans.add_item(language, get_language_string(language)) trans.set_help(_("The translation to be used for the report.")) addopt("trans", trans) diff --git a/gramps/plugins/textreport/detdescendantreport.py b/gramps/plugins/textreport/detdescendantreport.py index de74c1e77..09b11bd75 100644 --- a/gramps/plugins/textreport/detdescendantreport.py +++ b/gramps/plugins/textreport/detdescendantreport.py @@ -45,6 +45,7 @@ from functools import partial # GRAMPS modules # #------------------------------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.display.name import displayer as global_name_display from gramps.gen.errors import ReportError from gramps.gen.lib import FamilyRelType, Person, NoteType @@ -58,7 +59,6 @@ from gramps.gen.plug.report import endnotes from gramps.gen.plug.report import utils as ReportUtils from gramps.gen.plug.report import MenuReportOptions from gramps.plugins.lib.libnarrate import Narrator -from gramps.gen.utils.grampslocale import get_available_translations from gramps.plugins.lib.libtranslate import Translator, get_language_string #------------------------------------------------------------------------ @@ -928,7 +928,7 @@ class DetDescendantOptions(MenuReportOptions): trans = EnumeratedListOption(_("Translation"), Translator.DEFAULT_TRANSLATION_STR) trans.add_item(Translator.DEFAULT_TRANSLATION_STR, _("Default")) - for language in get_available_translations(): + for language in glocale.get_available_translations(): trans.add_item(language, get_language_string(language)) trans.set_help(_("The translation to be used for the report.")) add_option("trans", trans)