committed by
Timon Tschanz
6 changed files with 388 additions and 0 deletions
-
22web_translate_dialog/__init__.py
-
45web_translate_dialog/__openerp__.py
-
79web_translate_dialog/orm.py
-
6web_translate_dialog/static/src/css/base.css
-
202web_translate_dialog/static/src/js/web_translate_dialog.js
-
34web_translate_dialog/static/src/xml/base.xml
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Author: Guewen Baconnier |
||||
|
# Copyright 2012 Camptocamp SA |
||||
|
# |
||||
|
# This program is free software: you can redistribute it and/or modify |
||||
|
# it under the terms of the GNU Affero General Public License as |
||||
|
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################## |
||||
|
|
||||
|
from . import orm |
@ -0,0 +1,45 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Author: Guewen Baconnier |
||||
|
# Copyright 2012 Camptocamp SA |
||||
|
# |
||||
|
# This program is free software: you can redistribute it and/or modify |
||||
|
# it under the terms of the GNU Affero General Public License as |
||||
|
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################## |
||||
|
|
||||
|
{"name": "Web Translate Dialog", |
||||
|
"category": "Hidden", |
||||
|
"description": """ |
||||
|
Replace the standard translation view by an alternative one: |
||||
|
|
||||
|
* Add a "Translate" button item in the "More" menu |
||||
|
* The translations are displayed in a dialog (much like the OpenERP |
||||
|
6.1's one) |
||||
|
* The translation dialog displays empty fields for the untranslated fields, |
||||
|
instead of the source values. |
||||
|
* Support HTML fields |
||||
|
|
||||
|
""", |
||||
|
"version": "1.0", |
||||
|
"depends": ['web', |
||||
|
], |
||||
|
'js': ['static/src/js/web_translate_dialog.js', |
||||
|
], |
||||
|
'css': ['static/src/css/base.css', |
||||
|
], |
||||
|
'qweb': ["static/src/xml/base.xml", |
||||
|
], |
||||
|
'auto_install': False, |
||||
|
} |
@ -0,0 +1,79 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Author: Guewen Baconnier |
||||
|
# Copyright 2012-2013 Camptocamp SA |
||||
|
# |
||||
|
# This program is free software: you can redistribute it and/or modify |
||||
|
# it under the terms of the GNU Affero General Public License as |
||||
|
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################## |
||||
|
|
||||
|
import openerp.osv.orm |
||||
|
|
||||
|
|
||||
|
# add the method in the orm so we can use it from the TranslateDialog of the |
||||
|
# webclient instead of the normal read |
||||
|
def read_translations(self, cr, user, ids, fields=None, context=None, load='_classic_read'): |
||||
|
""" Read records with given ids with the given fields, if a field is not |
||||
|
translated, its value will be False instead of the source language's value. |
||||
|
|
||||
|
:param fields: optional list of field names to return (default: all fields would be returned) |
||||
|
:type fields: list (example ['field_name_1', ...]) |
||||
|
:return: list of dictionaries((dictionary per record asked)) with requested field values |
||||
|
:rtype: [{‘name_of_the_field’: value, ...}, ...] |
||||
|
:raise AccessError: * if user has no read rights on the requested object |
||||
|
* if user tries to bypass access rules for read on the requested object |
||||
|
|
||||
|
""" |
||||
|
|
||||
|
if context is None: |
||||
|
context = {} |
||||
|
self.check_access_rights(cr, user, 'read') |
||||
|
fields = self.check_field_access_rights(cr, user, 'read', fields) |
||||
|
if isinstance(ids, (int, long)): |
||||
|
select = [ids] |
||||
|
else: |
||||
|
select = ids |
||||
|
select = map(lambda x: isinstance(x, dict) and x['id'] or x, select) |
||||
|
result = self._read_flat(cr, user, select, fields, context, load) |
||||
|
|
||||
|
if context.get('lang') and context['lang'] != 'en_US': |
||||
|
fields_pre = [f for f in fields if |
||||
|
(f in self._columns and |
||||
|
getattr(self._columns[f], '_classic_write'))] + \ |
||||
|
self._inherits.values() |
||||
|
|
||||
|
for f in fields_pre: |
||||
|
if self._columns[f].translate: |
||||
|
res_ids = [x['id'] for x in result] |
||||
|
res_trans = self.pool.get('ir.translation')._get_ids( |
||||
|
cr, user, |
||||
|
self._name + ',' + f, |
||||
|
'model', |
||||
|
context['lang'], |
||||
|
res_ids) |
||||
|
for r in result: |
||||
|
if not res_trans.get(r['id']): |
||||
|
r[f] = None |
||||
|
|
||||
|
for r in result: |
||||
|
for key, v in r.iteritems(): |
||||
|
if v is None: |
||||
|
r[key] = False |
||||
|
|
||||
|
if isinstance(ids, (int, long, dict)): |
||||
|
return result and result[0] or False |
||||
|
return result |
||||
|
|
||||
|
openerp.osv.orm.BaseModel.read_translations = read_translations |
@ -0,0 +1,6 @@ |
|||||
|
.openerp .oe_trad_field { |
||||
|
width: 95%; |
||||
|
} |
||||
|
.openerp .oe_trad_field.touched { |
||||
|
border: 1px solid green !important; |
||||
|
} |
@ -0,0 +1,202 @@ |
|||||
|
openerp.web_translate_dialog = function (instance) { |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
var QWeb = instance.web.qweb, |
||||
|
_t = instance.web._t, |
||||
|
_lt = instance.web._lt; |
||||
|
|
||||
|
instance.web.FormView.include({ |
||||
|
load_form: function(data) { |
||||
|
var self = this; |
||||
|
this._super(data); |
||||
|
this.sidebar.add_items('other', _.compact([ |
||||
|
self.is_action_enabled('edit') && { label: _t('Translate'), callback: self.on_button_translate }, |
||||
|
])); |
||||
|
}, |
||||
|
on_button_translate: function() { |
||||
|
var self = this; |
||||
|
$.when(this.has_been_loaded).then(function() { |
||||
|
self.open_translate_dialog(this); |
||||
|
}); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
instance.web.View.include({ |
||||
|
open_translate_dialog: function() { |
||||
|
new instance.web_translate_dialog.TranslateDialog(this).open(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
instance.web_translate_dialog.TranslateDialog = instance.web.Dialog.extend({ |
||||
|
template: "TranslateDialog", |
||||
|
dialog_title: {toString: function () { return _t("Translations"); }}, |
||||
|
init: function(parent, options, content) { |
||||
|
this._super(parent, |
||||
|
{width: '90%', |
||||
|
height: '80%'}, |
||||
|
content); |
||||
|
this.view_language = this.session.user_context.lang; |
||||
|
this.view = parent; |
||||
|
this.view_type = parent.fields_view.type || ''; |
||||
|
this.$view_form = null; |
||||
|
this.$sidebar_form = null; |
||||
|
this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name;}); |
||||
|
this.languages = null; |
||||
|
this.languages_loaded = $.Deferred(); |
||||
|
(new instance.web.DataSetSearch(this, |
||||
|
'res.lang', |
||||
|
this.view.dataset.get_context(), |
||||
|
[['translatable', '=', '1']])) |
||||
|
.read_slice(['code', 'name'], { sort: 'id' }) |
||||
|
.then(this.on_languages_loaded); |
||||
|
}, |
||||
|
on_languages_loaded: function(langs) { |
||||
|
this.languages = langs; |
||||
|
this.languages_loaded.resolve(); |
||||
|
}, |
||||
|
open: function() { |
||||
|
var self = this, |
||||
|
sup = this._super; |
||||
|
// the template needs the languages
|
||||
|
$.when(this.languages_loaded).then(function() { |
||||
|
// if (self.view.translatable_fields && self.view.translatable_fields.length) {
|
||||
|
return sup.call(self); |
||||
|
}); |
||||
|
}, |
||||
|
start: function() { |
||||
|
var self = this; |
||||
|
this.$el.find('.oe_trad_field').change(function() { |
||||
|
$(this).toggleClass('touched', ($(this).val() != $(this).attr('data-value'))); |
||||
|
}); |
||||
|
this.$buttons.html(QWeb.render("TranslateDialog.buttons")); |
||||
|
this.$buttons.find(".oe_form_translate_dialog_save_button").click(function(){ |
||||
|
self.on_button_save(); |
||||
|
self.on_button_close(); |
||||
|
}); |
||||
|
this.$buttons.find(".oe_form_translate_dialog_cancel_button").click(function(){ |
||||
|
self.on_button_close(); |
||||
|
}); |
||||
|
var $textarea = self.$el.find('textarea.oe_trad_field'); |
||||
|
$textarea.css({minHeight:'100px'}); |
||||
|
$textarea.autosize(); |
||||
|
this.initialize_html_fields(); |
||||
|
|
||||
|
this.do_load_fields_values(); |
||||
|
}, |
||||
|
initialize_html_fields: function() { |
||||
|
this.$el.find('.oe_form_field_html textarea').each(function() { |
||||
|
var $textarea = $(this); |
||||
|
var width = 100; // forced to fixed size on initialization
|
||||
|
// will be changed to percentage right after
|
||||
|
// the creation
|
||||
|
var height = 250; |
||||
|
$textarea.cleditor({ |
||||
|
width: width, // width not including margins, borders or padding
|
||||
|
height: height, // height not including margins, borders or padding
|
||||
|
controls: // controls to add to the toolbar
|
||||
|
"bold italic underline strikethrough " + |
||||
|
"| removeformat | bullets numbering | outdent " + |
||||
|
"indent | link unlink | source", |
||||
|
bodyStyle: // style to assign to document body contained within the editor
|
||||
|
"margin:4px; color:#4c4c4c; font-size:13px; font-family:'Lucida Grande',Helvetica,Verdana,Arial,sans-serif; cursor:text" |
||||
|
}); |
||||
|
|
||||
|
var $cleditor = $textarea.cleditor()[0]; |
||||
|
// Down to -- end, this is a workaround for the bug
|
||||
|
// https://bugs.launchpad.net/openerp-web/+bug/1258463
|
||||
|
// The editor is initially created with a fixed size so
|
||||
|
// the buggy event is not bound to $(window), then we restore
|
||||
|
// a percentage width and bind the "normal" event without the
|
||||
|
// CHM's buggy change.
|
||||
|
$cleditor.$main.width('95%'); |
||||
|
$cleditor.options.width = '95%'; |
||||
|
$(window).resize(function() { |
||||
|
//Forcefully blurred iframe contentWindow, chrome, IE, safari doesn't trigger blur on window resize and due to which text disappears
|
||||
|
var contentWindow = $cleditor.$frame[0].contentWindow; |
||||
|
if(!$.browser.mozilla && contentWindow){ |
||||
|
$(contentWindow).trigger('blur'); |
||||
|
} |
||||
|
}); |
||||
|
$cleditor.refresh(); |
||||
|
// -- end
|
||||
|
|
||||
|
$cleditor.change(function() { |
||||
|
this.updateTextArea(); |
||||
|
this.$area.toggleClass('touched', |
||||
|
(this.$area.val() != this.$area.attr('data-value'))); |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
// use a `read_translations` method instead of a `read`
|
||||
|
// this latter leave the fields empty if there is no
|
||||
|
// translation for a field instead of taking the src field
|
||||
|
do_load_fields_values: function(callback) { |
||||
|
var self = this, |
||||
|
deferred = []; |
||||
|
|
||||
|
this.$el.find('.oe_trad_field').val('').removeClass('touched'); |
||||
|
_.each(self.languages, function(lg) { |
||||
|
var deff = $.Deferred(); |
||||
|
deferred.push(deff); |
||||
|
var callback = function(values) { |
||||
|
}; |
||||
|
self.view.dataset.call( |
||||
|
'read_translations', |
||||
|
[[self.view.datarecord.id], |
||||
|
self.translatable_fields_keys, |
||||
|
self.view.dataset.get_context({ |
||||
|
'lang': lg.code |
||||
|
})]).done(function (values) { |
||||
|
_.each(self.translatable_fields_keys, function(f) { |
||||
|
self.$el.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]') |
||||
|
.val(values[0][f] || '') |
||||
|
.attr('data-value', values[0][f] || ''); |
||||
|
|
||||
|
var $tarea = self.$el.find('.oe_form_field_html .oe_trad_field[name="' + lg.code + '-' + f + '"]'); |
||||
|
if ($tarea.length) { |
||||
|
$tarea.cleditor()[0].updateFrame(); |
||||
|
} |
||||
|
}); |
||||
|
deff.resolve(); |
||||
|
}); |
||||
|
}); |
||||
|
return deferred; |
||||
|
}, |
||||
|
on_button_save: function() { |
||||
|
var trads = {}, |
||||
|
self = this, |
||||
|
trads_mutex = new $.Mutex(); |
||||
|
self.$el.find('.oe_trad_field.touched').each(function() { |
||||
|
var field = $(this).attr('name').split('-'); |
||||
|
if (!trads[field[0]]) { |
||||
|
trads[field[0]] = {}; |
||||
|
} |
||||
|
trads[field[0]][field[1]] = $(this).val(); |
||||
|
}); |
||||
|
_.each(trads, function(data, code) { |
||||
|
if (code === self.view_language) { |
||||
|
_.each(data, function(value, field) { |
||||
|
self.view.fields[field].set_value(value); |
||||
|
}); |
||||
|
} |
||||
|
trads_mutex.exec(function() { |
||||
|
return new instance.web.DataSet(self, self.view.dataset.model, self.view.dataset.get_context()).write(self.view.datarecord.id, data, { context : { 'lang': code }}); |
||||
|
}); |
||||
|
}); |
||||
|
this.close(); |
||||
|
}, |
||||
|
on_button_close: function() { |
||||
|
this.close(); |
||||
|
}, |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
instance.web.form.AbstractField.include({ |
||||
|
on_translate: function() { |
||||
|
// the image next to the fields opens the translate dialog
|
||||
|
this.view.open_translate_dialog(); |
||||
|
}, |
||||
|
}); |
||||
|
}; |
||||
|
|
@ -0,0 +1,34 @@ |
|||||
|
<templates> |
||||
|
|
||||
|
<t t-name="TranslateDialog"> |
||||
|
<table t-if="widget.view.translatable_fields" class="oe_frame oe_forms oe_translation_form" border="0" cellpadding="0" cellspacing="0" width="100%"> |
||||
|
<tr> |
||||
|
<td class="oe_form_separator" width="1%" nowrap="nowrap"> |
||||
|
<div class="separator horizontal">Field</div> |
||||
|
</td> |
||||
|
<th t-foreach="widget.languages" align="left"> |
||||
|
<div class="separator horizontal"><t t-esc="name"/></div> |
||||
|
</th> |
||||
|
</tr> |
||||
|
<tr t-foreach="widget.view.translatable_fields" t-as="field" t-att-data-field="field.name"> |
||||
|
<td class="oe_form_frame_cell" width="1%" nowrap="nowrap"> |
||||
|
<label class="oe_label"><t t-esc="field.string"/>:</label> |
||||
|
</td> |
||||
|
<td t-foreach="widget.languages" t-as="lg" class="oe_form_frame_cell"> |
||||
|
<input t-if="field.field.type == 'char' || field.field.type == 'url'" type="text" t-attf-name="#{lg.code}-#{field.name}" value="" data-value="" class="oe_trad_field"/> |
||||
|
<textarea t-if="field.field.type == 'text'" t-attf-name="#{lg.code}-#{field.name}" data-value="" class="oe_trad_field" ></textarea> |
||||
|
<div t-if="field.field.type == 'html'" class="oe_form_field_html"> |
||||
|
<textarea class="oe_trad_field oe_form_field" t-attf-name="#{lg.code}-#{field.name}" data-value=""/> |
||||
|
</div> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
</t> |
||||
|
|
||||
|
<t t-name="TranslateDialog.buttons"> |
||||
|
<button class="oe_form_translate_dialog_save_button oe_button oe_highlight">Save</button> |
||||
|
<button class="oe_form_translate_dialog_cancel_button oe_button">Cancel</button> |
||||
|
</t> |
||||
|
|
||||
|
</templates> |
||||
|
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue