Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions assets/css/multilingual.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.ml-modal label{color:#333 !important;text-transform:none !important}
.field-multilingual{position:relative}
.field-multilingual .lang-code-display{margin-bottom:2rem;text-transform:uppercase}
.field-multilingual .form-control:focus{z-index:20}
.field-multilingual .ml-btn,
.field-multilingual .ml-copy-btn{z-index:12;position:absolute;top:1px;bottom:1px;right:1px;width:auto;width:44px;height:36px;color:#7b7b7b;background-color:#F8F6F3;box-shadow:none;font-size:11px;letter-spacing:1px;text-transform:uppercase;margin:0 !important;padding:0 12px;border-top-left-radius:0;border-bottom-left-radius:0;transition:color 420ms ease}
Expand Down Expand Up @@ -51,6 +53,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,
Expand Down
65 changes: 61 additions & 4 deletions assets/js/multilingual.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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){
Expand Down Expand Up @@ -99,6 +114,11 @@
return value ? value.val() : null
}

MultiLingual.prototype.getProvider = function() {
const $select = $('select[name^="translation_provider_"]', this.$modal);
return $select.val() ?? '';
}

MultiLingual.prototype.setLocaleValue = function(value, locale) {
if (locale) {
this.getLocaleElement(locale).val(value)
Expand All @@ -108,15 +128,52 @@
}
}

MultiLingual.prototype.copyLocale = function(copyFromLocale) {
if (!confirm(this.$el.data("copy-confirm"))) {
MultiLingual.prototype.autoTranslate = function(copyFromLocale, provider) {
var self = this
if (provider === '') {
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])
this.success(data)
},
complete: function() {
self.$el.loadIndicator('hide')
}
})
}

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) {
Expand Down
49 changes: 37 additions & 12 deletions assets/less/multilingual.less
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@
@copy-btn-right-offset: 41px;
@copy-btn-top-offset: 28px;

.ml-modal label {
color: #333 !important;
text-transform: none !important;
}

.field-multilingual {
position: relative;

.lang-code-display {
margin-bottom: 2rem;
text-transform: uppercase;
}
.form-control:focus {
z-index: 20;
}
Expand Down Expand Up @@ -37,21 +46,24 @@
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;
}


// 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;
}
}
Expand All @@ -60,15 +72,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;
Expand All @@ -87,12 +101,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;
Expand All @@ -105,18 +120,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;
Expand All @@ -128,11 +147,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;
}
}
Expand All @@ -141,6 +165,7 @@
// Fancy layout overrides
.fancy-layout {
.form-tabless-fields .field-multilingual {

.ml-btn,
.ml-copy-btn {
background-color: rgba(248, 246, 243, 0.75);
Expand Down
8 changes: 7 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment on lines 31 to +34
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

❓ Verification inconclusive

Verify PHP version compatibility with Guzzle 7.10.

Guzzle 7.10 requires PHP >=7.2.5, but this package specifies "php": ">=7.2". While this may work, there's a slight mismatch that could cause issues for environments running PHP 7.2.0-7.2.4.

Consider updating the PHP requirement to align with Guzzle's constraint:

-        "php": ">=7.2",
+        "php": ">=7.2.5",

Alternatively, verify that PHP 7.2.0-7.2.4 support is not needed by running:


🏁 Script executed:

#!/bin/bash
# Check Guzzle 7.10's PHP requirements
curl -s https://repo.packagist.org/p2/guzzlehttp/guzzle.json | jq '.packages["guzzlehttp/guzzle"]["7.10.0"].require.php'

Length of output: 65


I'll search for the actual PHP requirements for Guzzle 7.10 to verify the claim in the review comment.

Update PHP requirement to match Guzzle 7.10 dependency.

Guzzle 7.10.0 requires php: ^7.2.5 || ^8.0, but composer.json specifies "php": ">=7.2". Update the PHP requirement to prevent installation issues on PHP 7.2.0-7.2.4:

-        "php": ">=7.2",
+        "php": ">=7.2.5",
🤖 Prompt for AI Agents
In composer.json around lines 31-34, the "php" constraint is ">=7.2" but Guzzle
7.10 requires "php: ^7.2.5 || ^8.0"; update the "php" requirement to match
Guzzle (for example set "php": "^7.2.5 || ^8.0") to prevent allowing PHP
7.2.0-7.2.4, then run composer validate/composer update to ensure dependency
resolution succeeds.

},
"replace": {
"rainlab/translate-plugin": "~1.6"
},
"extra": {
"installer-name": "translate"
},
"config": {
"allow-plugins": {
"composer/installers": true
}
}
}
79 changes: 79 additions & 0 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,83 @@

'redirectStatus' => env('TRANSLATE_REDIRECT_STATUS', 302),

/*
|--------------------------------------------------------------------------
| Auto Translation Whitelist
|--------------------------------------------------------------------------
|
| Specifies which form inputs should be automatically translated when using
| auto-translation.
|
| Example scenario:
| fields:
| does_not_work: <- this is the key you put into the whitelist
| label: Does not work
| trigger:
| action: hide
| field: is_delayed
| condition: checked
|
| Important Notes:
| - Only applies to formwidgets that have multiple inputs (e.g. NestedForm),
| otherwise it's ignored.
|
| Example:
| 'autoTranslateWhiteList' => ['name', 'content']
|
*/

'autoTranslateWhiteList' => [],

/*
|--------------------------------------------------------------------------
| Default Auto Translation Provider
|--------------------------------------------------------------------------
|
| Sets the default provider used when performing auto translation.
| This must match one of the providers defined in the "providers" section,
| otherwise a standard copy will be performed (empty string = no translation).
|
| Default is empty string as users will need to setup access to a provider.
| Empty string means "None" - just copy content without translation.
|
| Example:
| TRANSLATE_PROVIDER=google
|
*/

'defaultProvider' => env('TRANSLATE_PROVIDER', ''),

/*
|--------------------------------------------------------------------------
| Auto Translation Providers
|--------------------------------------------------------------------------
|
| Configure the translation API services available to your application.
| You may define multiple providers; each provider will appear in the
| dropdown list when choosing a translation service.
|
| To create new providers create a new class that implements TranslationProvider
| and add it to the ProviderFactory, see translate/providers.
|
| Each provider must include:
| - url : API endpoint
| - key : API key or authentication token
|
*/

'providers' => [

'google' => [
'url' => env('GOOGLE_TRANSLATE_URL', 'https://translation.googleapis.com/language/translate/v2'),
'key' => env('GOOGLE_TRANSLATE_KEY', ''),
],

// Example for adding an additional provider:
//
// 'deepl' => [
// 'url' => env('DEEPL_API_URL', 'https://api.deepl.com/v2/translate'),
// 'key' => env('DEEPL_API_KEY', ''),
// ],
],
];
11 changes: 11 additions & 0 deletions formwidgets/MLBlocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
class MLBlocks extends Blocks
{
use \Winter\Translate\Traits\MLControl;
use \Winter\Translate\Traits\MLAutoTranslate;

/**
* {@inheritDoc}
Expand Down Expand Up @@ -107,8 +108,18 @@ public function onAddItem()
public function onCopyItemLocale()
{
$copyFromLocale = post('_blocks_copy_locale');
$currentLocale = post('_blocks_current_locale');
$provider = post('_provider');

$copyFromValues = $this->getLocaleSaveDataAsArray($copyFromLocale);
if ($provider !== '' && !empty($copyFromValues)) {
$copyFromValues = $this->autoTranslateArray(
$copyFromValues,
$currentLocale,
$copyFromLocale,
$provider
);
}

$this->reprocessLocaleItems($copyFromValues);
foreach ($this->formWidgets as $key => $widget) {
Expand Down
1 change: 1 addition & 0 deletions formwidgets/MLMarkdownEditor.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
class MLMarkdownEditor extends MarkdownEditor
{
use \Winter\Translate\Traits\MLControl;
use \Winter\Translate\Traits\MLAutoTranslate;

/**
* {@inheritDoc}
Expand Down
Loading
Loading