Loading...
Loading...
Use when translating a Spree 6 store — adding a locale, translating product/category/collection content, fixing missing translations, building a multilingual storefront, syncing translations from a TMS via the Admin API, making a custom model translatable, working with Mobility, or wrangling `Spree.t` / dashboard i18next keys. Common phrasings include "add Spanish to Spree", "translate products", "Mobility", "translation tables", "RTL", "missing translation", "Spree.t", "fallback locale", "translated slugs", "translations API", "translate dashboard plugin", "X-Spree-Locale".
npx skill4agent add spree/agent-skills spree-i18n| What | Mechanism | Where it lives |
|---|---|---|
| Merchant content (product names, descriptions, slugs, category names…) | Mobility translation tables | |
| Backend strings (emails, validation errors, API error messages) | Rails I18n + | |
| Dashboard UI (the React admin) | i18next JSON bundles | |
default_localesupported_localesspree-data-modelSpree::Api::V3::LocaleAndCurrencyX-Spree-CountryX-Spree-Locale?locale=current_store.supported_locales_listSpree::Current.localeI18n.localeSpree::Current.localeSpree::Current.content_locale = current_store.default_localeSpree::Locales::SetFallbackLocaleForStoreconst client = createClient({ baseUrl, publishableKey, locale: 'fr' })
client.setLocale('de')
await client.products.get('sac-spree', {}, { locale: 'fr' }) // per-request overrideeses-ESGET /api/v3/store/localesVary: Accept, x-spree-currency, x-spree-locale, x-spree-channelprivate, no-storeVaryx-spree-countryfetchTRANSLATABLE_FIELDSSpree::TranslatableResource| Model | Fields |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
Spree.translatable_resourcesRICH_TEXT_TRANSLATABLE_FIELDSSpree::CustomFieldtranslates(*TRANSLATABLE_FIELDS, column_fallback: Spree.mobility_column_fallback)Spree::Current.content_localeSpree::Config[:always_use_translations] = trueSPREE_ALWAYS_USE_TRANSLATIONSfalseMobility.with_locale(:es) { product.name } # translated or fallback
product.name(locale: :es, fallback: false) # nil if no Spanish row — detect gapscontent_localeSpree::Current.store = store
Spree::Current.content_locale = store.default_locale
Spree::Locales::SetFallbackLocaleForStore.new.call(store: store)product.upsert_translations(
'es' => { 'name' => 'Camiseta', 'description' => '<p>Una camiseta</p>' },
'fr' => { 'name' => 'T-shirt' }
)upsert_translations""nilsupported_locales_listActiveRecord::RecordInvalidMobility.with_locale(:es) { product.update!(name: 'Camiseta') }slugpermalink/produits/sac-spree| Endpoint | SDK ( | Purpose |
|---|---|---|
| | Registry: |
| — | Store's supported locales ( |
| | Matrix: source values + per-locale translations (+ nested children, e.g. option values) |
| — | Translation matrix inline on the record |
| | The write surface — atomic across records/types |
| | Per-locale coverage grid |
await admin.translations.batch([
{ resource_type: 'product', resource_id: 'prod_86Rf07xd4z',
values: { fr: { name: 'Sac Spree' }, de: { name: 'Spree Tasche' } } },
{ resource_type: 'option_value', resource_id: 'optval_…', values: { de: { label: 'Klein' } } },
])resource_typeproductoption_typeoption_valuecategorycollectionproduct_typestorepolicysellerwrite_<resource>PATCH /products/:idSpree::Exports::ProductTranslationsSpree::Imports::ProductTranslationsspree-reportingmodule Spree
class Brand < Spree.base_class
include Spree::TranslatableResource
TRANSLATABLE_FIELDS = %i[name description].freeze
RICH_TEXT_TRANSLATABLE_FIELDS = %i[description].freeze
translates(*TRANSLATABLE_FIELDS, column_fallback: Spree.mobility_column_fallback)
end
endspree_brand_translationsspree_brand_idlocale[spree_brand_id, locale]Rails.application.config.after_initialize do
Spree.translatable_resources += [Spree::Brand]
endPOST /translations/batchresource_type: 'brand'?expand=translationsSpree::Api::V3::Admin::TranslatableGET …/:id/translations:translatabletranslatesTRANSLATABLE_FIELDS << :xFrozenErrormodule Spree::ProductDecorator
def self.prepended(base)
fields = base::TRANSLATABLE_FIELDS + [:subtitle]
base.send(:remove_const, :TRANSLATABLE_FIELDS)
base.const_set(:TRANSLATABLE_FIELDS, fields.freeze)
base.translates :subtitle, column_fallback: Spree.mobility_column_fallback
end
Spree::Product.prepend self
endSpree.tSpree.t(:save) # spree.save
Spree.t(:paid, scope: 'payment_states')
Spree.t('custom_feature.title', default: 'Rewards')spree bundle add spree_i18nconfig/locales/<locale>.yml<locale>.spree.*config.i18n.fallbacks = [:en]config/application.rbI18n.fallbacksconfig.i18n.fallbacks = trueSpree::BaseMaileri18n-tasksnormalizemissingunusedspree.permissions_catalog.resources.<name>translationadmin.import en from './locales/en.json'
import de from './locales/de.json'
export default defineDashboardPlugin({
locales: { en, de }, // registered before the rest of the config
nav: [{ key: 'brands', label: i18n.t('admin.brands.title'), path: '/brands' }],
})admin.fields.<resource>.<attribute>.labeladmin.types.<family>.<code>spree-dashboard-pluginsSpree::Locale::RTL_LANGUAGE_CODESar he fa ur yiSpree::Locale.new(code:).rtl?.directionGET /api/v3/admin/localesrtldir="rtl"config/locales/es.ymlspree_i18nconfig.i18n.fallbacksessupported_localesproduct.name(locale: :es, fallback: false)store.supported_locales_listmarket.update!(supported_locales: %w[en es fr])I18n.available_localesproduct.reloadnode_modules/@spree/docs/dist/developer/core-concepts/translations.mdnode_modules/@spree/docs/dist/developer/core-concepts/slugs.mdnode_modules/@spree/docs/dist/developer/core-concepts/markets.mdnode_modules/@spree/docs/dist/developer/dashboard/customization/translations.md