Migration Guide · Version 04.26
Multi-language setup for Klaviyo
This guide shows you how to use Klaviyo’s translation feature to make sure your users receive emails in the correct language. For clarity, this guide assumes German is the default shop language and English is added as a second language. However, the steps shown can also be applied to other languages.
1. Background
In July 2025, Klaviyo rolled out a translation feature that allows you to translate emails directly inside Klaviyo. Similar to Shopify translations, you first create your email in one language and then translate it.
When the email is sent, Klaviyo determines which language version should be shown based on the profile’s selected language.
Core benefits
- AI translations are available.
- You spend less time creating duplicate emails.
- You reduce the risk of sending emails in the wrong language.
2. Setting up the translation feature
2.1 Basic setup
To use the translation feature go to Settings → Translation in Klaviyo and enable the translation feature.
Then choose which property Klaviyo should use for language information. Use the locale provided by Shopify.
The Shopify locale is automatically split into language and country in Klaviyo, making it easier to segment and manage flows and campaigns.
- Locale
- Locale: Country
- Locale: Language
After selecting the locale, specify your supported languages and choose a default language. If most of your users are German-speaking, German can be your default. If your audience is very international, English may be the better default.
2.2 Migrate existing profiles
If you’ve been using Klaviyo for a while, the next step is to make sure that all your existing, active profiles have their locale set.
First, create a segment with:
- Properties about someone → Locale = Not Set
- AND: Person can receive email marketing
Then reduce the number of profiles without a locale to 0 using the steps below.
01 Resynchronise Shopify
Go to the Shopify integration in Klaviyo and re-import your contacts. This can enrich older customer profiles where the locale was not previously passed to Klaviyo.
02 Use old language properties
If you previously stored your customers’ language in a different custom property, use your “Locale = Not Set” segment as the trigger for a flow.
- Add a conditional split based on your old language property.
- Route users for DE, EN, ES, FR or other languages into separate branches.
- Use an Update Profile Property action and set $locale to de, en, es, fr or the correct locale value.
- Set the flow to Live and use “Add past profiles” on your trigger (add them at the start of the flow).
03 Use list assignments
If users still remain in your “Locale = Not Set” segment, use existing lists to categorise them.
Use the same flow logic as above: trigger the flow from the “Locale = Not Set” segment, split users by lists, and update the locale property accordingly.
04 Set a default
If you have exhausted all options and still have profiles without a locale, assign a default locale to them.
Final target
Your “Locale = Not Set” segment should be 0.
2.3 Tagging new profiles
Once historical data has been migrated, make sure new profiles automatically receive a locale.
New profiles are usually created via:
- Klaviyo forms or onsite scripts
- BackInStock signups
- Shopify checkout
- Shopify newsletter forms
- External tools such as Typeform, quiz apps or consultation tools
2.3.1 Klaviyo onsite script
If a visitor browses your shop but does not start checkout, Klaviyo may not know which locale they used. Add this snippet to your theme.liquid before the closing body tag:
<!-- Start Klaviyo MultiLanguage -->
<script>
klaviyo.identify({
{% if customer.email %}
'$email': '{{ customer.email }}',
{% endif %}
'$locale': '{{ request.locale.iso_code }}-{{ localization.country.iso_code }}'
});
</script>
<!-- End Klaviyo MultiLanguage -->
This ensures that both language and country data are automatically synced with Klaviyo at all times. Whenever a new profile is created—whether through a Klaviyo form or Back-in-Stock request—the correct locale is passed instantly.
At the same time, any updates made later in your shop (e.g. if a customer changes their language or was previously assigned incorrectly) are continuously reflected in Klaviyo, keeping every profile accurate and up to date.
2.3.2 External tools / apps
External tools are slightly more complex—but they usually require a proper language mapping system anyway. Once that system is defined, you can use it to consistently assign the correct locale in Klaviyo—either directly via an “Update Profile Property” step or indirectly through Shopify Flow, including retroactive updates.
3. Configuration of sign-up forms
To ensure a consistent language experience across your forms (SignUp and BackInStock) and double opt-in emails—which directly impacts confirmation rates—you’ll need a small but important setup.
Start by creating a separate list for each language. For example:
- DE Newsletter
- EN Newsletter
- ES Newsletter
For each list, adapt the double opt-in email to the corresponding language. If the confirmation email is sent in the wrong language, it can significantly reduce your opt-in rate.
You can ignore other list-related pages like preference or unsubscribe pages, as Klaviyo will mostly use your default settings when sending campaigns or flows.
3.1 Klaviyo form setup
When using Klaviyo forms, you need to ensure that visitors always see the sign-up form in their correct language—so a German form doesn’t appear in an English version of your shop.
If you are using Shopify Markets, Shopify automatically structures your URLs by locale (e.g. /en_DE or /fr_FR). These URL patterns can be used to control which form is shown to which user.
This allows you to display the correct form based on language (and, where relevant, country).
Example
For an English version of your shop, URLs may contain /en (e.g. yourdomain.com/en_DE). Here, 'en' represents the language and 'DE' the country. Use these information to create your sign-up form:
01 1. Create your form
Create your sign-up form in the desired language (for example, start with the English version).
02 2. Set display targeting
In the form editor, go to Targeting → Targeting and set the form to display only on pages that contain yourdomain.com/en.
03 3. Configure submit behavior
Set the following for the submit button for the English language:
- Add subscribers to your English newsletter list (for the double opt-in email).
- Add a hidden custom field with Locale = en
04 4. Duplicate for other languages
Once your English form is set up, duplicate it and adapt it for other languages.
- Update all texts to the target language.
- Make sure to only show it on pages with your language identifier
- Connect the form to the corresponding newsletter list.
- Update the hidden locale field (e.g. 'de').
- Repeat this for all languages
Important: Your default language
For your default language you won't have an url identifier. In this case you proceed as following:
- Update all texts to the default language.
- Go to Targeting → Targeting and 'Don't show' it on URLs that contain your other languages (e.g. yourdomain.com/en, yourdomain.com/fr etc.).
- Connect the form to the default language newsletter list.
- Update the hidden locale field to your default language (e.g. 'de').
3.2 BackInStock setup
If you use Klaviyo’s standard BackInStock snippet to add a notification feature for out-of-stock products, you’ll face three challenges when setting up a multi-language system:
- How do you ensure the BackInStock form appears in the correct language?
- How is the user’s locale transferred to their Klaviyo profile?
- And how do you send the DOI email in the correct language if users also wish to subscribe to the newsletter?
The locale is automatically set when a profile is created via the script from section 2.3.1 (as long as it’s implemented correctly).
This means you only need to focus on two things: translations and assigning the correct list.
Follow these steps to set this up:
01 1. Add the BackInStock snippet
Insert the BackInStock snippet into your theme.liquid file before the closing </body> tag and define placeholders for all elements you want to localise.
<script src="https://a.klaviyo.com/media/js/onsite/onsite.js"></script>
<script>
var klaviyo = klaviyo || [];
klaviyo.init({
account: "XXXXX",
list: "{{ 'general.back_in_stock.list' | t }}",
platform: "shopify"
});
klaviyo.enable("backinstock", {
trigger: {
product_page_text: "{{ 'general.back_in_stock.product_page_text' | t }}",
product_page_class: "button w-full",
product_page_text_align: "center",
product_page_margin: "10px 0px 0px 0px",
product_page_width: "100%",
replace_anchor: false
},
modal: {
headline: "{product_name}",
body_content: "{{ 'general.back_in_stock.body_content' | t }}",
email_field_label: "{{ 'general.back_in_stock.body_email_field_label' | t }}",
button_label: "{{ 'general.back_in_stock.button_label' | t }}",
subscription_success_label: "{{ 'general.back_in_stock.subscription_success_label' | t }}",
footer_content: "",
additional_styles: "@import url('https://fonts.googleapis.com/css?family=Inter+Neue');",
drop_background_color: "#000",
background_color: "#fff",
text_color: "#000",
button_text_color: "#fff",
button_background_color: "#000",
close_button_color: "#ccc",
error_background_color: "#fcd6d7",
error_text_color: "#C72E2F",
success_background_color: "#d3efcd",
success_text_color: "#1B9500",
newsletter_subscribe_label: "{{ 'general.back_in_stock.newsletter_subscribe_label' | t }}"
}
});
</script>
02 2. Replace your Klaviyo account ID
Replace 'XXXXX' with your Klaviyo public API key. You can find this under Settings → API Keys → Public API Key.
03 3. Add translations for your default language
Go to your locale file (e.g. en.default.json) and add the following translation block into your 'general' section:
"back_in_stock": {
"body_content": "Register to receive a notification when this item comes back in stock.",
"email_field_label": "Email",
"button_label": "Notify me when available",
"subscription_success_label": "You're in! We'll let you know when it's back.",
"product_page_text": "Notify Me When Available!",
"newsletter_subscribe_label": "Save 10% when signing up for our newsletter.",
"list": "ID of your English list"
},
Customise the list ID and the translations to suit your needs
It should look similar to this here:
04 4. Add translations for other languages
Repeat this process for each language and adapt both the texts and the list ID accordingly. For example in your de.json add:
"back_in_stock": {
"body_content": "Erhalte eine Benachrichtigung, sobald dieser Artikel wieder verfügbar ist",
"email_field_label": "E-Mail-Adresse",
"button_label": "Sag mir Bescheid, wenn der Artikel wieder da ist!",
"subscription_success_label": "Geschafft! Wir informieren dich, sobald der Artikel zurück ist.",
"product_page_text": "Sag mir Bescheid, wenn der Artikel wieder da ist!",
"newsletter_subscribe_label": "Werde Teil der Community und spare zusätzlich 10% auf deinen Erstkauf.",
"list": "ID deiner deutschen Liste"
},
05 5. Repeat for all languages
Make sure each language has its own translation set and is connected to the correct list.
3.3 Shopify integration and language-based lists
The good news: New profiles from Shopify (e.g. via checkout or theme forms) automatically pass the customer's language to Klaviyo.
The problem: By default, Klaviyo can only add these users to a single list — regardless of their language.
Since the double opt-in email is tied to that list, it often gets sent in the wrong language.
The solution: You need to bypass the default list assignment and instead define yourself which list a user should be added to based on their language.
Follow these steps to set this up:
01 1. Disable default list assignment
Go to the Shopify integration in Klaviyo and disable the option that automatically adds profiles to a list.
02 2. Create a Shopify Flow
In Shopify, create a Shopify Flow that routes new email subscribers to different lists based on the selected language in your shop.
03 3. Assign profiles based on language
Configure the flow so that customers who subscribe to email marketing are added to the correct Klaviyo list depending on their locale.
4. Unsubscribe and preference pages
That leaves the question of how we can offer unsubscribe and preference pages in multiple languages.
In Klaviyo, unsubscribe and preference pages are managed in two places: under Settings → Other → Consent pages, and directly on individual lists.
List-specific pages only help if you are sending to those exact lists. However, if you send to segments—which is usually the recommended approach—Klaviyo uses the default consent pages instead.
Follow these steps to set this up:
01 1. Enable hosted pages
Go to Settings → Other → Consent Pages and enable Hosted Pages for your account. If your account is still new, you may first need to wait until Klaviyo verifies it.
02 2. Open the Hosted Pages tab
Once enabled, a new tab called “Hosted pages” will appear in the left-hand navigation.
03 3. Create your unsubscribe page
Create a new hosted page using unsubscribe_page.tmpl and add your unsubscribe page HTML.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>Unsubscribe</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<style>
:root{
--page-bg:#f3f3f3; /* CHANGE ME */
--card-bg:#ffffff; /* CHANGE ME */
--text:#050505; /* CHANGE ME */
--soft:#f4f4f4; /* CHANGE ME */
--accent:#429991; /* CHANGE ME */
--accent-hover:#357f79; /* CHANGE ME */
}
*{box-sizing:border-box;}
body{
background:var(--page-bg);
color:var(--text);
font-family:'Assistant', Arial, Helvetica, sans-serif;
font-size:14px;
line-height:1.45;
}
.container{max-width:100%;}
.col-12.col-md-6.offset-md-3{
max-width:600px;
margin:32px auto !important;
background:var(--card-bg);
padding:54px 42px 44px;
}
.lang-row{
display:flex;
justify-content:center;
margin-bottom:22px;
}
.lang-bar{
display:inline-flex;
gap:6px;
background:#f4f4f4;
padding:5px;
border-radius:999px;
}
.lang-bar .btn{
border:0;
border-radius:999px;
background:transparent;
color:#555;
font-size:11px;
font-weight:800;
letter-spacing:.04em;
padding:6px 10px;
line-height:1;
}
.lang-bar .btn:hover,
.lang-bar .btn:focus,
.lang-bar .btn.is-active{
background:#111;
color:#fff;
}
.logo-wrap{
padding:0 0 18px;
text-align:center;
}
.logo-wrap img{
max-height:66px;
display:inline-block;
}
.heading{
text-align:center;
font-size:28px;
line-height:1.05;
font-family:'Playfair Display', serif;
font-weight:900;
letter-spacing:-.03em;
text-transform:none;
margin:8px 0 12px;
}
.subcopy{
color:#222;
text-align:center;
max-width:390px;
margin:0 auto 28px;
font-size:13px;
line-height:1.5;
}
.panel-card,
.success-card,
.card-like{
border:0;
border-radius:0;
padding:0;
}
.form-label,
h6{
font-size:13px;
font-weight:800;
color:#050505;
margin-bottom:7px;
}
.mb-3{
margin-bottom:22px !important;
}
.form-control{
border:1px solid #d8d8d8;
border-radius:0;
height:42px;
font-size:13px;
padding:9px 12px;
color:#111;
background-color:#fff;
}
.form-control:focus{
border-color:#111;
box-shadow:none;
}
.help-text{
background:var(--soft);
border-left:3px solid #111;
padding:12px 14px;
color:#333;
font-size:12px;
line-height:1.45;
margin:10px 0 14px;
}
#freqSection .form-check{
display:flex;
align-items:center;
gap:10px;
margin:0 0 4px;
padding-left:28px;
}
.form-check-input{
position:static;
margin:0;
width:15px;
height:15px;
border-radius:0;
border:1px solid #999;
flex-shrink:0;
}
.form-check-input:checked{
background-color:#111;
border-color:#111;
}
.form-check-label{
font-size:14px;
color:#111;
line-height:1.3;
}
.btn-pill{
border-radius:0;
}
.btn-cta{
width:100%;
padding:15px 20px;
font-size:13px;
font-weight:900;
text-transform:uppercase;
letter-spacing:.02em;
background:var(--accent);
color:#fff;
border:1px solid var(--accent);
}
.btn-cta:hover,
.btn-cta:focus{
background:var(--accent-hover);
border-color:var(--accent-hover);
color:#fff;
}
#unsubscribeLink{
color:var(--accent);
text-decoration:none;
font-size:12px;
font-weight:600;
}
#unsubscribeLink:hover{
text-decoration:underline;
color:var(--accent-hover);
}
#orText{display:none;}
.text-center.mt-4{
margin-top:22px !important;
font-size:12px;
}
.divider{
height:36px;
border-bottom:1px solid #eee;
}
.success-icon{
width:54px;
height:54px;
border-radius:999px;
margin:0 auto 18px;
background:#f4f4f4;
color:#111;
display:flex;
align-items:center;
justify-content:center;
font-size:26px;
font-weight:900;
}
@media(max-width:576px){
body{background:#fff;}
.col-12.col-md-6.offset-md-3{
max-width:100%;
margin:0 auto !important;
padding:42px 24px 36px;
}
.heading{font-size:25px;}
#freqSection .form-check{
padding-left:12px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-12 col-md-6 offset-md-3 mt-4">
<div class="lang-row">
<div class="lang-bar">
<button type="button" class="btn btn-sm" data-lang-btn="de">DE</button>
<button type="button" class="btn btn-sm" data-lang-btn="en">EN</button>
<button type="button" class="btn btn-sm" data-lang-btn="es">ES</button>
<button type="button" class="btn btn-sm" data-lang-btn="fr">FR</button>
</div>
</div>
<div class="logo-wrap mb-2">
<a href="https://domain.de">
<img src="https://domain.de/Logo.png" alt="Logo">
</a>
</div>
<div class="card-like mt-3">
{% if request.GET|lookup:'success' == '1' or request.method == "POST" %}
<div class="success-card">
<div class="success-icon">✓</div>
<h5 id="successHeadline" class="heading">Geschafft!</h5>
<p id="successText" class="subcopy">
Deine Einstellungen wurden gespeichert. Danke, dass du dir kurz Zeit genommen hast.
</p>
<div class="d-grid gap-2 mt-4">
<a href="#" id="backLink" class="btn btn-pill btn-cta">Zurück</a>
</div>
</div>
{% else %}
<div class="panel-card">
<h5 id="headline" class="heading">Schade, dass du uns verlassen möchtest</h5>
<p id="intro" class="subcopy">
Bestätige hier deine Abmeldung oder entscheide, wie häufig du von uns E-Mails erhalten möchtest.
</p>
<form id="prefsForm" action="" method="POST" class="mt-3">
<input type="hidden" name="$fields" value="$email,EmailFrequency,$list:[LIST_ID_1],$list:[LIST_ID_2]" />
<input type="hidden" id="updatedUrl" name="$updated_profile_url" value="" />
<input type="hidden" id="unsubUrl" name="$unsubscribed_url" value="" />
<div class="mb-3">
<label for="email" class="form-label" id="emailLabel">E-Mail-Adresse</label>
<input type="email" class="form-control" id="email" name="$email" value="{{ person.email|default:'' }}">
</div>
<div class="mb-3 d-none" id="listSubscriptions" aria-hidden="true">
<h6 id="listsHeadline" class="mb-tight">Wähle, welche Listen du abonnieren möchtest</h6>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="$list:[LIST_ID_1]" id="breakingNewsList" value="true"
{% if '[LIST_ID_1]' in person|lookup:'$lists' or request.POST|lookup:'$list:[LIST_ID_1]' %}checked{% endif %}>
<label class="form-check-label" for="breakingNewsList" id="list1Label">Breaking News</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="$list:[LIST_ID_2]" id="opinionPiecesList" value="true"
{% if '[LIST_ID_2]' in person|lookup:'$lists' or request.POST|lookup:'$list:[LIST_ID_2]' %}checked{% endif %}>
<label class="form-check-label" for="opinionPiecesList" id="list2Label">Opinion Pieces</label>
</div>
<div class="help-text" id="listsHelp">Du kannst eine oder mehrere Listen auswählen.</div>
</div>
<div class="mb-3" id="freqSection">
<h6 id="freqHeadline" class="mb-tight">Wie häufig möchtest du unseren Newsletter erhalten?</h6>
<div class="help-text" id="freqHelp">
Du kannst statt einer vollständigen Abmeldung auch einfach deine E-Mail-Frequenz reduzieren.
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="EmailFrequency" value="always" id="newsletterFrequencyAlways"
{% if person|lookup:'EmailFrequency' == "always" %}checked{% endif %}>
<label class="form-check-label" for="newsletterFrequencyAlways" id="freqAlways">Gerne alle News</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="EmailFrequency" value="monthly" id="newsletterFrequencyMonthly"
{% if person|lookup:'EmailFrequency' == "monthly" %}checked{% endif %}>
<label class="form-check-label" for="newsletterFrequencyMonthly" id="freqMonthly">Einmal im Monat</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="EmailFrequency" value="sales" id="newsletterFrequencySale"
{% if person|lookup:'EmailFrequency' == "sales" %}checked{% endif %}>
<label class="form-check-label" for="newsletterFrequencySale" id="freqSale">Nur bei Sales & Produktlaunches</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="EmailFrequency" value="transactional" id="newsletterFrequencyTransactional"
{% if person|lookup:'EmailFrequency' == "transactional" %}checked{% endif %}>
<label class="form-check-label" for="newsletterFrequencyTransactional" id="freqTransactional">Nur transaktionale E-Mails</label>
</div>
</div>
<div class="d-grid gap-2 my-3">
<button type="submit" class="btn btn-pill btn-cta" id="saveBtn">
Präferenzen speichern
</button>
</div>
<input id="unsubscribeFromList" type="hidden" name="$unsubscribe" value="" />
<div class="text-center mt-4">
<span id="orText">oder</span>
<a href="#" id="unsubscribeLink">Von allen E-Mails abmelden</a>
</div>
</form>
</div>
{% endif %}
</div>
<div class="divider"></div>
</div>
</div>
</div>
<script>
const t = {
de: {
headline: "Schade, dass du uns verlassen möchtest",
intro: "Bestätige hier deine Abmeldung oder entscheide, wie häufig du von uns E-Mails erhalten möchtest.",
email: "E-Mail-Adresse",
freqH: "Wie häufig möchtest du unseren Newsletter erhalten?",
freqHelp: "Du kannst statt einer vollständigen Abmeldung auch einfach deine E-Mail-Frequenz reduzieren.",
a: "Gerne alle News",
m: "Einmal im Monat",
s: "Nur bei Sales & Produktlaunches",
transactional: "Nur transaktionale E-Mails",
save: "Präferenzen speichern",
or: "oder",
unsubAll: "Von allen E-Mails abmelden",
listsH: "Wähle, welche Listen du abonnieren möchtest",
list1: "Breaking News",
list2: "Opinion Pieces",
listsHelp: "Du kannst eine oder mehrere Listen auswählen.",
sHeadline: "Geschafft!",
sText: "Deine Einstellungen wurden gespeichert. Danke, dass du dir kurz Zeit genommen hast.",
uHeadline: "Abgemeldet",
uText: "Du wurdest erfolgreich von allen E-Mails abgemeldet.",
back: "Zurück"
},
en: {
headline: "Sorry to see you go.",
intro: "Confirm your unsubscription or choose how often you’d like to receive emails from us.",
email: "Your email address",
freqH: "How often would you like to receive our newsletter?",
freqHelp: "Instead of unsubscribing completely, you can simply reduce your email frequency.",
a: "All updates",
m: "Once a month",
s: "Only sales & product launches",
transactional: "Transactional emails only",
save: "Save preferences",
or: "or",
unsubAll: "Unsubscribe from all emails",
listsH: "Choose which lists you want to subscribe to",
list1: "Breaking News",
list2: "Opinion Pieces",
listsHelp: "You can select one or multiple lists.",
sHeadline: "All set!",
sText: "Your preferences have been saved. Thanks for taking a moment.",
uHeadline: "Unsubscribed",
uText: "You have been successfully unsubscribed from all emails.",
back: "Back"
},
es: {
headline: "Lamentamos que te vayas.",
intro: "Confirma tu baja o elige con qué frecuencia quieres recibir nuestros correos.",
email: "Tu correo electrónico",
freqH: "¿Con qué frecuencia quieres recibir nuestro boletín?",
freqHelp: "En lugar de darte de baja por completo, puedes simplemente reducir la frecuencia de tus emails.",
a: "Todas las novedades",
m: "Una vez al mes",
s: "Solo ofertas y lanzamientos",
transactional: "Solo correos transaccionales",
save: "Guardar preferencias",
or: "o",
unsubAll: "Darse de baja de todos los correos",
listsH: "Elige a qué listas quieres suscribirte",
list1: "Breaking News",
list2: "Opinion Pieces",
listsHelp: "Puedes seleccionar una o varias listas.",
sHeadline: "¡Listo!",
sText: "Tus preferencias se han guardado. Gracias por tomarte un momento.",
uHeadline: "Dado de baja",
uText: "Te has dado de baja correctamente de todos los correos.",
back: "Volver"
},
fr: {
headline: "Désolé de te voir partir.",
intro: "Confirme ton désabonnement ou choisis la fréquence à laquelle tu souhaites recevoir nos e-mails.",
email: "Ton adresse e-mail",
freqH: "À quelle fréquence souhaites-tu recevoir notre newsletter ?",
freqHelp: "Au lieu de te désabonner complètement, tu peux simplement réduire la fréquence de tes e-mails.",
a: "Toutes les nouveautés",
m: "Une fois par mois",
s: "Uniquement promos et lancements",
transactional: "Uniquement les e-mails transactionnels",
save: "Enregistrer les préférences",
or: "ou",
unsubAll: "Se désabonner de tous les e-mails",
listsH: "Choisis les listes auxquelles tu veux t’abonner",
list1: "Breaking News",
list2: "Opinion Pieces",
listsHelp: "Tu peux sélectionner une ou plusieurs listes.",
sHeadline: "C’est fait !",
sText: "Tes préférences ont été enregistrées. Merci d’avoir pris un moment.",
uHeadline: "Désabonné",
uText: "Tu as été désabonné(e) de tous les e-mails avec succès.",
back: "Retour"
}
};
const SUPPORTED = ["de","en","es","fr"];
function getLangFromUrl(){
const p = new URLSearchParams(window.location.search);
const lang = (p.get("lang") || "").toLowerCase();
return SUPPORTED.includes(lang) ? lang : null;
}
function detectBrowserLang(){
const raw = (navigator.language || navigator.userLanguage || "de").toLowerCase();
const primary = raw.split("-")[0];
return SUPPORTED.includes(primary) ? primary : "de";
}
function ensureLangInUrl(lang){
const url = new URL(window.location.href);
url.searchParams.set("lang", lang);
window.history.replaceState({}, "", url.toString());
}
function buildSuccessUrl(lang, action){
const url = new URL(window.location.href);
url.searchParams.set("success", "1");
url.searchParams.set("lang", lang);
if (action) url.searchParams.set("action", action);
return url.toString();
}
function getSuccessAction(){
const p = new URLSearchParams(window.location.search);
return (p.get("action") || "").toLowerCase();
}
function applyLanguage(lang){
const L = t[lang] || t.de;
document.documentElement.lang = lang;
document.querySelectorAll("[data-lang-btn]").forEach(btn => {
btn.classList.toggle("is-active", btn.getAttribute("data-lang-btn") === lang);
});
const byId = (id) => document.getElementById(id);
if (byId("headline")) byId("headline").innerText = L.headline;
if (byId("intro")) byId("intro").innerText = L.intro;
if (byId("emailLabel")) byId("emailLabel").innerText = L.email;
if (byId("listsHeadline")) byId("listsHeadline").innerText = L.listsH;
if (byId("list1Label")) byId("list1Label").innerText = L.list1;
if (byId("list2Label")) byId("list2Label").innerText = L.list2;
if (byId("listsHelp")) byId("listsHelp").innerText = L.listsHelp;
if (byId("freqHeadline")) byId("freqHeadline").innerText = L.freqH;
if (byId("freqHelp")) byId("freqHelp").innerText = L.freqHelp;
if (byId("freqAlways")) byId("freqAlways").innerText = L.a;
if (byId("freqMonthly")) byId("freqMonthly").innerText = L.m;
if (byId("freqSale")) byId("freqSale").innerText = L.s;
if (byId("freqTransactional")) byId("freqTransactional").innerText = L.transactional;
if (byId("saveBtn")) byId("saveBtn").innerText = L.save;
if (byId("orText")) byId("orText").innerText = L.or;
if (byId("unsubscribeLink")) byId("unsubscribeLink").innerText = L.unsubAll;
const action = getSuccessAction();
if (byId("successHeadline") && byId("successText")){
if (action === "unsub"){
byId("successHeadline").innerText = L.uHeadline || L.sHeadline;
byId("successText").innerText = L.uText || L.sText;
} else {
byId("successHeadline").innerText = L.sHeadline;
byId("successText").innerText = L.sText;
}
}
if (byId("backLink")) byId("backLink").innerText = L.back;
}
function setRedirectInputs(lang){
const updated = document.getElementById("updatedUrl");
const unsub = document.getElementById("unsubUrl");
if (updated) updated.value = buildSuccessUrl(lang, "updated");
if (unsub) unsub.value = buildSuccessUrl(lang, "unsub");
}
(function(){
let lang = getLangFromUrl();
if (!lang) lang = detectBrowserLang();
ensureLangInUrl(lang);
applyLanguage(lang);
setRedirectInputs(lang);
document.querySelectorAll("[data-lang-btn]").forEach(btn => {
btn.addEventListener("click", () => {
const l = btn.getAttribute("data-lang-btn");
ensureLangInUrl(l);
applyLanguage(l);
setRedirectInputs(l);
});
});
const form = document.getElementById("prefsForm");
if (form){
form.addEventListener("submit", () => {
const currentLang = getLangFromUrl() || lang || "de";
setRedirectInputs(currentLang);
});
}
const unsubLink = document.getElementById("unsubscribeLink");
if (unsubLink){
unsubLink.addEventListener("click", function(e){
e.preventDefault();
const currentLang = getLangFromUrl() || lang || "de";
setRedirectInputs(currentLang);
document.getElementById("unsubscribeFromList").value = "true";
const l1 = document.getElementById("breakingNewsList");
const l2 = document.getElementById("opinionPiecesList");
if (l1) l1.checked = false;
if (l2) l2.checked = false;
this.closest("form").submit();
});
}
const freqBoxes = Array.from(document.querySelectorAll("input[type='checkbox'][name='EmailFrequency']"));
if (freqBoxes.length){
freqBoxes.forEach(box => {
box.addEventListener("click", function(){
if (this.checked){
freqBoxes.forEach(other => {
if (other !== box) other.checked = false;
});
}
});
});
}
const back = document.getElementById("backLink");
if (back){
back.addEventListener("click", (e) => {
e.preventDefault();
const url = new URL(window.location.href);
url.searchParams.delete("success");
url.searchParams.delete("action");
window.location.href = url.toString();
});
}
})();
</script>
</body>
<!-- © GrowWithFlows. All rights reserved. -->
</html>
04 4. Customise the unsubscribe page
Adjust the HTML to match your colours and brand identity, replace the logo URL, update list IDs, and remove any languages you do not offer.
05 5. Create your preference page
Create another hosted page for your preference page and add your preference page HTML.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>Preference page</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<style>
:root{
--page-bg:#f3f3f3; /* CHANGE ME */
--card-bg:#ffffff; /* CHANGE ME */
--text:#050505; /* CHANGE ME */
--soft:#f4f4f4; /* CHANGE ME */
--accent:#429991; /* CHANGE ME */
--accent-hover:#357f79; /* CHANGE ME */
}
*{box-sizing:border-box;}
body{
background:var(--page-bg);
color:var(--text);
font-family:'Assistant', Arial, Helvetica, sans-serif;
font-size:14px;
line-height:1.45;
}
.container{max-width:100%;}
.col-12.col-md-6.offset-md-3{
max-width:600px;
margin:32px auto !important;
background:var(--card-bg);
padding:54px 42px 44px;
}
.lang-row{
display:flex;
justify-content:center;
margin-bottom:22px;
}
.lang-bar{
display:inline-flex;
gap:6px;
background:#f4f4f4;
padding:5px;
border-radius:999px;
}
.lang-bar .btn{
border:0;
border-radius:999px;
background:transparent;
color:#555;
font-size:11px;
font-weight:800;
letter-spacing:.04em;
padding:6px 10px;
line-height:1;
}
.lang-bar .btn:hover,
.lang-bar .btn:focus,
.lang-bar .btn.is-active{
background:#111;
color:#fff;
}
.logo-wrap{
padding:0 0 18px;
text-align:center;
}
.logo-wrap img{
max-height:66px;
}
.heading{
text-align:center;
font-size:28px;
line-height:1.05;
font-family:'Playfair Display', serif;
font-weight:900;
letter-spacing:-.03em;
margin:8px 0 12px;
}
.subcopy{
color:#222;
text-align:center;
max-width:330px;
margin:0 auto 28px;
font-size:13px;
line-height:1.45;
}
.panel-card,
.success-card,
.card-like{
border:0;
border-radius:0;
padding:0;
}
.form-label,
h6{
font-size:13px;
font-weight:800;
color:#050505;
margin-bottom:7px;
}
.mb-3{
margin-bottom:22px !important;
}
.row.mb-3{
display:block;
}
.row.mb-3 .col{
width:100%;
max-width:100%;
margin-bottom:18px;
}
.form-control,
.form-select{
border:1px solid #d8d8d8;
border-radius:0;
height:42px;
font-size:13px;
padding:9px 12px;
color:#111;
background-color:#fff;
}
.form-control:focus,
.form-select:focus{
border-color:#111;
box-shadow:none;
}
.help-text{
background:var(--soft);
border-left:3px solid #111;
padding:12px 14px;
color:#333;
font-size:12px;
line-height:1.45;
margin:10px 0 14px;
}
#freqSection{
padding-left:0;
margin-top:6px;
}
#freqSection .form-check{
display:flex;
align-items:center;
gap:10px;
margin:0 0 4px;
padding-left:28px;
}
.form-check-input{
position:static;
margin:0;
width:15px;
height:15px;
border-radius:0;
border:1px solid #999;
flex-shrink:0;
}
.form-check-input:checked{
background-color:#111;
border-color:#111;
}
.form-check-label{
font-size:14px;
color:#111;
line-height:1.3;
}
.btn-pill{
border-radius:0;
}
.btn-cta{
width:100%;
padding:15px 20px;
font-size:13px;
font-weight:900;
text-transform:uppercase;
letter-spacing:.02em;
background:var(--accent);
color:#fff;
border:1px solid var(--accent);
}
.btn-cta:hover,
.btn-cta:focus{
background:var(--accent-hover);
border-color:var(--accent-hover);
color:#fff;
}
#unsubscribeLink{
color:var(--accent);
text-decoration:none;
font-size:12px;
font-weight:500;
}
#unsubscribeLink:hover{
text-decoration:underline;
color:var(--accent-hover);
}
#orText{
display:none;
}
.text-center.mt-4{
margin-top:22px !important;
font-size:12px;
}
.divider{
height:36px;
border-bottom:1px solid #eee;
}
.success-icon{
width:54px;
height:54px;
border-radius:999px;
margin:0 auto 18px;
background:#f4f4f4;
color:#111;
display:flex;
align-items:center;
justify-content:center;
font-size:26px;
font-weight:900;
}
@media(max-width:576px){
body{
background:#fff;
}
.col-12.col-md-6.offset-md-3{
max-width:100%;
margin:0 auto !important;
padding:42px 24px 36px;
}
.heading{
font-size:25px;
}
#freqSection .form-check{
padding-left:12px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-12 col-md-6 offset-md-3 mt-4">
<div class="lang-row">
<div class="lang-bar">
<button type="button" class="btn btn-sm" data-lang-btn="en">EN</button>
<button type="button" class="btn btn-sm" data-lang-btn="de">DE</button>
<button type="button" class="btn btn-sm" data-lang-btn="es">ES</button>
<button type="button" class="btn btn-sm" data-lang-btn="fr">FR</button>
</div>
</div>
<div class="logo-wrap mb-2">
<img src="YOUR_LOGO_URL" alt="Logo" />
</div>
<div class="card-like mt-3">
{% if request.GET|lookup:'success' == '1' or request.method == "POST" %}
<div class="success-card">
<div class="success-icon">✓</div>
<h5 id="successHeadline" class="heading">
Geschafft!
</h5>
<p id="successText" class="subcopy">
</p>
<div class="d-grid gap-2 mt-4">
<a href="#" id="backLink" class="btn btn-pill btn-cta">
</a>
</div>
</div>
{% else %}
<div class="panel-card">
<h5 id="headline" class="heading">
</h5>
<p id="intro" class="subcopy">
</p>
<form id="prefsForm" action="" method="POST" class="mt-3">
<input
type="hidden"
name="$fields"
value="$first_name,$last_name,$email,Favourite Dog,EmailFrequency,Birthday"
/>
<input type="hidden" id="updatedUrl" name="$updated_profile_url" value="" />
<input type="hidden" id="unsubUrl" name="$unsubscribed_url" value="" />
<div class="row mb-3">
<div class="col">
<label for="firstName" class="form-label" id="firstNameLabel">
</label>
<input
type="text"
id="firstName"
class="form-control"
name="$first_name"
value="{{ person.first_name|default:'' }}"
/>
</div>
<div class="col">
<label for="lastName" class="form-label" id="lastNameLabel">
</label>
<input
type="text"
id="lastName"
class="form-control"
name="$last_name"
value="{{ person.last_name|default:'' }}"
/>
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label" id="emailLabel">
</label>
<input
type="email"
class="form-control"
id="email"
name="$email"
value="{{ person.email|default:'' }}"
/>
</div>
<div class="mb-3">
<label for="Birthday" class="form-label" id="birthdayLabel">
</label>
<div id="birthdayTitle" class="help-text">
</div>
<input
type="date"
id="Birthday"
class="form-control"
name="Birthday"
value="{{ person|lookup:'Birthday'|default:'' }}"
/>
</div>
<div class="mb-3">
<label for="favouriteDog" id="favDogLabel" class="form-label">
</label>
<select class="form-select" id="favouriteDog" name="Favourite Dog">
<option value="">
Love them all!
</option>
<option value="Springer Spaniel"
{% if person|lookup:'Favourite Dog' == "Springer Spaniel" %}selected{% endif %}>
Springer Spaniel
</option>
<option value="Poodle"
{% if person|lookup:'Favourite Dog' == "Poodle" %}selected{% endif %}>
Poodle
</option>
<option value="Pug"
{% if person|lookup:'Favourite Dog' == "Pug" %}selected{% endif %}>
Pug
</option>
<option value="Golden Retriever"
{% if person|lookup:'Favourite Dog' == "Golden Retriever" %}selected{% endif %}>
Golden Retriever
</option>
</select>
</div>
<div class="mb-3" id="freqSection">
<h6 id="freqHeadline">
</h6>
<div class="help-text" id="freqHelp">
</div>
<div class="form-check">
<input
type="checkbox"
class="form-check-input"
name="EmailFrequency"
value="always"
id="newsletterFrequencyAlways"
{% if person|lookup:'EmailFrequency' == "always" %}checked{% endif %}
/>
<label class="form-check-label" for="newsletterFrequencyAlways" id="freqAlways">
</label>
</div>
<div class="form-check">
<input
type="checkbox"
class="form-check-input"
name="EmailFrequency"
value="monthly"
id="newsletterFrequencyMonthly"
{% if person|lookup:'EmailFrequency' == "monthly" %}checked{% endif %}
/>
<label class="form-check-label" for="newsletterFrequencyMonthly" id="freqMonthly">
</label>
</div>
<div class="form-check">
<input
type="checkbox"
class="form-check-input"
name="EmailFrequency"
value="sales"
id="newsletterFrequencySales"
{% if person|lookup:'EmailFrequency' == "sales" %}checked{% endif %}
/>
<label class="form-check-label" for="newsletterFrequencySales" id="freqSales">
</label>
</div>
</div>
<div class="d-grid gap-2 my-3">
<button type="submit" class="btn btn-pill btn-cta" id="saveBtn">
</button>
</div>
<input id="unsubscribeFromList" type="hidden" name="$unsubscribe" value="" />
<div class="text-center mt-4">
<a href="#" id="unsubscribeLink">
</a>
</div>
</form>
</div>
{% endif %}
</div>
<div class="divider"></div>
</div>
</div>
</div>
<script>
const t = {
de: {
headline: "Aktualisiere deine Präferenzen",
intro: "Wähle aus, welche Inhalte du erhältst und wie häufig wir dir schreiben.",
first: "Vorname",
last: "Nachname",
email: "E-Mail-Adresse",
fav: "Lieblings-Hunderasse",
birthdayTitle: "Nenne uns dein Geburtstagsdatum, um an deinem besonderen Tag ein Geschenk zu erhalten.",
birthdayLabel: "Geburtstag",
freqH: "Wie häufig möchtest du unseren Newsletter erhalten?",
a: "Gerne alle News",
m: "Einmal im Monat",
s: "Nur bei Sales & Produktlaunches",
freqHelp: "Bitte wähle eine Option (Einfachauswahl).",
save: "Präferenzen speichern",
unsubAll: "Von allen E-Mails abmelden",
sHeadline: "Geschafft!",
sText: "Deine Einstellungen wurden gespeichert.",
uHeadline: "Abgemeldet",
uText: "Du wurdest erfolgreich von allen E-Mails abgemeldet.",
back: "Zurück"
},
en: {
headline: "Update your preferences",
intro: "Choose what you receive and how often we email you.",
first: "First name",
last: "Last name",
email: "Your email address",
fav: "Favourite dog breed",
birthdayTitle: "Tell us your birthday to receive a gift on your special day.",
birthdayLabel: "Birthday",
freqH: "How often would you like to receive our newsletter?",
a: "All updates",
m: "Once a month",
s: "Only sales & product launches",
freqHelp: "Please choose one option.",
save: "Save preferences",
unsubAll: "Unsubscribe from all emails",
sHeadline: "All set!",
sText: "Your preferences have been saved.",
uHeadline: "Unsubscribed",
uText: "You have been successfully unsubscribed.",
back: "Back"
},
es: {
headline: "Actualiza tus preferencias",
intro: "Elige qué recibir y con qué frecuencia te escribimos.",
first: "Nombre",
last: "Apellido",
email: "Tu correo electrónico",
fav: "Raza de perro favorita",
birthdayTitle: "Cuéntanos tu fecha de cumpleaños para recibir un regalo en tu día especial.",
birthdayLabel: "Cumpleaños",
freqH: "¿Con qué frecuencia quieres recibir nuestro boletín?",
a: "Todas las novedades",
m: "Una vez al mes",
s: "Solo ofertas y lanzamientos",
freqHelp: "Elige una opción.",
save: "Guardar preferencias",
unsubAll: "Darse de baja de todos los correos",
sHeadline: "¡Listo!",
sText: "Tus preferencias se han guardado.",
uHeadline: "Dado de baja",
uText: "Te has dado de baja correctamente.",
back: "Volver"
},
fr: {
headline: "Mets à jour tes préférences",
intro: "Choisis ce que tu reçois et la fréquence de nos e-mails.",
first: "Prénom",
last: "Nom",
email: "Ton adresse e-mail",
fav: "Race de chien préférée",
birthdayTitle: "Indique ta date d’anniversaire pour recevoir un cadeau le jour J.",
birthdayLabel: "Anniversaire",
freqH: "À quelle fréquence souhaites-tu recevoir notre newsletter ?",
a: "Toutes les nouveautés",
m: "Une fois par mois",
s: "Uniquement promos et lancements",
freqHelp: "Choisis une option.",
save: "Enregistrer les préférences",
unsubAll: "Se désabonner de tous les e-mails",
sHeadline: "C’est fait !",
sText: "Tes préférences ont été enregistrées.",
uHeadline: "Désabonné",
uText: "Tu as été désabonné(e) avec succès.",
back: "Retour"
}
};
const SUPPORTED = ["de","en","es","fr"];
function getLangFromUrl(){
const p = new URLSearchParams(window.location.search);
const lang = (p.get("lang") || "").toLowerCase();
return SUPPORTED.includes(lang) ? lang : null;
}
function detectBrowserLang(){
const raw = (navigator.language || navigator.userLanguage || "de").toLowerCase();
const primary = raw.split("-")[0];
return SUPPORTED.includes(primary) ? primary : "de";
}
function ensureLangInUrl(lang){
const url = new URL(window.location.href);
url.searchParams.set("lang", lang);
window.history.replaceState({}, "", url.toString());
}
function buildSuccessUrl(lang, action){
const url = new URL(window.location.href);
url.searchParams.set("success", "1");
url.searchParams.set("lang", lang);
if (action){
url.searchParams.set("action", action);
}
return url.toString();
}
function setRedirectInputs(lang){
const updated = document.getElementById("updatedUrl");
const unsub = document.getElementById("unsubUrl");
if (updated){
updated.value = buildSuccessUrl(lang, "updated");
}
if (unsub){
unsub.value = buildSuccessUrl(lang, "unsub");
}
}
function getSuccessAction(){
const p = new URLSearchParams(window.location.search);
return (p.get("action") || "").toLowerCase();
}
function applyLanguage(lang){
const L = t[lang] || t.de;
document.documentElement.lang = lang;
document.querySelectorAll("[data-lang-btn]").forEach(btn => {
btn.classList.toggle(
"is-active",
btn.getAttribute("data-lang-btn") === lang
);
});
const byId = (id) => document.getElementById(id);
if (byId("headline")) byId("headline").innerText = L.headline;
if (byId("intro")) byId("intro").innerText = L.intro;
if (byId("firstNameLabel")) byId("firstNameLabel").innerText = L.first;
if (byId("lastNameLabel")) byId("lastNameLabel").innerText = L.last;
if (byId("emailLabel")) byId("emailLabel").innerText = L.email;
if (byId("favDogLabel")) byId("favDogLabel").innerText = L.fav;
if (byId("birthdayTitle")) byId("birthdayTitle").innerText = L.birthdayTitle;
if (byId("birthdayLabel")) byId("birthdayLabel").innerText = L.birthdayLabel;
if (byId("freqHeadline")) byId("freqHeadline").innerText = L.freqH;
if (byId("freqAlways")) byId("freqAlways").innerText = L.a;
if (byId("freqMonthly")) byId("freqMonthly").innerText = L.m;
if (byId("freqSales")) byId("freqSales").innerText = L.s;
if (byId("freqHelp")) byId("freqHelp").innerText = L.freqHelp;
if (byId("saveBtn")) byId("saveBtn").innerText = L.save;
if (byId("unsubscribeLink")) byId("unsubscribeLink").innerText = L.unsubAll;
const action = getSuccessAction();
if (byId("successHeadline") && byId("successText")){
if (action === "unsub"){
byId("successHeadline").innerText = L.uHeadline;
byId("successText").innerText = L.uText;
} else {
byId("successHeadline").innerText = L.sHeadline;
byId("successText").innerText = L.sText;
}
}
if (byId("backLink")){
byId("backLink").innerText = L.back;
}
}
(function(){
let lang = getLangFromUrl();
if (!lang){
lang = detectBrowserLang();
}
ensureLangInUrl(lang);
applyLanguage(lang);
setRedirectInputs(lang);
document.querySelectorAll("[data-lang-btn]").forEach(btn => {
btn.addEventListener("click", () => {
const l = btn.getAttribute("data-lang-btn");
ensureLangInUrl(l);
applyLanguage(l);
setRedirectInputs(l);
});
});
const form = document.getElementById("prefsForm");
if (form){
form.addEventListener("submit", () => {
const currentLang = getLangFromUrl() || lang || "de";
setRedirectInputs(currentLang);
});
}
const unsubLink = document.getElementById("unsubscribeLink");
if (unsubLink){
unsubLink.addEventListener("click", function(e){
e.preventDefault();
const currentLang = getLangFromUrl() || lang || "de";
setRedirectInputs(currentLang);
document.getElementById("unsubscribeFromList").value = "true";
this.closest("form").submit();
});
}
const freqBoxes = Array.from(
document.querySelectorAll("input[type='checkbox'][name='EmailFrequency']")
);
freqBoxes.forEach(box => {
box.addEventListener("click", function(){
if (this.checked){
freqBoxes.forEach(other => {
if (other !== box){
other.checked = false;
}
});
}
});
});
const back = document.getElementById("backLink");
if (back){
back.addEventListener("click", (e) => {
e.preventDefault();
const url = new URL(window.location.href);
url.searchParams.delete("success");
url.searchParams.delete("action");
window.location.href = url.toString();
});
}
})();
</script>
</body>
<!-- © GrowWithFlows. All rights reserved. -->
</html>
06 6. Adapt custom properties
Make sure your custom properties, list IDs, and fields are correctly mapped — otherwise, this won’t work as expected. If necessary, ask a developer to help you adapt the HTML or reach out to us for support.
07 7. Assign your hosted pages
Go back to the overview and assign your hosted pages to the relevant preference and unsubscribe pages.
08 8. Test everything
Send a real email to yourself using a test profile — not just a preview. Go to the profile, open View Messages, click Send Mail, and verify that both the unsubscribe and preference functions work as expected.
5. Flow setup
Within your flows, you can also leverage the locale data coming from Shopify.
There are two main ways to approach this:
5.1 Separate flows or conditional splits
This is similar to the old setup: you either filter entire flows by language or use conditional splits inside the flow.
Advantages
- Easy differentiation by language or country.
- Completely different emails possible per market.
- Performance visible per language or country.
Disadvantages
- More flows or branches to manage.
- More manual work.
- Recipients can be locked into the initial language branch.
Choose this approach if you want full visibility into how each flow performs across different languages or markets.
5.2 Using the translation feature
If you mainly want to communicate in the correct language and do not need language-level reporting, use Klaviyo’s translation feature in one central flow.
Advantages
- You only need to maintain one email.
- AI translations are available — you only need to review them.
- Cleaner and easier-to-manage flow structure.
Disadvantages
- No built-in differentiation between markets.
- No reporting by language or country.
Choose this approach if you want a simpler setup and only need to communicate in the correct language, without analysing performance by market.
6. Campaign setup
With the new translation feature, you will (in theory) no longer need separate campaigns for each language; instead, you can send a single campaign email to everyone and simply control the language via the translation feature.
However, for analytics and market-level insights, it may still make sense to split campaigns by language or country. This is especially useful if you want to understand how a campaign performs in specific markets and/or languages.
So you need to decide which setup fits your use case best:
6.1 One campaign using the translation feature
If your main goal is to deliver emails in the correct language — and you don’t need detailed performance insights per language — follow these steps:
- Create the segments you want to target without specifying a language.
- Design and write your campaign email in one language first.
- Once the email is finalised, use Klaviyo’s translation feature to add the other language versions.
Advantages
- You manage segments based on behaviour, not language.
- You send one campaign to a larger audience, which can be helpful for A/B testing.
- Klaviyo AI can translate most of the email for you, so you only need to review and refine it.
Disadvantages
- Reporting is shown as one overall result, not broken down by language or country.
- You have less visibility into which language version performed best.
Choose this approach if you mainly care about sending the right language and want the simplest possible campaign setup.
6.2 Separate campaigns using language or country exclusions
Use this approach if it’s important for you to understand how individual languages or countries perform.
- Create your main campaign segments without language or country conditions.
- Create additional language segments, such as:
- German profiles (Locale: Language = de) and English profiles (Locale: Language = en).
- Country segments, such as profiles in Germany (Locale: Country = DE), Austria (Locale: Country = AT), or other relevant markets.
- For even deeper analysis, create language-country combinations such as German profiles in Germany (Locale = de_DE) or English profiles in Germany (Locale = en_DE).
- When sending campaigns, use your main segment as the Send to audience and exclude the languages or countries you do not want to target.
Sending by language
Use one core segment and control each campaign version with simple exclusions.
Advantages
- You can see how each campaign performs by language or country.
- Using language or country segments as exclusions allows you to reuse them across campaigns—instead of creating new language-specific segments every time.
- You gain more control over market-specific messaging and performance analysis.
Disadvantages
- Slightly more complex to manage (compared to Option 1), as you always have to think in terms of exclusions (e.g. for German emails, exclude English profiles—and vice versa).
- Requires manually creating a separate email for the second language each time.
- Campaign setup takes more time than using one translated campaign.
Choose this approach if you want better reporting and control by language, country, or market.
Conclusion
With the new translation feature, managing multi-language in Klaviyo has become much easier. If you follow the steps in this guide, you should end up with a clean setup where the locale is reliably passed from Shopify, sign-up forms appear in the correct language, and (double opt-in) emails are always sent in the right language.
This guide focuses specifically on the multilingual setup inside Klaviyo.
Since 2026, Klaviyo also supports Shopify Markets through Locale Aware Catalogs. This allows you to sync market-specific product data from Shopify into Klaviyo, including language, currency, pricing and localized product URLs — so your emails can show the right product information for each market.
Everything worked — your guide is on its way.
Check your inbox. If you don’t see it, also check spam or promotions.
Don’t want to figure this out yourself?
Let us implement your Multi-Language setup for you
We fully set up your multi-language system — clean, structured and ready to use in Shopify & Klaviyo.
- Migration of your existing language setup
- Cleanup of Klaviyo language properties
- Clean Shopify localization integration
- Structured flows and segments
- Scalable setup for multiple markets