1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
26 from osv import fields, osv
27 from locale import localeconv
29 from tools.safe_eval import safe_eval as eval
30 from tools.translate import _
32 _logger = logging.getLogger(__name__)
36 _description = "Languages"
38 _disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys()
39 _disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice
41 def install_lang(self, cr, uid, **args):
44 This method is called from openerp/addons/base/base_data.xml to load
45 some language and set it as the default for every partners. The
46 language is set via tools.config by the RPC 'create' method on the
47 'db' object. This is a fragile solution and something else should be
51 lang = tools.config.get('lang')
54 lang_ids = self.search(cr, uid, [('code','=', lang)])
56 self.load_lang(cr, uid, lang)
57 ir_values_obj = self.pool.get('ir.values')
58 default_value = ir_values_obj.get(cr, uid, 'default', False, ['res.partner'])
60 ir_values_obj.set(cr, uid, 'default', False, 'lang', ['res.partner'], lang)
63 def load_lang(self, cr, uid, lang, lang_name=None):
64 # create the language with locale information
66 iso_lang = tools.get_iso_codes(lang)
67 for ln in tools.get_locales(lang):
69 locale.setlocale(locale.LC_ALL, str(ln))
75 lc = locale.getdefaultlocale()[0]
76 msg = 'Unable to get information for locale %s. Information from the default locale (%s) have been used.'
77 _logger.warning(msg, lang, lc)
80 lang_name = tools.ALL_LANGUAGES.get(lang, lang)
84 """Fix badly-encoded non-breaking space Unicode character from locale.localeconv(),
85 coercing to utf-8, as some platform seem to output localeconv() in their system
86 encoding, e.g. Windows-1252"""
91 def fix_datetime_format(format):
92 """Python's strftime supports only the format directives
93 that are available on the platform's libc, so in order to
94 be 100% cross-platform we map to the directives required by
95 the C standard (1989 version), always available on platforms
96 with a C standard implementation."""
97 for pattern, replacement in tools.DATETIME_FORMATS_MAP.iteritems():
98 format = format.replace(pattern, replacement)
103 'iso_code': iso_lang,
106 'date_format' : fix_datetime_format(locale.nl_langinfo(locale.D_FMT)),
107 'time_format' : fix_datetime_format(locale.nl_langinfo(locale.T_FMT)),
108 'decimal_point' : fix_xa0(str(locale.localeconv()['decimal_point'])),
109 'thousands_sep' : fix_xa0(str(locale.localeconv()['thousands_sep'])),
113 lang_id = self.create(cr, uid, lang_info)
118 def _check_format(self, cr, uid, ids, context=None):
119 for lang in self.browse(cr, uid, ids, context=context):
120 for pattern in self._disallowed_datetime_patterns:
121 if (lang.time_format and pattern in lang.time_format)\
122 or (lang.date_format and pattern in lang.date_format):
126 def _get_default_date_format(self, cursor, user, context=None):
129 def _get_default_time_format(self, cursor, user, context=None):
133 'name': fields.char('Name', size=64, required=True),
134 'code': fields.char('Locale Code', size=16, required=True, help='This field is used to set/get locales for user'),
135 'iso_code': fields.char('ISO code', size=16, required=False, help='This ISO code is the name of po files to use for translations'),
136 'translatable': fields.boolean('Translatable'),
137 'active': fields.boolean('Active'),
138 'direction': fields.selection([('ltr', 'Left-to-Right'), ('rtl', 'Right-to-Left')], 'Direction',required=True),
139 'date_format':fields.char('Date Format',size=64,required=True),
140 'time_format':fields.char('Time Format',size=64,required=True),
141 'grouping':fields.char('Separator Format',size=64,required=True,help="The Separator Format should be like [,n] where 0 < n :starting from Unit digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. Provided ',' as the thousand separator in each case."),
142 'decimal_point':fields.char('Decimal Separator', size=64,required=True),
143 'thousands_sep':fields.char('Thousands Separator',size=64),
146 'active': lambda *a: 1,
147 'translatable': lambda *a: 0,
148 'direction': lambda *a: 'ltr',
149 'date_format':_get_default_date_format,
150 'time_format':_get_default_time_format,
151 'grouping':lambda *a: '[]',
152 'decimal_point':lambda *a: '.',
153 'thousands_sep':lambda *a: ',',
156 ('name_uniq', 'unique (name)', 'The name of the language must be unique !'),
157 ('code_uniq', 'unique (code)', 'The code of the language must be unique !'),
161 (_check_format, 'Invalid date/time format directive specified. Please refer to the list of allowed directives, displayed when you edit a language.', ['time_format', 'date_format'])
164 @tools.ormcache(skiparg=3)
165 def _lang_data_get(self, cr, uid, lang_id, monetary=False):
167 lang_obj = self.browse(cr, uid, lang_id)
168 thousands_sep = lang_obj.thousands_sep or conv[monetary and 'mon_thousands_sep' or 'thousands_sep']
169 decimal_point = lang_obj.decimal_point
170 grouping = lang_obj.grouping
171 return (grouping, thousands_sep, decimal_point)
173 def write(self, cr, uid, ids, vals, context=None):
175 self._lang_data_get.clear_cache(self)
176 return super(lang, self).write(cr, uid, ids, vals, context)
178 def unlink(self, cr, uid, ids, context=None):
181 languages = self.read(cr, uid, ids, ['code','active'], context=context)
182 for language in languages:
183 ctx_lang = context.get('lang')
184 if language['code']=='en_US':
185 raise osv.except_osv(_('User Error'), _("Base Language 'en_US' can not be deleted !"))
186 if ctx_lang and (language['code']==ctx_lang):
187 raise osv.except_osv(_('User Error'), _("You cannot delete the language which is User's Preferred Language !"))
188 if language['active']:
189 raise osv.except_osv(_('User Error'), _("You cannot delete the language which is Active !\nPlease de-activate the language first."))
190 trans_obj = self.pool.get('ir.translation')
191 trans_ids = trans_obj.search(cr, uid, [('lang','=',language['code'])], context=context)
192 trans_obj.unlink(cr, uid, trans_ids, context=context)
193 return super(lang, self).unlink(cr, uid, ids, context=context)
195 def format(self, cr, uid, ids, percent, value, grouping=False, monetary=False, context=None):
196 """ Format() will return the language-specific output for float values"""
198 if percent[0] != '%':
199 raise ValueError("format() must be given exactly one %char format specifier")
201 lang_grouping, thousands_sep, decimal_point = self._lang_data_get(cr, uid, ids[0], monetary)
202 eval_lang_grouping = eval(lang_grouping)
204 formatted = percent % value
205 # floats and decimal ints need special action!
206 if percent[-1] in 'eEfFgG':
208 parts = formatted.split('.')
211 parts[0], seps = intersperse(parts[0], eval_lang_grouping, thousands_sep)
213 formatted = decimal_point.join(parts)
215 sp = formatted.find(' ')
217 formatted = formatted[:sp] + formatted[sp+1:]
219 elif percent[-1] in 'diu':
221 formatted = intersperse(formatted, eval_lang_grouping, thousands_sep)[0]
225 # import re, operator
226 # _percent_re = re.compile(r'%(?:\((?P<key>.*?)\))?'
227 # r'(?P<modifiers>[-#0-9 +*.hlL]*?)[eEfFgGdiouxXcrs%]')
231 def original_group(s, grouping, thousands_sep=''):
245 while s and grouping:
246 # if grouping is -1, we are done
247 if grouping[0] == -1:
249 # 0: re-use last group ad infinitum
250 elif grouping[0] != 0:
253 grouping = grouping[1:]
255 result = s[-group:] + thousands_sep + result
260 if s and s[-1] not in "0123456789":
261 # the leading string is only spaces and signs
262 return s + result + spaces, seps
264 return s + spaces, seps
266 result = s + thousands_sep + result
268 return result + spaces, seps
270 def split(l, counts):
273 >>> split("hello world", [])
275 >>> split("hello world", [1])
277 >>> split("hello world", [2])
279 >>> split("hello world", [2,3])
280 ['he', 'llo', ' world']
281 >>> split("hello world", [2,3,0])
282 ['he', 'llo', ' wo', 'rld']
283 >>> split("hello world", [2,-1,3])
288 saved_count = len(l) # count to use when encoutering a zero
296 res.append(l[:saved_count])
299 res.append(l[:count])
306 intersperse_pat = re.compile('([^0-9]*)([^ ]*)(.*)')
308 def intersperse(string, counts, separator=''):
311 See the asserts below for examples.
314 left, rest, right = intersperse_pat.match(string).groups()
315 def reverse(s): return s[::-1]
316 splits = split(reverse(rest), counts)
317 res = separator.join(map(reverse, reverse(splits)))
318 return left + res + right, len(splits) > 0 and len(splits) -1 or 0
320 # TODO rewrite this with a unit test library
321 def _group_examples():
322 for g in [original_group, intersperse]:
323 # print "asserts on", g.func_name
324 assert g("", []) == ("", 0)
325 assert g("0", []) == ("0", 0)
326 assert g("012", []) == ("012", 0)
327 assert g("1", []) == ("1", 0)
328 assert g("12", []) == ("12", 0)
329 assert g("123", []) == ("123", 0)
330 assert g("1234", []) == ("1234", 0)
331 assert g("123456789", []) == ("123456789", 0)
332 assert g("&ab%#@1", []) == ("&ab%#@1", 0)
334 assert g("0", []) == ("0", 0)
335 assert g("0", [1]) == ("0", 0)
336 assert g("0", [2]) == ("0", 0)
337 assert g("0", [200]) == ("0", 0)
339 # breaks original_group:
340 if g.func_name == 'intersperse':
341 assert g("12345678", [0], '.') == ('12345678', 0)
342 assert g("", [1], '.') == ('', 0)
343 assert g("12345678", [1], '.') == ('1234567.8', 1)
344 assert g("12345678", [1], '.') == ('1234567.8', 1)
345 assert g("12345678", [2], '.') == ('123456.78', 1)
346 assert g("12345678", [2,1], '.') == ('12345.6.78', 2)
347 assert g("12345678", [2,0], '.') == ('12.34.56.78', 3)
348 assert g("12345678", [-1,2], '.') == ('12345678', 0)
349 assert g("12345678", [2,-1], '.') == ('123456.78', 1)
350 assert g("12345678", [2,0,1], '.') == ('12.34.56.78', 3)
351 assert g("12345678", [2,0,0], '.') == ('12.34.56.78', 3)
352 assert g("12345678", [2,0,-1], '.') == ('12.34.56.78', 3)
353 assert g("12345678", [3,3,3,3], '.') == ('12.345.678', 2)
355 assert original_group("abc1234567xy", [2], '.') == ('abc1234567.xy', 1)
356 assert original_group("abc1234567xy8", [2], '.') == ('abc1234567xy8', 0) # difference here...
357 assert original_group("abc12", [3], '.') == ('abc12', 0)
358 assert original_group("abc12", [2], '.') == ('abc12', 0)
359 assert original_group("abc12", [1], '.') == ('abc1.2', 1)
361 assert intersperse("abc1234567xy", [2], '.') == ('abc1234567.xy', 1)
362 assert intersperse("abc1234567xy8", [2], '.') == ('abc1234567x.y8', 1) # ... w.r.t. here.
363 assert intersperse("abc12", [3], '.') == ('abc12', 0)
364 assert intersperse("abc12", [2], '.') == ('abc12', 0)
365 assert intersperse("abc12", [1], '.') == ('abc1.2', 1)
368 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: