App Translations
A SitePack app is installed by merchants across the Netherlands, Belgium, Germany, France and Spain, and its storefront blocks are read by their visitors. Both sides read your app in their own language when you ship a translation dictionary with it: one JSON file per language, next to your app.json.
The same dictionary covers both audiences:
- the settings form the merchant fills in, which SitePack renders from your
settings_schema - the storefront blocks your app renders, through the
transfilter in your Twig templates
The dictionary holds the strings you write. Text the merchant writes in your settings form (a popup title, a button text) is translated by SitePack itself when you mark the setting translatable: see Translating what the merchant writes.
The translations/ Folder
your-app-directory/
├── app.json
├── templates/
│ └── whatsapp_widget.twig
└── translations/
├── en.json
├── nl.json
├── de.json
├── fr.json
└── es.json
Filenames must be lowercase and end in .json, the same rule that applies to themes. en.json is the base dictionary: whatever a language does not translate falls back to English, so you can ship a new key in English today and translate it later without breaking anything.
Files are nested JSON, and keys are addressed with dots:
{
"widget": {
"aria_label": "Chat with us on WhatsApp",
"close": "Close"
}
}
widget.close is the key you use in a template and in your manifest.
Only app.json and the templates, assets and translations folders are uploaded by sitepack app:publish. Anything else in your working copy stays on your machine.
Translating Your Settings Form
With settings_type: "abstract" SitePack renders the settings form for you. Write a translation key wherever a merchant-facing string appears, and SitePack resolves it against your dictionary for the merchant's admin language.
These fields are translated:
| Field | What it is |
|---|---|
title | The label above the field |
description | The help text under the field |
placeholder | The greyed-out example inside an empty text field |
enum_titles | The labels of a dropdown, in the same order as enum |
{
"admin": {
"settings_type": "abstract",
"settings_schema": {
"type": "object",
"properties": {
"phone_number": {
"type": "string",
"title": "admin.settings.phone_number.title",
"description": "admin.settings.phone_number.description",
"placeholder": "admin.settings.phone_number.placeholder"
},
"position": {
"type": "string",
"title": "admin.settings.position.title",
"enum": ["bottom_right", "bottom_left"],
"enum_titles": [
"admin.settings.position.options.bottom_right",
"admin.settings.position.options.bottom_left"
],
"default": "bottom_right"
}
},
"required": ["phone_number"]
}
}
}
{
"admin": {
"settings": {
"phone_number": {
"title": "WhatsApp-nummer",
"description": "Je WhatsApp (Business) nummer in internationaal formaat.",
"placeholder": "+31 6 12345678"
},
"position": {
"title": "Plek van de knop",
"options": {
"bottom_right": "Rechtsonder",
"bottom_left": "Linksonder"
}
}
}
}
}
The values in enum are what gets stored, so they are never translated. Only their labels are.
A key your dictionary does not define is shown as it stands in the manifest, so an existing app that writes plain English titles keeps working exactly as before.
Translating Your Storefront Blocks
Inside a block template, trans reads the same dictionary:
{% set greeting = settings.greeting_text|default('')|trim %}
{% set greeting = greeting is empty ? ('widget.default_greeting'|trans) : greeting %}
<a href="https://wa.me/{{ number }}" aria-label="{{ 'widget.aria_label'|trans }}">
...
</a>
The language is the one the page is rendered in, so a visitor on the Dutch version of a site gets nl.json and a visitor on the German version de.json, with English underneath for anything untranslated.
Your app's strings are yours alone: they cannot be overridden by the site's theme, and they never leak into it.
Translating What the Merchant Writes
Some settings are not configuration but content: the title and text of a popup, the label of a button, a short message. A merchant who sells in several languages wants their visitors to read that text in their own language, without you adding a field per language.
Mark such a setting "translatable": true:
{
"admin": {
"settings_type": "abstract",
"settings_schema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "admin.settings.title.title",
"translatable": true
},
"description": {
"type": "string",
"title": "admin.settings.description.title",
"format": "textarea",
"translatable": true
},
"accent_color": {
"type": "string",
"format": "color"
}
}
}
}
}
What happens then:
- The merchant writes it once, in the site's own language, on your settings page. The field carries a "Translatable" badge that points them to the translations screen.
- SitePack translates it with the rest of the site. Under Content > Translations the merchant picks the content type Apps and sees one entry per installed app, with every translatable setting next to its translation. They translate by hand or with the site's machine translation, exactly like a page. When they change the original later, the translation is flagged as outdated there.
- Your storefront block receives the translated value.
settings.titlealready holds the text for the language the page is rendered in, and falls back to what the merchant wrote when there is no translation for that language. Your template does not change:
<div class="exit-popup">
<h2>{{ settings.title }}</h2>
<p>{{ settings.description }}</p>
</div>
A script gets the same values through whatever your block hands it, for example a data- attribute or a JSON config rendered by the block.
The language is the one the page is served in: a page under a language folder (/de/...) gets that language's translation, a page in the site's own language gets what the merchant wrote. app.settings holds the same translated values as settings.
Which settings can be translatable
Only free text. The flag is honoured on a top-level string property without enum and without a color, uri, url, email or image format. On anything else (a number, a dropdown, a colour, an address, an image) it is ignored: translating those would break what your app does with them. The automated review checklist warns about a flag that is ignored this way, and about a translatable that is not true or false.
A setting inside an array item cannot be translatable yet.
What your backend sees
merchant_settings on GET /api/public/v1/app/settings holds the values as the merchant wrote them, in the site's own language. The translations are delivered to the storefront only.
Labels and values are two different things
| Written by | Translated through | |
|---|---|---|
title, description, placeholder, enum_titles of a setting | you | your translations/<locale>.json |
The value of a translatable setting | the merchant | Content > Translations on the merchant's site |
| The value of any other setting | the merchant | not translated |
Publishing
Translations are published with the rest of your app:
sitepack app:publish
Because the settings form and the storefront read the published version, a change to a dictionary only reaches merchants after a publish, exactly like a change to a template.
Next Steps
- Storefront Blocks & Events: What your block templates may use, and where they render.
- App Settings: The settings form merchants fill in.
- App Manifest (app.json): The full manifest reference.
- Publishing & Monetization: Getting your app to merchants.