Skip to main content

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 trans filter 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.

warning

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:

FieldWhat it is
titleThe label above the field
descriptionThe help text under the field
placeholderThe greyed-out example inside an empty text field
enum_titlesThe 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"]
}
}
}
translations/nl.json
{
"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:

  1. 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.
  2. 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.
  3. Your storefront block receives the translated value. settings.title already 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 byTranslated through
title, description, placeholder, enum_titles of a settingyouyour translations/<locale>.json
The value of a translatable settingthe merchantContent > Translations on the merchant's site
The value of any other settingthe merchantnot 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​