Shopify theme translation keys are dot-notation identifiers that connect a JSON locale file entry to a Liquid output via the t filter. When a buyer switches language, Shopify resolves the key against the active locale file and renders the correct string automatically. That one mechanism covers every UI label, cart message, section setting, and custom snippet you add to a theme.
Key takeaways
- Every translatable string lives in
locales/*.json; the Liquidtfilter resolves it at runtime. - Two file types exist:
.jsonfor storefront visitors,.schema.jsonfor the theme editor. - Schema labels use the
t:prefix, not thetfilter. - Hard limits: 3,400 keys per locale file, 1,000 characters per value.
- Hardcoded text in
.liquidfiles is invisible to every translation app, including Translate & Adapt.
How the file system is laid out
Every Shopify theme has a /locales directory containing JSON files that power the storefront UI. The source-of-truth file is en.default.json; sibling files like ja.json, fr.json, and de.json carry the per-language translations.
Two distinct file types live in that directory: .json files drive what visitors see, while .schema.json files only concern the theme editor.
Naming follows the IETF BCP 47 convention:
en.default.json- source language, always requiredja.json- Japanese (all regions)en-GB.json- English (UK regional variant)fr-CA.json- French (Canadian regional variant)
The default marker tells Shopify which file is authoritative. Fallback works top-down: if a key is missing from ja.json, Shopify silently renders the value from en.default.json rather than throwing an error, which is useful during development but a silent UX failure in production.
Storefront keys and the t filter
Rather than hard-coded text strings, theme layouts, templates, snippets, and Liquid assets reference translations with the Liquid t filter, which returns the appropriate translated string from the locale file for the active language.
Key anatomy follows a category.group.description pattern:
{{ 'products.product.add_to_cart' | t }}
{{ 'cart.general.title' | t }}
{{ 'general.404.title' | t }}
When referencing translation keys in Liquid, they must be wrapped in single quotes.
The corresponding JSON entry is straightforward:
// locales/en.default.json
{
"products": {
"product": {
"add_to_cart": "Add to cart"
}
}
}
For Japanese:
// locales/ja.json
{
"products": {
"product": {
"add_to_cart": "カートに追加"
}
}
}
Interpolation and pluralization
When using the t filter, you can interpolate and pluralize translations, as well as localize any dates and times.
Interpolation passes a named variable:
{{ 'cart.item_count' | t: count: cart.item_count }}
// en.default.json
{
"cart": {
"item_count": {
"one": "{{ count }} item",
"other": "{{ count }} items"
}
}
}
The following pluralization keys, defined by the Unicode Consortium's CLDR, are supported: few, many, one, other, two, and zero. Japanese does not grammatically pluralize, so a ja.json pluralization group only needs other. This is a real source of CI failures on multi-language stores: a group that is a proper pluralization object in the default locale must remain an object in every locale file, not collapse to a plain string.
Schema keys and the t: prefix
The schema namespace is separate. Inside a {% schema %} block, string values for label, info, name, and default use a t: prefix rather than the Liquid filter:
{% schema %}
{
"name": "t:sections.hero.name",
"settings": [
{
"type": "text",
"id": "heading",
"label": "t:sections.hero.settings.heading.label",
"default": "t:sections.hero.settings.heading.default"
}
]
}
{% endschema %}
Rather than hardcoding label and default text values in the schema JSON, you can reference translation keys using the t: prefix. This keeps your settings labels translatable without duplicating schema definitions for each language.
Retrofitting translation keys onto a theme with many hardcoded English labels across dozens of sections is an expensive afternoon. The payoff is that your theme ships to German, French, and Japanese merchants without any schema edits.
The matching entry lives in locales/en.default.schema.json, not in en.default.json. Mixing the two namespaces is a common mistake that produces visible t:sections.hero.name literals in the theme editor.
Common schema mistake
A frequent error is supplying per-language objects directly as label values, for example "label": {"en": "Example Setting", "fr": "Paramètre Exemple"}. Theme Check flags this because schema fields like label and name must be strings, not objects.
Hard limits you will hit on large themes
There is a limit on translation keys for locale files. The maximum is 3,400 translations in a single locale file, and each translation value cannot exceed 1,000 characters.
On a 10,000+ SKU catalog I maintain, we hit this ceiling inside en.default.schema.json when section count grew past roughly 60 complex sections. The fix was to audit for stale keys from removed sections, which brought the count back below 3,400 without losing anything active. A tool like shopify-theme-locale-validator can surface these unused keys automatically.
Shopify-managed keys you cannot own
Shopify provides its own translations for checkout, system messages, and customer accounts. These translated strings are stored outside your locale files, under the reserved shopify and customer_accounts top-level keys.
To override one of these translations, add its key to your storefront locale file. For example, to override the French checkout payment button label, add the key under shopify.checkout.general.pay_now_button_label in locales/fr.json.
This is how a Japanese store overrides the Shopify-default "Place order" string with brand-specific copy, without touching the checkout source.
The Translate & Adapt API layer
Theme locale files handle static UI strings. Dynamic content (product titles, descriptions, page content, metafield values) goes through a separate mechanism: the translationsRegister GraphQL mutation.
The mutation creates or updates translations for a resource's translatable content. Each translation requires a digest value from the resource's translatable content.
As of February 2026, using the translationsRegister GraphQL API, you can now register translations for resource handles in order to create localized online store URLs for merchants. That is a meaningful addition for Japanese stores where the URL slug matters for SEO.
The API also covers metafields: metafield translations are available via the GraphQL Admin API, and to register one you use the translationsRegister mutation with the metafield's resource ID and a content digest.
The two layers are deliberately separate:
| Layer | Mechanism | Managed by |
|---|---|---|
| Theme UI strings (buttons, labels, messages) | locales/*.json + t filter | Theme code + Language Editor |
| Theme editor settings (section names, labels) | locales/*.schema.json + t: prefix | Theme code only |
| Shopify system strings (checkout, accounts) | Reserved shopify.* keys in locale file | Shopify (override optional) |
| Dynamic content (products, pages, metafields) | translationsRegister GraphQL mutation | App or custom script |
| Localized URL handles (since Feb 2026) | translationsRegister on handle field | App or custom script |
The hardcoded-text problem
A string hardcoded in Liquid is not a translatable resource. The fix is locale files, keys and the t filter. This is the single most common reason a custom section or snippet is invisible in Translate & Adapt.
The fix is a one-time operation per string:
- Add the key and default value to
en.default.json. - Replace the hardcoded text in the
.liquidfile with{{ 'your.key' | t }}. - Add the translated value under the same key in every active locale file.
- Verify in the Translate & Adapt app that the key now appears as a translatable resource.
What breaks silently
These are the failure modes that pass visual QA but break on live multilingual stores:
- Missing pluralization
otherform. A pluralization group missing its mandatoryotherform, a translated string that silently dropped a{{ count }}or{{ product_title }}placeholder, a copy-pasted JSON key that quietly overwrote itself, and a| treference in Liquid that points at a key which was never defined. - Duplicate JSON keys. JSON parsers discard earlier values silently, so
"placeholder"defined twice in the same object gives you unpredictable output depending on the runtime. - Stale keys from renamed sections. Keys present in
ja.jsonbut absent fromen.default.jsonproduce a Theme Check warning but no runtime error, leading to translation sprawl over time. - Using the
tfilter inside schema. Thetfilter is for Liquid files. Inside{% schema %}, use thet:prefix string. Using the filter form will silently render the filter output as a literal label string.
Verifying the result
Use this checklist before pushing a multilingual theme to production:
- Run
shopify theme checklocally; zero errors is a hard requirement, not a suggestion. - For each active locale, open the Language Editor (Online Store > Themes > Edit languages) and confirm no
t:sections.*literals appear as raw strings in any section tab. - In the Translate & Adapt app, search for a key you added; if it appears, the wire is complete.
- Toggle the storefront language from the preview bar and spot-check at least one interpolated string (a cart count) and one pluralized string.
- Audit key counts:
cat locales/en.default.schema.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(sum(1 for _ in str(d).split()))"is a rough proxy; a proper count uses the 3,400-key limit as a ceiling.
For automated key audits, see the glossary entry on Shopify locale files for a description of how the storefront and schema namespaces interact.
Summary
The translation key system in Shopify themes is two namespaces (storefront JSON, schema JSON), one Liquid filter (t), one schema prefix (t:), one GraphQL mutation for dynamic content (translationsRegister), and two hard limits (3,400 keys, 1,000 characters). Every multilingual issue on a theme traces back to one of those five components being misconfigured. Getting the wiring right once means the Language Editor, Translate & Adapt, and any third-party translation app all see the same source of truth without further code changes.
For stores that would rather hand this off, Lintel is one studio that offers Shopify theme development.
Frequently asked questions
What is a Shopify theme translation key?
A translation key is a dot-notation string like 'products.product.add_to_cart' that maps to a value in a locale JSON file inside the theme's locales directory. Passing it through the Liquid t filter outputs the correct translation for the active storefront language.
What is the difference between a .json and a .schema.json locale file in Shopify?
The .json file controls strings that storefront visitors see, such as button labels and cart messages, and is accessed via the Liquid t filter. The .schema.json file controls labels inside the theme editor and is referenced using the t: prefix inside a section's schema block, not in Liquid itself.
How many translation keys can a Shopify locale file hold?
A single locale file is limited to 3,400 translation keys, and each individual translation value cannot exceed 1,000 characters. Exceeding the key limit causes the file upload to fail with a 'Too many translation keys' error.