From ed7ab05d40a79acd6ff76a7108050a7fc3d1d613 Mon Sep 17 00:00:00 2001 From: Isaiah Paget Date: Wed, 29 Oct 2025 20:44:28 -0700 Subject: [PATCH 1/4] Add auto-translation support with Google & DeepL providers --- ProviderFactory.php | 26 ++ assets/css/multilingual.css | 2 + assets/js/multilingual.js | 63 +++- assets/less/multilingual.less | 45 ++- composer.json | 8 +- config/config.php | 37 +++ contracts/TranslationProvider.php | 12 + formwidgets/MLBlocks.php | 11 + formwidgets/MLMarkdownEditor.php | 1 + formwidgets/MLNestedForm.php | 11 + formwidgets/MLRepeater.php | 11 + formwidgets/MLRichEditor.php | 1 + formwidgets/MLText.php | 3 + formwidgets/MLTextarea.php | 1 + formwidgets/mlblocks/assets/js/mlblocks.js | 7 +- .../assets/js/mlmarkdowneditor.js | 17 +- .../partials/_mlmarkdowneditor.htm | 1 + .../mlnestedform/assets/js/mlnestedform.js | 7 +- .../mlrepeater/assets/js/mlrepeater.js | 7 +- .../mlricheditor/assets/js/mlricheditor.js | 17 +- .../mlricheditor/partials/_mlricheditor.htm | 1 + formwidgets/mltext/assets/js/mltext.js | 90 ++++++ formwidgets/mltext/partials/_mltext.htm | 3 +- .../mltextarea/assets/js/mltextarea.js | 159 ++++++++-- .../mltextarea/partials/_mltextarea.htm | 4 +- lang/en/lang.php | 4 +- providers/DeepLTranslateProvider.php | 42 +++ providers/GoogleTranslateProvider.php | 45 +++ tests/unit/traits/MLAutoTranslateTest.php | 293 ++++++++++++++++++ traits/MLAutoTranslate.php | 144 +++++++++ traits/MLControl.php | 18 +- traits/mlcontrol/partials/_locale_copy.htm | 58 +++- 32 files changed, 1060 insertions(+), 89 deletions(-) create mode 100644 ProviderFactory.php create mode 100644 contracts/TranslationProvider.php create mode 100644 formwidgets/mltext/assets/js/mltext.js create mode 100644 providers/DeepLTranslateProvider.php create mode 100644 providers/GoogleTranslateProvider.php create mode 100644 tests/unit/traits/MLAutoTranslateTest.php create mode 100644 traits/MLAutoTranslate.php diff --git a/ProviderFactory.php b/ProviderFactory.php new file mode 100644 index 00000000..81f60d1e --- /dev/null +++ b/ProviderFactory.php @@ -0,0 +1,26 @@ + new GoogleTranslateProvider($config), + 'deepl' => new DeepLTranslateProvider($config), + default => throw new \Exception("No provider found: $provider"), + }; + } +} diff --git a/assets/css/multilingual.css b/assets/css/multilingual.css index 71259b4b..def05ea2 100644 --- a/assets/css/multilingual.css +++ b/assets/css/multilingual.css @@ -1,3 +1,4 @@ +.ml-modal label{color:#333 !important;text-transform:none !important} .field-multilingual{position:relative} .field-multilingual .form-control:focus{z-index:20} .field-multilingual .ml-btn, @@ -51,6 +52,7 @@ .field-multilingual.field-multilingual-richeditor .ml-btn{border-radius:0;border-bottom-left-radius:0.375rem} .field-multilingual.field-multilingual-markdowneditor .ml-dropdown-menu, .field-multilingual.field-multilingual-richeditor .ml-dropdown-menu{top:28px;right:1px} +.field-multilingual .loading-indicator{z-index:99} .field-multilingual.field-multilingual-repeater .ml-dropdown-menu, .field-multilingual.field-multilingual-nestedform .ml-dropdown-menu, .field-multilingual.field-multilingual-repeater .ml-copy-dropdown>.dropdown-menu, diff --git a/assets/js/multilingual.js b/assets/js/multilingual.js index c3ab0277..db9ae7c3 100644 --- a/assets/js/multilingual.js +++ b/assets/js/multilingual.js @@ -28,6 +28,8 @@ this.$copyDropdown = $('ul.ml-copy-dropdown-menu', this.$el) this.$dropdown = $('ul.ml-dropdown-menu', this.$el) this.$placeholder = $(this.options.placeholderField) + this.$modal = $('.ml-modal', this.$el) + /* * Init locale @@ -36,13 +38,26 @@ this.$activeField = this.getLocaleElement(this.activeLocale) this.$activeButton.text(this.activeLocale) + // MODAL + this.$modal.on('click', '[data-selected-locale]', function(_event) { + var copyFromLocale = $(this).attr('data-selected-locale') + self.copyLocale(copyFromLocale) + }); + this.$copyDropdown.on('click', '[data-copy-locale]', function(_event) { var currentLocale = self.activeLocale var copyFromLocale = $(this).data('copy-locale') if (!copyFromLocale || currentLocale === copyFromLocale) return; - self.copyLocale(copyFromLocale) + const spanCurrentLocale = $('[data-display-active-locale]', self.$modal) + const spanCopyFromLocale = $('[data-display-locale]', self.$modal) + spanCurrentLocale.text(currentLocale) + spanCopyFromLocale.text(copyFromLocale) + + const copyLocaleBtn = $('[data-selected-locale]', self.$modal) + copyLocaleBtn.attr('data-selected-locale', copyFromLocale) + self.$modal.modal("show") }); this.$dropdown.on('click', '[data-switch-locale]', this.$activeButton, function(event){ @@ -99,6 +114,11 @@ return value ? value.val() : null } + MultiLingual.prototype.getProvider = function() { + const $selected = $('input:checked', this.$modal) + return $selected.val() || 'standard' + } + MultiLingual.prototype.setLocaleValue = function(value, locale) { if (locale) { this.getLocaleElement(locale).val(value) @@ -108,15 +128,50 @@ } } - MultiLingual.prototype.copyLocale = function(copyFromLocale) { - if (!confirm(this.$el.data("copy-confirm"))) { + MultiLingual.prototype.autoTranslate = function(copyFromLocale, provider) { + var self = this + if (provider == "standard") { return } + var currentLocale = this.activeLocale + var copyFromValue = this.getLocaleValue(copyFromLocale) + + if (!copyFromValue || copyFromLocale === currentLocale) { + return + } + + this.$el + .addClass('loading-indicator-container size-form-field') + .loadIndicator() + + this.$el.request(this.options.autoTranslateHandler, { + data: { + _copy_from_locale: copyFromLocale, + _copy_from_value: copyFromValue, + _current_locale: currentLocale, + _provider: provider, + }, + success: function(data) { + self.$el.trigger('autoTranslateSuccess.oc.multilingual', [data]) + self.$el.loadIndicator('hide') + this.success(data) + } + }) + } + + MultiLingual.prototype.copyLocale = function(copyFromLocale) { + var currentLocale = this.activeLocale + const provider = this.getProvider() var copyFromLocaleValue = this.getLocaleValue(copyFromLocale) this.$activeField.val(copyFromLocaleValue) this.$placeholder.val(copyFromLocaleValue) - this.$el.trigger('copyLocale.oc.multilingual', [copyFromLocale, copyFromLocaleValue]) + this.$el.trigger('copyLocale.oc.multilingual', [{ + copyFromLocale: copyFromLocale, + copyFromValue: copyFromLocaleValue, + currentLocale: currentLocale, + provider: provider, + }]) } MultiLingual.prototype.setLocale = function(locale) { diff --git a/assets/less/multilingual.less b/assets/less/multilingual.less index b0bba95b..dbfe2231 100644 --- a/assets/less/multilingual.less +++ b/assets/less/multilingual.less @@ -4,6 +4,11 @@ @copy-btn-right-offset: 41px; @copy-btn-top-offset: 28px; +.ml-modal label { + color: #333 !important; + text-transform: none !important; +} + .field-multilingual { position: relative; @@ -37,8 +42,9 @@ right: 0; } } - .dropdown.ml-dropdown.open > .dropdown-menu, - .dropdown.ml-copy-dropdown.open > .dropdown-menu { + + .dropdown.ml-dropdown.open>.dropdown-menu, + .dropdown.ml-copy-dropdown.open>.dropdown-menu { top: @copy-btn-top-offset; } @@ -46,12 +52,14 @@ // Repeater-specific override &.field-multilingual-repeater, &.field-multilingual-nestedform { + .ml-btn, .ml-copy-btn { top: -36px; } - .dropdown.ml-dropdown.open > .dropdown-menu, - .dropdown.ml-copy-dropdown.open > .dropdown-menu { + + .dropdown.ml-dropdown.open>.dropdown-menu, + .dropdown.ml-copy-dropdown.open>.dropdown-menu { top: -10px; } } @@ -60,15 +68,17 @@ // media finder specific stuff &.field-multilingual-mediafinder { - .dropdown.ml-dropdown.open > .dropdown-menu, - .dropdown.ml-copy-dropdown.open > .dropdown-menu { + .dropdown.ml-dropdown.open>.dropdown-menu, + .dropdown.ml-copy-dropdown.open>.dropdown-menu { top: -10px; } - .dropdown.ml-dropdown:not(.open),// to stop it from overlapping with the upload button + .dropdown.ml-dropdown:not(.open), + // to stop it from overlapping with the upload button .dropdown.ml-copy-dropdown:not(.open) { height: 3px; } + .ml-btn, .ml-copy-btn { top: -36px; @@ -87,12 +97,13 @@ } .ml-dropdown-menu, - .ml-copy-dropdown > .dropdown-menu { + .ml-copy-dropdown>.dropdown-menu { top: @copy-btn-top-offset; } } &.field-multilingual-markdowneditor { + .mode-tab .editor-write, .mode-split .editor-preview { padding-right: 2rem; @@ -105,18 +116,22 @@ &.field-multilingual-markdowneditor, &.field-multilingual-richeditor { + .ml-btn, .ml-copy-btn { height: 38px; } - .dropdown.ml-dropdown.open > .dropdown-menu, - .dropdown.ml-copy-dropdown.open > .dropdown-menu { + + .dropdown.ml-dropdown.open>.dropdown-menu, + .dropdown.ml-copy-dropdown.open>.dropdown-menu { top: 37px; } + .dropdown.ml-dropdown:not(.open), .dropdown.ml-copy-dropdown:not(.open) { height: 3px; } + .ml-btn { border-radius: 0; border-bottom-left-radius: .375rem; @@ -128,11 +143,16 @@ } } + .loading-indicator { + z-index: 99; + } + &.field-multilingual-repeater, &.field-multilingual-nestedform { + .ml-dropdown-menu, - .ml-copy-dropdown > .dropdown-menu { - top: calc(@copy-btn-top-offset - 20px ); + .ml-copy-dropdown>.dropdown-menu { + top: calc(@copy-btn-top-offset - 20px); right: 0; } } @@ -141,6 +161,7 @@ // Fancy layout overrides .fancy-layout { .form-tabless-fields .field-multilingual { + .ml-btn, .ml-copy-btn { background-color: rgba(248, 246, 243, 0.75); diff --git a/composer.json b/composer.json index 10e3c7fd..9b320624 100644 --- a/composer.json +++ b/composer.json @@ -30,12 +30,18 @@ "require": { "php": ">=7.2", "winter/wn-backend-module": ">=1.2.8", - "composer/installers": "~1.0" + "composer/installers": "~1.0", + "guzzlehttp/guzzle": "^7.10" }, "replace": { "rainlab/translate-plugin": "~1.6" }, "extra": { "installer-name": "translate" + }, + "config": { + "allow-plugins": { + "composer/installers": true + } } } diff --git a/config/config.php b/config/config.php index 98e92414..f76d433a 100644 --- a/config/config.php +++ b/config/config.php @@ -63,4 +63,41 @@ 'redirectStatus' => env('TRANSLATE_REDIRECT_STATUS', 302), + /* + |-------------------------------------------------------------------------- + | Auto Translation Providers + |-------------------------------------------------------------------------- + | + | Configure the translation API services used by your application. + | You may define multiple providers and pick one as the default. + | + | Supported Examples: "google" + | + */ + + // NOTE: if you have multiple fields named the same thing they will be like name, name2, name3, etc + // this will only translate the ones explicitly defined, in this case only name will be translated + 'autoTranslateWhiteList' => [ + 'does_not_work', + 'does_not_work3', + 'name', + 'content', + 'works', + 'value', + ], + + 'defaultProvider' => env('TRANSLATE_PROVIDER', 'google'), + + 'providers' => [ + + 'google' => [ + 'url' => env('GOOGLE_TRANSLATE_URL', 'https://translation.googleapis.com/language/translate/v2'), + 'key' => env('GOOGLE_TRANSLATE_KEY', ''), + ], + + 'deepl' => [ + 'url' => env('DEEPL_API_URL', 'https://api.deepl.com/v2/translate'), + 'key' => env('DEEPL_API_KEY', ''), + ], + ], ]; diff --git a/contracts/TranslationProvider.php b/contracts/TranslationProvider.php new file mode 100644 index 00000000..68bb3ce5 --- /dev/null +++ b/contracts/TranslationProvider.php @@ -0,0 +1,12 @@ +getLocaleSaveDataAsArray($copyFromLocale); + if ($provider !== 'standard' && !empty($copyFromValues)) { + $copyFromValues = $this->autoTranslateArray( + $copyFromValues, + $currentLocale, + $copyFromLocale, + $provider + ); + } $this->reprocessLocaleItems($copyFromValues); foreach ($this->formWidgets as $key => $widget) { diff --git a/formwidgets/MLMarkdownEditor.php b/formwidgets/MLMarkdownEditor.php index 518e0f22..f8a291a0 100644 --- a/formwidgets/MLMarkdownEditor.php +++ b/formwidgets/MLMarkdownEditor.php @@ -15,6 +15,7 @@ class MLMarkdownEditor extends MarkdownEditor { use \Winter\Translate\Traits\MLControl; + use \Winter\Translate\Traits\MLAutoTranslate; /** * {@inheritDoc} diff --git a/formwidgets/MLNestedForm.php b/formwidgets/MLNestedForm.php index e656b755..91882c59 100644 --- a/formwidgets/MLNestedForm.php +++ b/formwidgets/MLNestedForm.php @@ -15,6 +15,7 @@ class MLNestedForm extends NestedForm { use \Winter\Translate\Traits\MLControl; + use \Winter\Translate\Traits\MLAutoTranslate; /** * {@inheritDoc} @@ -97,8 +98,18 @@ protected function getParentAssetPath() public function onCopyItemLocale() { $copyFromLocale = post('_repeater_copy_locale'); + $currentLocale = post('_repeater_current_locale'); + $provider = post('_provider'); $copyFromValues = $this->getLocaleSaveDataAsArray($copyFromLocale); + if ($provider !== 'standard' && !empty($copyFromValues)) { + $copyFromValues = $this->autoTranslateArray( + $copyFromValues, + $currentLocale, + $copyFromLocale, + $provider + ); + } $this->reprocessLocaleItems($copyFromValues); diff --git a/formwidgets/MLRepeater.php b/formwidgets/MLRepeater.php index 18b6ecd1..ba144eee 100644 --- a/formwidgets/MLRepeater.php +++ b/formwidgets/MLRepeater.php @@ -18,6 +18,7 @@ class MLRepeater extends Repeater { use \Winter\Translate\Traits\MLControl; + use \Winter\Translate\Traits\MLAutoTranslate; /** * {@inheritDoc} @@ -107,8 +108,18 @@ public function onAddItem() public function onCopyItemLocale() { $copyFromLocale = post('_repeater_copy_locale'); + $currentLocale = post('_repeater_current_locale'); + $provider = post('_provider'); $copyFromValues = $this->getLocaleSaveDataAsArray($copyFromLocale); + if ($provider !== 'standard' && !empty($copyFromValues)) { + $copyFromValues = $this->autoTranslateArray( + $copyFromValues, + $currentLocale, + $copyFromLocale, + $provider + ); + } $this->reprocessLocaleItems($copyFromValues); foreach ($this->formWidgets as $key => $widget) { diff --git a/formwidgets/MLRichEditor.php b/formwidgets/MLRichEditor.php index 1d0613e9..05c61022 100644 --- a/formwidgets/MLRichEditor.php +++ b/formwidgets/MLRichEditor.php @@ -15,6 +15,7 @@ class MLRichEditor extends RichEditor { use \Winter\Translate\Traits\MLControl; + use \Winter\Translate\Traits\MLAutoTranslate; /** * {@inheritDoc} diff --git a/formwidgets/MLText.php b/formwidgets/MLText.php index 872ed524..d2a00710 100644 --- a/formwidgets/MLText.php +++ b/formwidgets/MLText.php @@ -3,6 +3,7 @@ namespace Winter\Translate\FormWidgets; use Backend\Classes\FormWidgetBase; +use Exception; /** * ML Text @@ -14,6 +15,7 @@ class MLText extends FormWidgetBase { use \Winter\Translate\Traits\MLControl; + use \Winter\Translate\Traits\MLAutoTranslate; /** * {@inheritDoc} @@ -57,5 +59,6 @@ public function getSaveValue($value) protected function loadAssets() { $this->loadLocaleAssets(); + $this->addJs('js/mltext.js'); } } diff --git a/formwidgets/MLTextarea.php b/formwidgets/MLTextarea.php index 48884a19..b405d1dc 100644 --- a/formwidgets/MLTextarea.php +++ b/formwidgets/MLTextarea.php @@ -14,6 +14,7 @@ class MLTextarea extends FormWidgetBase { use \Winter\Translate\Traits\MLControl; + use \Winter\Translate\Traits\MLAutoTranslate; /** * {@inheritDoc} diff --git a/formwidgets/mlblocks/assets/js/mlblocks.js b/formwidgets/mlblocks/assets/js/mlblocks.js index 200034d2..530eefcb 100644 --- a/formwidgets/mlblocks/assets/js/mlblocks.js +++ b/formwidgets/mlblocks/assets/js/mlblocks.js @@ -90,9 +90,8 @@ this.$el.css('margin-top','36px') } } - MLBlocks.prototype.onCopyLocale = function(e, locale, localeValue) { - var self = this, - copyFromLocale = this.locale + MLBlocks.prototype.onCopyLocale = function(e, {copyFromLocale, copyFromValue, currentLocale, provider}) { + var self = this this.$el .addClass('loading-indicator-container size-form-field') @@ -101,6 +100,8 @@ this.$el.request(this.options.copyHandler, { data: { _blocks_copy_locale: copyFromLocale, + _blocks_current_locale: currentLocale, + _provider: provider, }, success: function(data) { self.$el.loadIndicator('hide') diff --git a/formwidgets/mlmarkdowneditor/assets/js/mlmarkdowneditor.js b/formwidgets/mlmarkdowneditor/assets/js/mlmarkdowneditor.js index 99b37398..42b4774b 100644 --- a/formwidgets/mlmarkdowneditor/assets/js/mlmarkdowneditor.js +++ b/formwidgets/mlmarkdowneditor/assets/js/mlmarkdowneditor.js @@ -49,6 +49,7 @@ this.$el.on('setLocale.oc.multilingual', this.proxy(this.onSetLocale)) this.$el.on('copyLocale.oc.multilingual', this.proxy(this.onCopyLocale)) + this.$el.on('autoTranslateSuccess.oc.multilingual', this.proxy(this.onAutoTranslateSuccess)) this.$textarea.on('changeContent.oc.markdowneditor', this.proxy(this.onChangeContent)) this.codeEditor.on('blur', this.proxy(this.toggleIsFocused)) @@ -64,6 +65,7 @@ MLMarkdownEditor.prototype.dispose = function() { this.$el.off('setLocale.oc.multilingual', this.proxy(this.onSetLocale)) this.$el.off('copyLocale.oc.multilingual', this.proxy(this.onCopyLocale)) + this.$el.off('autoTranslateSuccess.oc.multilingual', this.proxy(this.onAutoTranslateSuccess)) this.$textarea.off('changeContent.oc.markdowneditor', this.proxy(this.onChangeContent)) this.$el.off('dispose-control', this.proxy(this.dispose)) this.codeEditor.off('blur', this.proxy(this.toggleIsFocused)) @@ -86,10 +88,19 @@ } } - MLMarkdownEditor.prototype.onCopyLocale = function(e, locale, localeValue) { - if (typeof localeValue === 'string' && this.$markdownEditor.data('oc.markdownEditor')) { - this.$markdownEditor.markdownEditor('setContent', localeValue); + MLMarkdownEditor.prototype.onCopyLocale = function(e, {copyFromLocale, copyFromValue, currentLocale, provider}) { + if (typeof copyFromValue === 'string' && this.$markdownEditor.data('oc.markdownEditor')) { + this.$markdownEditor.markdownEditor('setContent', copyFromValue); + } + this.$el.multiLingual('autoTranslate', copyFromLocale, provider) + } + + MLMarkdownEditor.prototype.onAutoTranslateSuccess = function(e, data) { + const translatedValue = data.translatedValue[0] + if (typeof translatedValue != 'string' || !this.$markdownEditor.data('oc.markdownEditor')) { + return } + this.$markdownEditor.markdownEditor('setContent', translatedValue); } MLMarkdownEditor.prototype.onChangeContent = function(ev, markdowneditor, value) { diff --git a/formwidgets/mlmarkdowneditor/partials/_mlmarkdowneditor.htm b/formwidgets/mlmarkdowneditor/partials/_mlmarkdowneditor.htm index c597eccf..b3721bfd 100644 --- a/formwidgets/mlmarkdowneditor/partials/_mlmarkdowneditor.htm +++ b/formwidgets/mlmarkdowneditor/partials/_mlmarkdowneditor.htm @@ -6,6 +6,7 @@ data-copy-confirm="" data-default-locale="code ?>" data-placeholder-field="#getId('textarea') ?>" + data-auto-translate-handler="getEventHandler('onAutoTranslate') ?>" class="field-multilingual field-multilingual-markdowneditor dropdown " > diff --git a/formwidgets/mlnestedform/assets/js/mlnestedform.js b/formwidgets/mlnestedform/assets/js/mlnestedform.js index 3490f1ab..738d362f 100644 --- a/formwidgets/mlnestedform/assets/js/mlnestedform.js +++ b/formwidgets/mlnestedform/assets/js/mlnestedform.js @@ -77,9 +77,8 @@ BaseProto.dispose.call(this) } - MLNestedForm.prototype.onCopyLocale = function(e, locale, localeValue) { - var self = this, - copyFromLocale = this.locale + MLNestedForm.prototype.onCopyLocale = function(e, {copyFromLocale, copyFromValue, currentLocale, provider}) { + var self = this this.$el .addClass('loading-indicator-container size-form-field') @@ -88,6 +87,8 @@ this.$el.request(this.options.copyHandler, { data: { _repeater_copy_locale: copyFromLocale, + _repeater_current_locale: currentLocale, + _provider: provider, }, success: function(data) { self.$el.loadIndicator('hide') diff --git a/formwidgets/mlrepeater/assets/js/mlrepeater.js b/formwidgets/mlrepeater/assets/js/mlrepeater.js index 0c3076cd..2a013f10 100644 --- a/formwidgets/mlrepeater/assets/js/mlrepeater.js +++ b/formwidgets/mlrepeater/assets/js/mlrepeater.js @@ -80,9 +80,8 @@ this.$el.toggleClass('is-empty', isEmpty) } - MLRepeater.prototype.onCopyLocale = function(e, locale, localeValue) { - var self = this, - copyFromLocale = this.locale + MLRepeater.prototype.onCopyLocale = function(e, {copyFromLocale, copyFromValue, currentLocale, provider}) { + var self = this this.$el .addClass('loading-indicator-container size-form-field') @@ -91,6 +90,8 @@ this.$el.request(this.options.copyHandler, { data: { _repeater_copy_locale: copyFromLocale, + _repeater_current_locale: currentLocale, + _provider: provider, }, success: function(data) { self.$el.loadIndicator('hide') diff --git a/formwidgets/mlricheditor/assets/js/mlricheditor.js b/formwidgets/mlricheditor/assets/js/mlricheditor.js index 2c3965cb..0edc3194 100644 --- a/formwidgets/mlricheditor/assets/js/mlricheditor.js +++ b/formwidgets/mlricheditor/assets/js/mlricheditor.js @@ -48,6 +48,7 @@ this.$el.on('setLocale.oc.multilingual', this.proxy(this.onSetLocale)) this.$el.on('copyLocale.oc.multilingual', this.proxy(this.onCopyLocale)) + this.$el.on('autoTranslateSuccess.oc.multilingual', this.proxy(this.onAutoTranslateSuccess)) this.$textarea.on('syncContent.oc.richeditor', this.proxy(this.onSyncContent)) this.editor.events.on('focus', this.proxy(this.toggleIsFocused)); @@ -67,6 +68,7 @@ MLRichEditor.prototype.dispose = function() { this.$el.off('setLocale.oc.multilingual', this.proxy(this.onSetLocale)) this.$el.off('copyLocale.oc.multilingual', this.proxy(this.onCopyLocale)) + this.$el.off('autoTranslateSuccess.oc.multilingual', this.proxy(this.onAutoTranslateSuccess)) this.$textarea.off('syncContent.oc.richeditor', this.proxy(this.onSyncContent)) $(window).off('resize', this.proxy(this.updateLayout)) $(window).off('oc.updateUi', this.proxy(this.updateLayout)) @@ -91,10 +93,19 @@ this.$richeditor.richEditor('setContent', localeValue); } } - MLRichEditor.prototype.onCopyLocale = function(e, locale, localeValue) { - if (typeof localeValue === 'string' && this.$richeditor.data('oc.richEditor')) { - this.$richeditor.richEditor('setContent', localeValue); + MLRichEditor.prototype.onCopyLocale = function(e, {copyFromLocale, copyFromValue, currentLocale, provider}) { + if (typeof copyFromValue === 'string' && this.$richeditor.data('oc.richEditor')) { + this.$richeditor.richEditor('setContent', copyFromValue); + } + this.$el.multiLingual('autoTranslate', copyFromLocale, provider) + } + + MLRichEditor.prototype.onAutoTranslateSuccess = function(e, data) { + const translatedValue = data.translatedValue[0] + if (typeof translatedValue != 'string' || !this.$richeditor.data('oc.richEditor')) { + return } + this.$richeditor.richEditor('setContent', translatedValue); } MLRichEditor.prototype.onSyncContent = function(ev, richeditor, value) { diff --git a/formwidgets/mlricheditor/partials/_mlricheditor.htm b/formwidgets/mlricheditor/partials/_mlricheditor.htm index 7e6c0196..acc1af09 100644 --- a/formwidgets/mlricheditor/partials/_mlricheditor.htm +++ b/formwidgets/mlricheditor/partials/_mlricheditor.htm @@ -6,6 +6,7 @@ data-copy-confirm="" data-default-locale="code ?>" data-placeholder-field="#getId('textarea') ?>" + data-auto-translate-handler="getEventHandler('onAutoTranslate') ?>" class="field-multilingual field-multilingual-textarea field-multilingual-richeditor dropdown " > diff --git a/formwidgets/mltext/assets/js/mltext.js b/formwidgets/mltext/assets/js/mltext.js new file mode 100644 index 00000000..3a89a7e3 --- /dev/null +++ b/formwidgets/mltext/assets/js/mltext.js @@ -0,0 +1,90 @@ +/* + * MLText plugin + * + * Data attributes: + * - data-control="mltext" - enables the plugin on an element + * + * JavaScript API: + * $('a#someElement').mlText({ option: 'value' }) + * + */ + ++function($) { + + var Base = $.wn.foundation.base, + BaseProto = Base.prototype + + "use strict"; + + var MLText = function(element, options) { + this.options = options + this.$el = $(element) + $.wn.foundation.controlUtils.markDisposable(element) + Base.call(this) + + this.init() + } + + MLText.prototype = Object.create(BaseProto) + MLText.prototype.constructor = MLText + + MLText.DEFAULTS = { + autoTranslateHandler: null, + // switchHan + // defaultLocale: 'en' + } + + MLText.prototype.init = function() { + this.$el.multiLingual() + + // NOTE: this.$el.on('setLocale.oc.multilingual', this.proxy(this.onSetLocale)) + this.$el.on('copyLocale.oc.multilingual', this.proxy(this.onCopyLocale)) + this.$el.on('autoTranslateSuccess.oc.multilingual', this.proxy(this.onAutoTranslateSuccess)) + this.$el.one('dispose-control', this.proxy(this.dispose)) + } + + MLText.prototype.dispose = function() { + this.$el.off('copyLocale.oc.multilingual', this.proxy(this.onCopyLocale)) + this.$el.off('autoTranslateSuccess.oc.multilingual', this.proxy(this.onAutoTranslateSuccess)) + this.$el.off('dispose-control', this.proxy(this.dispose)) + + this.$el.removeData('oc.mlText') + this.$el = null + this.options = null + } + + MLText.prototype.onCopyLocale = function(e, {copyFromLocale, copyFromValue, currentLocale, provider}) { + this.$el.multiLingual('autoTranslate', copyFromLocale, provider) + } + + MLText.prototype.onAutoTranslateSuccess = function(e, data) { + var self = this + const translatedValue = data.translatedValue[0] + if (data.translatedValue && data.translatedLocale) { + const $visibleInput = $('input.form-control', self.$el) + $visibleInput.val(translatedValue).trigger('input') + self.$el.multiLingual('setLocaleValue', translatedValue, data.translatedLocale) + } + } + + $.fn.mlText = function(option) { + return this.each(function() { + var $this = $(this) + var data = $this.data('oc.mlText') + var options = $.extend({}, MLText.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('oc.mlText', (data = new MLText(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.mlText.noConflict = function() { + $.fn.mlText = old + return this + } + + $(document).render(function() { + $('[data-control="mltext"]').mlText() + }) + +}(window.jQuery); diff --git a/formwidgets/mltext/partials/_mltext.htm b/formwidgets/mltext/partials/_mltext.htm index e846d9a9..758bba3b 100644 --- a/formwidgets/mltext/partials/_mltext.htm +++ b/formwidgets/mltext/partials/_mltext.htm @@ -4,10 +4,11 @@