Adding Translations
A step-by-step guide for adding new translatable strings to Kona Theme, using translation references in Liquid and schema, auditing for hardcoded text, and fixing corrupted locale files.
Adding a New Storefront String
Storefront strings are customer-facing text -- button labels, error messages, headings, and any text visitors see on the store.
1. Add the key to the English source file
Edit theme/locales/en.default.json and add your key in the appropriate section:
{
"products": {
"product": {
"quantity_label": "Quantity",
"your_new_key": "Your new English string"
}
}
}Follow the existing nesting structure. Keys use snake_case.
2. Use the key in your Liquid template
Reference the key using the | t filter. The key path uses dot notation matching the JSON nesting:
<label>{{ 'products.product.your_new_key' | t }}</label>For strings containing HTML, use the _html suffix convention:
{{ 'general.password_page.admin_link_html' | t }}For strings with variables, pass them as filter parameters:
{{ 'general.pagination.page' | t: number: current_page }}This renders the English source "Page {{ number }}" with the variable substituted.
3. Run sync to translate across all languages
set -a && source .env && set +a
python3 scripts/translate-locales.py syncThe sync command detects the new key (its hash is absent from the cache) and translates it across all 30 languages. Existing translations for unchanged keys are preserved.
4. Verify the result
python3 scripts/translate-locales.py checkThis confirms all locale files contain the new key and no translations are stale.
Adding a New Schema String
Schema strings are merchant-facing text visible in the Shopify theme editor -- section names, setting labels, dropdown options, and help text.
1. Add the key to the English schema file
Edit theme/locales/en.default.schema.json:
{
"sections": {
"featured_collection": {
"settings": {
"show_vendor": {
"label": "Show product vendor"
}
}
}
}
}2. Reference the key in your schema block
Inside a {% schema %} block, use the t: prefix followed by the dot-notation key path:
{
"type": "checkbox",
"id": "show_vendor",
"label": "t:sections.featured_collection.settings.show_vendor.label",
"default": false
}The t: prefix works for name, label, info, and option label fields within schema JSON.
3. Run sync and verify
set -a && source .env && set +a
python3 scripts/translate-locales.py sync
python3 scripts/translate-locales.py checkTranslation Key Naming Conventions
Keys follow a consistent hierarchy:
Storefront keys (en.default.json)
{category}.{subcategory}.{key_name}| Top-level category | Examples |
|---|---|
general | general.search.submit, general.pagination.next |
accessibility | accessibility.close, accessibility.skip_to_text |
products | products.product.add_to_cart, products.product.price |
sections | sections.header.cart_count |
newsletter | newsletter.label, newsletter.success |
templates | templates.cart.title |
Schema keys (en.default.schema.json)
sections.{section_name}.{context}.{key_name}Where {context} is one of:
name-- The section's display namesettings.{setting_id}.label-- A setting's labelsettings.{setting_id}.info-- A setting's help textsettings.{setting_id}.options.{value}-- A select option labelpresets.name-- The section preset's display namepresets.blocks.{block_type}.{field}-- Default text for block presets
Naming rules
- Use
snake_casefor all key segments. - Suffix keys containing HTML with
_html(e.g.,admin_link_html). - Use
_labelfor form labels,_placeholderfor placeholder text,_errorfor validation messages. - Keep keys descriptive but concise:
add_to_cartnotbutton_text_for_adding_item_to_cart.
Using Translations in Liquid
Basic string
<h2>{{ 'templates.cart.title' | t }}</h2>String with variables
Define placeholders in the English source with double braces:
{
"general": {
"search": {
"no_results_html": "Your search for \"{{ terms }}\" did not return results."
}
}
}Pass the variable when rendering:
{{ 'general.search.no_results_html' | t: terms: search.terms }}Plural strings
Shopify handles pluralization automatically. Define plural forms in the English source:
{
"sections": {
"header": {
"cart_count": {
"one": "{{ count }} item",
"other": "{{ count }} items"
}
}
}
}Shopify selects the correct form based on the count variable:
{{ 'sections.header.cart_count' | t: count: cart.item_count }}The translation pipeline automatically generates the correct plural forms for each target language per its CLDR rules. See Translation Pipeline for details on how plural categories vary by language.
Auditing for Hardcoded Text
If you have Liquid templates with English text that is not wrapped in translation filters, the audit mode will find them.
Running an audit
Use the /shopify-liquid-translator Claude Code skill:
/shopify-liquid-translator auditThis scans all .liquid files and reports hardcoded English strings with their locations. After you confirm the findings, it:
- Adds the new keys to
en.default.jsonoren.default.schema.jsonas appropriate. - Replaces hardcoded text in the templates with
| tfilter calls ort:prefix references.
After the audit
Run sync to translate the newly extracted strings:
set -a && source .env && set +a
python3 scripts/translate-locales.py syncSkipping files
To exclude a file from the audit, add this comment at the top:
{%- comment -%}theme-translator:skip{%- endcomment -%}Regenerating a Corrupted Locale File
If a locale file has invalid JSON or incorrect translations, delete it and regenerate:
# Delete the corrupted files
rm theme/locales/fr.json theme/locales/fr.schema.json
# Regenerate (sync detects missing files and translates everything)
set -a && source .env && set +a
python3 scripts/translate-locales.py fr
# Verify
python3 scripts/translate-locales.py check frTo force a complete retranslation even if the files exist:
set -a && source .env && set +a
python3 scripts/translate-locales.py full frCI Validation
Every pull request targeting main runs the i18n-check.yml workflow, which executes python3 scripts/translate-locales.py check. This catches:
- New English strings that have not been translated yet.
- Keys removed from English but still present in locale files.
- English values that changed without a corresponding translation update.
If the check fails, run sync locally to update translations before pushing:
set -a && source .env && set +a
python3 scripts/translate-locales.py syncThen commit the updated locale files and push. For full details on the CI pipeline, see CI/CD.