Code

use config.DATABASE in cases where 'db' was still hard-coded
[roundup.git] / roundup / i18n.py
1 #
2 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
3 # This module is free software, and you may redistribute it and/or modify
4 # under the same terms as Python, so long as this copyright message and
5 # disclaimer are retained in their original form.
6 #
7 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
8 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
9 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
10 # POSSIBILITY OF SUCH DAMAGE.
11 #
12 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17 #
18 # $Id: i18n.py,v 1.15 2005-06-14 05:33:32 a1s Exp $
20 """
21 RoundUp Internationalization (I18N)
23 To use this module, the following code should be used::
25     from roundup.i18n import _
26     ...
27     print _("Some text that can be translated")
29 Note that to enable re-ordering of inserted texts in formatting strings
30 (which can easily happen if a sentence has to be re-ordered due to
31 grammatical changes), translatable formats should use named format specs::
33     ... _('Index of %(classname)s') % {'classname': cn} ...
35 Also, this eases the job of translators since they have some context what
36 the dynamic portion of a message really means.
37 """
38 __docformat__ = 'restructuredtext'
40 import errno
41 import gettext as gettext_module
42 import os
44 from roundup import msgfmt
46 # List of directories for mo file search (see SF bug 1219689)
47 LOCALE_DIRS = [
48     gettext_module._default_localedir,
49 ]
50 # compute mo location relative to roundup installation directory
51 # (prefix/lib/python/site-packages/roundup/msgfmt.py on posix systems,
52 # prefix/lib/site-packages/roundup/msgfmt.py on windows).
53 # locale root is prefix/share/locale.
54 if os.name == "nt":
55     _mo_path = [".."] * 4 + ["share", "locale"]
56 else:
57     _mo_path = [".."] * 5 + ["share", "locale"]
58 _mo_path = os.path.normpath(os.path.join(msgfmt.__file__, *_mo_path))
59 if _mo_path not in LOCALE_DIRS:
60     LOCALE_DIRS.append(_mo_path)
61 del _mo_path
63 # Roundup text domain
64 DOMAIN = "roundup"
66 if hasattr(gettext_module.GNUTranslations, "ngettext"):
67     # gettext_module has everything needed
68     RoundupNullTranslations = gettext_module.NullTranslations
69     RoundupTranslations = gettext_module.GNUTranslations
70 else:
71     # prior to 2.3, there was no plural forms.  mix simple emulation in
72     class PluralFormsMixIn:
73         def ngettext(self, singular, plural, count):
74             if count == 1:
75                 _msg = singular
76             else:
77                 _msg = plural
78             return self.gettext(_msg)
79         def ungettext(self, singular, plural, count):
80             if count == 1:
81                 _msg = singular
82             else:
83                 _msg = plural
84             return self.ugettext(_msg)
85     class RoundupNullTranslations(
86         gettext_module.NullTranslations, PluralFormsMixIn
87     ):
88         pass
89     class RoundupTranslations(
90         gettext_module.GNUTranslations, PluralFormsMixIn
91     ):
92         pass
94 def find_locales(language=None):
95     """Return normalized list of locale names to try for given language
97     Argument 'language' may be a single language code or a list of codes.
98     If 'language' is omitted or None, use locale settings in OS environment.
100     """
101     # body of this function is borrowed from gettext_module.find()
102     if language is None:
103         languages = []
104         for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
105             val = os.environ.get(envar)
106             if val:
107                 languages = val.split(':')
108                 break
109     elif isinstance(language, str) or  isinstance(language, unicode):
110         languages = [language]
111     else:
112         # 'language' must be iterable
113         languages = language
114     # now normalize and expand the languages
115     nelangs = []
116     for lang in languages:
117         for nelang in gettext_module._expand_lang(lang):
118             if nelang not in nelangs:
119                 nelangs.append(nelang)
120     return nelangs
122 def get_mofile(languages, localedir, domain=None):
123     """Return the first of .mo files found in localedir for languages
125     Parameters:
126         languages:
127             list of locale names to try
128         localedir:
129             path to directory containing locale files.
130             Usually this is either gettext_module._default_localedir
131             or 'locale' subdirectory in the tracker home.
132         domain:
133             optional name of messages domain.
134             If omitted or None, work with simplified
135             locale directory, as used in tracker homes:
136             message catalogs are kept in files locale.po
137             instead of locale/LC_MESSAGES/domain.po
139     Return the path of the first .mo file found.
140     If nothing found, return None.
142     Automatically compile .po files if necessary.
144     """
145     for locale in languages:
146         if locale == "C":
147             break
148         if domain:
149             basename = os.path.join(localedir, locale, "LC_MESSAGES", domain)
150         else:
151             basename = os.path.join(localedir, locale)
152         # look for message catalog files, check timestamps
153         mofile = basename + ".mo"
154         if os.path.isfile(mofile):
155             motime = os.path.getmtime(mofile)
156         else:
157             motime = 0
158         pofile = basename + ".po"
159         if os.path.isfile(pofile):
160             potime = os.path.getmtime(pofile)
161         else:
162             potime = 0
163         # see what we've found
164         if motime < potime:
165             # compile
166             msgfmt.make(pofile, mofile)
167         elif motime == 0:
168             # no files found - proceed to the next locale name
169             continue
170         # .mo file found or made
171         return mofile
172     return None
174 def get_translation(language=None, tracker_home=None,
175     translation_class=RoundupTranslations,
176     null_translation_class=RoundupNullTranslations
177 ):
178     """Return Translation object for given language and domain
180     Argument 'language' may be a single language code or a list of codes.
181     If 'language' is omitted or None, use locale settings in OS environment.
183     Arguments 'translation_class' and 'null_translation_class'
184     specify the classes that are instantiated for existing
185     and non-existing translations, respectively.
187     """
188     mofiles = []
189     # locale directory paths
190     if tracker_home is None:
191         tracker_locale = None
192     else:
193         tracker_locale = os.path.join(tracker_home, "locale")
194     # get the list of locales
195     locales = find_locales(language)
196     # add mofiles found in the tracker, then in the system locale directory
197     if tracker_locale:
198         mofiles.append(get_mofile(locales, tracker_locale))
199     for system_locale in LOCALE_DIRS:
200         mofiles.append(get_mofile(locales, system_locale, DOMAIN))
201     # we want to fall back to english unless english is selected language
202     if "en" not in locales:
203         locales = find_locales("en")
204         # add mofiles found in the tracker, then in the system locale directory
205         if tracker_locale:
206             mofiles.append(get_mofile(locales, tracker_locale))
207         for system_locale in LOCALE_DIRS:
208             mofiles.append(get_mofile(locales, system_locale, DOMAIN))
209     # filter out elements that are not found
210     mofiles = filter(None, mofiles)
211     if mofiles:
212         translator = translation_class(open(mofiles[0], "rb"))
213         for mofile in mofiles[1:]:
214             # note: current implementation of gettext_module
215             #   always adds fallback to the end of the fallback chain.
216             translator.add_fallback(translation_class(open(mofile, "rb")))
217     else:
218         translator = null_translation_class()
219     return translator
221 # static translations object
222 translation = get_translation()
223 # static translation functions
224 _ = gettext = translation.gettext
225 ugettext = translation.ugettext
226 ngettext = translation.ngettext
227 ungettext = translation.ungettext
229 # vim: set filetype=python sts=4 sw=4 et si :