587 lines
23 KiB
JavaScript
587 lines
23 KiB
JavaScript
define([
|
|
'jquery',
|
|
'Magento_Ui/js/modal/modal',
|
|
'Magento_Ui/js/modal/alert',
|
|
'mage/translate'
|
|
], function ($, modal, alert, $t) {
|
|
'use strict';
|
|
|
|
var modalSelector = '#esun-mlt-modal';
|
|
|
|
function showError(message) {
|
|
alert({content: message});
|
|
}
|
|
|
|
function storeHeading(store) {
|
|
var parts = [];
|
|
|
|
if (store && store.label) {
|
|
parts.push(store.label);
|
|
}
|
|
if (store && store.website && store.website !== store.label) {
|
|
parts.push(store.website);
|
|
}
|
|
|
|
return parts.join(' / ') || $t('Default Store View');
|
|
}
|
|
|
|
function ajaxJson(options) {
|
|
return new Promise(function (resolve, reject) {
|
|
$.ajax(options).done(resolve).fail(reject);
|
|
});
|
|
}
|
|
|
|
function createModal(config) {
|
|
var $existing = $(modalSelector);
|
|
|
|
if ($existing.length) {
|
|
try {
|
|
$existing.modal('closeModal').modal('destroy');
|
|
} catch (error) {
|
|
// The old element may already have been destroyed by Magento UI.
|
|
}
|
|
$existing.remove();
|
|
}
|
|
|
|
var stores = [],
|
|
providers = [],
|
|
loadedTargets = [],
|
|
$root = $('<div/>', {id: 'esun-mlt-modal'}),
|
|
$intro = $('<div/>', {class: 'esun-mlt-intro'}),
|
|
$settings = $('<details/>', {class: 'esun-mlt-settings'}),
|
|
$settingsSummary = $('<summary/>', {class: 'esun-mlt-settings__summary'}),
|
|
$settingsTitle = $('<strong/>').text($t('Translation Settings')),
|
|
$settingsMeta = $('<span/>').text($t('Loading store views...')),
|
|
$selectorArea = $('<div/>', {class: 'esun-mlt-selector'}),
|
|
$storeGroup = $('<div/>', {class: 'esun-mlt-selector__field'}),
|
|
$storeLabel = $('<label/>', {for: 'esun-mlt-target-store'}).text($t('Target Store Views')),
|
|
$storeHelp = $('<div/>', {class: 'note'}).text(
|
|
$t('All target store views are loaded automatically. You can select fewer views and reload if needed.')
|
|
),
|
|
$storeSelect = $('<select/>', {
|
|
id: 'esun-mlt-target-store',
|
|
class: 'admin__control-multiselect',
|
|
multiple: 'multiple',
|
|
size: 5
|
|
}),
|
|
$providerGroup = $('<div/>', {class: 'esun-mlt-selector__provider'}),
|
|
$providerLabel = $('<label/>', {for: 'esun-mlt-provider'}).text($t('Translation Provider')),
|
|
$providerSelect = $('<select/>', {
|
|
id: 'esun-mlt-provider',
|
|
class: 'admin__control-select'
|
|
}),
|
|
$load = $('<button/>', {type: 'button', class: 'action-primary'}).text($t('Load Fields')),
|
|
$workspace = $('<div/>', {class: 'esun-mlt-workspace'}).html(
|
|
'<div class="message message-notice">' + $t('Loading store views...') + '</div>'
|
|
),
|
|
$saveArea = $('<div/>', {class: 'esun-mlt-actions'}).hide(),
|
|
$autoTranslate = $('<button/>', {type: 'button', class: 'action-secondary'})
|
|
.text($t('Auto Translate')),
|
|
$save = $('<button/>', {type: 'button', class: 'action-primary'}).text($t('Save All Translations')),
|
|
$progress = $('<div/>', {
|
|
class: 'esun-mlt-progress',
|
|
role: 'status',
|
|
'aria-live': 'polite'
|
|
}).hide(),
|
|
$progressStatus = $('<div/>', {class: 'esun-mlt-progress__status'}),
|
|
$progressTrack = $('<div/>', {
|
|
class: 'esun-mlt-progress__track',
|
|
role: 'progressbar',
|
|
'aria-valuemin': 0,
|
|
'aria-valuemax': 100,
|
|
'aria-valuenow': 0
|
|
}),
|
|
$progressBar = $('<span/>', {class: 'esun-mlt-progress__bar'}),
|
|
$progressSummary = $('<div/>', {class: 'esun-mlt-progress__summary'});
|
|
|
|
$intro.append(
|
|
$('<strong/>').text($t('Entity ID: ') + config.entityId),
|
|
$('<span/>').text($t('Compare the source content with one or more target store views.'))
|
|
);
|
|
$settingsSummary.append($settingsTitle, $settingsMeta);
|
|
$storeGroup.append($storeLabel, $storeSelect, $storeHelp);
|
|
$providerGroup.append($providerLabel, $providerSelect);
|
|
$selectorArea.append($storeGroup, $load);
|
|
$settings.append($settingsSummary, $providerGroup, $selectorArea);
|
|
$progressTrack.append($progressBar);
|
|
$progress.append($progressStatus, $progressTrack, $progressSummary);
|
|
$saveArea.append($autoTranslate, $save);
|
|
$root.append($intro, $settings, $workspace, $progress, $saveArea);
|
|
$('body').append($root);
|
|
|
|
modal({
|
|
type: 'popup',
|
|
responsive: true,
|
|
innerScroll: true,
|
|
title: $t('Translate') + ' ' + String(config.entityType || '').toUpperCase(),
|
|
modalClass: 'esun-mlt-modal',
|
|
buttons: []
|
|
}, $root);
|
|
|
|
function sourceStore() {
|
|
var sourceId = parseInt(config.sourceStoreId || 0, 10),
|
|
match = null;
|
|
|
|
$.each(stores, function (index, store) {
|
|
if (parseInt(store.store_id, 10) === sourceId) {
|
|
match = store;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return match || {
|
|
store_id: 0,
|
|
code: 'default',
|
|
label: $t('Default Store View'),
|
|
website: ''
|
|
};
|
|
}
|
|
|
|
function updateSettingsSummary() {
|
|
var selectedCount = ($storeSelect.val() || []).length;
|
|
|
|
$settingsMeta.text(
|
|
selectedCount + ' ' + $t('Store Views Selected')
|
|
);
|
|
}
|
|
|
|
function fieldControl(field, value, readonly, targetStoreId) {
|
|
var rows = field.is_html ? 12 : 3,
|
|
$control = $('<textarea/>', {
|
|
class: 'admin__control-textarea esun-mlt-value',
|
|
rows: rows,
|
|
readonly: readonly
|
|
}).val(value || '');
|
|
|
|
$control.attr('data-code', field.code || '');
|
|
$control.attr('data-is-html', field.is_html ? '1' : '0');
|
|
if (targetStoreId) {
|
|
$control.attr('data-store-id', targetStoreId);
|
|
}
|
|
|
|
return $control;
|
|
}
|
|
|
|
function fieldCell(field, value, readonly, targetStoreId) {
|
|
var $cell = $('<div/>', {class: 'esun-mlt-cell'}),
|
|
$label = $('<label/>', {class: 'esun-mlt-field-label'}).text(field.label || field.code || ''),
|
|
$control = fieldControl(field, value, readonly, targetStoreId),
|
|
$inherit;
|
|
|
|
$label.append($('<small/>').text(field.code || ''));
|
|
$cell.append($label, $control);
|
|
if (!readonly && targetStoreId) {
|
|
$inherit = $('<label/>', {class: 'esun-mlt-use-default'}).append(
|
|
$('<input/>', {
|
|
type: 'checkbox',
|
|
class: 'esun-mlt-use-default-checkbox',
|
|
checked: field.use_default === true
|
|
}).attr({
|
|
'data-code': field.code || '',
|
|
'data-store-id': targetStoreId
|
|
}),
|
|
$('<span/>').text($t('Use Default Value'))
|
|
);
|
|
$control.prop('disabled', field.use_default === true);
|
|
$cell.append($inherit);
|
|
}
|
|
if (field.is_html) {
|
|
$cell.append($('<span/>', {class: 'esun-mlt-html-badge'}).text('HTML'));
|
|
}
|
|
|
|
return $cell;
|
|
}
|
|
|
|
function renderComparison(results) {
|
|
var sourceFields = results.length ? (results[0].fields || []) : [],
|
|
columnCount = results.length + 1,
|
|
$grid = $('<div/>', {class: 'esun-mlt-grid'}).css(
|
|
'grid-template-columns',
|
|
'repeat(' + columnCount + ', minmax(340px, 1fr))'
|
|
);
|
|
|
|
loadedTargets = results;
|
|
$workspace.empty();
|
|
$saveArea.hide();
|
|
|
|
if (!sourceFields.length) {
|
|
$workspace.append(
|
|
$('<div/>', {class: 'message message-warning'}).text($t('No translatable fields found.'))
|
|
);
|
|
return;
|
|
}
|
|
|
|
$grid.append(
|
|
$('<div/>', {class: 'esun-mlt-column-heading esun-mlt-column-heading--source'})
|
|
.append($('<strong/>').text(storeHeading(sourceStore())))
|
|
.append($('<span/>').text($t('Source')))
|
|
);
|
|
$.each(results, function (index, result) {
|
|
$grid.append(
|
|
$('<div/>', {class: 'esun-mlt-column-heading'})
|
|
.append($('<strong/>').text(storeHeading(result.store)))
|
|
.append($('<span/>').text(result.store.code))
|
|
);
|
|
});
|
|
|
|
$.each(sourceFields, function (fieldIndex, sourceField) {
|
|
$grid.append(fieldCell(sourceField, sourceField.source_value, true, null));
|
|
|
|
$.each(results, function (resultIndex, result) {
|
|
var targetField = (result.fields || [])[fieldIndex] || sourceField;
|
|
$grid.append(
|
|
fieldCell(
|
|
targetField,
|
|
targetField.target_value,
|
|
false,
|
|
result.store.store_id
|
|
)
|
|
);
|
|
});
|
|
});
|
|
|
|
$workspace.append($grid);
|
|
$saveArea.show();
|
|
}
|
|
|
|
function selectedStores() {
|
|
var selectedIds = $.map($storeSelect.val() || [], function (value) {
|
|
return parseInt(value, 10);
|
|
});
|
|
|
|
return $.grep(stores, function (store) {
|
|
return selectedIds.indexOf(parseInt(store.store_id, 10)) !== -1;
|
|
});
|
|
}
|
|
|
|
function loadFields() {
|
|
var targets = selectedStores(),
|
|
requests;
|
|
|
|
if (!targets.length) {
|
|
showError($t('Please select at least one target store view.'));
|
|
return;
|
|
}
|
|
|
|
$load.prop('disabled', true);
|
|
$workspace.html('<div class="message message-notice">' + $t('Loading fields...') + '</div>');
|
|
$saveArea.hide();
|
|
|
|
requests = $.map(targets, function (store) {
|
|
return ajaxJson({
|
|
url: config.fieldsUrl,
|
|
method: 'GET',
|
|
dataType: 'json',
|
|
data: {
|
|
form_key: window.FORM_KEY,
|
|
entity_type: config.entityType,
|
|
entity_id: config.entityId,
|
|
source_store_id: config.sourceStoreId || 0,
|
|
target_store_id: store.store_id
|
|
}
|
|
}).then(function (response) {
|
|
if (!response || response.success !== true) {
|
|
throw new Error(response && response.message ? response.message : $t('Failed to load fields.'));
|
|
}
|
|
return {store: store, fields: response.fields || []};
|
|
});
|
|
});
|
|
|
|
Promise.all(requests).then(renderComparison).catch(function (error) {
|
|
$workspace.empty();
|
|
showError(error.message || $t('Failed to load fields. Please check permission and try again.'));
|
|
}).finally(function () {
|
|
$load.prop('disabled', false);
|
|
});
|
|
}
|
|
|
|
function valuesForStore(storeId) {
|
|
var values = {};
|
|
|
|
$workspace.find('.esun-mlt-value[data-store-id="' + storeId + '"]').each(function () {
|
|
values[$(this).attr('data-code')] = $(this).val();
|
|
});
|
|
|
|
return values;
|
|
}
|
|
|
|
function sourceFieldsForTranslation() {
|
|
var values = {};
|
|
|
|
$workspace.find('.esun-mlt-value[readonly]').each(function () {
|
|
var $field = $(this),
|
|
code = $field.attr('data-code');
|
|
|
|
if (!code || $field.val() === '') {
|
|
return;
|
|
}
|
|
values[code] = {
|
|
value: $field.val(),
|
|
is_html: $field.attr('data-is-html') === '1' ? 1 : 0
|
|
};
|
|
});
|
|
|
|
return values;
|
|
}
|
|
|
|
function applyAutomaticTranslations(storeId, translations) {
|
|
$.each(translations || {}, function (code, value) {
|
|
var $control = $workspace.find(
|
|
'.esun-mlt-value[data-store-id="' + storeId + '"][data-code="' + code + '"]'
|
|
),
|
|
$inherit = $workspace.find(
|
|
'.esun-mlt-use-default-checkbox[data-store-id="' + storeId + '"][data-code="' + code + '"]'
|
|
);
|
|
|
|
$inherit.prop('checked', false);
|
|
$control.prop('disabled', false).val(value);
|
|
});
|
|
}
|
|
|
|
function updateTranslationProgress(completed, total, status, successCount, failureCount) {
|
|
var percent = total ? Math.round((completed / total) * 100) : 0;
|
|
|
|
$progress.show();
|
|
$progressStatus.text(status);
|
|
$progressBar.css('width', percent + '%');
|
|
$progressTrack.attr('aria-valuenow', percent);
|
|
$progressSummary.text(
|
|
$t('Completed') + ': ' + completed + '/' + total + ' · ' +
|
|
$t('Successful') + ': ' + successCount + ' · ' +
|
|
$t('Failed') + ': ' + failureCount
|
|
);
|
|
}
|
|
|
|
function autoTranslateFields() {
|
|
var fields = sourceFieldsForTranslation(),
|
|
provider = $providerSelect.val(),
|
|
providerLabel = $providerSelect.find(':selected').text(),
|
|
total = loadedTargets.length,
|
|
successCount = 0,
|
|
failures = [];
|
|
|
|
if (!provider) {
|
|
showError($t('Please enable and select a translation provider.'));
|
|
return;
|
|
}
|
|
if (!loadedTargets.length || $.isEmptyObject(fields)) {
|
|
showError($t('No source fields are available for automatic translation.'));
|
|
return;
|
|
}
|
|
|
|
$autoTranslate.prop('disabled', true);
|
|
$save.prop('disabled', true);
|
|
$load.prop('disabled', true);
|
|
$progress.removeClass('esun-mlt-progress--success esun-mlt-progress--warning');
|
|
updateTranslationProgress(0, total, $t('Preparing translation...'), 0, 0);
|
|
|
|
function translateStore(index) {
|
|
var result,
|
|
storeName;
|
|
|
|
if (index >= total) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
result = loadedTargets[index];
|
|
storeName = storeHeading(result.store);
|
|
$autoTranslate.text($t('Translating') + ' ' + (index + 1) + '/' + total);
|
|
updateTranslationProgress(
|
|
index,
|
|
total,
|
|
$t('Translating') + ' ' + (index + 1) + '/' + total + ': ' + storeName,
|
|
successCount,
|
|
failures.length
|
|
);
|
|
|
|
return ajaxJson({
|
|
url: config.autoTranslateUrl,
|
|
method: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
form_key: window.FORM_KEY,
|
|
provider: provider,
|
|
entity_type: config.entityType,
|
|
entity_id: config.entityId,
|
|
source_store_id: config.sourceStoreId || 0,
|
|
target_store_id: result.store.store_id,
|
|
fields: fields
|
|
}
|
|
}).then(function (response) {
|
|
if (!response || response.success !== true) {
|
|
throw new Error(
|
|
response && response.message ? response.message : $t('Automatic translation failed.')
|
|
);
|
|
}
|
|
applyAutomaticTranslations(result.store.store_id, response.translations || {});
|
|
successCount++;
|
|
}).catch(function (error) {
|
|
failures.push({
|
|
store: storeName,
|
|
message: error.message || $t('Automatic translation failed.')
|
|
});
|
|
}).then(function () {
|
|
updateTranslationProgress(
|
|
index + 1,
|
|
total,
|
|
$t('Processed') + ' ' + (index + 1) + '/' + total + ': ' + storeName,
|
|
successCount,
|
|
failures.length
|
|
);
|
|
return translateStore(index + 1);
|
|
});
|
|
}
|
|
|
|
translateStore(0).then(function () {
|
|
var status;
|
|
|
|
if (failures.length) {
|
|
status = providerLabel + ': ' + $t('Translation finished with errors.') + ' ' +
|
|
failures.map(function (failure) {
|
|
return failure.store + ' — ' + failure.message;
|
|
}).join(' | ');
|
|
$progress.addClass('esun-mlt-progress--warning');
|
|
} else {
|
|
status = providerLabel + ': ' +
|
|
$t('Translation completed. Review the result, then save translations.');
|
|
$progress.addClass('esun-mlt-progress--success');
|
|
}
|
|
updateTranslationProgress(total, total, status, successCount, failures.length);
|
|
}).finally(function () {
|
|
$autoTranslate.prop('disabled', false).text($t('Auto Translate'));
|
|
$save.prop('disabled', false);
|
|
$load.prop('disabled', false);
|
|
});
|
|
}
|
|
|
|
function useDefaultForStore(storeId) {
|
|
var values = {};
|
|
|
|
$workspace.find('.esun-mlt-use-default-checkbox[data-store-id="' + storeId + '"]').each(function () {
|
|
values[$(this).attr('data-code')] = $(this).is(':checked') ? 1 : 0;
|
|
});
|
|
|
|
return values;
|
|
}
|
|
|
|
function saveFields() {
|
|
var requests;
|
|
|
|
if (!loadedTargets.length) {
|
|
return;
|
|
}
|
|
|
|
$save.prop('disabled', true);
|
|
requests = $.map(loadedTargets, function (result) {
|
|
return ajaxJson({
|
|
url: config.applyUrl,
|
|
method: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
form_key: window.FORM_KEY,
|
|
entity_type: config.entityType,
|
|
entity_id: config.entityId,
|
|
source_store_id: config.sourceStoreId || 0,
|
|
target_store_id: result.store.store_id,
|
|
fields: valuesForStore(result.store.store_id),
|
|
use_default: useDefaultForStore(result.store.store_id)
|
|
}
|
|
}).then(function (response) {
|
|
if (!response || response.success !== true) {
|
|
throw new Error(response && response.message ? response.message : $t('Save failed.'));
|
|
}
|
|
return response;
|
|
});
|
|
});
|
|
|
|
Promise.all(requests).then(function () {
|
|
alert({content: $t('All translations were saved successfully.')});
|
|
loadFields();
|
|
}).catch(function (error) {
|
|
showError(error.message || $t('Save failed.'));
|
|
}).finally(function () {
|
|
$save.prop('disabled', false);
|
|
});
|
|
}
|
|
|
|
$load.on('click', function () {
|
|
updateSettingsSummary();
|
|
$settings.prop('open', false);
|
|
loadFields();
|
|
});
|
|
$storeSelect.on('change', updateSettingsSummary);
|
|
$providerSelect.on('change', updateSettingsSummary);
|
|
$autoTranslate.on('click', autoTranslateFields);
|
|
$save.on('click', saveFields);
|
|
$workspace.on('change', '.esun-mlt-use-default-checkbox', function () {
|
|
var $checkbox = $(this),
|
|
storeId = $checkbox.attr('data-store-id'),
|
|
code = $checkbox.attr('data-code');
|
|
|
|
$workspace.find(
|
|
'.esun-mlt-value[data-store-id="' + storeId + '"][data-code="' + code + '"]'
|
|
).prop('disabled', $checkbox.is(':checked'));
|
|
});
|
|
|
|
ajaxJson({
|
|
url: config.storesUrl,
|
|
method: 'GET',
|
|
dataType: 'json',
|
|
data: {form_key: window.FORM_KEY}
|
|
}).then(function (response) {
|
|
if (!response || response.success !== true) {
|
|
throw new Error($t('Failed to load store views.'));
|
|
}
|
|
|
|
stores = response.stores || [];
|
|
providers = response.providers || [];
|
|
$storeSelect.empty();
|
|
$providerSelect.empty();
|
|
$.each(providers, function (index, provider) {
|
|
$providerSelect.append(
|
|
$('<option/>', {value: provider.code})
|
|
.text(provider.label)
|
|
.prop('selected', provider.code === response.default_provider)
|
|
);
|
|
});
|
|
if (!providers.length) {
|
|
$providerSelect.append(
|
|
$('<option/>', {value: ''}).text($t('No enabled provider'))
|
|
);
|
|
$autoTranslate.prop('disabled', true);
|
|
}
|
|
var $websiteGroups = {};
|
|
|
|
$.each(stores, function (index, store) {
|
|
var isSource = parseInt(store.store_id, 10) === parseInt(config.sourceStoreId || 0, 10),
|
|
website = store.website || $t('Other Website'),
|
|
$option,
|
|
$group;
|
|
|
|
if (isSource) {
|
|
return;
|
|
}
|
|
|
|
if (!$websiteGroups[website]) {
|
|
$websiteGroups[website] = $('<optgroup/>', {label: website});
|
|
$storeSelect.append($websiteGroups[website]);
|
|
}
|
|
|
|
$group = $websiteGroups[website];
|
|
$option = $('<option/>', {value: store.store_id})
|
|
.text(store.label + ' (' + store.code + ' · ' + store.locale + ')')
|
|
.prop('selected', true);
|
|
$group.append($option);
|
|
});
|
|
updateSettingsSummary();
|
|
loadFields();
|
|
}).catch(function (error) {
|
|
showError(error.message || $t('Failed to load store views.'));
|
|
});
|
|
|
|
$root.modal('openModal');
|
|
}
|
|
|
|
return createModal;
|
|
});
|