summaryrefslogtreecommitdiff
path: root/bin/tools/translate.py
diff options
context:
space:
mode:
authorpinky <>2006-12-07 13:41:40 +0000
committerpinky <>2006-12-07 13:41:40 +0000
commit004a0b996ff8f269451e07346f71a129a1f3fbaf (patch)
tree48f43c1fc2ca4c1f4d5648e7e73553d3f4ccae06 /bin/tools/translate.py
New trunk
bzr revid: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
Diffstat (limited to 'bin/tools/translate.py')
-rw-r--r--bin/tools/translate.py340
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")
+