Merge lp:~mathieu-julius/aeroo/openerp7.0.x into lp:~anybox/aeroo/openerp7.0.x
- openerp7.0.x
- Merge into openerp7.0.x
Proposed by
Mathieu Vatel - Julius Network Solutions
Status: | Merged |
---|---|
Merged at revision: | 10 |
Proposed branch: | lp:~mathieu-julius/aeroo/openerp7.0.x |
Merge into: | lp:~anybox/aeroo/openerp7.0.x |
Diff against target: |
353 lines (+113/-102) 1 file modified
report_aeroo/translate.py (+113/-102) |
To merge this branch: | bzr merge lp:~mathieu-julius/aeroo/openerp7.0.x |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jean-Sébastien SUZANNE | Approve | ||
Review via email: mp+152150@code.launchpad.net |
Commit message
Description of the change
Change the translation method to fit the v7.0 translation process
To post a comment you must log in.
Revision history for this message
Jean-Sébastien SUZANNE (jssuzanne) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'report_aeroo/translate.py' |
2 | --- report_aeroo/translate.py 2012-12-17 14:37:23 +0000 |
3 | +++ report_aeroo/translate.py 2013-03-07 11:12:20 +0000 |
4 | @@ -20,28 +20,44 @@ |
5 | # |
6 | ############################################################################## |
7 | |
8 | +#import itertools |
9 | +#import codecs |
10 | +#import csv |
11 | +#import inspect |
12 | +#import locale |
13 | +#import openerp.sql_db as sql_db |
14 | +#import re |
15 | +#import tarfile |
16 | +#import tempfile |
17 | +#import threading |
18 | +#from datetime import datetime |
19 | +#from openerp import SUPERUSER_ID |
20 | + |
21 | import netsvc |
22 | +import tools |
23 | +from openerp.tools.translate import trans_parse_rml, trans_parse_xsl, extract_translatable_view_strings, WEB_TRANSLATION_COMMENT |
24 | +import fnmatch |
25 | import os |
26 | +import openerp.pooler as pooler |
27 | import logging |
28 | -import pooler |
29 | -import re |
30 | -import tools |
31 | -from tools.translate import trans_parse_rml, trans_parse_xsl, trans_parse_view |
32 | -import itertools |
33 | -import fnmatch |
34 | +from babel.messages import extract |
35 | from os.path import join |
36 | from lxml import etree |
37 | -from tools.misc import UpdateableStr |
38 | +from openerp.tools.config import config |
39 | +import tools.misc |
40 | +from tools.misc import UpdateableStr, SKIPPED_ELEMENT_TYPES |
41 | +import openerp.tools.osutil as osutil |
42 | + |
43 | +_logger = logging.getLogger(__name__) |
44 | |
45 | def extend_trans_generate(lang, modules, cr): |
46 | - logger = logging.getLogger('i18n') |
47 | dbname = cr.dbname |
48 | |
49 | pool = pooler.get_pool(dbname) |
50 | trans_obj = pool.get('ir.translation') |
51 | model_data_obj = pool.get('ir.model.data') |
52 | uid = 1 |
53 | - l = pool.obj_list() |
54 | + l = pool.models.items() |
55 | l.sort() |
56 | |
57 | query = 'SELECT name, model, res_id, module' \ |
58 | @@ -65,9 +81,14 @@ |
59 | cr.execute(query, query_param) |
60 | |
61 | _to_translate = [] |
62 | - def push_translation(module, type, name, id, source): |
63 | - tuple = (module, source, name, id, type) |
64 | - if source and tuple not in _to_translate: |
65 | + def push_translation(module, type, name, id, source, comments=None): |
66 | + tuple = (module, source, name, id, type, comments or []) |
67 | + # empty and one-letter terms are ignored, they probably are not meant to be |
68 | + # translated, and would be very hard to translate anyway. |
69 | + if not source or len(source.strip()) <= 1: |
70 | + _logger.debug("Ignoring empty or 1-letter source term: %r", tuple) |
71 | + return |
72 | + if tuple not in _to_translate: |
73 | _to_translate.append(tuple) |
74 | |
75 | def encode(s): |
76 | @@ -81,21 +102,22 @@ |
77 | xml_name = "%s.%s" % (module, encode(xml_name)) |
78 | |
79 | if not pool.get(model): |
80 | - logger.error("Unable to find object %r", model) |
81 | + _logger.error("Unable to find object %r", model) |
82 | continue |
83 | |
84 | exists = pool.get(model).exists(cr, uid, res_id) |
85 | if not exists: |
86 | - logger.warning("Unable to find object %r with id %d", model, res_id) |
87 | + _logger.warning("Unable to find object %r with id %d", model, res_id) |
88 | continue |
89 | obj = pool.get(model).browse(cr, uid, res_id) |
90 | |
91 | if model=='ir.ui.view': |
92 | d = etree.XML(encode(obj.arch)) |
93 | - for t in trans_parse_view(d): |
94 | + for t in extract_translatable_view_strings(d): |
95 | push_translation(module, 'view', encode(obj.model), 0, t) |
96 | elif model=='ir.actions.wizard': |
97 | service_name = 'wizard.'+encode(obj.wiz_name) |
98 | + import openerp.netsvc as netsvc |
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 | @@ -113,7 +135,7 @@ |
103 | |
104 | # export fields |
105 | if not result.has_key('fields'): |
106 | - logger.warning("res has no fields: %r", result) |
107 | + _logger.warning("res has no fields: %r", result) |
108 | continue |
109 | for field_name, field_def in result['fields'].iteritems(): |
110 | res_name = name + ',' + field_name |
111 | @@ -128,7 +150,7 @@ |
112 | arch = result['arch'] |
113 | if arch and not isinstance(arch, UpdateableStr): |
114 | d = etree.XML(arch) |
115 | - for t in trans_parse_view(d): |
116 | + for t in extract_translatable_view_strings(d): |
117 | push_translation(module, 'wizard_view', name, 0, t) |
118 | |
119 | # export button labels |
120 | @@ -142,7 +164,7 @@ |
121 | try: |
122 | field_name = encode(obj.name) |
123 | except AttributeError, exc: |
124 | - logger.error("name error in %s: %s", xml_name, str(exc)) |
125 | + _logger.error("name error in %s: %s", xml_name, str(exc)) |
126 | continue |
127 | objmodel = pool.get(obj.model) |
128 | if not objmodel or not field_name in objmodel._columns: |
129 | @@ -180,7 +202,7 @@ |
130 | if obj.report_type == 'aeroo': |
131 | trans_ids = trans_obj.search(cr, uid, [('type', '=', 'report'),('res_id', '=', obj.id)]) |
132 | for t in trans_obj.read(cr, uid, trans_ids, ['name','src']): |
133 | - push_translation(module, "report", t['name'], xml_name, t['src']) |
134 | + push_translation(module, "report", t['name'], xml_name, encode(t['src'])) |
135 | ############################## |
136 | else: |
137 | if obj.report_rml: |
138 | @@ -193,7 +215,7 @@ |
139 | report_type = "xsl" |
140 | if fname and obj.report_type in ('pdf', 'xsl'): |
141 | try: |
142 | - report_file = tools.file_open(fname) |
143 | + report_file = misc.file_open(fname) |
144 | try: |
145 | d = etree.parse(report_file) |
146 | for t in parse_func(d.iter()): |
147 | @@ -201,7 +223,7 @@ |
148 | finally: |
149 | report_file.close() |
150 | except (IOError, etree.XMLSyntaxError): |
151 | - logger.exception("couldn't export translation for report %s %s %s", name, report_type, fname) |
152 | + _logger.exception("couldn't export translation for report %s %s %s", name, report_type, fname) |
153 | |
154 | for field_name,field_def in obj._table._columns.items(): |
155 | if field_def.translate: |
156 | @@ -217,33 +239,39 @@ |
157 | cr.execute(query_models, query_param) |
158 | |
159 | def push_constraint_msg(module, term_type, model, msg): |
160 | - # Check presence of __call__ directly instead of using |
161 | - # callable() because it will be deprecated as of Python 3.0 |
162 | if not hasattr(msg, '__call__'): |
163 | - push_translation(module, term_type, model, 0, encode(msg)) |
164 | - |
165 | - for (model_id, model, module) in cr.fetchall(): |
166 | - module = encode(module) |
167 | - model = encode(model) |
168 | - |
169 | + push_translation(encode(module), term_type, encode(model), 0, encode(msg)) |
170 | + |
171 | + def push_local_constraints(module, model, cons_type='sql_constraints'): |
172 | + """Climb up the class hierarchy and ignore inherited constraints |
173 | + from other modules""" |
174 | + term_type = 'sql_constraint' if cons_type == 'sql_constraints' else 'constraint' |
175 | + msg_pos = 2 if cons_type == 'sql_constraints' else 1 |
176 | + for cls in model.__class__.__mro__: |
177 | + if getattr(cls, '_module', None) != module: |
178 | + continue |
179 | + constraints = getattr(cls, '_local_' + cons_type, []) |
180 | + for constraint in constraints: |
181 | + push_constraint_msg(module, term_type, model._name, constraint[msg_pos]) |
182 | + |
183 | + for (_, model, module) in cr.fetchall(): |
184 | model_obj = pool.get(model) |
185 | |
186 | if not model_obj: |
187 | - logging.getLogger("i18n").error("Unable to find object %r", model) |
188 | + _logger.error("Unable to find object %r", model) |
189 | continue |
190 | |
191 | - for constraint in getattr(model_obj, '_constraints', []): |
192 | - push_constraint_msg(module, 'constraint', model, constraint[1]) |
193 | - |
194 | - for constraint in getattr(model_obj, '_sql_constraints', []): |
195 | - push_constraint_msg(module, 'sql_constraint', model, constraint[2]) |
196 | - |
197 | - # parse source code for _() calls |
198 | + if model_obj._constraints: |
199 | + push_local_constraints(module, model_obj, 'constraints') |
200 | + |
201 | + if model_obj._sql_constraints: |
202 | + push_local_constraints(module, model_obj, 'sql_constraints') |
203 | + |
204 | def get_module_from_path(path, mod_paths=None): |
205 | if not mod_paths: |
206 | # First, construct a list of possible paths |
207 | - def_path = os.path.abspath(os.path.join(tools.config['root_path'], 'addons')) # default addons path (base) |
208 | - ad_paths= map(lambda m: os.path.abspath(m.strip()),tools.config['addons_path'].split(',')) |
209 | + def_path = os.path.abspath(os.path.join(config['root_path'], 'addons')) # default addons path (base) |
210 | + ad_paths= map(lambda m: os.path.abspath(m.strip()),config['addons_path'].split(',')) |
211 | mod_paths=[def_path] |
212 | for adp in ad_paths: |
213 | mod_paths.append(adp) |
214 | @@ -261,9 +289,9 @@ |
215 | installed_modids = modobj.search(cr, uid, [('state', '=', 'installed')]) |
216 | installed_modules = map(lambda m: m['name'], modobj.read(cr, uid, installed_modids, ['name'])) |
217 | |
218 | - root_path = os.path.join(tools.config['root_path'], 'addons') |
219 | + root_path = os.path.join(config['root_path'], 'addons') |
220 | |
221 | - apaths = map(os.path.abspath, map(str.strip, tools.config['addons_path'].split(','))) |
222 | + apaths = map(os.path.abspath, map(str.strip, config['addons_path'].split(','))) |
223 | if root_path in apaths: |
224 | path_list = apaths |
225 | else : |
226 | @@ -271,83 +299,66 @@ |
227 | |
228 | # Also scan these non-addon paths |
229 | for bin_path in ['osv', 'report' ]: |
230 | - path_list.append(os.path.join(tools.config['root_path'], bin_path)) |
231 | + path_list.append(os.path.join(config['root_path'], bin_path)) |
232 | |
233 | - logger.debug("Scanning modules at paths: ", path_list) |
234 | + _logger.debug("Scanning modules at paths: ", path_list) |
235 | |
236 | mod_paths = [] |
237 | - join_dquotes = re.compile(r'([^\\])"[\s\\]*"', re.DOTALL) |
238 | - join_quotes = re.compile(r'([^\\])\'[\s\\]*\'', re.DOTALL) |
239 | - re_dquotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*"(.+?)"[\s]*?\)', re.DOTALL) |
240 | - re_quotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*\'(.+?)\'[\s]*?\)', re.DOTALL) |
241 | |
242 | - def export_code_terms_from_file(fname, path, root, terms_type): |
243 | + def verified_module_filepaths(fname, path, root): |
244 | fabsolutepath = join(root, fname) |
245 | frelativepath = fabsolutepath[len(path):] |
246 | + display_path = "addons%s" % frelativepath |
247 | module = get_module_from_path(fabsolutepath, mod_paths=mod_paths) |
248 | - is_mod_installed = module in installed_modules |
249 | - if (('all' in modules) or (module in modules)) and is_mod_installed: |
250 | - logger.debug("Scanning code of %s at module: %s", frelativepath, module) |
251 | - src_file = tools.file_open(fabsolutepath, subdir='') |
252 | + if ('all' in modules or module in modules) and module in installed_modules: |
253 | + return module, fabsolutepath, frelativepath, display_path |
254 | + return None, None, None, None |
255 | + |
256 | + def babel_extract_terms(fname, path, root, extract_method="python", trans_type='code', |
257 | + extra_comments=None, extract_keywords={'_': None}): |
258 | + module, fabsolutepath, _, display_path = verified_module_filepaths(fname, path, root) |
259 | + extra_comments = extra_comments or [] |
260 | + if module: |
261 | + src_file = open(fabsolutepath, 'r') |
262 | try: |
263 | - code_string = src_file.read() |
264 | + for lineno, message, comments in extract.extract(extract_method, src_file, |
265 | + keywords=extract_keywords): |
266 | + push_translation(module, trans_type, display_path, lineno, |
267 | + encode(message), comments + extra_comments) |
268 | + except Exception: |
269 | + _logger.exception("Failed to extract terms from %s", fabsolutepath) |
270 | finally: |
271 | src_file.close() |
272 | - if module in installed_modules: |
273 | - frelativepath = str("addons" + frelativepath) |
274 | - ite = re_dquotes.finditer(code_string) |
275 | - code_offset = 0 |
276 | - code_line = 1 |
277 | - for i in ite: |
278 | - src = i.group(1) |
279 | - if src.startswith('""'): |
280 | - assert src.endswith('""'), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30]) |
281 | - src = src[2:-2] |
282 | - else: |
283 | - src = join_dquotes.sub(r'\1', src) |
284 | - # try to count the lines from the last pos to our place: |
285 | - code_line += code_string[code_offset:i.start(1)].count('\n') |
286 | - # now, since we did a binary read of a python source file, we |
287 | - # have to expand pythonic escapes like the interpreter does. |
288 | - src = src.decode('string_escape') |
289 | - push_translation(module, terms_type, frelativepath, code_line, encode(src)) |
290 | - code_line += i.group(1).count('\n') |
291 | - code_offset = i.end() # we have counted newlines up to the match end |
292 | - |
293 | - ite = re_quotes.finditer(code_string) |
294 | - code_offset = 0 #reset counters |
295 | - code_line = 1 |
296 | - for i in ite: |
297 | - src = i.group(1) |
298 | - if src.startswith("''"): |
299 | - assert src.endswith("''"), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30]) |
300 | - src = src[2:-2] |
301 | - else: |
302 | - src = join_quotes.sub(r'\1', src) |
303 | - code_line += code_string[code_offset:i.start(1)].count('\n') |
304 | - src = src.decode('string_escape') |
305 | - push_translation(module, terms_type, frelativepath, code_line, encode(src)) |
306 | - code_line += i.group(1).count('\n') |
307 | - code_offset = i.end() # we have counted newlines up to the match end |
308 | |
309 | for path in path_list: |
310 | - logger.debug("Scanning files of modules at %s", path) |
311 | - for root, dummy, files in tools.osutil.walksymlinks(path): |
312 | - for fname in itertools.chain(fnmatch.filter(files, '*.py')): |
313 | - export_code_terms_from_file(fname, path, root, 'code') |
314 | - for fname in itertools.chain(fnmatch.filter(files, '*.mako')): |
315 | - export_code_terms_from_file(fname, path, root, 'report') |
316 | - |
317 | - |
318 | - out = [["module","type","name","res_id","src","value"]] # header |
319 | + _logger.debug("Scanning files of modules at %s", path) |
320 | + for root, dummy, files in osutil.walksymlinks(path): |
321 | + for fname in fnmatch.filter(files, '*.py'): |
322 | + babel_extract_terms(fname, path, root) |
323 | + # mako provides a babel extractor: http://docs.makotemplates.org/en/latest/usage.html#babel |
324 | + for fname in fnmatch.filter(files, '*.mako'): |
325 | + babel_extract_terms(fname, path, root, 'mako', trans_type='report') |
326 | + # Javascript source files in the static/src/js directory, rest is ignored (libs) |
327 | + if fnmatch.fnmatch(root, '*/static/src/js*'): |
328 | + for fname in fnmatch.filter(files, '*.js'): |
329 | + babel_extract_terms(fname, path, root, 'javascript', |
330 | + extra_comments=[WEB_TRANSLATION_COMMENT], |
331 | + extract_keywords={'_t': None, '_lt': None}) |
332 | + # QWeb template files |
333 | + if fnmatch.fnmatch(root, '*/static/src/xml*'): |
334 | + for fname in fnmatch.filter(files, '*.xml'): |
335 | + babel_extract_terms(fname, path, root, 'openerp.tools.translate:babel_extract_qweb', |
336 | + extra_comments=[WEB_TRANSLATION_COMMENT]) |
337 | + |
338 | + out = [] |
339 | _to_translate.sort() |
340 | # translate strings marked as to be translated |
341 | - for module, source, name, id, type in _to_translate: |
342 | - trans = trans_obj._get_source(cr, uid, name, type, lang, source) |
343 | - out.append([module, type, name, id, source, encode(trans) or '']) |
344 | - |
345 | + for module, source, name, id, type, comments in _to_translate: |
346 | + trans = '' if not lang else trans_obj._get_source(cr, uid, name, type, lang, source) |
347 | + out.append([module, type, name, id, source, encode(trans) or '', comments]) |
348 | return out |
349 | |
350 | import sys |
351 | sys.modules['tools.translate'].trans_generate = extend_trans_generate |
352 | |
353 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |