Premiere version : mise en route du suivi.
[auf_roundup.git] / build / lib / 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 $
19
20 """
21 RoundUp Internationalization (I18N)
22
23 To use this module, the following code should be used::
24
25 from roundup.i18n import _
26 ...
27 print _("Some text that can be translated")
28
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::
32
33 ... _('Index of %(classname)s') % {'classname': cn} ...
34
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'
39
40 import errno
41 import gettext as gettext_module
42 import os
43
44 from roundup import msgfmt
45
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
62
63 # Roundup text domain
64 DOMAIN = "roundup"
65
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
93
94 def find_locales(language=None):
95 """Return normalized list of locale names to try for given language
96
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.
99
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
121
122 def get_mofile(languages, localedir, domain=None):
123 """Return the first of .mo files found in localedir for languages
124
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
138
139 Return the path of the first .mo file found.
140 If nothing found, return None.
141
142 Automatically compile .po files if necessary.
143
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
173
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
179
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.
182
183 Arguments 'translation_class' and 'null_translation_class'
184 specify the classes that are instantiated for existing
185 and non-existing translations, respectively.
186
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
220
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
228
229 # vim: set filetype=python sts=4 sw=4 et si :