diff --git a/app/templates/storefront/base.html b/app/templates/storefront/base.html index b6508a59..2e5f38f9 100644 --- a/app/templates/storefront/base.html +++ b/app/templates/storefront/base.html @@ -377,7 +377,7 @@ // Wrapped in DOMContentLoaded so deferred i18n.js has loaded document.addEventListener('DOMContentLoaded', async function() { const modules = {% block i18n_modules %}[]{% endblock %}; - await I18n.init('{{ storefront_language | default("en") }}', modules); + await I18n.init('{{ current_language | default("en") }}', modules); }); diff --git a/static/shared/js/i18n.js b/static/shared/js/i18n.js index 51c0ba02..ff8ae793 100644 --- a/static/shared/js/i18n.js +++ b/static/shared/js/i18n.js @@ -11,9 +11,13 @@ * // Or load modules later * await I18n.loadModule('inventory'); * - * // Translate + * // Translate (in JS) * const message = I18n.t('catalog.messages.product_created'); * const withVars = I18n.t('common.welcome', { name: 'John' }); + * + * // Translate (in Alpine templates — reactive, updates when translations load) + * // + * // */ // Create logger for i18n module (with silent fallback if LogConfig not yet loaded) @@ -43,6 +47,7 @@ const I18n = { if (modules && modules.length > 0) { await Promise.all(modules.map(m => this.loadModule(m))); } + this._notifyReady(); }, /** @@ -159,12 +164,44 @@ const I18n = { await this.loadModule(module); } } + this._notifyReady(); } catch (e) { i18nLog.error('Failed to change language:', e); } + }, + + /** + * Notify Alpine (and other listeners) that translations are ready. + * Bumps the Alpine store version so reactive $t() bindings re-evaluate. + */ + _notifyReady() { + this._ready = true; + // Bump Alpine store version if available (triggers $t() re-evaluation) + if (typeof Alpine !== 'undefined' && Alpine.store) { + const store = Alpine.store('i18n'); + if (store) store._v++; + } + document.dispatchEvent(new CustomEvent('i18n:ready')); } }; +// Register Alpine magic $t() — reactive wrapper around I18n.t() +// Works across all frontends (merchant, admin, store, storefront) +document.addEventListener('alpine:init', () => { + // Store with a reactive version counter + Alpine.store('i18n', { _v: 0 }); + + // $t('key', {vars}) — use in x-text, x-html, or any Alpine expression + // Before translations load, returns the key (which matches fallback text); + // after _notifyReady() bumps _v, Alpine re-evaluates with loaded translations. + Alpine.magic('t', (el) => { + return (key, vars) => { + void Alpine.store('i18n')._v; // reactive dependency + return I18n.t(key, vars); + }; + }); +}); + // Export for module usage if (typeof window !== 'undefined') { window.I18n = I18n;