shopify-i18n-basics
Internationalization fundamentals for multi-language Shopify themes
When & Why to Use This Skill
This Claude skill provides a comprehensive technical framework for implementing internationalization (i18n) in Shopify themes. It covers the essential structure of locale files, the use of Liquid translation filters, and best practices for localizing both customer-facing content and Theme Editor schemas to build scalable, multi-language e-commerce stores.
Use Cases
- Scenario 1: Setting up a multi-language Shopify store by creating and organizing JSON locale files in the locales/ directory.
- Scenario 2: Replacing hardcoded text in Liquid templates with dynamic translation keys using the '| t' filter to support automatic language switching.
- Scenario 3: Localizing the Shopify Theme Editor by implementing schema translations, allowing merchants to manage their store in their native language.
- Scenario 4: Handling complex UI strings that require pluralization, variable interpolation (e.g., item counts), or HTML formatting within translation files.
| name | Shopify i18n Basics |
|---|---|
| description | Internationalization fundamentals for multi-language Shopify themes |
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
1. 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"
}
}
2. 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
1. Use Clear, Descriptive Keys
{%- # Good -%}
{
"product": {
"add_to_cart": "Add to Cart"
}
}
{%- # Bad -%}
{
"prod": {
"btn1": "Add to Cart"
}
}
2. Organize Logically
{
"general": { ... }, # General UI
"navigation": { ... }, # Navigation items
"product": { ... }, # Product-related
"cart": { ... }, # Cart-related
"forms": { ... } # Forms
}
3. 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"
}
}
4. Use Nested Objects for Clarity
{%- # Good -%}
{
"forms": {
"contact": {
"name": "Name",
"email": "Email"
}
}
}
{%- # Less clear -%}
{
"forms": {
"contact_name": "Name",
"contact_email": "Email"
}
}
5. 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 %}
6. Document Variables
{
"_comment": "{{ price }} will be replaced with the actual price",
"product": {
"from_price": "From {{ price }}"
}
}
7. 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.