Add "Do not translate" functionality to TinyMCE editor

- Add a custom TinyMCE plugin to mark content with `translate="no"`.
- Update WYSIWYG editor to support the "Do not translate" toggle button and menu item.
- Highlight non-translatable content with custom styles in the editor.
- Localize plugin strings for English and Russian.
This commit is contained in:
2026-06-20 23:54:25 +05:00
parent 993350073c
commit 9e6e699db0
3 changed files with 96 additions and 2 deletions
+6
View File
@@ -0,0 +1,6 @@
<?php declare(strict_types=1);
return [
'Do not translate' => 'Do not translate',
'Mark as not to translate' => 'Mark as not to translate',
];
+6
View File
@@ -0,0 +1,6 @@
<?php declare(strict_types=1);
return [
'Do not translate' => 'Не переводить',
'Mark as not to translate' => 'Пометить как не переводить',
];
@@ -95,14 +95,96 @@
xhr.send(formData);
});
@endif
tinymce.PluginManager.add('translateNo', (editor) => {
const ATTR = 'translate';
const VALUE = 'no';
const isTranslateNo = (node) =>
node && node.nodeType === 1 && node.getAttribute(ATTR) === VALUE;
const toggleTranslateNo = () => {
const selectedNode = editor.selection.getNode();
if (isTranslateNo(selectedNode)) {
// Remove translate="no", expand span
if (selectedNode.tagName === 'SPAN') {
editor.dom.remove(selectedNode, true); // true = сохранить детей
} else {
editor.dom.setAttrib(selectedNode, ATTR, null);
}
return;
}
// If inside translate="no", remove the mark
const wrapper = editor.dom.getParent(selectedNode, `[${ATTR}="${VALUE}"]`);
if (wrapper) {
if (wrapper.tagName === 'SPAN') {
editor.dom.remove(wrapper, true);
} else {
editor.dom.setAttrib(wrapper, ATTR, null);
}
return;
}
// Otherwise, wrap the selection
editor.formatter.register('translateNo', {
inline: 'span',
attributes: { [ATTR]: VALUE },
remove: 'all'
});
editor.formatter.apply('translateNo');
};
editor.ui.registry.addToggleButton('translateNo', {
icon: 'translate',
tooltip: '{{ __('editor.Do not translate') }}',
onAction: toggleTranslateNo,
onSetup: (api) => {
const setActive = () => {
const node = editor.selection.getNode();
api.setActive(
isTranslateNo(node) ||
!!editor.dom.getParent(node, `[${ATTR}="${VALUE}"]`)
);
};
editor.on('NodeChange', setActive);
return () => editor.off('NodeChange', setActive);
}
});
editor.ui.registry.addMenuItem('translateNo', {
text: '{{ __('editor.Mark as not to translate') }}',
icon: 'translate',
onAction: toggleTranslateNo
});
});
tinymce.init({
content_style: `
span[translate="no"] {
background: rgba(255, 215, 0, 0.35);
outline: 1px dashed #e0a800;
position: relative;
}
span[translate="no"]:hover::before {
position: absolute;
content: "{{ __('editor.Do not translate') }}";
left: 0;
bottom: 100%;
background: #e0a800;
white-space: nowrap;
padding: 0.25rem;
border: 1px solid #ddd;
}
`,
selector: '#{{ $tinyId }}',
@if(in_array(app()->getLocale(), ['ru'], true))
language: '{{ app()->getLocale() }}',
@endif
license_key: '{{ $tinymceLicenseKey }}',
plugins: 'advlist code emoticons link lists table codesample media my-image',
toolbar: 'bold italic | bullist numlist | link image emoticons media codesample',
plugins: 'advlist code emoticons link lists table codesample media my-image translateNo',
toolbar: 'bold italic | bullist numlist | link image emoticons media codesample translateNo',
referrer_policy: 'origin',
@if($storageUpload !== null)
images_upload_handler: imageUpload,