Skip to content

Commit eea6819

Browse files
author
Alexander Smishlajev
committed
translator objects now have the following search path:
- selected locale messages in the tracker locale dir - selected locale messages in the system locale dir - english messages in the tracker locale dir - english messages in the system locale dir automatically compile .mo files if needed (found .po file and .mo is missing or .po mtime is greater that .mo mtime) removed support for python < 2.0. gettext module is now required. get_translation: removed 'domain' argument, added 'tracker_home' argument
1 parent ef5af9a commit eea6819

File tree

1 file changed

+114
-89
lines changed

1 file changed

+114
-89
lines changed

roundup/i18n.py

Lines changed: 114 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: i18n.py,v 1.10 2004-07-14 07:27:21 a1s Exp $
18+
# $Id: i18n.py,v 1.11 2004-10-23 14:03:34 a1s Exp $
1919

2020
"""
2121
RoundUp Internationalization (I18N)
@@ -38,48 +38,19 @@
3838
__docformat__ = 'restructuredtext'
3939

4040
import errno
41+
import gettext as gettext_module
42+
import os
43+
44+
from roundup import msgfmt
4145

4246
# Roundup text domain
4347
DOMAIN = "roundup"
4448

45-
# first, we try to import gettext; this probably never fails, but we make
46-
# sure we survive this anyway
47-
try:
48-
import gettext as gettext_module
49-
except ImportError:
50-
gettext_module = None
51-
52-
# use or emulate features of gettext_module
53-
if not gettext_module:
54-
# no gettext engine available.
55-
# implement emulation for Translations class
56-
# and find_translation() function
57-
class RoundupNullTranslations:
58-
"""Dummy Translations class
59-
60-
Only methods used by Roundup are implemented.
61-
62-
"""
63-
def add_fallback(self, fallback):
64-
pass
65-
def gettext(self, text):
66-
return text
67-
def ugettext(self, text):
68-
return unicode(text)
69-
def ngettext(self, singular, plural, count):
70-
if count == 1: return singular
71-
return plural
72-
def ungettext(self, singular, plural, count):
73-
return unicode(self.ngettext(singular, plural, count))
74-
75-
RoundupTranslations = RoundupNullTranslations
76-
77-
def find_translation(domain, localedir=None, languages=None, class_=None):
78-
"""Always raise IOError (no message catalogs available)"""
79-
raise IOError(errno.ENOENT,
80-
"No translation file found for domain", domain)
81-
82-
elif not hasattr(gettext_module.GNUTranslations, "ngettext"):
49+
if hasattr(gettext_module.GNUTranslations, "ngettext"):
50+
# gettext_module has everything needed
51+
RoundupNullTranslations = gettext_module.NullTranslations
52+
RoundupTranslations = gettext_module.GNUTranslations
53+
else:
8354
# prior to 2.3, there was no plural forms. mix simple emulation in
8455
class PluralFormsMixIn:
8556
def ngettext(self, singular, plural, count):
@@ -102,16 +73,84 @@ class RoundupTranslations(
10273
gettext_module.GNUTranslations, PluralFormsMixIn
10374
):
10475
pass
105-
# lookup function is available
106-
find_translation = gettext_module.translation
107-
else:
108-
# gettext_module has everything needed
109-
RoundupNullTranslations = gettext_module.NullTranslations
110-
RoundupTranslations = gettext_module.GNUTranslations
111-
find_translation = gettext_module.translation
11276

77+
def find_locales(language=None):
78+
"""Return normalized list of locale names to try for given language
79+
80+
If language is None, use OS environment variables as a starting point
11381
114-
def get_translation(language=None, domain=DOMAIN,
82+
"""
83+
# body of this function is borrowed from gettext_module.find()
84+
if language is None:
85+
languages = []
86+
for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
87+
val = os.environ.get(envar)
88+
if val:
89+
languages = val.split(':')
90+
break
91+
else:
92+
languages = [language]
93+
# now normalize and expand the languages
94+
nelangs = []
95+
for lang in languages:
96+
for nelang in gettext_module._expand_lang(lang):
97+
if nelang not in nelangs:
98+
nelangs.append(nelang)
99+
return nelangs
100+
101+
def get_mofile(languages, localedir, domain=None):
102+
"""Return the first of .mo files found in localedir for languages
103+
104+
Parameters:
105+
languages:
106+
list of locale names to try
107+
localedir:
108+
path to directory containing locale files.
109+
Usually this is either gettext_module._default_localedir
110+
or 'locale' subdirectory in the tracker home.
111+
domain:
112+
optional name of messages domain.
113+
If omitted or None, work with simplified
114+
locale directory, as used in tracker homes:
115+
message catalogs are kept in files locale.po
116+
instead of locale/LC_MESSAGES/domain.po
117+
118+
Return the path of the first .mo file found.
119+
If nothing found, return None.
120+
121+
Automatically compile .po files if necessary.
122+
123+
"""
124+
for locale in languages:
125+
if locale == "C":
126+
break
127+
if domain:
128+
basename = os.path.join(localedir, locale, "LC_MESSAGES", domain)
129+
else:
130+
basename = os.path.join(localedir, locale)
131+
# look for message catalog files, check timestamps
132+
mofile = basename + ".mo"
133+
if os.path.isfile(mofile):
134+
motime = os.path.getmtime(mofile)
135+
else:
136+
motime = 0
137+
pofile = basename + ".po"
138+
if os.path.isfile(pofile):
139+
potime = os.path.getmtime(pofile)
140+
else:
141+
potime = 0
142+
# see what we've found
143+
if motime < potime:
144+
# compile
145+
msgfmt.make(pofile, mofile)
146+
elif motime == 0:
147+
# no files found - proceed to the next locale name
148+
continue
149+
# .mo file found or made
150+
return mofile
151+
return None
152+
153+
def get_translation(language=None, tracker_home=None,
115154
translation_class=RoundupTranslations,
116155
null_translation_class=RoundupNullTranslations
117156
):
@@ -120,51 +159,37 @@ def get_translation(language=None, domain=DOMAIN,
120159
Arguments 'translation_class' and 'null_translation_class'
121160
specify the classes that are instantiated for existing
122161
and non-existing translations, respectively.
162+
123163
"""
124-
if language:
125-
_languages = [language]
164+
mofiles = []
165+
# locale directory paths
166+
system_locale = gettext_module._default_localedir
167+
if tracker_home is not None:
168+
tracker_locale = os.path.join(tracker_home, "locale")
126169
else:
127-
# use OS environment
128-
_languages = None
129-
# except for english ("en") language, add english fallback if available
130-
if language == "en":
131-
_fallback = None
170+
tracker_locale = None
171+
# get the list of locales
172+
locales = find_locales(language)
173+
# add mofiles found in the tracker, then in the system locale directory
174+
if tracker_locale:
175+
mofiles.append(get_mofile(locales, tracker_locale))
176+
mofiles.append(get_mofile(locales, system_locale, DOMAIN))
177+
# we want to fall back to english unless english is selected language
178+
if "en" not in locales:
179+
locales = find_locales("en")
180+
# add mofiles found in the tracker, then in the system locale directory
181+
if tracker_locale:
182+
mofiles.append(get_mofile(locales, tracker_locale))
183+
mofiles.append(get_mofile(locales, system_locale, DOMAIN))
184+
# filter out elements that are not found
185+
mofiles = filter(None, mofiles)
186+
if mofiles:
187+
translator = translation_class(open(mofiles[0], "rb"))
188+
for mofile in mofiles:
189+
translator.add_fallback(translation_class(open(mofile, "rb")))
132190
else:
133-
try:
134-
_fallback = find_translation(domain=domain, languages=["en"],
135-
class_=translation_class)
136-
# gettext.translation returns a cached translation
137-
# even if it is not of the desired class.
138-
# This is a quick-and-dirty solution for this problem.
139-
# It works with current codebase, because all translators
140-
# inherit from respective base translation classes
141-
# defined in the gettext module, i.e. have same internal data.
142-
# The cached instance is not affected by this hack,
143-
# 'cause gettext made a copy for us.
144-
# XXX Consider making a copy of gettext.translation function
145-
# with class bug fixed...
146-
if _fallback.__class__ != translation_class:
147-
_fallback.__class__ = translation_class
148-
except IOError:
149-
# no .mo files found
150-
_fallback = None
151-
# get the translation
152-
try:
153-
_translation = find_translation(domain=domain, languages=_languages,
154-
class_=translation_class)
155-
# XXX See the comment after first find_translation() call
156-
if _translation.__class__ != translation_class:
157-
_translation.__class__ = translation_class
158-
except IOError:
159-
_translation = None
160-
# see what's found
161-
if _translation and _fallback:
162-
_translation.add_fallback(_fallback)
163-
elif _fallback:
164-
_translation = _fallback
165-
elif not _translation:
166-
_translation = null_translation_class()
167-
return _translation
191+
translator = null_translation_class()
192+
return translator
168193

169194
# static translations object
170195
translation = get_translation()

0 commit comments

Comments
 (0)