diff --git a/modules/salesforce-translation-import/assets/css/app.css b/modules/salesforce-translation-import/assets/css/app.css index 9c120fbe..b5798b4d 100644 --- a/modules/salesforce-translation-import/assets/css/app.css +++ b/modules/salesforce-translation-import/assets/css/app.css @@ -13,8 +13,10 @@ } .salesforce-translation-import { - min-height: 100%; + height: 100%; + min-height: 0; padding: 24px; + overflow: auto; } .salesforce-translation-import *, @@ -27,6 +29,7 @@ display: flex; flex-direction: column; gap: 20px; + min-height: min-content; } .salesforce-translation-import__hero, diff --git a/modules/salesforce-translation-import/assets/js/app.js b/modules/salesforce-translation-import/assets/js/app.js index 69f8ea2b..5d9e149c 100644 --- a/modules/salesforce-translation-import/assets/js/app.js +++ b/modules/salesforce-translation-import/assets/js/app.js @@ -2,7 +2,7 @@ 'use strict'; const template = ` -
+

Integration

@@ -43,6 +43,7 @@
+
@@ -192,6 +193,11 @@ unit.remove(); } + function csvEscape(value) { + const text = String(value ?? ''); + return '"' + text.replaceAll('"', '""') + '"'; + } + function escapeHtml(value) { return String(value) .replaceAll('&', '&') @@ -222,6 +228,7 @@ const genericSourceSelect = host.querySelector('#salesforce-translation-import-generic-source-select'); const analyzeBtn = host.querySelector('#salesforce-translation-import-analyze-btn'); const exportBtn = host.querySelector('#salesforce-translation-import-export-btn'); + const extractBtn = host.querySelector('#salesforce-translation-import-extract-btn'); const sourceStatus = host.querySelector('#salesforce-translation-import-source-status'); const targetStatus = host.querySelector('#salesforce-translation-import-target-status'); const statsNode = host.querySelector('#salesforce-translation-import-stats'); @@ -434,6 +441,64 @@ }); } + function collectExtractionRows() { + const rows = []; + + state.sourceFiles + .filter((source) => !source.error) + .forEach((source) => { + const xml = parseXml(source.text); + const units = Array.from(xml.getElementsByTagName('trans-unit')); + + units.forEach((unit) => { + const details = getUnitDetails(unit); + + if (!details) { + return; + } + + const sourceNode = unit.getElementsByTagName('source')[0]; + const targetNode = unit.getElementsByTagName('target')[0]; + + rows.push({ + object: details.objectName, + field: details.fieldName, + target: targetNode ? targetNode.textContent ?? '' : '', + language: source.languageLabel, + source: sourceNode ? sourceNode.textContent ?? '' : '', + file: source.name + }); + }); + }); + + return rows; + } + + function downloadCsv(filename, rows) { + const header = ['object', 'field', 'target', 'language', 'source', 'file']; + const lines = [ + header.map(csvEscape).join(','), + ...rows.map((row) => [ + row.object, + row.field, + row.target, + row.language, + row.source, + row.file + ].map(csvEscape).join(',')) + ]; + + const blob = new Blob(["\uFEFF" + lines.join('\n')], { type: 'text/csv;charset=utf-8' }); + const link = document.createElement('a'); + const downloadUrl = URL.createObjectURL(blob); + + link.href = downloadUrl; + link.download = filename; + link.click(); + + window.setTimeout(() => URL.revokeObjectURL(downloadUrl), 1000); + } + function genericSourceSelectionIsMissing() { return Boolean(state.targetFileName) && !state.targetHasLanguageColumn && @@ -798,6 +863,24 @@ window.setTimeout(() => URL.revokeObjectURL(downloadUrl), 1000); } + async function extractCsv() { + await state.sourceLoadPromise; + + if (state.sourceFiles.filter((entry) => !entry.error).length === 0) { + window.alert('Select at least one valid Salesforce XLIFF source file.'); + return; + } + + const rows = collectExtractionRows(); + + if (rows.length === 0) { + window.alert('No translatable Salesforce rows were found in the selected XLIFF files.'); + return; + } + + downloadCsv('salesforce_translation_template.csv', rows); + } + xmlInput.addEventListener('change', () => { state.sourceLoadPromise = loadSourceFiles(); }); @@ -808,6 +891,7 @@ }); analyzeBtn.addEventListener('click', analyze); exportBtn.addEventListener('click', exportZip); + extractBtn.addEventListener('click', extractCsv); renderFileStatus(); renderPreview();