Skip to content

Internationalization & RTL

TL;DR: What Shopify actually requires for language and region support, and what’s best practice beyond that.

Requirement✅ Do❌ Don’t
Locale filesStore every piece of theme text in locales/*.jsonHardcode English strings directly in Liquid or schema
The lang attributeDrive it dynamically from ShopifyHardcode lang="en" regardless of the storefront’s actual locale
Language selectorProvide one if selling in multiple languages, following Shopify’s UX guidelinesSilently support multiple languages with no way for a customer to switch
Country/currency selectorProvide one if selling in multiple regions/currenciesAssume every customer sees the same currency
Dynamic URLsUse the routes object everywhereHardcode paths like href="/" or href="/products/"
<!-- ❌ WRONG: hardcoded, breaks the moment a second language is added -->
<html lang="en">
<!-- ✅ RIGHT: driven by Shopify, correct for every locale automatically -->
<html lang="{{ request.locale.iso_code }}">
{% comment %} ❌ WRONG — breaks once a store adds a second language, since Shopify prefixes localized URLs {% endcomment %}
<a href="/">Home</a>
<a href="/collections/all">Shop all</a>
{% comment %} ✅ RIGHT — always resolves to the correct localized path {% endcomment %}
<a href="{{ routes.root_url }}">Home</a>
<a href="{{ routes.all_products_collection_url }}">Shop all</a>
// locales/en.default.json (excerpt)
{
"products": {
"price": {
"sale": "Sale",
"regular_price": "Regular price",
"sold_out": "Sold out"
}
}
}
{% comment %} ❌ WRONG — hardcoded, can't be translated {% endcomment %}
<span class="badge">Sold out</span>
{% comment %} ✅ RIGHT — translatable {% endcomment %}
<span class="badge">{{ 'products.price.sold_out' | t }}</span>

RTL — our recommendation, not a documented requirement

Section titled “RTL — our recommendation, not a documented requirement”

RTL stands for “right-to-left” — Arabic, Hebrew, and other right-to-left languages. These languages are common among stores using Shopify Markets. Supporting them well means:

✅ Do❌ Avoid
Use CSS logical properties (margin-inline-start, padding-inline-end, inset-inline-start)Physical properties (margin-left, padding-right, left) that don’t flip with dir="rtl"
Use text-align: start / endtext-align: left / right hardcoded
Mirror directional icons (arrows, chevrons) in RTLAssume a “next” arrow always points visually right
Test with a genuinely long RTL string, not just a mirrored layout screenshotAssume RTL support is “done” once the layout visually flips
/* ❌ WRONG — breaks in RTL: this margin stays on the physical left
even when the page direction flips */
.card {
margin-left: 1rem;
text-align: left;
}
/* ✅ RIGHT — flips automatically with dir="rtl" */
.card {
margin-inline-start: 1rem;
text-align: start;
}
<!-- ❌ WRONG: an arrow icon that always points right, even in RTL where
"next" visually should point left -->
<button class="next-btn">
<svg><!-- right-pointing arrow --></svg>
</button>
<!-- ✅ RIGHT: mirror direction-dependent icons using a CSS rule keyed to [dir] -->
<button class="next-btn">
<svg class="icon-chevron"><!-- right-pointing arrow --></svg>
</button>
[dir="rtl"] .icon-chevron {
transform: scaleX(-1);
}
✅ Do❌ Don’t
Run every piece of text through t:/| t (Shopify’s translation filter) from the start of a section’s development. Don’t leave it for a cleanup pass, fixing dozens of hardcoded strings later is tedious and easy to get wrong.Hardcoding lang="en" early “to get something working,” then forgetting to make it dynamic before shipping.
Use CSS logical properties by default in new code, even before RTL is a concrete requirement. They cost nothing in left-to-right layouts and make RTL support almost free later.Using physical CSS properties (margin-left) everywhere, then discovering you need a whole separate RTL stylesheet instead of one that just works automatically.
Test at least one real RTL locale, not just a mirrored screenshot, before you consider internationalization “done” for a section.Forgetting to mirror directional icons. A “next” chevron pointing the wrong way in RTL is a subtle but very visible bug.
Hardcoding internal links with / prefixes. These quietly break the moment a store adds a second language, because Shopify starts prefixing localized URLs.
  • Required: locale files, dynamic lang attribute, language/country selectors (if multi-language/currency), routes object for all links.
  • Not a documented hard requirement, but our recommendation: RTL support via CSS logical properties (margin-inline-start, text-align: start) and mirrored directional icons.