Compare commits
2 Commits
c2c0e3c740
...
169a774b9c
| Author | SHA1 | Date | |
|---|---|---|---|
| 169a774b9c | |||
| ebbe6d62b8 |
@@ -127,7 +127,7 @@ def trace_platform_resolution(
|
|||||||
# ── Step 4: StoreContextMiddleware detection ──
|
# ── Step 4: StoreContextMiddleware detection ──
|
||||||
# Simulate what StoreContextManager.detect_store_context() would see
|
# Simulate what StoreContextManager.detect_store_context() would see
|
||||||
# It reads request.state.platform_clean_path which is set to clean_path
|
# It reads request.state.platform_clean_path which is set to clean_path
|
||||||
store_context = _simulate_store_detection(clean_path, host)
|
store_context = StoreContextManager._detect_store_from_host_and_path(host, clean_path, platform)
|
||||||
if store_context and platform:
|
if store_context and platform:
|
||||||
store_context["_platform"] = platform
|
store_context["_platform"] = platform
|
||||||
|
|
||||||
@@ -274,68 +274,6 @@ def trace_platform_resolution(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _simulate_store_detection(clean_path: str, host: str) -> dict | None:
|
|
||||||
"""
|
|
||||||
Simulate StoreContextManager.detect_store_context() for a given path/host.
|
|
||||||
|
|
||||||
Reproduces the same logic without needing a real Request object.
|
|
||||||
"""
|
|
||||||
from app.core.config import settings
|
|
||||||
from app.modules.tenancy.models import StoreDomain
|
|
||||||
|
|
||||||
host_without_port = host.split(":")[0] if ":" in host else host
|
|
||||||
|
|
||||||
# Method 1: Custom domain
|
|
||||||
platform_domain = getattr(settings, "platform_domain", "platform.com")
|
|
||||||
is_custom_domain = (
|
|
||||||
host_without_port
|
|
||||||
and not host_without_port.endswith(f".{platform_domain}")
|
|
||||||
and host_without_port != platform_domain
|
|
||||||
and host_without_port not in ["localhost", "127.0.0.1", "admin.localhost", "admin.127.0.0.1"]
|
|
||||||
and not host_without_port.startswith("admin.")
|
|
||||||
)
|
|
||||||
if is_custom_domain:
|
|
||||||
normalized_domain = StoreDomain.normalize_domain(host_without_port)
|
|
||||||
return {
|
|
||||||
"domain": normalized_domain,
|
|
||||||
"detection_method": "custom_domain",
|
|
||||||
"host": host_without_port,
|
|
||||||
"original_host": host,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Method 2: Subdomain
|
|
||||||
if "." in host_without_port:
|
|
||||||
parts = host_without_port.split(".")
|
|
||||||
if len(parts) >= 2 and parts[0] not in ["www", "admin", "api"]:
|
|
||||||
subdomain = parts[0]
|
|
||||||
return {
|
|
||||||
"subdomain": subdomain,
|
|
||||||
"detection_method": "subdomain",
|
|
||||||
"host": host_without_port,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Method 3: Path-based
|
|
||||||
if clean_path.startswith(("/store/", "/stores/", "/storefront/")):
|
|
||||||
if clean_path.startswith("/storefront/"):
|
|
||||||
prefix_len = len("/storefront/")
|
|
||||||
elif clean_path.startswith("/stores/"):
|
|
||||||
prefix_len = len("/stores/")
|
|
||||||
else:
|
|
||||||
prefix_len = len("/store/")
|
|
||||||
|
|
||||||
path_parts = clean_path[prefix_len:].split("/")
|
|
||||||
if len(path_parts) >= 1 and path_parts[0]:
|
|
||||||
store_code = path_parts[0]
|
|
||||||
return {
|
|
||||||
"subdomain": store_code,
|
|
||||||
"detection_method": "path",
|
|
||||||
"path_prefix": clean_path[:prefix_len + len(store_code)],
|
|
||||||
"full_prefix": clean_path[:prefix_len],
|
|
||||||
"host": host_without_port,
|
|
||||||
}
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _sanitize(d: dict | None) -> dict | None:
|
def _sanitize(d: dict | None) -> dict | None:
|
||||||
"""Remove non-serializable objects from dict."""
|
"""Remove non-serializable objects from dict."""
|
||||||
|
|||||||
@@ -377,7 +377,7 @@
|
|||||||
// Wrapped in DOMContentLoaded so deferred i18n.js has loaded
|
// Wrapped in DOMContentLoaded so deferred i18n.js has loaded
|
||||||
document.addEventListener('DOMContentLoaded', async function() {
|
document.addEventListener('DOMContentLoaded', async function() {
|
||||||
const modules = {% block i18n_modules %}[]{% endblock %};
|
const modules = {% block i18n_modules %}[]{% endblock %};
|
||||||
await I18n.init('{{ storefront_language | default("en") }}', modules);
|
await I18n.init('{{ current_language | default("en") }}', modules);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,13 @@
|
|||||||
* // Or load modules later
|
* // Or load modules later
|
||||||
* await I18n.loadModule('inventory');
|
* await I18n.loadModule('inventory');
|
||||||
*
|
*
|
||||||
* // Translate
|
* // Translate (in JS)
|
||||||
* const message = I18n.t('catalog.messages.product_created');
|
* const message = I18n.t('catalog.messages.product_created');
|
||||||
* const withVars = I18n.t('common.welcome', { name: 'John' });
|
* const withVars = I18n.t('common.welcome', { name: 'John' });
|
||||||
|
*
|
||||||
|
* // Translate (in Alpine templates — reactive, updates when translations load)
|
||||||
|
* // <span x-text="$t('catalog.messages.product_created')"></span>
|
||||||
|
* // <span x-text="$t('common.welcome', {name: user})"></span>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Create logger for i18n module (with silent fallback if LogConfig not yet loaded)
|
// Create logger for i18n module (with silent fallback if LogConfig not yet loaded)
|
||||||
@@ -43,6 +47,7 @@ const I18n = {
|
|||||||
if (modules && modules.length > 0) {
|
if (modules && modules.length > 0) {
|
||||||
await Promise.all(modules.map(m => this.loadModule(m)));
|
await Promise.all(modules.map(m => this.loadModule(m)));
|
||||||
}
|
}
|
||||||
|
this._notifyReady();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,12 +164,44 @@ const I18n = {
|
|||||||
await this.loadModule(module);
|
await this.loadModule(module);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this._notifyReady();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
i18nLog.error('Failed to change language:', 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
|
// Export for module usage
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.I18n = I18n;
|
window.I18n = I18n;
|
||||||
|
|||||||
Reference in New Issue
Block a user