Antonio Espinosa
8 years ago
committed by
Timon Tschanz
6 changed files with 333 additions and 259 deletions
-
48web_translate_dialog/README.rst
-
56web_translate_dialog/__openerp__.py
-
3web_translate_dialog/static/src/css/base.css
-
403web_translate_dialog/static/src/js/web_translate_dialog.js
-
71web_translate_dialog/static/src/xml/base.xml
-
11web_translate_dialog/view/web_translate.xml
@ -1,32 +1,26 @@ |
|||
# -*- 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": "Web", |
|||
"summary": "Easy-to-use pop-up to translate fields in several languages", |
|||
"license": "AGPL-3", |
|||
"author": "Camptocamp,Odoo Community Association (OCA)", |
|||
"version": "8.0.1.0.0", |
|||
"depends": ['web'], |
|||
'data': ['view/web_translate.xml'], |
|||
'qweb': ["static/src/xml/base.xml"], |
|||
'installable': False, |
|||
} |
|||
# Copyright 2012 Guewen Baconnier (Camptocamp SA) |
|||
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
{ |
|||
"name": "Web Translate Dialog", |
|||
"summary": "Easy-to-use pop-up to translate fields in several languages", |
|||
"version": "9.0.1.0.0", |
|||
"category": "Web", |
|||
"website": "https://odoo-community.org/", |
|||
"author": "Camptocamp, " |
|||
"Tecnativa, " |
|||
"Odoo Community Association (OCA)", |
|||
"license": "AGPL-3", |
|||
"application": False, |
|||
"installable": True, |
|||
"depends": [ |
|||
"web", |
|||
], |
|||
"data": [ |
|||
"view/web_translate.xml", |
|||
], |
|||
"qweb": [ |
|||
"static/src/xml/base.xml", |
|||
] |
|||
} |
@ -1,209 +1,228 @@ |
|||
openerp.web_translate_dialog = function (instance) { |
|||
/* Copyright 2012 Guewen Baconnier (Camptocamp SA) |
|||
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> |
|||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
|||
|
|||
"use strict"; |
|||
odoo.define('web_translate_dialog.translate_dialog', function(require){ |
|||
"use strict"; |
|||
|
|||
var QWeb = instance.web.qweb, |
|||
_t = instance.web._t, |
|||
_lt = instance.web._lt; |
|||
var _ = require('_'); |
|||
var $ = require('$'); |
|||
|
|||
instance.web.FormView.include({ |
|||
load_form: function(data) { |
|||
var self = this; |
|||
this._super(data); |
|||
if (this.sidebar) { |
|||
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); |
|||
}); |
|||
}, |
|||
}); |
|||
var core = require('web.core'); |
|||
var data = require('web.data'); |
|||
var common = require('web.form_common'); |
|||
|
|||
instance.web.View.include({ |
|||
open_translate_dialog: function() { |
|||
new instance.web_translate_dialog.TranslateDialog(this).open(); |
|||
} |
|||
}); |
|||
var FormView = require('web.FormView'); |
|||
var View = require('web.View'); |
|||
var Dialog = require('web.Dialog'); |
|||
|
|||
instance.web_translate_dialog.TranslateDialog = instance.web.Dialog.extend({ |
|||
template: "TranslateDialog", |
|||
init: function(parent, options, content) { |
|||
this._super(parent, |
|||
{title: _t("Translations"), |
|||
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() { |
|||
return sup.call(self); |
|||
}); |
|||
}, |
|||
start: function() { |
|||
var self = this; |
|||
this.$el.find('.oe_translation_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(); |
|||
}); |
|||
this.initialize_html_fields(); |
|||
var _t = core._t; |
|||
var QWeb = core.qweb; |
|||
|
|||
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 translateDialog = Dialog.extend({ |
|||
template: "TranslateDialog", |
|||
init: function(parent, field, content) { |
|||
this._super(parent, |
|||
{title: _t("Translations"), |
|||
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; |
|||
if (!!field) { |
|||
this.translatable_fields_keys = [field]; |
|||
this.translatable_fields = _.filter( |
|||
this.view.translatable_fields || [], |
|||
function(i) {return i.name == field;} |
|||
); |
|||
} else { |
|||
this.translatable_fields_keys = _.map( |
|||
this.view.translatable_fields || [], |
|||
function(i) {return i.name;} |
|||
); |
|||
this.translatable_fields = this.view.translatable_fields.slice(0); |
|||
} |
|||
this.languages = null; |
|||
this.languages_loaded = $.Deferred(); |
|||
(new data.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() { |
|||
return sup.call(self); |
|||
}); |
|||
}, |
|||
start: function() { |
|||
var self = this; |
|||
this.$el.find('.oe_translation_field').change(function() { |
|||
$(this).toggleClass('touched', ($(this).val() != $(this).attr('data-value'))); |
|||
}); |
|||
this.$footer.html(QWeb.render("TranslateDialog.buttons")); |
|||
this.$footer.find(".oe_form_translate_dialog_save_button").click(function(){ |
|||
self.on_button_save(); |
|||
self.on_button_close(); |
|||
}); |
|||
this.$footer.find(".oe_form_translate_dialog_cancel_button").click(function(){ |
|||
self.on_button_close(); |
|||
}); |
|||
|
|||
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'); |
|||
this.do_load_fields_values(); |
|||
}, |
|||
initialize_html_fields: function(lang) { |
|||
var self = this; |
|||
_.each(this.translatable_fields_keys, function(f) { |
|||
// Initialize summernote if HTML field
|
|||
self.$el.find('.oe_form_field_html .oe_translation_field[name="' + lang.code + '-' + f + '"]').each(function() { |
|||
var $parent = $(this).summernote({ |
|||
'focus': false, |
|||
'toolbar': [ |
|||
['style', ['style']], |
|||
['font', ['bold', 'italic', 'underline', 'clear']], |
|||
['fontsize', ['fontsize']], |
|||
['color', ['color']], |
|||
['para', ['ul', 'ol', 'paragraph']], |
|||
['table', ['table']], |
|||
['insert', ['link', 'picture']], |
|||
['history', ['undo', 'redo']] |
|||
], |
|||
'prettifyHtml': false, |
|||
'styleWithSpan': false, |
|||
'inlinemedia': ['p'], |
|||
'lang': "odoo", |
|||
'onChange': function (value) { |
|||
$(this).toggleClass('touched', (value != $(this).attr('data-value'))); |
|||
} |
|||
}).parent(); |
|||
// Triggers a mouseup to refresh the editor toolbar
|
|||
$parent.find('.note-editable').trigger('mouseup'); |
|||
$parent.find('.note-editing-area').css({ |
|||
minHeight:'100px', |
|||
minWidth:'260px', |
|||
}); |
|||
$cleditor.refresh(); |
|||
// -- end
|
|||
}); |
|||
}); |
|||
}, |
|||
set_fields_values: function(lang, values) { |
|||
var self = this; |
|||
_.each(this.translatable_fields_keys, function(f) { |
|||
self.$el.find('.oe_translation_field[name="' + lang.code + '-' + f + '"]') |
|||
.val(values[f] || '') |
|||
.attr('data-value', values[f] || ''); |
|||
}); |
|||
this.$el.find('textarea.oe_translation_field').css({ |
|||
minHeight:'100px', |
|||
}); |
|||
$(window).resize(); // triggers the autosize
|
|||
this.initialize_html_fields(lang); |
|||
}, |
|||
do_load_fields_values: function() { |
|||
var self = this, |
|||
deferred = []; |
|||
|
|||
$cleditor.change(function() { |
|||
this.updateTextArea(); |
|||
this.$area.toggleClass('touched', |
|||
(this.$area.val() != this.$area.attr('data-value'))); |
|||
this.$el.find('.oe_translation_field').val('').removeClass('touched'); |
|||
_.each(self.languages, function(lg) { |
|||
var deff = $.Deferred(); |
|||
deferred.push(deff); |
|||
if (lg.code === self.view_language) { |
|||
var values = {}; |
|||
_.each(self.translatable_fields_keys, function(field) { |
|||
values[field] = self.view.fields[field].get_value(); |
|||
}); |
|||
self.set_fields_values(lg, values); |
|||
deff.resolve(); |
|||
} else { |
|||
self.view.dataset.call( |
|||
'read', |
|||
[[self.view.datarecord.id], |
|||
self.translatable_fields_keys, |
|||
self.view.dataset.get_context({ |
|||
'lang': lg.code |
|||
})]).done(function (rows) { |
|||
self.set_fields_values(lg, rows[0]); |
|||
deff.resolve(); |
|||
}); |
|||
} |
|||
}); |
|||
return deferred; |
|||
}, |
|||
on_button_save: function() { |
|||
var translations = {}, |
|||
self = this, |
|||
translation_mutex = new $.Mutex(); |
|||
self.$el.find('.oe_translation_field.touched').each(function() { |
|||
var field = $(this).attr('name').split('-'); |
|||
if (!translations[field[0]]) { |
|||
translations[field[0]] = {}; |
|||
} |
|||
translations[field[0]][field[1]] = $(this).val(); |
|||
}); |
|||
_.each(translations, function(text, code) { |
|||
if (code === self.view_language) { |
|||
self.view.set_values(text); |
|||
} |
|||
translation_mutex.exec(function() { |
|||
return new data.DataSet(self, self.view.dataset.model, |
|||
self.view.dataset.get_context()) |
|||
.write(self.view.datarecord.id, text, |
|||
{ context : { 'lang': code }}); |
|||
}); |
|||
}, |
|||
set_fields_values: function(lang, values) { |
|||
var self = this; |
|||
_.each(this.translatable_fields_keys, function(f) { |
|||
self.$el.find('.oe_translation_field[name="' + lang.code + '-' + f + '"]') |
|||
.val(values[f] || '') |
|||
.attr('data-value', values[f] || ''); |
|||
}); |
|||
this.close(); |
|||
}, |
|||
on_button_close: function() { |
|||
this.close(); |
|||
}, |
|||
|
|||
var $tarea = self.$el.find('.oe_form_field_html .oe_translation_field[name="' + lang.code + '-' + f + '"]'); |
|||
if ($tarea.length) { |
|||
$tarea.cleditor()[0].updateFrame(); |
|||
} |
|||
}); |
|||
var $textarea = this.$el.find('textarea.oe_translation_field'); |
|||
$textarea.css({minHeight:'100px'}); |
|||
$textarea.autosize(); |
|||
$(window).resize(); // triggers the autosize
|
|||
}, |
|||
do_load_fields_values: function() { |
|||
var self = this, |
|||
deferred = []; |
|||
}); |
|||
|
|||
this.$el.find('.oe_translation_field').val('').removeClass('touched'); |
|||
_.each(self.languages, function(lg) { |
|||
var deff = $.Deferred(); |
|||
deferred.push(deff); |
|||
if (lg.code === self.view_language) { |
|||
var values = {}; |
|||
_.each(self.translatable_fields_keys, function(field) { |
|||
values[field] = self.view.fields[field].get_value(); |
|||
}); |
|||
self.set_fields_values(lg, values); |
|||
deff.resolve(); |
|||
} else { |
|||
self.view.dataset.call( |
|||
'read', |
|||
[[self.view.datarecord.id], |
|||
self.translatable_fields_keys, |
|||
self.view.dataset.get_context({ |
|||
'lang': lg.code |
|||
})]).done(function (rows) { |
|||
self.set_fields_values(lg, rows[0]); |
|||
deff.resolve(); |
|||
}); |
|||
}; |
|||
}); |
|||
return deferred; |
|||
}, |
|||
on_button_save: function() { |
|||
var translations = {}, |
|||
self = this, |
|||
translation_mutex = new $.Mutex(); |
|||
self.$el.find('.oe_translation_field.touched').each(function() { |
|||
var field = $(this).attr('name').split('-'); |
|||
if (!translations[field[0]]) { |
|||
translations[field[0]] = {}; |
|||
} |
|||
translations[field[0]][field[1]] = $(this).val(); |
|||
}); |
|||
_.each(translations, function(data, code) { |
|||
if (code === self.view_language) { |
|||
self.view.set_values(data); |
|||
} |
|||
translation_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(); |
|||
}, |
|||
FormView.include({ |
|||
render_sidebar: function($node) { |
|||
this._super($node); |
|||
if (this.sidebar) { |
|||
this.sidebar.add_items('other', _.compact([ |
|||
this.is_action_enabled('edit') && |
|||
this.translatable_fields.length > 0 && { |
|||
label: _t('Translate'), |
|||
callback: this.on_button_translate |
|||
}, |
|||
])); |
|||
} |
|||
}, |
|||
on_button_translate: function() { |
|||
var self = this; |
|||
$.when(this.has_been_loaded).then(function() { |
|||
self.open_translate_dialog(); |
|||
}); |
|||
}, |
|||
}); |
|||
|
|||
}); |
|||
View.include({ |
|||
open_translate_dialog: function(field) { |
|||
new translateDialog(this, field).open(); |
|||
} |
|||
}); |
|||
|
|||
instance.web.form.AbstractField.include({ |
|||
on_translate: function() { |
|||
// the image next to the fields opens the translate dialog
|
|||
this.view.open_translate_dialog(); |
|||
}, |
|||
}); |
|||
common.AbstractField.include({ |
|||
on_translate: function() { |
|||
// the image next to the fields opens the translate dialog
|
|||
this.view.open_translate_dialog(this.name); |
|||
}, |
|||
}); |
|||
|
|||
return { |
|||
translateDialog: translateDialog, |
|||
}; |
|||
|
|||
}); // odoo.define
|
@ -1,34 +1,51 @@ |
|||
<templates> |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!-- Copyright 2012 Guewen Baconnier (Camptocamp SA) |
|||
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> |
|||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
|||
<template> |
|||
|
|||
<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_translation_field"/> |
|||
<textarea t-if="field.field.type == 'text'" t-attf-name="#{lg.code}-#{field.name}" data-value="" class="oe_translation_field" ></textarea> |
|||
<div t-if="field.field.type == 'html'" class="oe_form_field_html"> |
|||
<textarea class="oe_translation_field oe_form_field" t-attf-name="#{lg.code}-#{field.name}" data-value=""/> |
|||
</div> |
|||
</td> |
|||
</tr> |
|||
</table> |
|||
<div class="modal-body"> |
|||
<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.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_translation_field"/> |
|||
<textarea t-if="field.field.type == 'text'" |
|||
t-attf-name="#{lg.code}-#{field.name}" data-value="" |
|||
class="oe_translation_field" ></textarea> |
|||
<div t-if="field.field.type == 'html'" class="oe_form_field_html"> |
|||
<textarea class="oe_translation_field oe_form_field" |
|||
t-attf-name="#{lg.code}-#{field.name}" data-value=""/> |
|||
</div> |
|||
</td> |
|||
</tr> |
|||
</table> |
|||
</div> |
|||
</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> |
|||
<button class="btn btn-sm oe_button btn-primary oe_form_translate_dialog_save_button"> |
|||
<span>Save</span> |
|||
</button> |
|||
<button class="btn btn-sm oe_button btn-default oe_form_translate_dialog_cancel_button"> |
|||
<span>Cancel</span> |
|||
</button> |
|||
</t> |
|||
|
|||
</templates> |
|||
|
|||
</template> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue