2018-01-16 06:58:15 +01:00
# -*- coding: utf-8 -*-
2018-01-16 11:34:37 +01:00
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
2018-01-16 06:58:15 +01:00
import logging
import re
import time
import uuid
2018-01-16 11:34:37 +01:00
from flectra import api , fields , models , tools , _
from flectra . exceptions import UserError , ValidationError
2018-01-16 06:58:15 +01:00
_logger = logging . getLogger ( __name__ )
try :
from gengo import Gengo
except ImportError :
_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 ' )
GENGO_DEFAULT_LIMIT = 20
class BaseGengoTranslations ( models . TransientModel ) :
GENGO_KEY = " Gengo.UUID "
GROUPS = [ ' base.group_system ' ]
_name = ' base.gengo.translations '
@api.model
def default_get ( self , fields ) :
res = super ( BaseGengoTranslations , self ) . default_get ( fields )
res [ ' authorized_credentials ' ] , gengo = self . gengo_authentication ( )
if ' lang_id ' in fields :
res [ ' lang_id ' ] = self . env [ ' res.lang ' ] . search ( [
( ' code ' , ' = ' , self . env . context . get ( ' lang ' ) or ' en_US ' )
] , limit = 1 ) . id
return res
sync_type = fields . Selection ( [
( ' send ' , ' Send New Terms ' ) ,
( ' receive ' , ' Receive Translation ' ) ,
( ' both ' , ' Both ' )
] , " Sync Type " , default = ' both ' , required = True )
lang_id = fields . Many2one ( ' res.lang ' , ' Language ' , required = True )
sync_limit = fields . Integer ( " No. of terms to sync " , default = 20 )
authorized_credentials = fields . Boolean ( ' The private and public keys are valid ' )
@api.model_cr
def init ( self ) :
icp = self . env [ ' ir.config_parameter ' ] . sudo ( )
if not icp . get_param ( self . GENGO_KEY ) :
icp . set_param ( self . GENGO_KEY , str ( uuid . uuid4 ( ) ) )
@api.model_cr
def get_gengo_key ( self ) :
icp = self . env [ ' ir.config_parameter ' ] . sudo ( )
return icp . get_param ( self . GENGO_KEY , default = " Undefined " )
@api.multi
def open_company ( self ) :
self . ensure_one ( )
return {
' type ' : ' ir.actions.act_window ' ,
' view_type ' : ' form ' ,
' view_mode ' : ' form ' ,
2018-04-05 10:25:40 +02:00
' res_model ' : ' res.config.settings ' ,
' target ' : ' inline ' ,
' context ' : { ' module ' : ' general_settings ' } ,
2018-01-16 06:58:15 +01:00
}
@api.model
def gengo_authentication ( self ) :
'''
This method tries to open a connection with Gengo . For that , it uses the Public and Private
keys that are linked to the company ( given by Gengo on subscription ) . It returns a tuple with
* as first element : a boolean depicting if the authentication was a success or not
* as second element : the connection , if it was a success , or the error message returned by
Gengo when the connection failed .
This error message can either be displayed in the server logs ( if the authentication was called
by the cron ) or in a dialog box ( if requested by the user ) , thus it ' s important to return it
translated .
'''
user = self . env . user
if not user . company_id . gengo_public_key or not user . company_id . gengo_private_key :
return ( False , _ ( " Gengo `Public Key` or `Private Key` are missing. Enter your Gengo authentication parameters under `Settings > Companies > Gengo Parameters`. " ) )
try :
gengo = Gengo (
public_key = user . company_id . gengo_public_key . encode ( ' ascii ' ) ,
private_key = user . company_id . gengo_private_key . encode ( ' ascii ' ) ,
sandbox = user . company_id . gengo_sandbox ,
)
gengo . getAccountStats ( )
return ( True , gengo )
except Exception as e :
_logger . exception ( ' Gengo connection failed ' )
return ( False , _ ( " Gengo connection failed with this message: \n `` %s `` " ) % e )
@api.multi
def act_update ( self ) :
'''
Function called by the wizard .
'''
flag , gengo = self . gengo_authentication ( )
if not flag :
raise UserError ( gengo )
for wizard in self :
supported_langs = self . env [ ' ir.translation ' ] . _get_all_supported_languages ( )
language = self . env [ ' ir.translation ' ] . _get_gengo_corresponding_language ( wizard . lang_id . code )
if language not in supported_langs :
raise UserError ( _ ( ' This language is not supported by the Gengo translation services. ' ) )
ctx = self . env . context . copy ( )
ctx [ ' gengo_language ' ] = wizard . lang_id . id
if wizard . sync_limit > 200 or wizard . sync_limit < 1 :
raise UserError ( _ ( ' The number of terms to sync should be between 1 to 200 to work with Gengo translation services. ' ) )
if wizard . sync_type in [ ' send ' , ' both ' ] :
self . with_context ( ctx ) . _sync_request ( wizard . sync_limit )
if wizard . sync_type in [ ' receive ' , ' both ' ] :
self . with_context ( ctx ) . _sync_response ( wizard . sync_limit )
return { ' type ' : ' ir.actions.act_window_close ' }
@api.model
def _sync_response ( self , limit = GENGO_DEFAULT_LIMIT ) :
"""
This method will be called by cron services to get translations from
Gengo . It will read translated terms and comments from Gengo and will
2018-01-16 11:34:37 +01:00
update respective ir . translation in Flectra .
2018-01-16 06:58:15 +01:00
"""
IrTranslation = self . env [ ' ir.translation ' ]
flag , gengo = self . gengo_authentication ( )
if not flag :
_logger . warning ( " %s " , gengo )
else :
offset = 0
all_translation_ids = IrTranslation . search ( [
( ' state ' , ' = ' , ' inprogress ' ) ,
( ' gengo_translation ' , ' in ' , ( ' machine ' , ' standard ' , ' pro ' , ' ultra ' ) ) ,
( ' order_id ' , " != " , False ) ] )
while True :
translation_ids = all_translation_ids [ offset : offset + limit ]
offset + = limit
if not translation_ids :
break
terms_progress = {
' gengo_order_ids ' : set ( ) ,
' ir_translation_ids ' : set ( ) ,
}
for term in translation_ids :
terms_progress [ ' gengo_order_ids ' ] . add ( term . order_id )
terms_progress [ ' ir_translation_ids ' ] . add ( tools . ustr ( term . id ) )
for order_id in terms_progress [ ' gengo_order_ids ' ] :
order_response = gengo . getTranslationOrderJobs ( id = order_id )
jobs_approved = order_response . get ( ' response ' , [ ] ) . get ( ' order ' , [ ] ) . get ( ' jobs_approved ' , [ ] )
gengo_ids = ' , ' . join ( jobs_approved )
if gengo_ids : # Need to check, because getTranslationJobBatch don't catch this case and so call the getTranslationJobs because no ids in url
try :
job_response = gengo . getTranslationJobBatch ( id = gengo_ids )
except :
continue
if job_response [ ' opstat ' ] == ' ok ' :
for job in job_response [ ' response ' ] . get ( ' jobs ' , [ ] ) :
if job . get ( ' custom_data ' ) in terms_progress [ ' ir_translation_ids ' ] :
self . _update_terms_job ( job )
return True
@api.model
def _update_terms_job ( self , job ) :
translation = self . env [ ' ir.translation ' ] . browse ( int ( job [ ' custom_data ' ] ) )
vals = { }
if job . get ( ' status ' , False ) in ( ' queued ' , ' available ' , ' pending ' , ' reviewable ' ) :
vals [ ' state ' ] = ' inprogress '
if job . get ( ' body_tgt ' , False ) and job . get ( ' status ' , False ) == ' approved ' :
vals [ ' value ' ] = job [ ' body_tgt ' ]
if job . get ( ' status ' , False ) in ( ' approved ' , ' canceled ' ) :
vals [ ' state ' ] = ' translated '
if vals :
try :
translation . write ( vals )
except ValidationError :
pass
@api.model
def _update_terms ( self , response , term_ids ) :
"""
Update the terms after their translation were requested to Gengo
"""
vals = {
' order_id ' : response . get ( ' order_id ' , ' ' ) ,
' state ' : ' inprogress '
}
term_ids . write ( vals )
jobs = response . get ( ' jobs ' , [ ] )
if jobs :
for t_id , job in jobs . items ( ) :
self . _update_terms_job ( job )
return
@api.model
def pack_jobs_request ( self , term_ids , context = None ) :
''' prepare the terms that will be requested to gengo and returns them in a dictionary with following format
{ ' jobs ' : {
' term1.id ' : { . . . }
' term2.id ' : { . . . }
}
} '''
base_url = self . env [ ' ir.config_parameter ' ] . sudo ( ) . get_param ( ' web.base.url ' )
IrTranslation = self . env [ ' ir.translation ' ]
jobs = { }
user = self . env . user
auto_approve = 1 if user . company_id . gengo_auto_approve else 0
for term in term_ids :
if re . search ( r " \ w " , term . src or " " ) :
comment = user . company_id . gengo_comment or ' '
if term . gengo_comment :
comment + = ' \n ' + term . gengo_comment
jobs [ time . strftime ( ' % Y % m %d % H % M % S ' ) + ' - ' + str ( term . id ) ] = {
' type ' : ' text ' ,
' slug ' : ' Single :: English to ' + term . lang ,
' tier ' : tools . ustr ( term . gengo_translation ) ,
' custom_data ' : str ( term . id ) ,
' body_src ' : term . src ,
' lc_src ' : ' en ' ,
' lc_tgt ' : IrTranslation . _get_gengo_corresponding_language ( term . lang ) ,
' auto_approve ' : auto_approve ,
' comment ' : comment ,
' callback_url ' : " %s /website/gengo_callback?pgk= %s &db= %s " % ( base_url , self . get_gengo_key ( ) , self . env . cr . dbname )
}
return { ' jobs ' : jobs , ' as_group ' : 0 }
@api.model
def _send_translation_terms ( self , term_ids ) :
"""
Send a request to Gengo with all the term_ids in a different job , get the response and update the terms in
database accordingly .
"""
flag , gengo = self . gengo_authentication ( )
if flag :
request = self . pack_jobs_request ( term_ids )
if request [ ' jobs ' ] :
result = gengo . postTranslationJobs ( jobs = request )
if result [ ' opstat ' ] == ' ok ' :
self . _update_terms ( result [ ' response ' ] , term_ids )
else :
_logger . error ( gengo )
return True
@api.model
def _sync_request ( self , limit = GENGO_DEFAULT_LIMIT ) :
"""
This scheduler will send a job request to the gengo , which terms are
waiing to be translated and for which gengo_translation is enabled .
A special key ' gengo_language ' can be passed in the context in order to
request only translations of that language only . Its value is the language
2018-01-16 11:34:37 +01:00
ID in Flectra .
2018-01-16 06:58:15 +01:00
"""
domain = [
( ' state ' , ' = ' , ' to_translate ' ) ,
( ' gengo_translation ' , ' in ' , ( ' machine ' , ' standard ' , ' pro ' , ' ultra ' ) ) ,
( ' order_id ' , " = " , False ) ]
if self . env . context . get ( ' gengo_language ' , False ) :
lc = self . env [ ' res.lang ' ] . browse ( self . env . context [ ' gengo_language ' ] ) . code
domain . append ( ( ' lang ' , ' = ' , lc ) )
all_term_ids = self . env [ ' ir.translation ' ] . search ( domain )
try :
offset = 0
while True :
#search for the n first terms to translate
term_ids = all_term_ids [ offset : offset + limit ]
if term_ids :
offset + = limit
self . _send_translation_terms ( term_ids )
_logger . info ( " %s Translation terms have been posted to Gengo successfully " , len ( term_ids ) )
if not len ( term_ids ) == limit :
break
except Exception as e :
_logger . error ( " %s " , e )