diff options
Diffstat (limited to 'bin/tools/translate.py')
| -rw-r--r-- | bin/tools/translate.py | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/bin/tools/translate.py b/bin/tools/translate.py new file mode 100644 index 00000000000..16a15eb4013 --- /dev/null +++ b/bin/tools/translate.py @@ -0,0 +1,340 @@ +############################################################################## +# +# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import os +from os.path import join +import fnmatch +import csv, xml.dom, re +import osv, tools, pooler +import ir +import netsvc +from tools.misc import UpdateableStr + +# +# TODO: a caching method +# +def translate(cr, uid, name, source_type, lang, source=None): + if source and name: + 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)) + elif name: + cr.execute('select value from ir_translation where lang=%s and type=%s and name=%s', (lang, source_type, str(name))) + elif source: + cr.execute('select value from ir_translation where lang=%s and type=%s and src=%s', (lang, source_type, source)) + res_trans = cr.fetchone() + res = res_trans and res_trans[0] or False + return res + +def translate_code(cr, uid, source, context): + lang = context.get('lang', False) + if lang: + return translate(cr, uid, None, 'code', lang, source) + else: + return source + +_ = lambda source: translate_code(cr, uid, source, context) + +# Methods to export the translation file + +def trans_parse_xsl(de): + res = [] + for n in [i for i in de.childNodes if (i.nodeType == i.ELEMENT_NODE)]: + if n.hasAttribute("t"): + for m in [j for j in n.childNodes if (j.nodeType == j.TEXT_NODE)]: + l = m.data.strip().replace('\n',' ') + if len(l): + res.append(l.encode("utf8")) + res.extend(trans_parse_xsl(n)) + return res + +def trans_parse_rml(de): + res = [] + for n in [i for i in de.childNodes if (i.nodeType == i.ELEMENT_NODE)]: + for m in [j for j in n.childNodes if (j.nodeType == j.TEXT_NODE)]: + string_list = [s.replace('\n', ' ').strip() for s in re.split('\[\[.+?\]\]', m.data)] + for s in string_list: + if s: + res.append(s.encode("utf8")) + res.extend(trans_parse_rml(n)) + return res + +def trans_parse_view(de): + res = [] + if de.hasAttribute("string"): + s = de.getAttribute('string') + if s: + res.append(s.encode("utf8")) + for n in [i for i in de.childNodes if (i.nodeType == i.ELEMENT_NODE)]: + res.extend(trans_parse_view(n)) + return res + +# tests whether an object is in a list of modules +def in_modules(object_name, modules): + if 'all' in modules: + return True + + module_dict = { + 'ir': 'base', + 'res': 'base', + 'workflow': 'base', + } + module = object_name.split('.')[0] + module = module_dict.get(module, module) + return module in modules + +def trans_generate(lang, modules): + pool = pooler.get_pool(tools.config['db_name']) + trans_obj = pool.get('ir.translation') + cr = pooler.get_db(tools.config['db_name']).cursor() + uid = 1 + l = pool.obj_pool.items() + l.sort() + out = [["type","name","res_id","src","value"]] + +#TODO: do everything through to_translate, for that, we'll probably need to change its +#format and have records = list of tuples (id, name, source) instead of just source + to_translate = [] + + # object fields + for obj_name, obj in l: + if in_modules(obj_name, modules): + for field_name, field_def in obj._columns.iteritems(): + name = obj_name + "," + field_name + value = "" + if lang: + cr.execute("SELECT * FROM ir_translation WHERE type='field' AND name=%s AND lang=%s", (name,lang)) + res = cr.dictfetchall() + if len(res): + value = res[0]['value'] + out.append(["field", name, "0", field_def.string.encode('utf8'), value]) + if field_def.translate: + ids = osv.orm.orm.search(obj, cr, uid, []) + obj_values = obj.read(cr, uid, ids, [field_name]) + for obj_value in obj_values: + trans = "" + if lang: + cr.execute("SELECT * FROM ir_translation WHERE type='model' AND name=%s AND res_id=%d AND lang=%s", (name, obj_value['id'], lang)) + res = cr.dictfetchall() + if len(res): + trans = res[0]['value'] + out.append(["model", name, obj_value['id'], obj_value[field_name], trans]) + if hasattr(field_def, 'selection') and isinstance(field_def.selection, (list, tuple)): + for key, val in field_def.selection: + to_translate.append(["selection", name, [val.encode('utf8')]]) + + # reports (xsl and rml) + obj = pool.get("ir.actions.report.xml") + for i in obj.read(cr, uid, osv.orm.orm.search(obj, cr, uid, [])): + if in_modules(i["model"], modules): + name = i["report_name"] + fname = "" + if i["report_rml"]: + fname = i["report_rml"] + parse_func = trans_parse_rml + report_type = "rml" + elif i["report_xsl"]: + fname = i["report_xsl"] + parse_func = trans_parse_xsl + report_type = "xsl" + try: + xmlstr = tools.file_open(fname).read() + d = xml.dom.minidom.parseString(xmlstr) + to_translate.append([report_type, name, parse_func(d.documentElement)]) + except IOError: + if fname: + print "Warning: couldn't export translation for report %s" % name, report_type, fname + + # views + obj = pool.get("ir.ui.view") + for i in obj.read(cr, uid, osv.orm.orm.search(obj, cr, uid, [])): + if in_modules(i["model"], modules): + d = xml.dom.minidom.parseString(i['arch']) + to_translate.append(["view", i['model'], trans_parse_view(d.documentElement)]) + + # wizards + for service_name, obj in netsvc._service.iteritems(): + if service_name.startswith('wizard.'): + for state_name, state_def in obj.states.iteritems(): + if 'result' in state_def: + result = state_def['result'] + if result['type'] != 'form': + break + + name = obj.wiz_name + ',' + state_name + + # export fields + for field_name, field_def in result['fields'].iteritems(): + if 'string' in field_def: + source = field_def['string'] + res_name = name + ',' + field_name + to_translate.append(["wizard_field", res_name, [source]]) + + # export arch + arch = result['arch'] + if not isinstance(arch, UpdateableStr): + d = xml.dom.minidom.parseString(arch) + to_translate.append(["wizard_view", name, trans_parse_view(d.documentElement)]) + + # export button labels + for but_args in result['state']: + button_name = but_args[0] + button_label = but_args[1] + res_name = name + ',' + button_name + to_translate.append(["wizard_button", res_name, [button_label]]) + + # code + for root, dirs, files in os.walk(tools.config['root_path']): + for fname in fnmatch.filter(files, '*.py'): + frelativepath = join(root, fname) + code_string = tools.file_open(frelativepath, subdir='').read() + +# TODO: add support for """ and '''... These should use the DOTALL flag +# DOTALL +# Make the "." special character match any character at all, including a +# newline; without this flag, "." will match anything except a newline. + # *? is the non-greedy version of the * qualifier + iter = re.finditer( + '[^a-zA-Z0-9_]_\([\s]*["\'](.*?)["\'][\s]*\)', + code_string) + for i in iter: + source = i.group(1).encode('utf8') +# TODO: check whether the same string has already been exported + res = trans_obj._get_source(cr, uid, frelativepath, 'code', lang, source) or '' + out.append(["code", frelativepath, "0", source, res]) + + # translate strings marked as to be translated + for type, name, sources in to_translate: + for source in sources: + trans = trans_obj._get_source(cr, uid, name, type, lang, source) + out.append([type, name, "0", source, trans or '']) + + cr.close() + return out + +def trans_load(db_name, filename, lang, strict=False): + logger = netsvc.Logger() + try: + data=file(filename,'r').read().split('\n') + except IOError: + logger.notifyChannel("init", netsvc.LOG_ERROR, "couldn't read file") + return trans_load_data(db_name, data, lang, strict=False) + +def trans_load_data(db_name, data, lang, strict=False): + logger = netsvc.Logger() + logger.notifyChannel("init", netsvc.LOG_INFO, 'loading translation file for language %s' % (lang)) + pool = pooler.get_pool(db_name) + lang_obj = pool.get('res.lang') + trans_obj = pool.get('ir.translation') + try: + uid = 1 + cr = pooler.get_db(db_name).cursor() + + ids = lang_obj.search(cr, uid, [('code','=',lang)]) + if not ids: + lang_name=lang + languages=tools.get_languages() + if lang in languages: + lang_name=languages[lang] + ids = lang_obj.create(cr, uid, {'code':lang, 'name':lang_name, 'translatable':1}) + else: + lang_obj.write(cr, uid, ids, {'translatable':1}) + lang_ids = lang_obj.search(cr, uid, []) + langs = lang_obj.read(cr, uid, lang_ids) + ls = map(lambda x: (x['code'],x['name']), langs) + + ir.ir_set(cr, uid, 'meta', 'lang', 'lang', [('res.users',False,)], 'en', True, False, meta = {'type':'selection', 'string':'Language', 'selection':ls}) + + ids = pool.get('res.users').search(cr, uid, []) + for id in ids: + ir.ir_set(cr, uid, 'meta', 'lang', 'lang', [('res.users',id,)], lang, True, False) + + reader = csv.reader(data) + + # read the first line of the file (it contains columns titles) + for row in reader: + f = row + break + + # read the rest of the file + line = 1 + for row in reader: + line += 1 + try: + # skip empty rows and rows where the translation field is empty + if (not row) or (not row[4]): + continue + + # dictionary which holds values for this line of the csv file + # {'lang': ..., 'type': ..., 'name': ..., 'res_id': ..., 'src': ..., 'value': ...} + dic = {'lang': lang} + for i in range(len(f)): + if trans_obj._columns[f[i]]._type=='integer': + row[i] = row[i] and int(row[i]) or False + dic[f[i]] = row[i] + + if dic['type'] == 'model' and not strict: + (model, field) = dic['name'].split(',') + + # get the ids of the resources of this model which share + # the same source + obj = pool.get(model) + if obj: + ids = osv.orm.orm.search(obj, cr, uid, [(field, '=', dic['src'])]) + + # if the resource id (res_id) is in that list, use it, otherwise use the whole list + ids = (dic['res_id'] in ids) and [dic['res_id']] or ids + for id in ids: + dic['res_id'] = id + ids = trans_obj.search(cr, uid, [ + ('lang', '=', lang), + ('type', '=', dic['type']), + ('name', '=', dic['name']), + ('src', '=', dic['src']), + ('res_id', '=', dic['res_id']) + ]) + if ids: + trans_obj.write(cr, uid, ids, {'value': dic['value']}) + else: + trans_obj.create(cr, uid, dic) + else: + ids = trans_obj.search(cr, uid, [ + ('lang', '=', lang), + ('type', '=', dic['type']), + ('name', '=', dic['name']), + ('src', '=', dic['src']) + ]) + if ids: + trans_obj.write(cr, uid, ids, {'value': dic['value']}) + else: + trans_obj.create(cr, uid, dic) + except Exception, e: + print 'Import error', e, 'on line %d: %s!' % (line, row) + cr.commit() + cr.close() + logger.notifyChannel("init", netsvc.LOG_INFO, "translation file loaded succesfully") + except IOError: + logger.notifyChannel("init", netsvc.LOG_ERROR, "couldn't read file") + |
