Hi,
I have Form that has translation on the form but when I save the form to the repository the form is save with the default labels and not with the translated labels that the user selected.
Anyone else have this problem?
Hi,
I have Form that has translation on the form but when I save the form to the repository the form is save with the default labels and not with the translated labels that the user selected.
Anyone else have this problem?
How familiar are you with the JavaScript? I actually found a bug in my code that was causing it not to run properly when saving to the repository.
If you didn't modify the code I wrote, you can remove everything including and below the "DO NOT MODIFY" and replace it with
/** * DO NOT MODIFY THE BELOW CODE UNLESS YOU KNOW WHAT YOU ARE DOING */ /** * */ let isTranslationSupported = true; let isTranslationHelperNeeded = false; if (Object.keys(translations).length === 0) { (async () => { isTranslationSupported = false; const translationError = `Form Translation Error: No translations found. Please have an admin setup translations.`; console.error( translationError.concat('\n See https://docs.laserfiche.com') ); await LFForm.changeFieldSettings( { fieldId: translateLangFieldId }, { description: translationError } ); await LFForm.disableFields({ fieldId: translateLangFieldId }); const translateCustomHtmlField = LFForm.findFieldsByFieldId( translateCustomHtmlFieldId )?.[0]; if ( translateCustomHtmlField !== undefined && translateCustomHtmlField.componentType === 'CustomHTML' ) { await LFForm.changeFieldSettings( { fieldId: translateCustomHtmlFieldId }, { content: `<div class="btn-container"> <button ${GOOGLE_TRANSLATE_API_KEY === '' ? 'disabled' : ''} class="btn btn-default" onclick="getFieldtranslations()" title="Generate translations with Google"> Google Translate </button> <button class="btn btn-default" onclick="getFieldtranslations(true)" title="Generate an empty translation JSON"> Custom Translation </button> </div> `, } ); await LFForm.showFields({ fieldId: translateCustomHtmlFieldId }); isTranslationHelperNeeded = true; } })(); } const main = async () => { if (!isTranslationSupported) return; let targetLang = LFForm.getFieldValues({ fieldId: translateLangFieldId, }).value; /** * * @param {string} newTargetLang * @returns {Promise<void>} */ const runTranlateFields = async (newTargetLang) => { if (targetLang === newTargetLang) return; targetLang = newTargetLang; const translationTarget = translations[targetLang] || {}; let isChangeFieldOptionsSupported = true; for (const field in translationTarget) { const translation = translationTarget[field]; const fieldId = parseInt(field.split(' - ')[0]); const fieldType = /\((\w+)\)/.exec(field)?.[1]; for (const setting in translation) { const text = translation[setting]; if (field === 'Form') { await setFormSettings(setting, text).catch((e) => console.error(e)); } else { await setFieldSettings( fieldId, setting, text, fieldType, isChangeFieldOptionsSupported ).catch((e) => { if ( e.message === 'Changing field options is only supported on self-hosted Forms.' ) { isChangeFieldOptionsSupported = false; } else { console.error( `Error setting field ${fieldId} setting ${setting}`, e ); } }); } } } }; // Run translation when language field is changed. // NOTE: on field change does not natively support async so we need to disable the field while we run the translation. LFForm.onFieldChange( async () => { LFForm.disableFields({ fieldId: translateLangFieldId }); const newTargetLang = LFForm.getFieldValues({ fieldId: translateLangFieldId, }).value; await runTranlateFields(newTargetLang); LFForm.enableFields({ fieldId: translateLangFieldId }); }, { fieldId: translateLangFieldId } ); // Run translation on form load if the language default is not in the default language. if ( targetLang === defaultFormLang || targetLang === null || targetLang === '' ) return; const initLang = targetLang; targetLang = defaultFormLang; await runTranlateFields(initLang); }; // Run the main function on load void main(); // #region Field settings const basicTranslatableFieldSettings /** @type {const} */ = { label: 'label', subtext: 'subtext', options: 'options', tooltip: 'tooltip', default: 'default', signButtonLabel: 'signButtonLabel', rowLabels: 'rowLabels', prevBtn: 'prevBtn', nextBtn: 'nextBtn', }; const otherTranslatableFieldSettings /** @type {const} */ = { addLinkText: 'addLinkText', buttonLabel: 'buttonLabel', addressOptions: 'addressOptions', }; /** * @typedef {keyof typeof basicTranslatableFieldSettings | keyof typeof otherTranslatableFieldSettings} TranslatableFieldSettings */ /** * @param {number} fieldId * @param {TranslatableFieldSettings} setting * @param {string} text * @returns {Promise<void>} */ async function setFieldSettings( fieldId, setting, text, fieldType, isChangeFieldOptionsSupported ) { if (text === undefined || text === '' || text.length === 0) return; if (setting === 'default' && fieldType !== 'CustomHTML') { await LFForm.setFieldValues({ fieldId }, text); } else if ( setting === 'label' || setting === 'subtext' || setting === 'tooltip' || setting === 'default' || setting === 'signButtonLabel' || setting === 'rowLabels' || setting === 'prevBtn' || setting === 'nextBtn' ) { const parsedSetting = setting === 'prevBtn' ? 'prevButton' : setting === 'nextBtn' ? 'nextButton' : setting; await LFForm.changeFieldSettings({ fieldId }, { [parsedSetting]: text }); } else if (setting === 'addLinkText') { await LFForm.changeFieldSettings({ fieldId }, { addRowButtonLabel: text }); } else if (setting === 'buttonLabel') { await LFForm.changeFieldSettings({ fieldId }, { uploadButtonLabel: text }); } else if (setting === 'addressOptions') { if (typeof text !== 'object') throw new Error('Address options must be an array of objects'); await LFForm.changeFieldSettings( { fieldId }, { addressOptions: /** @type {any} */ (text) } ); } else if (isChangeFieldOptionsSupported && setting === 'options') { if (typeof text !== 'object') throw new Error('Change options text must be an array'); await LFForm.changeFieldOptions( { fieldId }, /** @type {any} */ (text), 'replace' ); } else { console.error('unknown field setting', setting, text); } } /** * @param {string} setting * @param {'title' | 'description' | 'actionButtons' | string} text * @returns {Promise<void>} */ async function setFormSettings(setting, text) { if (text === undefined || text === '') return; if (setting === 'title') { await LFForm.changeFormSettings({ title: text }); } else if (setting === 'description') { await LFForm.changeFormSettings({ description: text }); } else if (setting === 'actionButtons') { const actionButtons = Object.keys(text).map((key) => ({ action: key, label: /** @type {any} **/ (text)[key], })); await LFForm.changeActionButtons(actionButtons); } else { console.error('unknown form setting', setting, text); } } // #endregion Field settings // #region Translation Helpers /** * @typedef {Record<string, { settings: Record<string, string>, type: string }>} FormFieldSettings */ /** * * @returns {FormFieldSettings} */ const getFormFieldSettings = () => { /** * @type {FormFieldSettings} */ const formFieldSettings = {}; LFForm.findFields((f) => { const { settings } = f; /** * @type {{ type: string, settings: Record<string, any>}} */ const fieldSetting = { type: f.componentType, settings: {}, }; const variableName = f.settings.attributeName; const label = f.settings.label; const fieldKeyLabel = variableName ? `${variableName} (${f.componentType})` : label ? `${f.settings.label} (${f.componentType})` : `(${f.componentType})`; const fieldIdKey = `${f.fieldId} - ${fieldKeyLabel}`; if ( f.componentType === 'CustomHTML' && f.fieldId === translateCustomHtmlFieldId ) { // do not translate the translation helper return true; } else if (f.componentType === 'Form') { fieldSetting.settings = { title: settings.title, description: settings.description, actionButtons: { Submit: 'Submit', Approve: 'Approve', Reject: 'Reject', SaveAsDraft: 'Save as Draft', }, }; formFieldSettings['Form'] = fieldSetting; } else { for (const translatable in { ...basicTranslatableFieldSettings, ...otherTranslatableFieldSettings, }) { if (settings[translatable] === undefined) continue; if ( translatable === 'default' && (f.componentType === 'Checkbox' || f.componentType === 'Radio' || f.componentType === 'Dropdown') ) { continue; } if (settings[translatable] !== undefined) { fieldSetting.settings[translatable] = settings[translatable]; } } formFieldSettings[fieldIdKey] = fieldSetting; } }); return formFieldSettings; }; window.getFormFieldSettings = getFormFieldSettings; /** * * @param {string} text * @param {string} targetLang * @returns {Promise<string>} * @description Translate text to target language. This should be removed from your code when a static translation is saved to the translations variable. * Replace PUT_YOUT_API_KEY_HERE with your Google Cloud API key. */ async function getTranslation(text, targetLang, runCount = 0) { const sourceLang = defaultFormLang; const url = `https://translation.googleapis.com/language/translate/v2?key=${GOOGLE_TRANSLATE_API_KEY}`; return fetch(url, { referrer: 'app.laserfiche.com', method: 'POST', headers: { 'Content-Type': 'application/json', // Authorization: `Bearer ${GOOGLE_TRANSLATE_API_KEY}`, // 'x-goog-user-project': 'laserfiche-cloud', }, body: JSON.stringify({ q: text, source: sourceLang, target: targetLang, }), }) .then((response) => response.json()) .catch((error) => { if (runCount >= 3) throw new Error('Failed to translate text'); console.error('Error:', error); setTimeout(async () => { await getTranslation(text, targetLang, runCount + 1); }, 1000); }) .then((data) => { const translatedText = data.data.translations[0].translatedText; return translatedText; }); } /** * For each field, get the translation of the field settings. * This should be run once when the form is first designed, and then removed from the code. */ /** * * @param {string} targetLang * @returns {Promise<Record<number, Record<string, string>>>} */ const getFieldtranslationForLanguage = async ( targetLang, generateEmptyTranslations = false ) => { const formFieldInfo = getFormFieldSettings(); /** * @type {Record<number, Record<string, string>>} */ const translatedForm = {}; for (const field in formFieldInfo) { const fieldInfo = formFieldInfo[field]; // get the translation for each field setting that is already setup const newTranslation = translations[field] || {}; const fieldSettings = fieldInfo.settings; for (const f in fieldSettings) { const setting = fieldSettings[f]; if ( setting === undefined || setting === '' || targetLang === defaultFormLang || generateEmptyTranslations === true ) { newTranslation[f] = setting; continue; } let finalTranslation; try { if (setting === '' || setting === undefined || setting.length === 0) { finalTranslation = setting; } else if ( fieldInfo.type === 'Address' && (f === 'addressOptions' || f === 'default') ) { if (f === 'default') { finalTranslation = {}; for (const label in setting) { const addressProp = setting[label]; if (addressProp === '') { finalTranslation[label] = ''; continue; } const translatedProp = await getTranslation( addressProp, targetLang ); finalTranslation[label] = translatedProp; } } else { finalTranslation = []; for (const addressOption of setting) { const { label } = addressOption; const translatedLabel = await getTranslation(label, targetLang); finalTranslation.push({ ...addressOption, label: translatedLabel, }); } } } else if ( f === 'options' && (fieldInfo.type === 'Checkbox' || fieldInfo.type === 'Radio' || fieldInfo.type === 'Dropdown') ) { finalTranslation = []; for (const option of setting) { const label = option.label; const translatedOption = await getTranslation(label, targetLang); finalTranslation.push({ ...option, label: translatedOption }); } } else if (fieldInfo.type === 'Form' && f === 'actionButtons') { finalTranslation = {}; for (const action in setting) { const label = setting[action]; const translatedLabel = await getTranslation(label, targetLang); finalTranslation[action] = translatedLabel; } } else { const translatedText = await getTranslation(setting, targetLang); finalTranslation = translatedText; } console.log(setting, finalTranslation); newTranslation[f] = finalTranslation; } catch (e) { console.error(`Field ${field} failed to translate property ${f}`, e); } } translatedForm[field] = newTranslation; } return translatedForm; }; /** * For each language, get the translation of the field settings. */ window.getFieldtranslations = async (generateEmptyTranslations = false) => { const supportedLanguages = LFForm.findFieldsByFieldId(translateLangFieldId)[0].options; /** * @type {Record<string, Record<number, Record<string, string>>>} */ const translatedForm = {}; const originalCustomHTMLContent = isTranslationHelperNeeded ? LFForm.findFieldsByFieldId(translateCustomHtmlFieldId)[0].data : ''; if (isTranslationHelperNeeded && generateEmptyTranslations === false) { // display loading await LFForm.changeFieldSettings( { fieldId: translateCustomHtmlFieldId }, { content: `<div class="translate-modal" style="position: fixed; z-index: 100; background: rgba(1,1,1,0.8); left: 0; top: 0; width: 100vw; height: 100vh; padding: 16px; display: flex; flex-direction: column; align-items: center; justify-content: center;"> <div class="translate-modal-content" style="background: white; padding: 16px; flex: .75; border-radius: .75rem; width: 100%;"> <div class="translate-modal-body" style="height: 90%"> <div class="translate-modal-body-header"> <h3>Translations</h3> <p>Please wait for translations to complete</p> </div> <div class="translate-modal-body-content" style="height: 100%"> <div class="progress"> <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div> </div> </div> </div> </div>`, } ); } for (const lang of supportedLanguages) { const newLanguage = await getFieldtranslationForLanguage( lang.value, generateEmptyTranslations ); translatedForm[lang.value] = newLanguage; } if (isTranslationHelperNeeded) { const translatedFormAsString = JSON.stringify(translatedForm); //, null, 2); const bytes = new TextEncoder().encode(translatedFormAsString); const blob = new Blob([bytes], { type: 'application/json;charset=utf-8', }); window.openTranslation = () => { const url = URL.createObjectURL(blob); window.open(url, '_blank'); }; window.resetForm = async () => { await LFForm.changeFieldSettings( { fieldId: translateCustomHtmlFieldId }, { content: originalCustomHTMLContent } ); }; await LFForm.changeFieldSettings( { fieldId: translateCustomHtmlFieldId }, { content: `<div class="translate-modal" style="position: fixed; z-index: 100; background: rgba(1,1,1,0.8); left: 0; top: 0; width: 100vw; height: 100vh; padding: 16px; display: flex; flex-direction: column; align-items: center; justify-content: center;"> <div class="translate-modal-content" style="background: white; padding: 16px; flex: .75; border-radius: .75rem; width: 100%;"> <span class="translate-modal-close btn" style="float: right;" onclick="resetForm()">×</span> <div class="translate-modal-body" style="height: 90%"> <div class="translate-modal-body-header"> <h3>Translations</h3> <p>Click the button below to open your translations in a new tab. (Please allow popups if clicking this is blocked, or open the dev console to see the logged JSON)</p> </div> <div class="translate-modal-body-content" style="height: 100%"> <button onclick="openTranslation()" class="btn btn-primary">Download Translations</button> </div> </div> </div>`, // <p class="user-select-all" readonly style="border: lightgray 1px solid; background: none; resize: none; height: 100%; overflow: auto; white-space: pre;">${jsonHtml.outerHTML}</p> } ); await LFForm.showFields({ fieldId: translateCustomHtmlFieldId }); } console.log( 'Copy the following JSON into the translations variable at the start of the code.' ); console.log(translatedForm); };
Perhaps your translation script did not handle the case for readonly form.
Did you have custom script or did you use the example in Google Translate in Forms Version 11?
It was unfortunately not built to run when saved to the repository as it was intended to save in the working language of the owner. I can look into it a little more but I'm not sure if this will be possible right now.
What is the intention behind saving the translated copy?
The intention is that I have to save the translated form so I can email a copy to the next person in my flow before they start their form processes and it has to be saved because I use a work flow to send the email. I currently have a work around where I have a form in the translation language (default French labels) and one form for the English labels and I just use the language variable to decide save in French or English. It okay just want some feedback if you had other ideas for this problem.
Thanks,
How familiar are you with the JavaScript? I actually found a bug in my code that was causing it not to run properly when saving to the repository.
If you didn't modify the code I wrote, you can remove everything including and below the "DO NOT MODIFY" and replace it with
/** * DO NOT MODIFY THE BELOW CODE UNLESS YOU KNOW WHAT YOU ARE DOING */ /** * */ let isTranslationSupported = true; let isTranslationHelperNeeded = false; if (Object.keys(translations).length === 0) { (async () => { isTranslationSupported = false; const translationError = `Form Translation Error: No translations found. Please have an admin setup translations.`; console.error( translationError.concat('\n See https://docs.laserfiche.com') ); await LFForm.changeFieldSettings( { fieldId: translateLangFieldId }, { description: translationError } ); await LFForm.disableFields({ fieldId: translateLangFieldId }); const translateCustomHtmlField = LFForm.findFieldsByFieldId( translateCustomHtmlFieldId )?.[0]; if ( translateCustomHtmlField !== undefined && translateCustomHtmlField.componentType === 'CustomHTML' ) { await LFForm.changeFieldSettings( { fieldId: translateCustomHtmlFieldId }, { content: `<div class="btn-container"> <button ${GOOGLE_TRANSLATE_API_KEY === '' ? 'disabled' : ''} class="btn btn-default" onclick="getFieldtranslations()" title="Generate translations with Google"> Google Translate </button> <button class="btn btn-default" onclick="getFieldtranslations(true)" title="Generate an empty translation JSON"> Custom Translation </button> </div> `, } ); await LFForm.showFields({ fieldId: translateCustomHtmlFieldId }); isTranslationHelperNeeded = true; } })(); } const main = async () => { if (!isTranslationSupported) return; let targetLang = LFForm.getFieldValues({ fieldId: translateLangFieldId, }).value; /** * * @param {string} newTargetLang * @returns {Promise<void>} */ const runTranlateFields = async (newTargetLang) => { if (targetLang === newTargetLang) return; targetLang = newTargetLang; const translationTarget = translations[targetLang] || {}; let isChangeFieldOptionsSupported = true; for (const field in translationTarget) { const translation = translationTarget[field]; const fieldId = parseInt(field.split(' - ')[0]); const fieldType = /\((\w+)\)/.exec(field)?.[1]; for (const setting in translation) { const text = translation[setting]; if (field === 'Form') { await setFormSettings(setting, text).catch((e) => console.error(e)); } else { await setFieldSettings( fieldId, setting, text, fieldType, isChangeFieldOptionsSupported ).catch((e) => { if ( e.message === 'Changing field options is only supported on self-hosted Forms.' ) { isChangeFieldOptionsSupported = false; } else { console.error( `Error setting field ${fieldId} setting ${setting}`, e ); } }); } } } }; // Run translation when language field is changed. // NOTE: on field change does not natively support async so we need to disable the field while we run the translation. LFForm.onFieldChange( async () => { LFForm.disableFields({ fieldId: translateLangFieldId }); const newTargetLang = LFForm.getFieldValues({ fieldId: translateLangFieldId, }).value; await runTranlateFields(newTargetLang); LFForm.enableFields({ fieldId: translateLangFieldId }); }, { fieldId: translateLangFieldId } ); // Run translation on form load if the language default is not in the default language. if ( targetLang === defaultFormLang || targetLang === null || targetLang === '' ) return; const initLang = targetLang; targetLang = defaultFormLang; await runTranlateFields(initLang); }; // Run the main function on load void main(); // #region Field settings const basicTranslatableFieldSettings /** @type {const} */ = { label: 'label', subtext: 'subtext', options: 'options', tooltip: 'tooltip', default: 'default', signButtonLabel: 'signButtonLabel', rowLabels: 'rowLabels', prevBtn: 'prevBtn', nextBtn: 'nextBtn', }; const otherTranslatableFieldSettings /** @type {const} */ = { addLinkText: 'addLinkText', buttonLabel: 'buttonLabel', addressOptions: 'addressOptions', }; /** * @typedef {keyof typeof basicTranslatableFieldSettings | keyof typeof otherTranslatableFieldSettings} TranslatableFieldSettings */ /** * @param {number} fieldId * @param {TranslatableFieldSettings} setting * @param {string} text * @returns {Promise<void>} */ async function setFieldSettings( fieldId, setting, text, fieldType, isChangeFieldOptionsSupported ) { if (text === undefined || text === '' || text.length === 0) return; if (setting === 'default' && fieldType !== 'CustomHTML') { await LFForm.setFieldValues({ fieldId }, text); } else if ( setting === 'label' || setting === 'subtext' || setting === 'tooltip' || setting === 'default' || setting === 'signButtonLabel' || setting === 'rowLabels' || setting === 'prevBtn' || setting === 'nextBtn' ) { const parsedSetting = setting === 'prevBtn' ? 'prevButton' : setting === 'nextBtn' ? 'nextButton' : setting; await LFForm.changeFieldSettings({ fieldId }, { [parsedSetting]: text }); } else if (setting === 'addLinkText') { await LFForm.changeFieldSettings({ fieldId }, { addRowButtonLabel: text }); } else if (setting === 'buttonLabel') { await LFForm.changeFieldSettings({ fieldId }, { uploadButtonLabel: text }); } else if (setting === 'addressOptions') { if (typeof text !== 'object') throw new Error('Address options must be an array of objects'); await LFForm.changeFieldSettings( { fieldId }, { addressOptions: /** @type {any} */ (text) } ); } else if (isChangeFieldOptionsSupported && setting === 'options') { if (typeof text !== 'object') throw new Error('Change options text must be an array'); await LFForm.changeFieldOptions( { fieldId }, /** @type {any} */ (text), 'replace' ); } else { console.error('unknown field setting', setting, text); } } /** * @param {string} setting * @param {'title' | 'description' | 'actionButtons' | string} text * @returns {Promise<void>} */ async function setFormSettings(setting, text) { if (text === undefined || text === '') return; if (setting === 'title') { await LFForm.changeFormSettings({ title: text }); } else if (setting === 'description') { await LFForm.changeFormSettings({ description: text }); } else if (setting === 'actionButtons') { const actionButtons = Object.keys(text).map((key) => ({ action: key, label: /** @type {any} **/ (text)[key], })); await LFForm.changeActionButtons(actionButtons); } else { console.error('unknown form setting', setting, text); } } // #endregion Field settings // #region Translation Helpers /** * @typedef {Record<string, { settings: Record<string, string>, type: string }>} FormFieldSettings */ /** * * @returns {FormFieldSettings} */ const getFormFieldSettings = () => { /** * @type {FormFieldSettings} */ const formFieldSettings = {}; LFForm.findFields((f) => { const { settings } = f; /** * @type {{ type: string, settings: Record<string, any>}} */ const fieldSetting = { type: f.componentType, settings: {}, }; const variableName = f.settings.attributeName; const label = f.settings.label; const fieldKeyLabel = variableName ? `${variableName} (${f.componentType})` : label ? `${f.settings.label} (${f.componentType})` : `(${f.componentType})`; const fieldIdKey = `${f.fieldId} - ${fieldKeyLabel}`; if ( f.componentType === 'CustomHTML' && f.fieldId === translateCustomHtmlFieldId ) { // do not translate the translation helper return true; } else if (f.componentType === 'Form') { fieldSetting.settings = { title: settings.title, description: settings.description, actionButtons: { Submit: 'Submit', Approve: 'Approve', Reject: 'Reject', SaveAsDraft: 'Save as Draft', }, }; formFieldSettings['Form'] = fieldSetting; } else { for (const translatable in { ...basicTranslatableFieldSettings, ...otherTranslatableFieldSettings, }) { if (settings[translatable] === undefined) continue; if ( translatable === 'default' && (f.componentType === 'Checkbox' || f.componentType === 'Radio' || f.componentType === 'Dropdown') ) { continue; } if (settings[translatable] !== undefined) { fieldSetting.settings[translatable] = settings[translatable]; } } formFieldSettings[fieldIdKey] = fieldSetting; } }); return formFieldSettings; }; window.getFormFieldSettings = getFormFieldSettings; /** * * @param {string} text * @param {string} targetLang * @returns {Promise<string>} * @description Translate text to target language. This should be removed from your code when a static translation is saved to the translations variable. * Replace PUT_YOUT_API_KEY_HERE with your Google Cloud API key. */ async function getTranslation(text, targetLang, runCount = 0) { const sourceLang = defaultFormLang; const url = `https://translation.googleapis.com/language/translate/v2?key=${GOOGLE_TRANSLATE_API_KEY}`; return fetch(url, { referrer: 'app.laserfiche.com', method: 'POST', headers: { 'Content-Type': 'application/json', // Authorization: `Bearer ${GOOGLE_TRANSLATE_API_KEY}`, // 'x-goog-user-project': 'laserfiche-cloud', }, body: JSON.stringify({ q: text, source: sourceLang, target: targetLang, }), }) .then((response) => response.json()) .catch((error) => { if (runCount >= 3) throw new Error('Failed to translate text'); console.error('Error:', error); setTimeout(async () => { await getTranslation(text, targetLang, runCount + 1); }, 1000); }) .then((data) => { const translatedText = data.data.translations[0].translatedText; return translatedText; }); } /** * For each field, get the translation of the field settings. * This should be run once when the form is first designed, and then removed from the code. */ /** * * @param {string} targetLang * @returns {Promise<Record<number, Record<string, string>>>} */ const getFieldtranslationForLanguage = async ( targetLang, generateEmptyTranslations = false ) => { const formFieldInfo = getFormFieldSettings(); /** * @type {Record<number, Record<string, string>>} */ const translatedForm = {}; for (const field in formFieldInfo) { const fieldInfo = formFieldInfo[field]; // get the translation for each field setting that is already setup const newTranslation = translations[field] || {}; const fieldSettings = fieldInfo.settings; for (const f in fieldSettings) { const setting = fieldSettings[f]; if ( setting === undefined || setting === '' || targetLang === defaultFormLang || generateEmptyTranslations === true ) { newTranslation[f] = setting; continue; } let finalTranslation; try { if (setting === '' || setting === undefined || setting.length === 0) { finalTranslation = setting; } else if ( fieldInfo.type === 'Address' && (f === 'addressOptions' || f === 'default') ) { if (f === 'default') { finalTranslation = {}; for (const label in setting) { const addressProp = setting[label]; if (addressProp === '') { finalTranslation[label] = ''; continue; } const translatedProp = await getTranslation( addressProp, targetLang ); finalTranslation[label] = translatedProp; } } else { finalTranslation = []; for (const addressOption of setting) { const { label } = addressOption; const translatedLabel = await getTranslation(label, targetLang); finalTranslation.push({ ...addressOption, label: translatedLabel, }); } } } else if ( f === 'options' && (fieldInfo.type === 'Checkbox' || fieldInfo.type === 'Radio' || fieldInfo.type === 'Dropdown') ) { finalTranslation = []; for (const option of setting) { const label = option.label; const translatedOption = await getTranslation(label, targetLang); finalTranslation.push({ ...option, label: translatedOption }); } } else if (fieldInfo.type === 'Form' && f === 'actionButtons') { finalTranslation = {}; for (const action in setting) { const label = setting[action]; const translatedLabel = await getTranslation(label, targetLang); finalTranslation[action] = translatedLabel; } } else { const translatedText = await getTranslation(setting, targetLang); finalTranslation = translatedText; } console.log(setting, finalTranslation); newTranslation[f] = finalTranslation; } catch (e) { console.error(`Field ${field} failed to translate property ${f}`, e); } } translatedForm[field] = newTranslation; } return translatedForm; }; /** * For each language, get the translation of the field settings. */ window.getFieldtranslations = async (generateEmptyTranslations = false) => { const supportedLanguages = LFForm.findFieldsByFieldId(translateLangFieldId)[0].options; /** * @type {Record<string, Record<number, Record<string, string>>>} */ const translatedForm = {}; const originalCustomHTMLContent = isTranslationHelperNeeded ? LFForm.findFieldsByFieldId(translateCustomHtmlFieldId)[0].data : ''; if (isTranslationHelperNeeded && generateEmptyTranslations === false) { // display loading await LFForm.changeFieldSettings( { fieldId: translateCustomHtmlFieldId }, { content: `<div class="translate-modal" style="position: fixed; z-index: 100; background: rgba(1,1,1,0.8); left: 0; top: 0; width: 100vw; height: 100vh; padding: 16px; display: flex; flex-direction: column; align-items: center; justify-content: center;"> <div class="translate-modal-content" style="background: white; padding: 16px; flex: .75; border-radius: .75rem; width: 100%;"> <div class="translate-modal-body" style="height: 90%"> <div class="translate-modal-body-header"> <h3>Translations</h3> <p>Please wait for translations to complete</p> </div> <div class="translate-modal-body-content" style="height: 100%"> <div class="progress"> <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div> </div> </div> </div> </div>`, } ); } for (const lang of supportedLanguages) { const newLanguage = await getFieldtranslationForLanguage( lang.value, generateEmptyTranslations ); translatedForm[lang.value] = newLanguage; } if (isTranslationHelperNeeded) { const translatedFormAsString = JSON.stringify(translatedForm); //, null, 2); const bytes = new TextEncoder().encode(translatedFormAsString); const blob = new Blob([bytes], { type: 'application/json;charset=utf-8', }); window.openTranslation = () => { const url = URL.createObjectURL(blob); window.open(url, '_blank'); }; window.resetForm = async () => { await LFForm.changeFieldSettings( { fieldId: translateCustomHtmlFieldId }, { content: originalCustomHTMLContent } ); }; await LFForm.changeFieldSettings( { fieldId: translateCustomHtmlFieldId }, { content: `<div class="translate-modal" style="position: fixed; z-index: 100; background: rgba(1,1,1,0.8); left: 0; top: 0; width: 100vw; height: 100vh; padding: 16px; display: flex; flex-direction: column; align-items: center; justify-content: center;"> <div class="translate-modal-content" style="background: white; padding: 16px; flex: .75; border-radius: .75rem; width: 100%;"> <span class="translate-modal-close btn" style="float: right;" onclick="resetForm()">×</span> <div class="translate-modal-body" style="height: 90%"> <div class="translate-modal-body-header"> <h3>Translations</h3> <p>Click the button below to open your translations in a new tab. (Please allow popups if clicking this is blocked, or open the dev console to see the logged JSON)</p> </div> <div class="translate-modal-body-content" style="height: 100%"> <button onclick="openTranslation()" class="btn btn-primary">Download Translations</button> </div> </div> </div>`, // <p class="user-select-all" readonly style="border: lightgray 1px solid; background: none; resize: none; height: 100%; overflow: auto; white-space: pre;">${jsonHtml.outerHTML}</p> } ); await LFForm.showFields({ fieldId: translateCustomHtmlFieldId }); } console.log( 'Copy the following JSON into the translations variable at the start of the code.' ); console.log(translatedForm); };
I am very familiar with JavaScript and I see that you added code to the region field settings and added fieldType and isChangeFieldOptionsSupported to the function setFieldSettings. The change also worked for me thanks for your help.
Thanks for finding the bug!