08dfb0182ae4f5c15158954f1f01dc20dcb09a34
[odoo/odoo.git] / addons / base_gengo / wizard / base_gengo_translations.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Business Applications
5 #    Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 import logging
23 import re
24 import time
25
26 from openerp.osv import osv, fields
27 from openerp import tools
28 from openerp.tools.translate import _
29
30 _logger = logging.getLogger(__name__)
31
32 try:
33     from gengo import Gengo
34 except ImportError:
35     _logger.warning('Gengo library not found, Gengo features disabled. If you plan to use it, please install the gengo library from http://pypi.python.org/pypi/gengo')
36
37 GENGO_DEFAULT_LIMIT = 20
38
39 class base_gengo_translations(osv.osv_memory):
40
41     _name = 'base.gengo.translations'
42     _columns = {
43         'sync_type': fields.selection([('send', 'Send New Terms'),
44                                        ('receive', 'Receive Translation'),
45                                        ('both', 'Both')], "Sync Type"),
46         'lang_id': fields.many2one('res.lang', 'Language', required=True),
47         'sync_limit': fields.integer("No. of terms to sync"),
48     }
49     _defaults = {'sync_type' : 'both',
50                  'sync_limit' : 20
51          }
52     def gengo_authentication(self, cr, uid, context=None):
53         '''
54         This method tries to open a connection with Gengo. For that, it uses the Public and Private
55         keys that are linked to the company (given by Gengo on subscription). It returns a tuple with
56          * as first element: a boolean depicting if the authentication was a success or not
57          * as second element: the connection, if it was a success, or the error message returned by
58             Gengo when the connection failed.
59             This error message can either be displayed in the server logs (if the authentication was called
60             by the cron) or in a dialog box (if requested by the user), thus it's important to return it
61             translated.
62         '''
63         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
64         if not user.company_id.gengo_public_key or not user.company_id.gengo_private_key:
65             return (False, _("Gengo `Public Key` or `Private Key` are missing. Enter your Gengo authentication parameters under `Settings > Companies > Gengo Parameters`."))
66         try:
67             gengo = Gengo(
68                 public_key=user.company_id.gengo_public_key.encode('ascii'),
69                 private_key=user.company_id.gengo_private_key.encode('ascii'),
70                 sandbox=user.company_id.gengo_sandbox,
71             )
72             gengo.getAccountStats()
73             return (True, gengo)
74         except Exception, e:
75             _logger.exception('Gengo connection failed')
76             return (False, _("Gengo connection failed with this message:\n``%s``") % e)
77
78     def act_update(self, cr, uid, ids, context=None):
79         '''
80         Function called by the wizard.
81         '''
82         if context is None:
83             context = {}
84
85         flag, gengo = self.gengo_authentication(cr, uid, context=context)
86         if not flag:
87             raise osv.except_osv(_('Gengo Authentication Error'), gengo)
88         for wizard in self.browse(cr, uid, ids, context=context):
89             supported_langs = self.pool.get('ir.translation')._get_all_supported_languages(cr, uid, context=context)
90             language = self.pool.get('ir.translation')._get_gengo_corresponding_language(wizard.lang_id.code)
91             if language not in supported_langs:
92                 raise osv.except_osv(_("Warning"), _('This language is not supported by the Gengo translation services.'))
93
94             ctx = context.copy()
95             ctx['gengo_language'] = wizard.lang_id.id
96             if wizard.sync_limit > 200 or wizard.sync_limit < 1:
97                 raise osv.except_osv(_("Warning"), _('Sync limit should between 1 to 200 for Gengo translation services.'))
98             if wizard.sync_type in ['send', 'both']:
99                 self._sync_request(cr, uid, wizard.sync_limit, context=ctx)
100             if wizard.sync_type in ['receive', 'both']:
101                 self._sync_response(cr, uid, wizard.sync_limit, context=ctx)
102         return {'type': 'ir.actions.act_window_close'}
103
104     def _sync_response(self, cr, uid, limit=GENGO_DEFAULT_LIMIT, context=None):
105         """
106         This method will be called by cron services to get translations from
107         Gengo. It will read translated terms and comments from Gengo and will
108         update respective ir.translation in openerp.
109         """
110         translation_pool = self.pool.get('ir.translation')
111         flag, gengo = self.gengo_authentication(cr, uid, context=context)
112         if not flag:
113             _logger.warning("%s", gengo)
114         else:
115             offset = 0
116             all_translation_ids = translation_pool.search(cr, uid, [('state', '=', 'inprogress'), ('gengo_translation', 'in', ('machine', 'standard', 'pro', 'ultra')), ('job_id', "!=", False)], context=context)
117             while True:
118                 translation_ids = all_translation_ids[offset:offset + limit]
119                 offset += limit
120                 if not translation_ids:
121                     break
122                 translation_terms = translation_pool.browse(cr, uid, translation_ids, context=context)
123                 gengo_job_id = [term.job_id for term in translation_terms]
124                 if gengo_job_id:
125                     gengo_ids = ','.join(gengo_job_id)
126                     try:
127                         job_response = gengo.getTranslationJobBatch(id=gengo_ids)
128                     except:
129                         continue
130                     if job_response['opstat'] == 'ok':
131                         for job in job_response['response'].get('jobs', []):
132                             self._update_terms_job(cr, uid, job, context=context)
133         return True
134
135     def _update_terms_job(self, cr, uid, job, context=None):
136         translation_pool = self.pool.get('ir.translation')
137         tid = int(job['custom_data'])
138         vals = {}
139         if job.get('job_id', False):
140             vals['job_id'] = job['job_id']
141             vals['state'] = 'inprogress'
142         if job.get('status', False) in ('queued','available','pending','reviewable'):
143             vals['state'] = 'inprogress'
144         if job.get('body_tgt', False) and job.get('status', False)=='approved':
145             vals['value'] = job['body_tgt']
146         if job.get('status', False) in ('approved', 'canceled'):
147             vals['state'] = 'translated'
148         if vals:
149             translation_pool.write(cr, uid, [tid], vals, context=context)
150
151     def _update_terms(self, cr, uid, response, context=None):
152         """
153         Update the terms after their translation were requested to Gengo
154         """
155         for jobs in response.get('jobs', []):
156             for t_id, res in jobs.items():
157                 self._update_terms_job(cr, uid, res, context=context)
158         return
159
160     def pack_jobs_request(self, cr, uid, term_ids, context=None):
161         ''' prepare the terms that will be requested to gengo and returns them in a dictionary with following format
162             {'jobs': {
163                 'term1.id': {...}
164                 'term2.id': {...}
165                 }
166             }'''
167
168         translation_pool = self.pool.get('ir.translation')
169         jobs = {}
170         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
171         auto_approve = 1 if user.company_id.gengo_auto_approve else 0
172         for term in translation_pool.browse(cr, uid, term_ids, context=context):
173             if re.search(r"\w", term.src or ""):
174                 comment = user.company_id.gengo_comment or ''
175                 if term.gengo_comment:
176                     comment+='\n' + term.gengo_comment
177                 jobs[time.strftime('%Y%m%d%H%M%S') + '-' + str(term.id)] = {
178                     'type': 'text',
179                     'slug': 'Single :: English to ' + term.lang,
180                     'tier': tools.ustr(term.gengo_translation),
181                     'custom_data': str(term.id),
182                     'body_src': term.src,
183                     'lc_src': 'en',
184                     'lc_tgt': translation_pool._get_gengo_corresponding_language(term.lang),
185                     'auto_approve': auto_approve,
186                     'comment': comment,
187                     'callback_url': self.pool.get('ir.config_parameter').get_param(cr, uid,'web.base.url') + '/website/gengo_callback'
188                 }
189         return {'jobs': jobs, 'as_group': 1}
190
191
192     def _send_translation_terms(self, cr, uid, term_ids, context=None):
193         """
194         Send a request to Gengo with all the term_ids in a different job, get the response and update the terms in
195         database accordingly.
196         """
197         flag, gengo = self.gengo_authentication(cr, uid, context=context)
198         if flag:
199             request = self.pack_jobs_request(cr, uid, term_ids, context=context)
200             if request['jobs']:
201                 result = gengo.postTranslationJobs(jobs=request)
202                 if result['opstat'] == 'ok':
203                     self._update_terms(cr, uid, result['response'], context=context)
204         else:
205             _logger.error(gengo)
206         return True
207
208     def _sync_request(self, cr, uid, limit=GENGO_DEFAULT_LIMIT, context=None):
209         """
210         This scheduler will send a job request to the gengo , which terms are
211         waiing to be translated and for which gengo_translation is enabled.
212
213         A special key 'gengo_language' can be passed in the context in order to
214         request only translations of that language only. Its value is the language
215         ID in openerp.
216         """
217         if context is None:
218             context = {}
219         language_pool = self.pool.get('res.lang')
220         translation_pool = self.pool.get('ir.translation')
221         domain = [('state', '=', 'to_translate'), ('gengo_translation', 'in', ('machine', 'standard', 'pro', 'ultra')), ('job_id', "=", False)]
222         if context.get('gengo_language', False):
223             lc = language_pool.browse(cr, uid, context['gengo_language'], context=context).code
224             domain.append( ('lang', '=', lc) )
225
226         all_term_ids = translation_pool.search(cr, uid, domain, context=context)
227         try:
228             offset = 0
229             while True:
230                 #search for the n first terms to translate
231                 term_ids = all_term_ids[offset:offset + limit]
232                 if term_ids:
233                     offset += limit
234                     self._send_translation_terms(cr, uid, term_ids, context=context)
235                     _logger.info("%s Translation terms have been posted to Gengo successfully", len(term_ids))
236                 if not len(term_ids) == limit:
237                     break
238         except Exception, e:
239             _logger.error("%s", e)
240
241 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: