diff --git a/addons/website/static/src/css/editor.css b/addons/website/static/src/css/editor.css index 127939f6caf..b6cc8505fed 100644 --- a/addons/website/static/src/css/editor.css +++ b/addons/website/static/src/css/editor.css @@ -107,6 +107,27 @@ table.editorbar-panel td.selected { background-color: #b1c9d9; } +.oe_translate_examples li { + margin: 10px; + padding: 4px; +} + +.oe_translatable_text { + outline: 1px solid black; +} + +.oe_translatable_field { + outline: 1px dashed black; +} + +.oe_translatable_text.oe_dirty, .oe_translatable_field.oe_dirty { + outline-color: red; +} + +.oe_translatable_todo { + background: #ffffb6; +} + /* ---- RTE ---- */ .oe_editable .btn { -webkit-user-select: auto; diff --git a/addons/website/static/src/css/editor.sass b/addons/website/static/src/css/editor.sass index 6eb47eab614..b5c0b21a197 100644 --- a/addons/website/static/src/css/editor.sass +++ b/addons/website/static/src/css/editor.sass @@ -93,6 +93,20 @@ table.editorbar-panel td.selected background-color: #b1c9d9 +// ---- TRANSLATIONS ---- {{{ +.oe_translate_examples li + margin: 10px + padding: 4px +.oe_translatable_text + outline: 1px solid black +.oe_translatable_field + outline: 1px dashed black +.oe_translatable_text.oe_dirty, .oe_translatable_field.oe_dirty + outline-color: red +.oe_translatable_todo + background: rgb(255, 255, 182) +// }}} + /* ---- RTE ---- */ // bootstrap makes .btn elements unselectable -> RTE double-click can't know @@ -481,3 +495,5 @@ $navbar_height: 51px &.oe_ace_closed width: 0 opacity: 0 + +// vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker: diff --git a/addons/website/static/src/js/website.translator.js b/addons/website/static/src/js/website.translator.js new file mode 100644 index 00000000000..ac4c6d298d2 --- /dev/null +++ b/addons/website/static/src/js/website.translator.js @@ -0,0 +1,151 @@ +(function () { + 'use strict'; + + var website = openerp.website; + website.templates.push('/website/static/src/xml/website.translator.xml'); + + website.EditorBar.include({ + events: _.extend({}, website.EditorBar.prototype.events, { + 'click a[data-action=js_website_translator]': function (ev) { + var self = this; + ev.preventDefault(); + if (!localStorage['website_translator_nodialog']) { + var dialog = new website.TranslatorDialog(); + dialog.appendTo($(document.body)); + dialog.on('activate', this, function () { + dialog.$el.modal('hide'); + this.translateMode(); + }); + } else { + this.translateMode(); + } + }, + }), + temporaryHack: function() { + // TODO: refactor once xmo's feature branch is merged + debugger + this.$buttons.edit.prop('disabled', true); + this.$('#website-top-view').hide(); + this.$('#website-top-edit').show(); + $('.css_non_editable_mode_hidden').removeClass("css_non_editable_mode_hidden"); + this.$el.find('[data-action=snippet]').hide(); + this.save = this.saveTranslations; + }, + translateMode: function () { + var self = this; + this.temporaryHack(); + // this.edit(); -- Will need a translation mode + this.translations = null; + openerp.jsonRpc('/website/get_view_translations', 'call', { + 'xml_id': $(document.documentElement).data('view-xmlid'), + 'context': website.get_context(), + }).then(function (translations) { + self.translations = translations; + self.processTranslatableNodes(); + }); + }, + processTranslatableNodes: function () { + var self = this; + var $editables = $('[data-oe-model]') + .not('link, script') + .not('.oe_snippets,.oe_snippet, .oe_snippet *') + .not('[data-oe-type]'); + $editables.each(function () { + self.transNode(this); + }); + $('.oe_translatable_text').prop('contenteditable', true).on('paste', function () { + var node = $(this); + setTimeout(function () { + self.sanitizeNode(node); + }, 0); + }); + $(document).on('blur keyup paste', '.oe_translatable_text[contenteditable]', function(ev) { + var $node = $(this); + setTimeout(function () { + // Doing stuff next tick because past and keyup are + // fired before the content is changed + if (ev.type == 'paste') { + self.sanitizeNode($node[0]); + } + if ($node.data('initial_content') !== $node.text()) { + $node.addClass('oe_dirty').removeClass('oe_translatable_todo'); + } + }, 0); + }); + }, + sanitizeNode: function (node) { + node.text(node.text()); + }, + isTextNode: function (node) { + return node.nodeType === 3 || node.nodeType === 4; + }, + isTranslatable: function (text) { + return text && _.str.trim(text) !== ''; + }, + markTranslatableNode: function (node) { + // TODO: link nodes with same content + node.className += ' oe_translatable_text oe_translatable_todo'; + node.contentEditable = true; + $(node).data('initial_content', node.childNodes[0].data); + }, + saveTranslations: function () { + var trans = {}; + $('.oe_translatable_text.oe_dirty').each(function () { + var $node = $(this); + var text = $node.text(); + var view_id = $(this).parents('[data-oe-model="ir.ui.view"]') + .not('[data-oe-type]').first().data().oeId; + if (!trans[view_id]) { + trans[view_id] = []; + } + trans[view_id].push({ + initial_content: $node.data('initial_content'), + new_content: text + }); + }); + openerp.jsonRpc('/website/set_translations', 'call', { + 'data': trans, + 'lang': website.get_context()['lang'], + }).then(function () { + website.reload(); + }).fail(function () { + // TODO: bootstrap alert with error message + alert("Could not save translation"); + }); + }, + transNode: function (node, context) { + if (node.childNodes.length === 1 + && this.isTextNode(node.childNodes[0]) + && !node.getAttribute('data-oe-model')) { + this.markTranslatableNode(node); + } else { + for (var i = 0, l = node.childNodes.length; i < l; i ++) { + var n = node.childNodes[i]; + if (this.isTextNode(n)) { + if (this.isTranslatable(n.data)) { + var container = document.createElement('span'); + node.insertBefore(container, n); + container.appendChild(n); + this.markTranslatableNode(container); + } + } else { + this.transNode(n); + } + } + } + }, + }); + + website.TranslatorDialog = openerp.Widget.extend({ + events: _.extend({}, website.EditorBar.prototype.events, { + 'hidden.bs.modal': 'destroy', + 'click button[data-action=activate]': function (ev) { + this.trigger('activate'); + }, + }), + template: 'website.TranslatorDialog', + start: function () { + this.$el.modal(); + }, + }); +})(); diff --git a/addons/website/static/src/xml/website.translator.xml b/addons/website/static/src/xml/website.translator.xml new file mode 100644 index 00000000000..f35cf583da9 --- /dev/null +++ b/addons/website/static/src/xml/website.translator.xml @@ -0,0 +1,53 @@ + + + + + +