From 5705e370c201ca3c518d4b26cfc5c0cdc36c1762 Mon Sep 17 00:00:00 2001 From: Aleksandar Angelov Date: Wed, 5 Nov 2025 23:50:47 +0200 Subject: [PATCH 1/5] Translation from edit on element This is related to issue #564 --- app/lang/i18n.de.json | 3 + app/lang/i18n.en.json | 3 + src/vue-components/modals/editElement.vue | 16 +- .../modals/editElementTranslation.vue | 222 ++++++++++++++++++ 4 files changed, 238 insertions(+), 6 deletions(-) create mode 100644 src/vue-components/modals/editElementTranslation.vue diff --git a/app/lang/i18n.de.json b/app/lang/i18n.de.json index a1ed4576c2..6e938eabec 100644 --- a/app/lang/i18n.de.json +++ b/app/lang/i18n.de.json @@ -832,6 +832,7 @@ "TAB_GENERAL": "Allgemein", "TAB_IMAGE": "Bild", "TAB_WORDFORMS": "Wortformen", + "TAB_TRANSLATION": "Übersetzung", "TAB_ACTIONS": "Aktionen", "TAB_LANGUAGE": "Sprache", "TAB_APPEARANCE": "Darstellung", @@ -1109,6 +1110,8 @@ "importexportDataTofromAllGrids": "Daten von/in alle Grids exportieren/importieren", "pronunciation": "Aussprache", "pronunciationOf": "Aussprache von \"{0}\"", + "testPronunciation": "Aussprache testen", + "translationTabInfo": "Übersetzungen werden automatisch gespeichert, wenn Sie das Element speichern. Sie können Übersetzungen für bereits verwendete Sprachen hinzufügen oder eine neue Sprache aus dem Dropdown auswählen.", "deleteAll": "Alle löschen", "toggleInCollectionElementIfAddedMultipleTimes": "Bei Mehrfachauswahl in Sammelelement hinzufügen und wieder entfernen", "httpMethod": "HTTP Methode", diff --git a/app/lang/i18n.en.json b/app/lang/i18n.en.json index a79ae60ecc..e4ccf83a5a 100644 --- a/app/lang/i18n.en.json +++ b/app/lang/i18n.en.json @@ -832,6 +832,7 @@ "TAB_GENERAL": "General", "TAB_IMAGE": "Image", "TAB_WORDFORMS": "Word forms", + "TAB_TRANSLATION": "Translation", "TAB_ACTIONS": "Actions", "TAB_LANGUAGE": "Language", "TAB_APPEARANCE": "Appearance", @@ -1109,6 +1110,8 @@ "importexportDataTofromAllGrids": "Import/export data to/from all grids", "pronunciation": "Pronunciation", "pronunciationOf": "Pronunciation of \"{0}\"", + "testPronunciation": "Test pronunciation", + "translationTabInfo": "Translations are saved automatically when you save the element. You can add translations for languages already used in your grid set, or translate to a new language by selecting it from the dropdown.", "deleteAll": "Delete all", "toggleInCollectionElementIfAddedMultipleTimes": "Toggle in collection element if added multiple times", "httpMethod": "HTTP method", diff --git a/src/vue-components/modals/editElement.vue b/src/vue-components/modals/editElement.vue index 1f07790118..c32a2dbc7b 100644 --- a/src/vue-components/modals/editElement.vue +++ b/src/vue-components/modals/editElement.vue @@ -19,6 +19,7 @@ + @@ -68,13 +69,15 @@ import EditElementWordForms from "./editElementWordForms.vue"; import EditElementLive from './editElementLive.vue'; import EditElementMatrix from './editElementMatrix.vue'; + import EditElementTranslation from './editElementTranslation.vue'; const TAB_GENERAL = 'TAB_GENERAL'; const TAB_IMAGE = 'TAB_IMAGE'; const TAB_WORDFORMS = 'TAB_WORDFORMS'; + const TAB_TRANSLATION = 'TAB_TRANSLATION'; const TAB_ACTIONS = 'TAB_ACTIONS'; const TAB_LIVE_DATA = 'TAB_LIVE_DATA'; - const TABS = {TAB_GENERAL, TAB_IMAGE, TAB_WORDFORMS, TAB_ACTIONS, TAB_LIVE_DATA}; + const TABS = {TAB_GENERAL, TAB_IMAGE, TAB_WORDFORMS, TAB_TRANSLATION, TAB_ACTIONS, TAB_LIVE_DATA}; export default { props: ['editElementIdParam', 'gridDataId', 'undoService', 'newPosition'], @@ -84,6 +87,7 @@ EditElementWordForms, EditElementHeader, EditElementCollect, + EditElementTranslation, NavTabs, EditElementGeneral, EditElementImage, EditElementActions, EditElementYoutube }, data: function () { @@ -177,17 +181,17 @@ thiz.gridData.gridElements.push(thiz.gridElement); } if (thiz.gridElement.type === GridElement.ELEMENT_TYPE_NORMAL) { - this.possibleTabs = { TAB_GENERAL, TAB_IMAGE, TAB_WORDFORMS, TAB_ACTIONS }; + this.possibleTabs = { TAB_GENERAL, TAB_IMAGE, TAB_WORDFORMS, TAB_TRANSLATION, TAB_ACTIONS }; } else if (thiz.gridElement.type === GridElement.ELEMENT_TYPE_YT_PLAYER) { - this.possibleTabs = { TAB_GENERAL, TAB_ACTIONS }; + this.possibleTabs = { TAB_GENERAL, TAB_TRANSLATION, TAB_ACTIONS }; } else if (thiz.gridElement.type === GridElement.ELEMENT_TYPE_COLLECT) { - this.possibleTabs = { TAB_GENERAL, TAB_ACTIONS }; + this.possibleTabs = { TAB_GENERAL, TAB_TRANSLATION, TAB_ACTIONS }; } else if (thiz.gridElement.type === GridElement.ELEMENT_TYPE_PREDICTION) { this.possibleTabs = { TAB_ACTIONS }; } else if (thiz.gridElement.type === GridElement.ELEMENT_TYPE_LIVE) { - this.possibleTabs = { TAB_GENERAL, TAB_LIVE_DATA, TAB_IMAGE, TAB_ACTIONS }; + this.possibleTabs = { TAB_GENERAL, TAB_LIVE_DATA, TAB_IMAGE, TAB_TRANSLATION, TAB_ACTIONS }; } else if (thiz.gridElement.type === GridElement.ELEMENT_TYPE_MATRIX_CONVERSATION) { - this.possibleTabs = { TAB_GENERAL, TAB_ACTIONS }; + this.possibleTabs = { TAB_GENERAL, TAB_TRANSLATION, TAB_ACTIONS }; } thiz.originalGridElement = JSON.parse(JSON.stringify(thiz.gridElement)); }); diff --git a/src/vue-components/modals/editElementTranslation.vue b/src/vue-components/modals/editElementTranslation.vue new file mode 100644 index 0000000000..67d8c93220 --- /dev/null +++ b/src/vue-components/modals/editElementTranslation.vue @@ -0,0 +1,222 @@ + + + + + From 6e67f0f4185f58b5654cd3854c277059e55d5d02 Mon Sep 17 00:00:00 2001 From: Aleksandar Angelov Date: Fri, 7 Nov 2025 01:26:12 +0200 Subject: [PATCH 2/5] Export Element Labels Since it was related to translation decided to add the solution to this problem here. This is a referance to which problem im referencing since it didnt have linked issue number: Problem: Currently, to "get" all content of a grid set one has to go to 'translate grid', choose the 'show all grids' from the first dropdown and then use the 'copy column' option to get the words. --- app/lang/i18n.de.json | 11 ++ app/lang/i18n.en.json | 11 ++ .../modals/exportLabelsModal.vue | 177 ++++++++++++++++++ src/vue-components/views/manageGridsView.vue | 20 +- 4 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 src/vue-components/modals/exportLabelsModal.vue diff --git a/app/lang/i18n.de.json b/app/lang/i18n.de.json index 6e938eabec..178fc9bee2 100644 --- a/app/lang/i18n.de.json +++ b/app/lang/i18n.de.json @@ -314,6 +314,17 @@ "show": "Öffnen", "clone": "Duplizieren", "export": "Exportieren", + "exportElementLabels": "Element-Beschriftungen exportieren", + "exportLabelsDescription": "Exportieren Sie alle Element-Beschriftungen aus Ihren Grids als einfache Textdatei. Dies ist nützlich, um einen Überblick über Ihr Vokabular zu erhalten oder für externe Verarbeitung.", + "selectLanguage": "Sprache auswählen", + "sortOrder": "Sortierreihenfolge", + "alphabetically": "Alphabetisch", + "byGrid": "Nach Grid", + "includeGridNames": "Grid-Namen einbeziehen", + "removeDuplicateLabels": "Doppelte Beschriftungen entfernen", + "preview": "Vorschau", + "labels": "Beschriftungen", + "exportToFile": "In Datei exportieren", "saveAsPdf": "Als PDF speichern", "gridList": "Grid-Liste", "gridsToShow": "Anzuzeigende Grids", diff --git a/app/lang/i18n.en.json b/app/lang/i18n.en.json index e4ccf83a5a..6fdf9ca4d1 100644 --- a/app/lang/i18n.en.json +++ b/app/lang/i18n.en.json @@ -314,6 +314,17 @@ "show": "Show", "clone": "Clone", "export": "Export", + "exportElementLabels": "Export element labels", + "exportLabelsDescription": "Export all element labels from your grids as a simple text file. This is useful for getting an overview of your vocabulary or for external processing.", + "selectLanguage": "Select language", + "sortOrder": "Sort order", + "alphabetically": "Alphabetically", + "byGrid": "By grid", + "includeGridNames": "Include grid names", + "removeDuplicateLabels": "Remove duplicate labels", + "preview": "Preview", + "labels": "labels", + "exportToFile": "Export to file", "saveAsPdf": "Save as PDF", "gridList": "Grid list", "gridsToShow": "Grids to show", diff --git a/src/vue-components/modals/exportLabelsModal.vue b/src/vue-components/modals/exportLabelsModal.vue new file mode 100644 index 0000000000..de4d3b7996 --- /dev/null +++ b/src/vue-components/modals/exportLabelsModal.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/src/vue-components/views/manageGridsView.vue b/src/vue-components/views/manageGridsView.vue index 3c630e0f1d..4e432d2f23 100644 --- a/src/vue-components/views/manageGridsView.vue +++ b/src/vue-components/views/manageGridsView.vue @@ -109,6 +109,7 @@ +
@@ -133,6 +134,7 @@ import {localStorageService} from "../../js/service/data/localStorageService.js"; import {util} from "../../js/util/util.js"; import ExportModal from "../modals/exportModal.vue"; + import ExportLabelsModal from "../modals/exportLabelsModal.vue"; import ImportModal from "../modals/importModal.vue"; import NoGridsPage from "../components/noGridsPage.vue"; import {GridActionNavigate} from "../../js/model/GridActionNavigate.js"; @@ -154,7 +156,7 @@ let vueApp = null; let vueConfig = { components: { - NoGridsPage, ImportModal, ExportModal, ExportPdfModal, GridLinkModal, Accordion, HeaderIcon}, + NoGridsPage, ImportModal, ExportModal, ExportLabelsModal, ExportPdfModal, GridLinkModal, Accordion, HeaderIcon}, data() { return { metadata: null, @@ -180,6 +182,9 @@ show: false, exportOptions: {} }, + labelsModal: { + show: false + }, importModal: { show: false }, @@ -275,6 +280,9 @@ this.pdfModal.printGridId = gridId; this.pdfModal.show = true; }, + exportLabels() { + this.labelsModal.show = true; + }, importBackupFromFile: async function (event) { let importFile = event && event.target && event.target.files[0] ? event.target.files[0] : null; let name = importFile ? importFile.name : ''; @@ -643,6 +651,7 @@ var CONTEXT_NEW = "CONTEXT_NEW"; var CONTEXT_EXPORT = "CONTEXT_EXPORT"; var CONTEXT_EXPORT_CUSTOM = "CONTEXT_EXPORT_CUSTOM"; + var CONTEXT_EXPORT_LABELS = "CONTEXT_EXPORT_LABELS"; var CONTEXT_IMPORT = "CONTEXT_IMPORT"; var CONTEXT_IMPORT_BACKUP = "CONTEXT_IMPORT_BACKUP"; var CONTEXT_EXPORT_PDF_MODAL = "CONTEXT_EXPORT_PDF_MODAL"; @@ -662,6 +671,11 @@ icon: "fas fa-file-export", disabled: noGrids }, + CONTEXT_EXPORT_LABELS: { + name: i18nService.t('exportElementLabels'), + icon: "fas fa-file-alt", + disabled: noGrids + }, CONTEXT_EXPORT_PDF_MODAL: { name: i18nService.t('saveGridsToPdfGrids'), icon: "far fa-file-pdf", @@ -717,6 +731,10 @@ vueApp.exportCustom(); break; } + case CONTEXT_EXPORT_LABELS: { + vueApp.exportLabels(); + break; + } case CONTEXT_RESET: { vueApp.deleteAll(); break; From aa59698d4ddc18ff6264c155199b17e7efe0df74 Mon Sep 17 00:00:00 2001 From: Aleksandar Angelov Date: Sat, 8 Nov 2025 00:34:56 +0200 Subject: [PATCH 3/5] Requested changes --- .../modals/editElementTranslation.vue | 40 +++++++++---------- .../modals/exportLabelsModal.vue | 17 +++++++- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/vue-components/modals/editElementTranslation.vue b/src/vue-components/modals/editElementTranslation.vue index 67d8c93220..c342144d61 100644 --- a/src/vue-components/modals/editElementTranslation.vue +++ b/src/vue-components/modals/editElementTranslation.vue @@ -16,13 +16,13 @@
{{ $t('textsIn') }}
-
+

{{ $t('label') }}

@@ -83,12 +83,6 @@
- -
-

- {{ $t('translationTabInfo') }} -

-
@@ -116,6 +110,18 @@ chosenLangTranslated: function () { return this.getLocaleTranslation(this.chosenLocale); }, + languagesToShow: function () { + // If there are used locales, only show those (plus any not yet used) + // This matches the behavior in settings + if (this.usedLocales.length > 0) { + return this.allLanguages.filter(lang => { + return lang.code !== this.currentLocale && + (this.usedLocales.includes(lang.code) || lang.code === this.chosenLocale); + }); + } + // If no used locales yet, show all languages + return this.allLanguages.filter(lang => lang.code !== this.currentLocale); + }, customSpeakActions: function () { if (!this.gridElement || !this.gridElement.actions) { return []; @@ -190,11 +196,15 @@ margin-top: 1em; } + .label-section { + margin-top: 2em; + } + h3 { font-weight: normal; font-size: 1.1em; margin-top: 1em; - margin-bottom: 0.5em; + margin-bottom: 0.8em; } .input-button { @@ -207,16 +217,4 @@ box-shadow: none; background-color: transparent; } - - .info-box { - background-color: #e8f4f8; - padding: 1em; - border-left: 4px solid #3498db; - margin: 1em 0; - } - - .info-box i { - margin-right: 0.5em; - color: #3498db; - } diff --git a/src/vue-components/modals/exportLabelsModal.vue b/src/vue-components/modals/exportLabelsModal.vue index de4d3b7996..0c08e64dd8 100644 --- a/src/vue-components/modals/exportLabelsModal.vue +++ b/src/vue-components/modals/exportLabelsModal.vue @@ -12,14 +12,14 @@

{{ $t('exportLabelsDescription') }}

- +
- + + +
+
@@ -63,6 +68,7 @@ import {i18nService} from "../../js/service/i18nService"; import {dataService} from "../../js/service/data/dataService"; import {util} from "../../js/util/util"; + import {GridData} from "../../js/model/GridData"; import './../../css/modal.css'; export default { @@ -74,6 +80,7 @@ sortOrder: 'alphabetical', includeGridNames: true, removeDuplicates: false, + hideKeyboards: true, allLabels: [] } }, @@ -88,6 +95,12 @@ methods: { generateLabelText() { let grids = this.gridsData || []; + + // Filter out keyboards if hideKeyboards is enabled + if (this.hideKeyboards) { + grids = grids.filter(grid => grid.keyboardMode !== GridData.KEYBOARD_ENABLED); + } + this.allLabels = []; let output = ''; From 32cb5af26954cc20530bdbdec219d4bef42bcf05 Mon Sep 17 00:00:00 2001 From: Aleksandar Angelov Date: Fri, 14 Nov 2025 01:00:43 +0200 Subject: [PATCH 4/5] Show already used languages only for export element labels --- .../modals/editElementTranslation.vue | 3 ++ .../modals/exportLabelsModal.vue | 48 ++++++++++++++++++- .../modals/gridTranslateModal.vue | 3 ++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/vue-components/modals/editElementTranslation.vue b/src/vue-components/modals/editElementTranslation.vue index c342144d61..37a2edefb3 100644 --- a/src/vue-components/modals/editElementTranslation.vue +++ b/src/vue-components/modals/editElementTranslation.vue @@ -216,5 +216,8 @@ padding: 0 1em; box-shadow: none; background-color: transparent; + cursor: pointer; + z-index: 1; + pointer-events: auto; } diff --git a/src/vue-components/modals/exportLabelsModal.vue b/src/vue-components/modals/exportLabelsModal.vue index 0c08e64dd8..4d22149a9b 100644 --- a/src/vue-components/modals/exportLabelsModal.vue +++ b/src/vue-components/modals/exportLabelsModal.vue @@ -14,7 +14,7 @@
@@ -77,6 +77,7 @@ return { selectedLanguage: i18nService.getContentLang(), availableLanguages: i18nService.getAllLanguages(), + usedLocales: [], sortOrder: 'alphabetical', includeGridNames: true, removeDuplicates: false, @@ -90,6 +91,16 @@ }, labelCount() { return this.allLabels.length; + }, + languagesToShow() { + // If there are used locales, only show those + if (this.usedLocales.length > 0) { + return this.availableLanguages.filter(lang => { + return this.usedLocales.includes(lang.code); + }); + } + // If no used locales yet, show all languages + return this.availableLanguages; } }, methods: { @@ -172,9 +183,44 @@ link.click(); this.$emit('close'); + }, + findUsedLocales() { + this.usedLocales = []; + let grids = this.gridsData || []; + + for (let grid of grids) { + // Check grid label languages + if (grid.label && typeof grid.label === 'object') { + for (let lang of Object.keys(grid.label)) { + if (!this.usedLocales.includes(lang) && !!grid.label[lang]) { + this.usedLocales.push(lang); + } + } + } + + // Check element label languages + for (let element of grid.gridElements) { + if (element.label && typeof element.label === 'object') { + for (let lang of Object.keys(element.label)) { + if (!this.usedLocales.includes(lang) && !!element.label[lang]) { + this.usedLocales.push(lang); + } + } + } + } + } + + // Sort alphabetically by language name + this.usedLocales.sort((a, b) => { + let langA = this.availableLanguages.find(l => l.code === a); + let langB = this.availableLanguages.find(l => l.code === b); + if (!langA || !langB) return 0; + return i18nService.getTranslationAppLang(langA).localeCompare(i18nService.getTranslationAppLang(langB)); + }); } }, mounted() { + this.findUsedLocales(); } } diff --git a/src/vue-components/modals/gridTranslateModal.vue b/src/vue-components/modals/gridTranslateModal.vue index 861f8e89e9..48d1979a7d 100644 --- a/src/vue-components/modals/gridTranslateModal.vue +++ b/src/vue-components/modals/gridTranslateModal.vue @@ -410,5 +410,8 @@ padding: 0 1em; box-shadow: none; background-color: transparent; + cursor: pointer; + z-index: 1; + pointer-events: auto; } \ No newline at end of file From 83b48dddaf6e95d399efee958d4bdcf9fdc4d62d Mon Sep 17 00:00:00 2001 From: Aleksandar Angelov Date: Mon, 17 Nov 2025 14:06:55 +0200 Subject: [PATCH 5/5] Reverting the change to now show all languages on edit --- .../modals/editElementTranslation.vue | 323 +++++++++--------- 1 file changed, 171 insertions(+), 152 deletions(-) diff --git a/src/vue-components/modals/editElementTranslation.vue b/src/vue-components/modals/editElementTranslation.vue index 37a2edefb3..cc15df0ff6 100644 --- a/src/vue-components/modals/editElementTranslation.vue +++ b/src/vue-components/modals/editElementTranslation.vue @@ -3,20 +3,33 @@
- +
-
- {{ $t('textsIn') }} {{currentLangTranslated}} ({{currentLocale}}) +
+ {{ $t('textsIn') }} + {{ currentLangTranslated }} ({{ currentLocale }})
{{ $t('textsIn') }}
@@ -26,18 +39,22 @@

{{ $t('label') }}

- +
- +
@@ -46,20 +63,28 @@

{{ $t('pronunciation') }}

- - + +
- - + +
@@ -68,18 +93,22 @@

{{ `${$t('actions')} "${$t('GridActionSpeakCustom')}"` }}

- +
- +
@@ -87,137 +116,127 @@