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;