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.
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.
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.
18 # $Id: i18n.py,v 1.15 2005-06-14 05:33:32 a1s Exp $
21 RoundUp Internationalization (I18N)
23 To use this module, the following code should be used::
25 from roundup.i18n import _
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.
38 __docformat__
= 'restructuredtext'
41 import gettext
as gettext_module
44 from roundup
import msgfmt
46 # List of directories for mo file search (see SF bug 1219689)
48 gettext_module
._default_localedir
,
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.
55 _mo_path
= [".."] * 4 + ["share", "locale"]
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
)
66 if hasattr(gettext_module
.GNUTranslations
, "ngettext"):
67 # gettext_module has everything needed
68 RoundupNullTranslations
= gettext_module
.NullTranslations
69 RoundupTranslations
= gettext_module
.GNUTranslations
71 # prior to 2.3, there was no plural forms. mix simple emulation in
72 class PluralFormsMixIn
:
73 def ngettext(self
, singular
, plural
, count
):
78 return self
.gettext(_msg
)
79 def ungettext(self
, singular
, plural
, count
):
84 return self
.ugettext(_msg
)
85 class RoundupNullTranslations(
86 gettext_module
.NullTranslations
, PluralFormsMixIn
89 class RoundupTranslations(
90 gettext_module
.GNUTranslations
, PluralFormsMixIn
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.
101 # body of this function is borrowed from gettext_module.find()
104 for envar
in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
105 val
= os
.environ
.get(envar
)
107 languages
= val
.split(':')
109 elif isinstance(language
, str) or isinstance(language
, unicode):
110 languages
= [language
]
112 # 'language' must be iterable
114 # now normalize and expand the languages
116 for lang
in languages
:
117 for nelang
in gettext_module
._expand_lang(lang
):
118 if nelang
not in nelangs
:
119 nelangs
.append(nelang
)
122 def get_mofile(languages
, localedir
, domain
=None):
123 """Return the first of .mo files found in localedir for languages
127 list of locale names to try
129 path to directory containing locale files.
130 Usually this is either gettext_module._default_localedir
131 or 'locale' subdirectory in the tracker home.
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.
145 for locale
in languages
:
149 basename
= os
.path
.join(localedir
, locale
, "LC_MESSAGES", domain
)
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
)
158 pofile
= basename
+ ".po"
159 if os
.path
.isfile(pofile
):
160 potime
= os
.path
.getmtime(pofile
)
163 # see what we've found
166 msgfmt
.make(pofile
, mofile
)
168 # no files found - proceed to the next locale name
170 # .mo file found or made
174 def get_translation(language
=None, tracker_home
=None,
175 translation_class
=RoundupTranslations
,
176 null_translation_class
=RoundupNullTranslations
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.
189 # locale directory paths
190 if tracker_home
is None:
191 tracker_locale
= None
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
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
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
)
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")))
218 translator
= null_translation_class()
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 :