1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6
# Copyright (c) 2009-2011 Alistek Ltd (http://www.alistek.com).
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU Affero General Public License as
10
# published by the Free Software Foundation, either version 3 of the
11
# License, or (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU Affero General Public License for more details.
18
# You should have received a copy of the GNU Affero General Public License
19
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21
##############################################################################
29
from tools.translate import trans_parse_rml, trans_parse_xsl, trans_parse_view
32
from os.path import join
33
from lxml import etree
34
from tools.misc import UpdateableStr
36
def extend_trans_generate(lang, modules, cr):
37
logger = logging.getLogger('i18n')
40
pool = pooler.get_pool(dbname)
41
trans_obj = pool.get('ir.translation')
42
model_data_obj = pool.get('ir.model.data')
47
query = 'SELECT name, model, res_id, module' \
50
query_models = """SELECT m.id, m.model, imd.module
51
FROM ir_model AS m, ir_model_data AS imd
52
WHERE m.id = imd.res_id AND imd.model = 'ir.model' """
54
if 'all_installed' in modules:
55
query += ' WHERE module IN ( SELECT name FROM ir_module_module WHERE state = \'installed\') '
56
query_models += " AND imd.module in ( SELECT name FROM ir_module_module WHERE state = 'installed') "
58
if 'all' not in modules:
59
query += ' WHERE module IN %s'
60
query_models += ' AND imd.module in %s'
61
query_param = (tuple(modules),)
62
query += ' ORDER BY module, model, name'
63
query_models += ' ORDER BY module, model'
65
cr.execute(query, query_param)
68
def push_translation(module, type, name, id, source):
69
tuple = (module, source, name, id, type)
70
if source and tuple not in _to_translate:
71
_to_translate.append(tuple)
74
if isinstance(s, unicode):
75
return s.encode('utf8')
78
for (xml_name,model,res_id,module) in cr.fetchall():
79
module = encode(module)
81
xml_name = "%s.%s" % (module, encode(xml_name))
83
if not pool.get(model):
84
logger.error("Unable to find object %r", model)
87
exists = pool.get(model).exists(cr, uid, res_id)
89
logger.warning("Unable to find object %r with id %d", model, res_id)
91
obj = pool.get(model).browse(cr, uid, res_id)
93
if model=='ir.ui.view':
94
d = etree.XML(encode(obj.arch))
95
for t in trans_parse_view(d):
96
push_translation(module, 'view', encode(obj.model), 0, t)
97
elif model=='ir.actions.wizard':
98
service_name = 'wizard.'+encode(obj.wiz_name)
99
if netsvc.Service._services.get(service_name):
100
obj2 = netsvc.Service._services[service_name]
101
for state_name, state_def in obj2.states.iteritems():
102
if 'result' in state_def:
103
result = state_def['result']
104
if result['type'] != 'form':
106
name = "%s,%s" % (encode(obj.wiz_name), state_name)
109
'string': ('wizard_field', lambda s: [encode(s)]),
110
'selection': ('selection', lambda s: [encode(e[1]) for e in ((not callable(s)) and s or [])]),
111
'help': ('help', lambda s: [encode(s)]),
115
if not result.has_key('fields'):
116
logger.warning("res has no fields: %r", result)
118
for field_name, field_def in result['fields'].iteritems():
119
res_name = name + ',' + field_name
121
for fn in def_params:
123
transtype, modifier = def_params[fn]
124
for val in modifier(field_def[fn]):
125
push_translation(module, transtype, res_name, 0, val)
128
arch = result['arch']
129
if arch and not isinstance(arch, UpdateableStr):
131
for t in trans_parse_view(d):
132
push_translation(module, 'wizard_view', name, 0, t)
134
# export button labels
135
for but_args in result['state']:
136
button_name = but_args[0]
137
button_label = but_args[1]
138
res_name = name + ',' + button_name
139
push_translation(module, 'wizard_button', res_name, 0, button_label)
141
elif model=='ir.model.fields':
143
field_name = encode(obj.name)
144
except AttributeError, exc:
145
logger.error("name error in %s: %s", xml_name, str(exc))
147
objmodel = pool.get(obj.model)
148
if not objmodel or not field_name in objmodel._columns:
150
field_def = objmodel._columns[field_name]
152
name = "%s,%s" % (encode(obj.model), field_name)
153
push_translation(module, 'field', name, 0, encode(field_def.string))
156
push_translation(module, 'help', name, 0, encode(field_def.help))
158
if field_def.translate:
159
ids = objmodel.search(cr, uid, [])
160
obj_values = objmodel.read(cr, uid, ids, [field_name])
161
for obj_value in obj_values:
162
res_id = obj_value['id']
163
if obj.name in ('ir.model', 'ir.ui.menu'):
165
model_data_ids = model_data_obj.search(cr, uid, [
166
('model', '=', model),
167
('res_id', '=', res_id),
169
if not model_data_ids:
170
push_translation(module, 'model', name, 0, encode(obj_value[field_name]))
172
if hasattr(field_def, 'selection') and isinstance(field_def.selection, (list, tuple)):
173
for dummy, val in field_def.selection:
174
push_translation(module, 'selection', name, 0, encode(val))
176
elif model=='ir.actions.report.xml':
177
name = encode(obj.report_name)
179
##### Changes for Aeroo ######
180
if obj.report_type == 'aeroo':
181
trans_ids = trans_obj.search(cr, uid, [('type', '=', 'report'),('res_id', '=', obj.id)])
182
for t in trans_obj.read(cr, uid, trans_ids, ['name','src']):
183
push_translation(module, "report", t['name'], xml_name, t['src'])
184
##############################
187
fname = obj.report_rml
188
parse_func = trans_parse_rml
189
report_type = "report"
191
fname = obj.report_xsl
192
parse_func = trans_parse_xsl
194
if fname and obj.report_type in ('pdf', 'xsl'):
196
report_file = tools.file_open(fname)
198
d = etree.parse(report_file)
199
for t in parse_func(d.iter()):
200
push_translation(module, report_type, name, 0, t)
203
except (IOError, etree.XMLSyntaxError):
204
logger.exception("couldn't export translation for report %s %s %s", name, report_type, fname)
206
for field_name,field_def in obj._table._columns.items():
207
if field_def.translate:
208
name = model + "," + field_name
210
trad = getattr(obj, field_name) or ''
213
push_translation(module, 'model', name, xml_name, encode(trad))
215
# End of data for ir.model.data query results
217
cr.execute(query_models, query_param)
219
def push_constraint_msg(module, term_type, model, msg):
220
# Check presence of __call__ directly instead of using
221
# callable() because it will be deprecated as of Python 3.0
222
if not hasattr(msg, '__call__'):
223
push_translation(module, term_type, model, 0, encode(msg))
225
for (model_id, model, module) in cr.fetchall():
226
module = encode(module)
227
model = encode(model)
229
model_obj = pool.get(model)
232
logging.getLogger("i18n").error("Unable to find object %r", model)
235
for constraint in getattr(model_obj, '_constraints', []):
236
push_constraint_msg(module, 'constraint', model, constraint[1])
238
for constraint in getattr(model_obj, '_sql_constraints', []):
239
push_constraint_msg(module, 'sql_constraint', model, constraint[2])
241
# parse source code for _() calls
242
def get_module_from_path(path, mod_paths=None):
244
# First, construct a list of possible paths
245
def_path = os.path.abspath(os.path.join(tools.config['root_path'], 'addons')) # default addons path (base)
246
ad_paths= map(lambda m: os.path.abspath(m.strip()),tools.config['addons_path'].split(','))
249
mod_paths.append(adp)
250
if not os.path.isabs(adp):
251
mod_paths.append(adp)
252
elif adp.startswith(def_path):
253
mod_paths.append(adp[len(def_path)+1:])
255
if path.startswith(mp) and (os.path.dirname(path) != mp):
256
path = path[len(mp)+1:]
257
return path.split(os.path.sep)[0]
258
return 'base' # files that are not in a module are considered as being in 'base' module
260
modobj = pool.get('ir.module.module')
261
installed_modids = modobj.search(cr, uid, [('state', '=', 'installed')])
262
installed_modules = map(lambda m: m['name'], modobj.read(cr, uid, installed_modids, ['name']))
264
root_path = os.path.join(tools.config['root_path'], 'addons')
266
apaths = map(os.path.abspath, map(str.strip, tools.config['addons_path'].split(',')))
267
if root_path in apaths:
270
path_list = [root_path,] + apaths
272
# Also scan these non-addon paths
273
for bin_path in ['osv', 'report' ]:
274
path_list.append(os.path.join(tools.config['root_path'], bin_path))
276
logger.debug("Scanning modules at paths: ", path_list)
279
join_dquotes = re.compile(r'([^\\])"[\s\\]*"', re.DOTALL)
280
join_quotes = re.compile(r'([^\\])\'[\s\\]*\'', re.DOTALL)
281
re_dquotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*"(.+?)"[\s]*?\)', re.DOTALL)
282
re_quotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*\'(.+?)\'[\s]*?\)', re.DOTALL)
284
def export_code_terms_from_file(fname, path, root, terms_type):
285
fabsolutepath = join(root, fname)
286
frelativepath = fabsolutepath[len(path):]
287
module = get_module_from_path(fabsolutepath, mod_paths=mod_paths)
288
is_mod_installed = module in installed_modules
289
if (('all' in modules) or (module in modules)) and is_mod_installed:
290
logger.debug("Scanning code of %s at module: %s", frelativepath, module)
291
src_file = tools.file_open(fabsolutepath, subdir='')
293
code_string = src_file.read()
296
if module in installed_modules:
297
frelativepath = str("addons" + frelativepath)
298
ite = re_dquotes.finditer(code_string)
303
if src.startswith('""'):
304
assert src.endswith('""'), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30])
307
src = join_dquotes.sub(r'\1', src)
308
# try to count the lines from the last pos to our place:
309
code_line += code_string[code_offset:i.start(1)].count('\n')
310
# now, since we did a binary read of a python source file, we
311
# have to expand pythonic escapes like the interpreter does.
312
src = src.decode('string_escape')
313
push_translation(module, terms_type, frelativepath, code_line, encode(src))
314
code_line += i.group(1).count('\n')
315
code_offset = i.end() # we have counted newlines up to the match end
317
ite = re_quotes.finditer(code_string)
318
code_offset = 0 #reset counters
322
if src.startswith("''"):
323
assert src.endswith("''"), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30])
326
src = join_quotes.sub(r'\1', src)
327
code_line += code_string[code_offset:i.start(1)].count('\n')
328
src = src.decode('string_escape')
329
push_translation(module, terms_type, frelativepath, code_line, encode(src))
330
code_line += i.group(1).count('\n')
331
code_offset = i.end() # we have counted newlines up to the match end
333
for path in path_list:
334
logger.debug("Scanning files of modules at %s", path)
335
for root, dummy, files in tools.osutil.walksymlinks(path):
336
for fname in itertools.chain(fnmatch.filter(files, '*.py')):
337
export_code_terms_from_file(fname, path, root, 'code')
338
for fname in itertools.chain(fnmatch.filter(files, '*.mako')):
339
export_code_terms_from_file(fname, path, root, 'report')
342
out = [["module","type","name","res_id","src","value"]] # header
344
# translate strings marked as to be translated
345
for module, source, name, id, type in _to_translate:
346
trans = trans_obj._get_source(cr, uid, name, type, lang, source)
347
out.append([module, type, name, id, source, encode(trans) or ''])
352
sys.modules['tools.translate'].trans_generate = extend_trans_generate