diff --git a/packages/utils/package-lock.json b/packages/utils/package-lock.json index 439c7f3d6ae..9607775fed2 100644 --- a/packages/utils/package-lock.json +++ b/packages/utils/package-lock.json @@ -1,12 +1,12 @@ { "name": "@builder.io/utils", - "version": "1.1.29", + "version": "1.1.30-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@builder.io/utils", - "version": "1.1.29", + "version": "1.1.30-0", "dependencies": { "@types/lodash": "^4.14.182", "lodash": "^4.17.21", diff --git a/packages/utils/package.json b/packages/utils/package.json index ed5000092cd..8c899d2311a 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@builder.io/utils", - "version": "1.1.29", + "version": "1.1.30-0", "description": "Utils for working with Builder.io content", "main": "./dist/index.js", "scripts": { diff --git a/packages/utils/src/translation-helpers.ts b/packages/utils/src/translation-helpers.ts index e921d54af47..588872f65ef 100644 --- a/packages/utils/src/translation-helpers.ts +++ b/packages/utils/src/translation-helpers.ts @@ -94,6 +94,25 @@ function recordValue({ } } +function extractStringLeaves( + value: any, + path: string, + results: TranslateableFields, + instructions: string +) { + if (typeof value === 'string') { + results[path] = { value, instructions }; + } else if (value?.['@type'] === localizedType) { + extractStringLeaves(value.Default, path, results, instructions); + } else if (Array.isArray(value)) { + value.forEach((item, i) => extractStringLeaves(item, `${path}[${i}]`, results, instructions)); + } else if (typeof value === 'object' && value !== null) { + Object.entries(value).forEach(([k, v]) => + extractStringLeaves(v, `${path}.${k}`, results, instructions) + ); + } +} + function resolveTranslation({ data, basePath, @@ -247,12 +266,16 @@ export function getTranslateableFields( .filter(input => get(el.component?.options || {}, `${input}.@type`) === localizedType) .forEach(inputKey => { const inputValue = get(el.component?.options || {}, inputKey); - const valueToBeTranslated = inputValue?.[sourceLocaleId] || inputValue?.Default; - if (valueToBeTranslated) { - results[`blocks.${el.id}#${inputKey}`] = { - instructions: el.meta?.instructions || defaultInstructions, - value: valueToBeTranslated, - }; + const valueToBeTranslated = inputValue?.[sourceLocaleId] ?? inputValue?.Default; + if (valueToBeTranslated === undefined || valueToBeTranslated === null) return; + + const basePath = `blocks.${el.id}#${inputKey}`; + const instructions = el.meta?.instructions || defaultInstructions; + + if (typeof valueToBeTranslated === 'string') { + results[basePath] = { instructions, value: valueToBeTranslated }; + } else { + extractStringLeaves(valueToBeTranslated, basePath, results, instructions); } }); } @@ -271,9 +294,10 @@ export function getTranslateableFields( if (el && el.id && el.component?.name === 'Core:Button' && !isExcluded) { const componentText = el.component.options?.text; if (componentText) { - const textValue = typeof componentText === 'string' - ? componentText - : componentText?.[sourceLocaleId] || componentText?.Default; + const textValue = + typeof componentText === 'string' + ? componentText + : componentText?.[sourceLocaleId] || componentText?.Default; if (textValue) { results[`blocks.${el.id}#text`] = { value: textValue, @@ -283,7 +307,7 @@ export function getTranslateableFields( } } - if (el && el.id && el.component?.name === 'Symbol'&& !isExcluded) { + if (el && el.id && el.component?.name === 'Symbol' && !isExcluded) { const symbolInputs = Object.entries(el.component?.options?.symbol?.data) || []; if (symbolInputs.length) { const basePath = `blocks.${el.id}.symbolInput`; @@ -473,7 +497,7 @@ export function applyTranslation( }, }, }; - + this.update(updatedElement); } @@ -482,27 +506,53 @@ export function applyTranslation( // there's a localized input const keys = el.meta?.localizedTextInputs as string[]; let options = el.component.options; + let didUpdate = false; keys.forEach(key => { - if (translation[`blocks.${el.id}#${key}`]) { + const exactKey = `blocks.${el.id}#${key}`; + if (translation[exactKey]) { + // String LocalizedValue — current behavior set(options, key, { ...(get(options, key) || {}), - [locale]: unescapeStringOrObject(translation[`blocks.${el.id}#${key}`].value), - }); - - this.update({ - ...el, - meta: { - ...el.meta, - translated: true, - }, - component: { - ...el.component, - options, - }, + [locale]: unescapeStringOrObject(translation[exactKey].value), }); + didUpdate = true; + } else { + // List/object LocalizedValue — reconstruct locale value from leaf paths + const prefix = `${exactKey}[`; + const prefixDot = `${exactKey}.`; + const subEntries = Object.entries(translation).filter( + ([k]) => k.startsWith(prefix) || k.startsWith(prefixDot) + ); + if (subEntries.length) { + const inputValue = get(options, key); + const localeValue = JSON.parse(JSON.stringify(inputValue?.Default ?? [])); + subEntries.forEach(([k, entry]) => { + const subPath = k.slice(exactKey.length); + set(localeValue, subPath, unescapeStringOrObject(entry.value)); + }); + set(options, key, { + ...inputValue, + [locale]: localeValue, + }); + didUpdate = true; + } } }); + + if (didUpdate) { + this.update({ + ...el, + meta: { + ...el.meta, + translated: true, + }, + component: { + ...el.component, + options, + }, + }); + } } }); content.data = { diff --git a/plugins/smartling/package-lock.json b/plugins/smartling/package-lock.json index f053574ad9d..1a652cf3446 100644 --- a/plugins/smartling/package-lock.json +++ b/plugins/smartling/package-lock.json @@ -1,15 +1,15 @@ { "name": "@builder.io/plugin-smartling", - "version": "0.1.0", + "version": "0.1.1-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@builder.io/plugin-smartling", - "version": "0.1.0", + "version": "0.1.1-0", "license": "MIT", "dependencies": { - "@builder.io/utils": "1.1.29", + "@builder.io/utils": "1.1.30-0", "fast-json-stable-stringify": "^2.1.0", "lodash": "^4.17.21", "object-hash": "^3.0.0", @@ -1487,9 +1487,9 @@ } }, "node_modules/@builder.io/utils": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/@builder.io/utils/-/utils-1.1.29.tgz", - "integrity": "sha512-NYP1ZEXyZouLf9zFUAH9Hw/uxkz8VU213YcZfOE5jSmOZtcfWMztZo1/8G6pJM112qSw6m85Jd+BLQIsz6UhpQ==", + "version": "1.1.30-0", + "resolved": "https://registry.npmjs.org/@builder.io/utils/-/utils-1.1.30-0.tgz", + "integrity": "sha512-mIL7uxknzzqBTAY6eDzNonwnyC1v/BgTj4q/wzVbNmImqoPodpgMkBHgmqlJmTrDL3laTjw1FQUnwqYykctdBg==", "dependencies": { "@types/lodash": "^4.14.182", "lodash": "^4.17.21", @@ -23428,9 +23428,9 @@ } }, "@builder.io/utils": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/@builder.io/utils/-/utils-1.1.29.tgz", - "integrity": "sha512-NYP1ZEXyZouLf9zFUAH9Hw/uxkz8VU213YcZfOE5jSmOZtcfWMztZo1/8G6pJM112qSw6m85Jd+BLQIsz6UhpQ==", + "version": "1.1.30-0", + "resolved": "https://registry.npmjs.org/@builder.io/utils/-/utils-1.1.30-0.tgz", + "integrity": "sha512-mIL7uxknzzqBTAY6eDzNonwnyC1v/BgTj4q/wzVbNmImqoPodpgMkBHgmqlJmTrDL3laTjw1FQUnwqYykctdBg==", "requires": { "@types/lodash": "^4.14.182", "lodash": "^4.17.21", diff --git a/plugins/smartling/package.json b/plugins/smartling/package.json index eeb53ee4791..bdf35d809c6 100644 --- a/plugins/smartling/package.json +++ b/plugins/smartling/package.json @@ -1,6 +1,6 @@ { "name": "@builder.io/plugin-smartling", - "version": "0.1.0", + "version": "0.1.1-0", "description": "", "keywords": [], "main": "dist/plugin.system.js", @@ -125,7 +125,7 @@ "typescript": "^3.0.3" }, "dependencies": { - "@builder.io/utils": "1.1.29", + "@builder.io/utils": "1.1.30-0", "fast-json-stable-stringify": "^2.1.0", "lodash": "^4.17.21", "object-hash": "^3.0.0",