Localization: Re-implement localization as a class, GrampsLocale

GrampsLocale is effectively a singleton: An instance is created in
const.py and retrieved everywhere.

Translations are provided via Translations classes, which are derived
from GNUTranslations and NullTranslations to provide extra functions
like sgettext.

svn: r21143
This commit is contained in:
John Ralls 2013-01-17 19:42:11 +00:00
parent eecf57a0f6
commit d3c2a8a490
15 changed files with 517 additions and 322 deletions

View File

@ -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

View File

@ -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

View File

@ -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'),

View File

@ -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

View File

@ -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"

View File

@ -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")),

View File

@ -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)

View File

@ -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
#-------------------------------------------------------------------------
#

View File

@ -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))

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)