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 ##############################################################################
32 from os.path import join
35 from datetime import datetime
36 from lxml import etree
40 from tools.misc import UpdateableStr
43 'af_ZA': 'Afrikaans_South Africa',
44 'sq_AL': 'Albanian_Albania',
45 'ar_SA': 'Arabic_Saudi Arabia',
46 'eu_ES': 'Basque_Spain',
47 'be_BY': 'Belarusian_Belarus',
48 'bs_BA': 'Serbian (Latin)',
49 'bg_BG': 'Bulgarian_Bulgaria',
50 'ca_ES': 'Catalan_Spain',
51 'hr_HR': 'Croatian_Croatia',
52 'zh_CN': 'Chinese_China',
53 'zh_TW': 'Chinese_Taiwan',
54 'cs_CZ': 'Czech_Czech Republic',
55 'da_DK': 'Danish_Denmark',
56 'nl_NL': 'Dutch_Netherlands',
57 'et_EE': 'Estonian_Estonia',
58 'fa_IR': 'Farsi_Iran',
59 'ph_PH': 'Filipino_Philippines',
60 'fi_FI': 'Finnish_Finland',
61 'fr_FR': 'French_France',
62 'fr_BE': 'French_France',
63 'fr_CH': 'French_France',
64 'fr_CA': 'French_France',
65 'ga': 'Scottish Gaelic',
66 'gl_ES': 'Galician_Spain',
67 'ka_GE': 'Georgian_Georgia',
68 'de_DE': 'German_Germany',
69 'el_GR': 'Greek_Greece',
70 'gu': 'Gujarati_India',
71 'he_IL': 'Hebrew_Israel',
73 'hu': 'Hungarian_Hungary',
74 'is_IS': 'Icelandic_Iceland',
75 'id_ID': 'Indonesian_indonesia',
76 'it_IT': 'Italian_Italy',
77 'ja_JP': 'Japanese_Japan',
80 'ko_KR': 'Korean_Korea',
82 'lt_LT': 'Lithuanian_Lithuania',
83 'lat': 'Latvian_Latvia',
84 'ml_IN': 'Malayalam_India',
85 'id_ID': 'Indonesian_indonesia',
87 'mn': 'Cyrillic_Mongolian',
88 'no_NO': 'Norwegian_Norway',
89 'nn_NO': 'Norwegian-Nynorsk_Norway',
90 'pl': 'Polish_Poland',
91 'pt_PT': 'Portuguese_Portugal',
92 'pt_BR': 'Portuguese_Brazil',
93 'ro_RO': 'Romanian_Romania',
94 'ru_RU': 'Russian_Russia',
96 'sr_CS': 'Serbian (Cyrillic)_Serbia and Montenegro',
97 'sk_SK': 'Slovak_Slovakia',
98 'sl_SI': 'Slovenian_Slovenia',
99 'es_ES': 'Spanish_Spain',
100 'sv_SE': 'Swedish_Sweden',
101 'ta_IN': 'English_Australia',
102 'th_TH': 'Thai_Thailand',
104 'tr_TR': 'Turkish_Turkey',
105 'uk_UA': 'Ukrainian_Ukraine',
106 'vi_VN': 'Vietnamese_Viet Nam',
107 'tlh_TLH': 'Klingon',
112 class UNIX_LINE_TERMINATOR(csv.excel):
113 lineterminator = '\n'
115 csv.register_dialect("UNIX", UNIX_LINE_TERMINATOR)
118 # Warning: better use self.pool.get('ir.translation')._get_source if you can
120 def translate(cr, name, source_type, lang, source=None):
122 cr.execute('select value from ir_translation where lang=%s and type=%s and name=%s and src=%s', (lang, source_type, str(name), source))
124 cr.execute('select value from ir_translation where lang=%s and type=%s and name=%s', (lang, source_type, str(name)))
126 cr.execute('select value from ir_translation where lang=%s and type=%s and src=%s', (lang, source_type, source))
127 res_trans = cr.fetchone()
128 res = res_trans and res_trans[0] or False
131 logger = logging.getLogger('translate')
133 class GettextAlias(object):
135 def _get_cr(self, frame):
137 cr = frame.f_locals.get('cr')
139 s = frame.f_locals.get('self', {})
140 cr = getattr(s, 'cr', False)
142 if frame.f_globals.get('pooler', False):
143 # TODO: we should probably get rid of the 'is_new_cr' case: no cr in locals -> no translation for you
144 dbs = frame.f_globals['pooler'].pool_dic.keys()
146 cr = pooler.get_db(dbs[0]).cursor()
150 def _get_lang(self, frame):
151 lang = frame.f_locals.get('context', {}).get('lang', False)
153 args = frame.f_locals.get('args', False)
155 lang = args[-1].get('lang', False)
157 s = frame.f_locals.get('self', {})
158 c = getattr(s, 'localcontext', {})
159 lang = c.get('lang', False)
162 def __call__(self, source):
166 frame = inspect.stack()[1][0]
167 cr, is_new_cr = self._get_cr(frame)
168 lang = self._get_lang(frame)
170 cr.execute('SELECT value FROM ir_translation WHERE lang=%s AND type IN (%s, %s) AND src=%s', (lang, 'code','sql_constraint', source))
171 res_trans = cr.fetchone()
172 res = res_trans and res_trans[0] or source
174 logger.warn('translation went wrong for string %s', repr(source))
183 # class to handle po files
184 class TinyPoFile(object):
185 def __init__(self, buffer):
186 self.logger = netsvc.Logger()
190 self.logger.notifyChannel("i18n", netsvc.LOG_WARNING, msg)
194 self.lines = self._get_lines()
195 self.lines_count = len(self.lines);
201 def _get_lines(self):
202 lines = self.buffer.readlines()
203 # remove the BOM (Byte Order Mark):
205 lines[0] = unicode(lines[0], 'utf8').lstrip(unicode( codecs.BOM_UTF8, "utf8"))
207 lines.append('') # ensure that the file ends with at least an empty line
211 return (self.lines_count - len(self.lines))
215 return str[1:-1].replace("\\n", "\n") \
216 .replace("\\\\ ", "\\ ") \
219 type = name = res_id = source = trad = None
222 type, name, res_id, source, trad = self.tnrs.pop(0)
228 if 0 == len(self.lines):
229 raise StopIteration()
230 line = self.lines.pop(0).strip()
231 while line.startswith('#'):
232 if line.startswith('#~ '):
234 if line.startswith('#:'):
235 if ' ' in line[2:].strip():
236 for lpart in line[2:].strip().split(' '):
237 tmp_tnrs.append(lpart.strip().split(':',2))
239 tmp_tnrs.append( line[2:].strip().split(':',2) )
240 elif line.startswith('#,') and (line[2:].strip() == 'fuzzy'):
242 line = self.lines.pop(0).strip()
244 # allow empty lines between comments and msgid
245 line = self.lines.pop(0).strip()
246 if line.startswith('#~ '):
247 while line.startswith('#~ ') or not line.strip():
248 if 0 == len(self.lines):
249 raise StopIteration()
250 line = self.lines.pop(0)
251 # This has been a deprecated entry, don't return anything
254 if not line.startswith('msgid'):
255 raise Exception("malformed file: bad line: %s" % line)
256 source = unquote(line[6:])
257 line = self.lines.pop(0).strip()
258 if not source and self.first:
259 # if the source is "" and it's the first msgid, it's the special
260 # msgstr with the informations about the traduction and the
261 # traductor; we skip it
264 line = self.lines.pop(0).strip()
267 while not line.startswith('msgstr'):
269 raise Exception('malformed file at %d'% self.cur_line())
270 source += unquote(line)
271 line = self.lines.pop(0).strip()
273 trad = unquote(line[7:])
274 line = self.lines.pop(0).strip()
276 trad += unquote(line)
277 line = self.lines.pop(0).strip()
279 if tmp_tnrs and not fuzzy:
280 type, name, res_id = tmp_tnrs.pop(0)
281 for t, n, r in tmp_tnrs:
282 self.tnrs.append((t, n, r, source, trad))
287 self.warn('Missing "#:" formated comment for the following source:\n\t%s' % (source,))
289 return type, name, res_id, source, trad
291 def write_infos(self, modules):
293 self.buffer.write("# Translation of %(project)s.\n" \
294 "# This file contains the translation of the following modules:\n" \
299 '''"Project-Id-Version: %(project)s %(version)s\\n"\n''' \
300 '''"Report-Msgid-Bugs-To: %(bugmail)s\\n"\n''' \
301 '''"POT-Creation-Date: %(now)s\\n"\n''' \
302 '''"PO-Revision-Date: %(now)s\\n"\n''' \
303 '''"Last-Translator: <>\\n"\n''' \
304 '''"Language-Team: \\n"\n''' \
305 '''"MIME-Version: 1.0\\n"\n''' \
306 '''"Content-Type: text/plain; charset=UTF-8\\n"\n''' \
307 '''"Content-Transfer-Encoding: \\n"\n''' \
308 '''"Plural-Forms: \\n"\n''' \
311 % { 'project': release.description,
312 'version': release.version,
313 'modules': reduce(lambda s, m: s + "#\t* %s\n" % m, modules, ""),
314 'bugmail': release.support_email,
315 'now': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')+"+0000",
319 def write(self, modules, tnrs, source, trad):
321 return '"%s"' % s.replace('"','\\"') \
322 .replace('\n', '\\n"\n"') \
323 .replace(' \\ ',' \\\\ ')
326 plurial = len(modules) > 1 and 's' or ''
327 self.buffer.write("#. module%s: %s\n" % (plurial, ', '.join(modules)))
331 for typy, name, res_id in tnrs:
332 self.buffer.write("#: %s:%s:%s\n" % (typy, name, res_id))
337 # only strings in python code are python formated
338 self.buffer.write("#, python-format\n")
340 if not isinstance(trad, unicode):
341 trad = unicode(trad, 'utf8')
342 if not isinstance(source, unicode):
343 source = unicode(source, 'utf8')
347 % (quote(source), quote(trad))
348 self.buffer.write(msg.encode('utf8'))
351 # Methods to export the translation file
353 def trans_export(lang, modules, buffer, format, dbname=None):
355 def _process(format, modules, rows, buffer, lang, newlang):
357 writer=csv.writer(buffer, 'UNIX')
362 writer = tools.TinyPoFile(buffer)
363 writer.write_infos(modules)
365 # we now group the translations by source. That means one translation per source.
367 for module, type, name, res_id, src, trad in rows:
368 row = grouped_rows.setdefault(src, {})
369 row.setdefault('modules', set()).add(module)
370 if ('translation' not in row) or (not row['translation']):
371 row['translation'] = trad
372 row.setdefault('tnrs', []).append((type, name, res_id))
374 for src, row in grouped_rows.items():
375 writer.write(row['modules'], row['tnrs'], src, row['translation'])
377 elif format == 'tgz':
382 rows_by_module.setdefault(module, []).append(row)
384 tmpdir = tempfile.mkdtemp()
385 for mod, modrows in rows_by_module.items():
386 tmpmoddir = join(tmpdir, mod, 'i18n')
387 os.makedirs(tmpmoddir)
388 pofilename = (newlang and mod or lang) + ".po" + (newlang and 't' or '')
389 buf = file(join(tmpmoddir, pofilename), 'w')
390 _process('po', [mod], modrows, buf, lang, newlang)
393 tar = tarfile.open(fileobj=buffer, mode='w|gz')
398 raise Exception(_('Bad file format'))
400 newlang = not bool(lang)
403 trans = trans_generate(lang, modules, dbname)
404 if newlang and format!='csv':
407 modules = set([t[0] for t in trans[1:]])
408 _process(format, modules, trans, buffer, lang, newlang)
412 def trans_parse_xsl(de):
416 for m in [j for j in n if j.text]:
417 l = m.text.strip().replace('\n',' ')
419 res.append(l.encode("utf8"))
420 res.extend(trans_parse_xsl(n))
423 def trans_parse_rml(de):
426 for m in [j for j in n if j.text]:
427 string_list = [s.replace('\n', ' ').strip() for s in re.split('\[\[.+?\]\]', m.text)]
428 for s in string_list:
430 res.append(s.encode("utf8"))
431 res.extend(trans_parse_rml(n))
434 def trans_parse_view(de):
437 res.append(de.get('string').encode("utf8"))
439 res.append(de.get('sum').encode("utf8"))
441 res.extend(trans_parse_view(n))
444 # tests whether an object is in a list of modules
445 def in_modules(object_name, modules):
454 module = object_name.split('.')[0]
455 module = module_dict.get(module, module)
456 return module in modules
458 def trans_generate(lang, modules, dbname=None):
459 logger = netsvc.Logger()
461 dbname=tools.config['db_name']
465 pool = pooler.get_pool(dbname)
466 trans_obj = pool.get('ir.translation')
467 model_data_obj = pool.get('ir.model.data')
468 cr = pooler.get_db(dbname).cursor()
470 l = pool.obj_pool.items()
473 query = 'SELECT name, model, res_id, module' \
474 ' FROM ir_model_data'
475 if 'all_installed' in modules:
476 query += ' WHERE module IN ( SELECT name FROM ir_module_module WHERE state = \'installed\') '
478 if 'all' not in modules:
479 query += ' WHERE module IN %s'
480 query_param = (tuple(modules),)
481 query += ' ORDER BY module, model, name'
483 cr.execute(query, query_param)
486 def push_translation(module, type, name, id, source):
487 tuple = (module, source, name, id, type)
488 if source and tuple not in _to_translate:
489 _to_translate.append(tuple)
492 if isinstance(s, unicode):
493 return s.encode('utf8')
496 for (xml_name,model,res_id,module) in cr.fetchall():
497 module = encode(module)
498 model = encode(model)
499 xml_name = "%s.%s" % (module, encode(xml_name))
501 if not pool.get(model):
502 logger.notifyChannel("db", netsvc.LOG_ERROR, "Unable to find object %r" % (model,))
505 exists = pool.get(model).exists(cr, uid, res_id)
507 logger.notifyChannel("db", netsvc.LOG_WARNING, "Unable to find object %r with id %d" % (model, res_id))
509 obj = pool.get(model).browse(cr, uid, res_id)
511 if model=='ir.ui.view':
512 d = etree.XML(encode(obj.arch))
513 for t in trans_parse_view(d):
514 push_translation(module, 'view', encode(obj.model), 0, t)
515 elif model=='ir.actions.wizard':
516 service_name = 'wizard.'+encode(obj.wiz_name)
517 if netsvc.Service._services.get(service_name):
518 obj2 = netsvc.Service._services[service_name]
519 for state_name, state_def in obj2.states.iteritems():
520 if 'result' in state_def:
521 result = state_def['result']
522 if result['type'] != 'form':
524 name = "%s,%s" % (encode(obj.wiz_name), state_name)
527 'string': ('wizard_field', lambda s: [encode(s)]),
528 'selection': ('selection', lambda s: [encode(e[1]) for e in ((not callable(s)) and s or [])]),
529 'help': ('help', lambda s: [encode(s)]),
533 if not result.has_key('fields'):
534 logger.notifyChannel("db",netsvc.LOG_WARNING,"res has no fields: %r" % result)
536 for field_name, field_def in result['fields'].iteritems():
537 res_name = name + ',' + field_name
539 for fn in def_params:
541 transtype, modifier = def_params[fn]
542 for val in modifier(field_def[fn]):
543 push_translation(module, transtype, res_name, 0, val)
546 arch = result['arch']
547 if arch and not isinstance(arch, UpdateableStr):
549 for t in trans_parse_view(d):
550 push_translation(module, 'wizard_view', name, 0, t)
552 # export button labels
553 for but_args in result['state']:
554 button_name = but_args[0]
555 button_label = but_args[1]
556 res_name = name + ',' + button_name
557 push_translation(module, 'wizard_button', res_name, 0, button_label)
559 elif model=='ir.model.fields':
561 field_name = encode(obj.name)
562 except AttributeError, exc:
563 logger.notifyChannel("db", netsvc.LOG_ERROR, "name error in %s: %s" % (xml_name,str(exc)))
565 objmodel = pool.get(obj.model)
566 if not objmodel or not field_name in objmodel._columns:
568 field_def = objmodel._columns[field_name]
570 name = "%s,%s" % (encode(obj.model), field_name)
571 push_translation(module, 'field', name, 0, encode(field_def.string))
574 push_translation(module, 'help', name, 0, encode(field_def.help))
576 if field_def.translate:
577 ids = objmodel.search(cr, uid, [])
578 obj_values = objmodel.read(cr, uid, ids, [field_name])
579 for obj_value in obj_values:
580 res_id = obj_value['id']
581 if obj.name in ('ir.model', 'ir.ui.menu'):
583 model_data_ids = model_data_obj.search(cr, uid, [
584 ('model', '=', model),
585 ('res_id', '=', res_id),
587 if not model_data_ids:
588 push_translation(module, 'model', name, 0, encode(obj_value[field_name]))
590 if hasattr(field_def, 'selection') and isinstance(field_def.selection, (list, tuple)):
591 for dummy, val in field_def.selection:
592 push_translation(module, 'selection', name, 0, encode(val))
594 elif model=='ir.actions.report.xml':
595 name = encode(obj.report_name)
598 fname = obj.report_rml
599 parse_func = trans_parse_rml
600 report_type = "report"
602 fname = obj.report_xsl
603 parse_func = trans_parse_xsl
605 if fname and obj.report_type in ('pdf', 'xsl'):
607 d = etree.parse(tools.file_open(fname))
608 for t in parse_func(d.iter()):
609 push_translation(module, report_type, name, 0, t)
610 except (IOError, etree.XMLSyntaxError):
611 logging.getLogger("i18n").exception("couldn't export translation for report %s %s %s", name, report_type, fname)
613 for constraint in pool.get(model)._constraints:
615 # Check presence of __call__ directly instead of using
616 # callable() because it will be deprecated as of Python 3.0
617 if not hasattr(msg, '__call__'):
618 push_translation(module, 'constraint', model, 0, encode(msg))
620 for field_name,field_def in pool.get(model)._columns.items():
621 if field_def.translate:
622 name = model + "," + field_name
624 trad = getattr(obj, field_name) or ''
627 push_translation(module, 'model', name, xml_name, encode(trad))
629 # parse source code for _() calls
630 def get_module_from_path(path, mod_paths=None):
632 ## First, construct a list of possible paths
633 # def_path = os.path.abspath(os.path.join(tools.config['root_path'], 'addons')) # default addons path (base)
634 # ad_paths= map(lambda m: os.path.abspath(m.strip()),tools.config['addons_path'].split(','))
635 # mod_paths=[def_path]
636 # for adp in ad_paths:
637 # mod_paths.append(adp)
638 # if not adp.startswith('/'):
639 # mod_paths.append(os.path.join(def_path,adp))
640 # elif adp.startswith(def_path):
641 # mod_paths.append(adp[len(def_path)+1:])
642 # for mp in mod_paths:
643 # if path.startswith(mp) and (os.path.dirname(path) != mp):
644 # path = path[len(mp)+1:]
645 # return path.split(os.path.sep)[0]
646 path_dir = os.path.dirname(path[1:])
648 if os.path.exists(os.path.join(tools.config['addons_path'],path[1:])):
649 return path.split(os.path.sep)[1]
651 root_addons = os.path.join(tools.config['root_path'], 'addons')
652 if os.path.exists(os.path.join(root_addons,path[1:])):
653 return path.split(os.path.sep)[1]
654 return 'base' # files that are not in a module are considered as being in 'base' module
656 modobj = pool.get('ir.module.module')
657 installed_modids = modobj.search(cr, uid, [('state', '=', 'installed')])
658 installed_modules = map(lambda m: m['name'], modobj.read(cr, uid, installed_modids, ['name']))
660 root_path = os.path.join(tools.config['root_path'], 'addons')
662 if root_path in tools.config['addons_path'] :
663 path_list = [root_path]
665 path_list = [root_path,tools.config['addons_path']]
667 def export_code_terms_from_file(fname, path, root, terms_type):
668 fabsolutepath = join(root, fname)
669 frelativepath = fabsolutepath[len(path):]
670 module = get_module_from_path(frelativepath)
671 is_mod_installed = module in installed_modules
672 if (('all' in modules) or (module in modules)) and is_mod_installed:
673 code_string = tools.file_open(fabsolutepath, subdir='').read()
674 iter = re.finditer('[^a-zA-Z0-9_]_\([\s]*["\'](.+?)["\'][\s]*\)', code_string, re.S)
675 if module in installed_modules:
676 frelativepath = str("addons" + frelativepath)
678 push_translation(module, terms_type, frelativepath, 0, encode(i.group(1)))
680 for path in path_list:
681 for root, dummy, files in tools.osutil.walksymlinks(path):
682 for fname in itertools.chain(fnmatch.filter(files, '*.py')):
683 export_code_terms_from_file(fname, path, root, 'code')
684 for fname in itertools.chain(fnmatch.filter(files, '*.mako')):
685 export_code_terms_from_file(fname, path, root, 'report')
688 out = [["module","type","name","res_id","src","value"]] # header
690 # translate strings marked as to be translated
691 for module, source, name, id, type in _to_translate:
692 trans = trans_obj._get_source(cr, uid, name, type, lang, source)
693 out.append([module, type, name, id, source, encode(trans) or ''])
698 def trans_load(db_name, filename, lang, strict=False, verbose=True):
699 logger = netsvc.Logger()
701 fileobj = open(filename,'r')
702 fileformat = os.path.splitext(filename)[-1][1:].lower()
703 r = trans_load_data(db_name, fileobj, fileformat, lang, strict=strict, verbose=verbose)
708 logger.notifyChannel("i18n", netsvc.LOG_ERROR, "couldn't read translation file %s" % (filename,))
711 def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name=None, verbose=True):
712 logger = netsvc.Logger()
714 logger.notifyChannel("i18n", netsvc.LOG_INFO, 'loading translation file for language %s' % (lang))
715 pool = pooler.get_pool(db_name)
716 lang_obj = pool.get('res.lang')
717 trans_obj = pool.get('ir.translation')
718 model_data_obj = pool.get('ir.model.data')
719 iso_lang = tools.get_iso_codes(lang)
722 cr = pooler.get_db(db_name).cursor()
723 ids = lang_obj.search(cr, uid, [('code','=', lang)])
726 # lets create the language with locale information
728 for ln in get_locales(lang):
730 locale.setlocale(locale.LC_ALL, str(ln))
736 lc = locale.getdefaultlocale()[0]
737 msg = 'Unable to get information for locale %s. Information from the default locale (%s) have been used.'
738 logger.notifyChannel('i18n', netsvc.LOG_WARNING, msg % (lang, lc))
741 lang_name = tools.get_languages().get(lang, lang)
750 'iso_code': iso_lang,
753 'date_format' : str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y')),
754 'time_format' : str(locale.nl_langinfo(locale.T_FMT)),
755 'decimal_point' : fix_xa0(str(locale.localeconv()['decimal_point'])),
756 'thousands_sep' : fix_xa0(str(locale.localeconv()['thousands_sep'])),
760 lang_obj.create(cr, uid, lang_info)
765 # now, the serious things: we read the language file
767 if fileformat == 'csv':
768 reader = csv.reader(fileobj, quotechar='"', delimiter=',')
769 # read the first line of the file (it contains columns titles)
773 elif fileformat == 'po':
774 reader = TinyPoFile(fileobj)
775 f = ['type', 'name', 'res_id', 'src', 'value']
777 raise Exception(_('Bad file format'))
779 # read the rest of the file
783 # skip empty rows and rows where the translation field (=last fiefd) is empty
784 #if (not row) or (not row[-1]):
787 # dictionary which holds values for this line of the csv file
788 # {'lang': ..., 'type': ..., 'name': ..., 'res_id': ...,
789 # 'src': ..., 'value': ...}
791 for i in range(len(f)):
792 if f[i] in ('module',):
797 dic['res_id'] = int(dic['res_id'])
799 model_data_ids = model_data_obj.search(cr, uid, [
800 ('model', '=', dic['name'].split(',')[0]),
801 ('module', '=', dic['res_id'].split('.', 1)[0]),
802 ('name', '=', dic['res_id'].split('.', 1)[1]),
805 dic['res_id'] = model_data_obj.browse(cr, uid,
806 model_data_ids[0]).res_id
808 dic['res_id'] = False
810 if dic['type'] == 'model' and not strict:
811 (model, field) = dic['name'].split(',')
813 # get the ids of the resources of this model which share
815 obj = pool.get(model)
817 if field not in obj.fields_get_keys(cr, uid):
819 ids = obj.search(cr, uid, [(field, '=', dic['src'])])
821 # if the resource id (res_id) is in that list, use it,
822 # otherwise use the whole list
825 ids = (dic['res_id'] in ids) and [dic['res_id']] or ids
828 ids = trans_obj.search(cr, uid, [
830 ('type', '=', dic['type']),
831 ('name', '=', dic['name']),
832 ('src', '=', dic['src']),
833 ('res_id', '=', dic['res_id'])
836 trans_obj.write(cr, uid, ids, {'value': dic['value']})
838 trans_obj.create(cr, uid, dic)
840 ids = trans_obj.search(cr, uid, [
842 ('type', '=', dic['type']),
843 ('name', '=', dic['name']),
844 ('src', '=', dic['src'])
847 trans_obj.write(cr, uid, ids, {'value': dic['value']})
849 trans_obj.create(cr, uid, dic)
853 logger.notifyChannel("i18n", netsvc.LOG_INFO,
854 "translation file loaded succesfully")
856 filename = '[lang: %s][format: %s]' % (iso_lang or 'new', fileformat)
857 logger.notifyChannel("i18n", netsvc.LOG_ERROR, "couldn't read translation file %s" % (filename,))
859 def get_locales(lang=None):
861 lang = locale.getdefaultlocale()[0]
864 lang = _LOCALE2WIN32.get(lang, lang)
867 ln = locale._build_localename((lang, enc))
869 nln = locale.normalize(ln)
873 for x in process('utf8'): yield x
875 prefenc = locale.getpreferredencoding()
877 for x in process(prefenc): yield x
881 'iso-8859-1': 'iso8859-15',
883 }.get(prefenc.lower())
885 for x in process(prefenc): yield x
892 # locale.resetlocale is bugged with some locales.
893 for ln in get_locales():
895 return locale.setlocale(locale.LC_ALL, ln)
899 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: