Quill 2.0 Locales
simongiesen opened this issue · 3 comments
Recently, it came out that it is extremely unsatisfying using locals with Quill.
I am using the new Quill 2.x WYSIWYG editor. I am trying to implement german localization. Here is my approach so far:
const de = {
'ui': {
'font': 'Schriftart',
'size': 'Größe',
'bold': 'Fett',
'italic': 'Kursiv',
'underline': 'Unterstrichen',
'strike': 'Durchgestrichen',
'color': 'Farbe',
'background': 'Hintergrund',
'script': 'Skript',
'header': 'Überschrift',
'blockquote': 'Zitat',
'code-block': 'Codeblock',
'indent': 'Einrücken',
'list': 'Liste',
'direction': 'Textrichtung',
'link': 'Link',
'image': 'Bild',
'video': 'Video',
'formula': 'Formel',
},
'tooltips': {
'bold': 'Fett',
'italic': 'Kursiv',
'underline': 'Unterstrichen',
'strike': 'Durchgestrichen',
'color': 'Textfarbe',
'background': 'Hintergrundfarbe',
'header': 'Überschrift',
'font': 'Schriftart',
'size': 'Schriftgröße',
'link': 'Link einfügen',
'image': 'Bild einfügen',
'blockquote': 'Zitat',
'code-block': 'Codeblock',
'indent': {
'+1': 'Einrücken',
'-1': 'Ausrücken'
},
'align': {
'center': 'Zentriert ausrichten',
'right': 'Rechts ausrichten',
'justify': 'Blocksatz'
},
'list': {
'ordered': 'Nummerierte Liste',
'bullet': 'Aufzählungsliste'
},
'direction': 'Textrichtung',
'link': 'Link einfügen',
'formula': 'Formel einfügen'
},
'placeholder': 'Geben Sie hier Ihren Text ein...',
'missing': {
'image': 'Bildbeschreibung',
'link': 'Wohin soll dieser Link führen?'
},
'action': {
'apply': 'Anwenden',
'cancel': 'Abbrechen'
}
};
var quill = new Quill('#editorRisikobewertung', {
modules: {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline'],
['link', 'blockquote', 'code-block'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }]
],
},
language: 'de',
i18n: { de },
theme: 'snow'
});
Somehow I am missing the point, can you help me out? Nothing happens, the locales are not applied. What is the best approach to implement localization for Quill? I find nothing detailed in the Knowledge Base.
BTW:
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.0-rc.2/dist/quill.js"></script>it does not make any difference it is just not working.
it turns out that localization can be applied via CSS:
<style>
.ql-snow .ql-tooltip::before {
content: "URL besuchen:" !important;
}
.ql-snow .ql-tooltip input[type=text]::placeholder {
content: "https://www.beispiel.de";
}
.ql-snow .ql-tooltip a.ql-action::after {
content: 'Bearbeiten' !important;
}
.ql-snow .ql-tooltip a.ql-remove::before {
content: 'Entfernen' !important;
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
content: 'Speichern' !important;
}
.ql-snow .ql-tooltip[data-mode=link]::before {
content: "Link eingeben:" !important;
}
.ql-snow .ql-tooltip[data-mode=formula]::before {
content: "Formel eingeben:" !important;
}
.ql-snow .ql-tooltip[data-mode=video]::before {
content: "Video-URL eingeben:" !important;
}
/* Zusätzliche Stile für bessere Anpassung */
.ql-snow .ql-tooltip input[type=text] {
width: 200px; /* Breite anpassen, falls nötig */
}
.ql-snow .ql-tooltip a.ql-preview {
max-width: 250px; /* Maximale Breite für Vorschau-Links anpassen */
}
</style>
Yeah, it's kind of crazy.
But don't rely on !important
. Create a separate file with the css class and then connect it during initialization, so your solution will be more flexible. Although with things like embed url
placeholder
it won't work...
This example reproduces a case with a translation into Russian and modification of the Font size
representation.
QuillEditor.vue
<script setup lang="ts">
// ...
onMounted(() => {
quill = editor.value?.initialize(Quill)!
Quill.register({
'blots/mention': MentionBlot,
'modules/mention': Mention
})
if (userStore.user?.language.current_lang_key === 'ru') {
(editor.value?.$el as HTMLDivElement).classList.add('ql__i18n_ru')
}
})
// ...
</script>
<template>
<QuillyEditor
id="editor"
ref="editor"
v-model="model"
:options="options"
@update:model-value="onModelValueChange"
@text-change="onTextChange"
@selection-change="onSelectionChange"
@editor-change="onEditorChange"
@contextmenu.prevent="onContextMenu"
/>
</template>
<style scoped lang="scss">
@import '~/assets/mixins';
@import '~/assets/quill-i18n/ru';
</style>
_i18n_ru.scss
.ql__i18n_ru {
&.ql-bubble {
& :deep(.ql-picker) {
&.ql-size {
$fontSvg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='white' viewBox='0 0 20 20'%3E%3Cpath d='M9.816 11.5 7.038 4.785 4.261 11.5h5.555zm.62 1.5H3.641l-1.666 4.028H.312l5.789-14h1.875l5.789 14h-1.663L10.436 13zm7.55 2.279.779-.779.707.707-2.265 2.265-2.193-2.265.707-.707.765.765V4.825c0-.042 0-.083.002-.123l-.77.77-.707-.707L17.207 2.5l2.265 2.265-.707.707-.782-.782c.002.043.003.089.003.135v10.454z'%3E%3C/path%3E%3C/svg%3E");
$selectedColor: #2e3238;
width: 48px;
& .ql-picker-options {
padding: 4px 0;
& > span {
padding: 5px 8px;
}
}
& .ql-picker-label {
opacity: 0.75;
&:hover {
opacity: 1;
}
}
&:has(.ql-picker-label:not(:is([data-value='small'], [data-value='large'], [data-value='huge']))) {
.ql-picker-item:not(:is([data-value='small'], [data-value='large'], [data-value='huge'])) {
background-color: $selectedColor;
}
}
&:has(.ql-picker-label[data-value='small']) {
.ql-picker-item[data-value='small'] {
background-color: $selectedColor;
}
}
&:has(.ql-picker-label[data-value='large']) {
.ql-picker-item[data-value='large'] {
background-color: $selectedColor;
}
}
&:has(.ql-picker-label[data-value='huge']) {
.ql-picker-item[data-value='huge'] {
background-color: $selectedColor;
}
}
& :is(.ql-picker-item)::before {
content: 'По умолчанию';
}
& :is(.ql-picker-item)[data-value='small']::before {
content: 'Маленький';
}
& :is(.ql-picker-item)[data-value='large']::before {
content: 'Крупный';
}
& :is(.ql-picker-item)[data-value='huge']::before {
content: 'Очень крупный';
}
& .ql-picker-label::before {
content: $fontSvg;
width: 18px;
height: 18px;
position: absolute;
top: 50%;
left: 0;
transform: translate(50%, -50%);
}
}
&.ql-header {
width: 130px;
& :is(.ql-picker-label, .ql-picker-item)::before {
content: 'По умолчанию';
}
& :is(.ql-picker-label, .ql-picker-item)[data-value='1']::before {
content: 'Заголовок 1';
}
& :is(.ql-picker-label, .ql-picker-item)[data-value='2']::before {
content: 'Заголовок 2';
}
& :is(.ql-picker-label, .ql-picker-item)[data-value='3']::before {
content: 'Заголовок 3';
}
& :is(.ql-picker-label, .ql-picker-item)[data-value='4']::before {
content: 'Заголовок 4';
}
& :is(.ql-picker-label, .ql-picker-item)[data-value='5']::before {
content: 'Заголовок 5';
}
& :is(.ql-picker-label, .ql-picker-item)[data-value='6']::before {
content: 'Заголовок 6';
}
}
}
}
}
Quill doesn't seem to have any awareness for multi-lingual use or for screen-reader suitability.
I use these for tooltips and labels, each get passed an array of tuples where the first tuple element is a css selector and the second a string display value:
// set titles on toolbar buttons - multilang support
addTooltips(tooltips) {
if (tooltips) {
tooltips.map(([selector, tooltip]) => {
try {
this.toolbarContainer.querySelectorAll(selector).forEach(element => {
element.setAttribute('title', tooltip);
});
} catch (error) {
console.warn(`Quill Tooltips - Invalid selector: ${selector}. Error: ${error.message}`);
}
});
}
}
// set labels on toolbar items - multilang support for dropdown items such as font/heading size
replaceLabels(labels) {
if (labels) {
try {
const style = document.createElement('style');
style.innerHTML = labels.map(([selector, label]) => {
return `${selector} { content: '${label}' !important; }`;
}).join(' ');
document.head.appendChild(style);
} catch (error) {
console.warn(`Quill Toolbar Labels - Error: ${error.message}`);
}
}
}
And also for button icons:
replaceButtonIcons(buttonIcons) {
if (buttonIcons) {
buttonIcons.map(([selector, svg]) => {
try {
this.toolbarContainer.querySelectorAll(selector).forEach(element => {
element.innerHTML = svg;
});
} catch (error) {
console.warn(`Quill ButtonIcons - Invalid selector: ${selector}. Error: ${error.message}`);
}
});
}
}
Where buttonIcons is an array of tuples of selector + svg