Hey Trailblazers, in the first two posts of this series, we built a multilingual Account Engagement (Pardot) form that:
- Switches language automatically (no language dropdown)
- Tracks UTM parameters and passes them through
- Can show campaign-specific thank you messages by UTM + language
This time, we are going to zoom in on two things that usually complicate global forms:
- Privacy and consent rules that change by country
- Thank you messages that need to be translated and tailored by form type
All of this still happens on a single global form, using the same layout template. No form cloning for each market.
The reality: one form, three privacy “buckets”
From a legal standpoint, you usually end up with three broad groups:
- GDPR-like countries
- EU, UK, Switzerland, etc
- Need clear consent language and often a visible checkbox
- Mainland China (separate handling)
- Specific consent wording
- Usually requires an explicit checkbox and stronger language
- Everyone else
- Still needs disclosure
- Often handled via a statement below the form rather than an extra required checkbox
On top of that, Taiwan often needs to be handled more like GDPR, and regions like Hong Kong or Macao may not show a separate opt-in at all.
Instead of maintaining different forms for each group, we let the layout template react to the selected Country field and adjust:
- Whether the TEMP_Opt_In checkbox is visible
- Whether the checkbox is required
- Whether we show a “below the form” privacy statement instead
- What copy is used, and in which language
The form setup in Account Engagement
On the form side, you only need a couple of fields:
- A standard Country (or Country/Region) select
- A checkbox field, something like TEMP_Opt_In
- An optional “privacy note” paragraph below the form, which we give a class, for example:
<p class="non-privacy-note"> <!-- Fallback text, will be replaced by script --> Cognex may send me information and offers about their products and services.</p>
The layout template script takes it from there.
Step 1: Detect the country and language
The layout already knows which language to use thanks to the detectLanguage() helper. It looks at:
?lang=query string if present- Language in the URL (
/en,/fr,/es, etc) - And falls back to English
For privacy, we also read the Country select value and label:
function getCountrySelect() { return document.querySelector( "p.form-field.country select, p.form-field.country_region select" );}function getSelectedCountry() { const sel = getCountrySelect(); if (!sel) return { value: "", label: "" }; const opt = sel.options[sel.selectedIndex]; return { value: (opt && opt.value ? String(opt.value).trim() : ""), label: (opt && opt.text ? String(opt.text).trim() : "") };}
This lets us make decisions based on either the option value, its label, or both.
Step 2: Bucket countries for different privacy behaviors
In the layout template script, we define sets to represent how each region should behave:
// GDPR-style marketsconst PRIVACY_COUNTRIES = new Set([ "austria","belgium","bulgaria","croatia","cyprus","czech republic","denmark", "estonia","finland","france","germany","greece","hungary","iceland","ireland", "italy","latvia","liechtenstein","lithuania","luxembourg","malta","netherlands", "norway","poland","portugal","romania","slovakia","slovenia","spain","sweden", "switzerland","united kingdom", // ISO codes "at","be","bg","hr","cy","cz","dk","ee","fi","fr","de","gr","hu","is","ie","it", "lv","li","lt","lu","mt","nl","no","pl","pt","ro","sk","si","es","se","ch","gb","uk"]);// Regions that don’t show opt-in UI at allconst NO_OPTIN = new Set([ "", "hong kong s.a.r., china", "macao s.a.r., china", "hk", "mo"]);// The actual value used by the China option in the picklistconst CHINA_VALUE = "549670";
Then we add helper functions to identify Mainland China and Taiwan:
function isMainlandChina(value, label) { const v = (value || "").toLowerCase(); const l = (label || "").toLowerCase(); return ( value === CHINA_VALUE || v === "cn" || v === "china" || l === "china" || (l.includes("china") && !l.includes("hong kong") && !l.includes("macao") && !l.includes("taiwan")) || (value && /中国/.test(value)) || (label && /中国/.test(label)) );}function isTaiwan(value, label) { const v = (value || "").toLowerCase(); const l = (label || "").toLowerCase(); return ( v === "tw" || v === "taiwan" || (l && l.includes("taiwan")) || (value && /台灣|台湾/.test(value)) || (label && /台灣|台湾/.test(label)) );}
This gives us three core buckets:
- Mainland China
- Taiwan
- GDPR, non-GDPR, and “no opt-in” markets
Step 3: Swap between checkbox vs. statement below the form
The main toggle function wires everything together:
function toggleOptIn() { const privacyOptIn = document.querySelector("p.form-field.TEMP_Opt_In"); const nonPrivacyNote = document.querySelector("p.non-privacy-note"); const checkbox = privacyOptIn ? privacyOptIn.querySelector('input[type="checkbox"]') : null; if (privacyOptIn) privacyOptIn.style.display = "none"; if (nonPrivacyNote) nonPrivacyNote.style.display = "none"; const { value, label } = getSelectedCountry(); const vLower = (value || "").toLowerCase(); const lLower = (label || "").toLowerCase(); const isChina = isMainlandChina(value, label); const isTw = isTaiwan(value, label); const isPrivacyCountry = PRIVACY_COUNTRIES.has(vLower) || PRIVACY_COUNTRIES.has(lLower) || isChina || isTw; const isNoOptIn = NO_OPTIN.has(vLower) || NO_OPTIN.has(lLower); // 1) GDPR countries + China + Taiwan if (isPrivacyCountry) { if (privacyOptIn) privacyOptIn.style.display = ""; if (isChina) { // Mainland China: special copy + required checkbox enhanceChineseOptinLabelForChinaUsingTranslation(); setOptInRequired(true); } else { // GDPR + Taiwan: GDPR-style consent message enhanceOptinLabelForGdprWithPrivacyLink(); setOptInRequired(isTw); // Taiwan required, others optional } clearOptInError(); if (nonPrivacyNote) nonPrivacyNote.style.display = "none"; return; } // 2) Hong Kong / Macao etc - no visible opt-in, auto checked if (isNoOptIn) { setOptInRequired(false); clearOptInError(); if (checkbox) checkbox.checked = true; return; } // 3) Everyone else - hidden checkbox, statement below the form if (checkbox) checkbox.checked = true; setOptInRequired(false); clearOptInError(); if (nonPrivacyNote) { // Restore default text in the note if needed if (nonPrivacyNote.__defaultHtml && nonPrivacyNote.__appliedContent !== 'default') { nonPrivacyNote.innerHTML = nonPrivacyNote.__defaultHtml; nonPrivacyNote.__appliedContent = 'default'; } nonPrivacyNote.style.display = ""; }}
Result:
- If the country is GDPR or Taiwan:
- The checkbox field (TEMP_Opt_In) is visible
- The message next to it includes a link to the privacy policy in the current language
- If the country is Mainland China:
- The checkbox is visible and required
- The wording uses the
optinchinatranslation, which is country-specific and stronger
- If the country is in the “no opt-in UI” bucket:
- The checkbox is silently checked
- No extra statement is shown
- For other countries:
- The checkbox is hidden but auto-checked
- A statement appears below the form in the current language
Step 4: Localize the privacy text with the same i18n dictionary
The layout already has a translations object with per-language copies like:
const translations = { en: { i18n: { shortdisclaimerLine: "I would like to receive information and offers about Cognex products and services.", privacyPolicyLabel: "Privacy Policy", privacyPolicyUrl: "www.cognex.com/en/company/privacy-policy", optinchina: "I understand and agree that Cognex may send me information and offers..." // ... }}, fr: { i18n: { /* French texts, URLs */ }}, es: { i18n: { /* Spanish texts, URLs */ }}, // etc...};
The functions that paint the text next to the checkbox reuse that:
For GDPR + Taiwan:
function enhanceOptinLabelForGdprWithPrivacyLink() { const { value, label } = getSelectedCountry(); if (isMainlandChina(value, label)) return; const lang = detectLanguage() || "en"; const langPack = translations[lang] && translations[lang].i18n; if (!langPack) return; const text = langPack.shortdisclaimerLine; const url = langPack.privacyPolicyUrl; const labelText = langPack.privacyPolicyLabel; if (!text || !url || !labelText) return; const wrapper = document.querySelector( "p.form-field.TEMP_Opt_In .value span" ); if (!wrapper) return; const checkbox = wrapper.querySelector('input[type="checkbox"]'); if (!checkbox) return; let textSpan = wrapper.querySelector(".optin-text"); if (!textSpan) { textSpan = document.createElement("span"); textSpan.className = "optin-text"; wrapper.appendChild(textSpan); } textSpan.innerHTML = text + ' <a href="https://' + url + '" target="_blank" rel="noopener noreferrer">' + labelText + "</a>"; }
For Mainland China:
function enhanceChineseOptinLabelForChinaUsingTranslation() { const { value, label } = getSelectedCountry(); if (!isMainlandChina(value, label)) return; const lang = detectLanguage() || "en"; const langPack = translations[lang] && translations[lang].i18n; if (!langPack) return; const txt = langPack.optinchina || langPack.optin; if (!txt) return; const wrapper = document.querySelector( "p.form-field.TEMP_Opt_In .value span" ); if (!wrapper) return; const checkbox = wrapper.querySelector('input[type="checkbox"]'); if (!checkbox) return; let textSpan = wrapper.querySelector(".optin-text"); if (!textSpan) { textSpan = document.createElement("span"); textSpan.className = "optin-text"; wrapper.appendChild(textSpan); } textSpan.innerHTML = txt; }
Because this all comes from the same translations object, you only maintain privacy text in one place per language.
Step 5: Require opt-in for China and Taiwan only
Some markets require an explicit “yes” to marketing communications before you can send anything. In this setup:
- China and Taiwan must check the box before the form will submit
- Everyone else either has an informational statement or an optional checkbox
The validation hook looks like this:
function validateOptInIfNeeded(e) { const { value, label } = getSelectedCountry(); const mustOptIn = isMainlandChina(value, label) || isTaiwan(value, label); if (!mustOptIn) { clearOptInError(); return true; } const { checkbox } = getOptInEls(); if (!checkbox || !checkbox.checked) { if (e) { e.preventDefault(); e.stopPropagation(); } showOptInError(); if (checkbox) checkbox.focus(); return false; } clearOptInError(); return true;}
This is wired to the form’s submit event in capture mode, so it runs before Pardot’s own validation.
Step 6: Multilingual thank you messages by form type
The same translation dictionary also handles your thank you messages. For example, English includes:
en: { i18n: { short_thankyou: "Thanks for subscribing. We are excited to stay connected!", highvalue_thankyou: "Thank you for reaching out! Our Sales team will be in touch with you shortly...", asset_thankyou: "All set! Here's your download."}}
Other languages have their own localized versions of short_thankyou, highvalue_thankyou, and asset_thankyou.
In your thank you layout (or in a content block that only shows after submit), you just drop the placeholder:
<h3>[[i18n.highvalue_thankyou]]</h3>
The same layout template that translated the form labels will also replace this with the right language after detectLanguage() runs.
This way:
- Short “newsletter” forms can use
[[i18n.short_thankyou]] - High intent “contact sales” forms can use
[[i18n.highvalue_thankyou]] - Asset download forms can use
[[i18n.asset_thankyou]]+ a translated download button label
All of it stays in one layout, one translations object, and one country-aware privacy script.
In the next post, we will look at a slightly different pattern: what to do when you do not need a fancy thank you message at all, and just want to send people to a language-specific page as soon as they hit Submit.