Shopify i18n Basics
Locale File Structure
Shopify themes use JSON files in the locales/ directory:
locales/ ├── en.default.json # English (default language) ├── en.default.schema.json # English schema translations ├── fr.json # French translations ├── fr.schema.json # French schema translations ├── de.json # German translations └── de.schema.json # German schema translations
File Types
- Content Translations (en.default.json)
Used for theme content displayed to customers:
{ "general": { "search": "Search", "cart": "Cart", "menu": "Menu", "close": "Close", "loading": "Loading...", "continue_shopping": "Continue Shopping" }, "product": { "add_to_cart": "Add to Cart", "sold_out": "Sold Out", "unavailable": "Unavailable", "price": "Price", "vendor": "Vendor" }, "cart": { "title": "Your Cart", "empty": "Your cart is empty", "subtotal": "Subtotal", "checkout": "Checkout" } }
- Schema Translations (en.default.schema.json)
Used for Theme Editor labels:
{ "sections": { "featured_products": { "name": "Featured Products", "settings": { "heading": { "label": "Heading", "info": "Section heading text" }, "collection": { "label": "Collection" } } } } }
Using Translations in Liquid
Basic Translation
{{ 'general.search' | t }} {{ 'product.add_to_cart' | t }} {{ 'cart.title' | t }}
Translation with Variables
{%- # In locale file -%} { "cart": { "item_count": "{{ count }} items" } }
{%- # In Liquid -%} {{ 'cart.item_count' | t: count: cart.item_count }}
Translation with HTML
{%- # In locale file -%} { "general": { "continue_html": "Continue <span>shopping</span>" } }
{%- # In Liquid -%} {{ 'general.continue_html' | t }}
Translation in Schema
{% schema %} { "name": "t:sections.featured_products.name", "settings": [ { "type": "text", "id": "heading", "label": "t:sections.featured_products.settings.heading.label" } ] } {% endschema %}
Common Translation Patterns
General UI
{ "general": { "search": "Search", "cart": "Cart", "menu": "Menu", "close": "Close", "loading": "Loading...", "buttons": { "learn_more": "Learn More", "shop_now": "Shop Now", "view_all": "View All" }, "forms": { "submit": "Submit", "email": "Email", "name": "Name", "message": "Message" } } }
Product-Specific
{ "product": { "add_to_cart": "Add to Cart", "sold_out": "Sold Out", "unavailable": "Unavailable", "in_stock": "In Stock", "out_of_stock": "Out of Stock", "price": { "from": "From {{ price }}", "regular_price": "Regular price", "sale_price": "Sale price" } } }
Cart & Checkout
{ "cart": { "title": "Cart", "empty": "Your cart is empty", "item_count": "{{ count }} items", "subtotal": "Subtotal", "shipping": "Shipping", "total": "Total", "checkout": "Checkout", "remove": "Remove" } }
Forms & Errors
{ "forms": { "contact": { "name": "Name", "email": "Email", "message": "Message", "send": "Send Message", "success": "Thank you for your message!", "error": "Please correct the errors below" } }, "errors": { "general": "An error occurred", "required_field": "This field is required", "invalid_email": "Please enter a valid email" } }
Adding New Languages
Step 1: Create Translation Files
locales/fr.json (French content):
{ "general": { "search": "Rechercher", "cart": "Panier", "menu": "Menu", "close": "Fermer" }, "product": { "add_to_cart": "Ajouter au panier", "sold_out": "Épuisé" }, "cart": { "title": "Votre panier", "empty": "Votre panier est vide", "checkout": "Commander" } }
locales/fr.schema.json (French schema):
{ "sections": { "featured_products": { "name": "Produits vedettes", "settings": { "heading": { "label": "Titre" }, "collection": { "label": "Collection" } } } } }
Step 2: Use in Liquid
The | t filter automatically uses the correct translation based on the store's language:
{%- # Automatically shows "Search" in English, "Rechercher" in French -%} <button>{{ 'general.search' | t }}</button>
Best Practices
- Use Clear, Descriptive Keys
{%- # Good -%} { "product": { "add_to_cart": "Add to Cart" } }
{%- # Bad -%} { "prod": { "btn1": "Add to Cart" } }
- Organize Logically
{ "general": { ... }, # General UI "navigation": { ... }, # Navigation items "product": { ... }, # Product-related "cart": { ... }, # Cart-related "forms": { ... } # Forms }
- Keep Keys Consistent Across Languages
en.default.json:
{ "product": { "add_to_cart": "Add to Cart" } }
fr.json (same structure):
{ "product": { "add_to_cart": "Ajouter au panier" } }
- Use Nested Objects for Clarity
{%- # Good -%} { "forms": { "contact": { "name": "Name", "email": "Email" } } }
{%- # Less clear -%} { "forms": { "contact_name": "Name", "contact_email": "Email" } }
- Handle Pluralization
{ "cart": { "item_count_one": "{{ count }} item", "item_count_other": "{{ count }} items" } }
{% if cart.item_count == 1 %} {{ 'cart.item_count_one' | t: count: cart.item_count }} {% else %} {{ 'cart.item_count_other' | t: count: cart.item_count }} {% endif %}
- Document Variables
{ "_comment": "{{ price }} will be replaced with the actual price", "product": { "from_price": "From {{ price }}" } }
- Avoid Hardcoding Text
{%- # Bad -%} <h2>Featured Products</h2>
{%- # Good -%} <h2>{{ 'sections.featured_products.heading' | t }}</h2>
Common Translation Keys
Navigation
{ "navigation": { "home": "Home", "shop": "Shop", "collections": "Collections", "about": "About", "contact": "Contact" } }
Accessibility
{ "accessibility": { "skip_to_content": "Skip to content", "close_menu": "Close menu", "open_menu": "Open menu", "next_slide": "Next slide", "previous_slide": "Previous slide" } }
Date & Time
{ "date": { "months": { "january": "January", "february": "February" }, "days": { "monday": "Monday", "tuesday": "Tuesday" } } }
Complete Example
locales/en.default.json:
{ "general": { "search": "Search", "cart": "Cart", "menu": "Menu" }, "product": { "add_to_cart": "Add to Cart", "from_price": "From {{ price }}" }, "cart": { "title": "Your Cart", "empty": "Your cart is empty" } }
sections/featured-products.liquid:
<section> <h2>{{ 'sections.featured_products.heading' | t }}</h2>
{%- for product in collection.products -%} <div> <h3>{{ product.title }}</h3> <p>{{ 'product.from_price' | t: price: product.price | money }}</p> <button>{{ 'product.add_to_cart' | t }}</button> </div> {%- endfor -%} </section>
Use translation keys consistently to create themes that work seamlessly in multiple languages.