This commit completes the migration to a fully module-driven architecture: ## Models Migration - Moved all domain models from models/database/ to their respective modules: - tenancy: User, Admin, Vendor, Company, Platform, VendorDomain, etc. - cms: MediaFile, VendorTheme - messaging: Email, VendorEmailSettings, VendorEmailTemplate - core: AdminMenuConfig - models/database/ now only contains Base and TimestampMixin (infrastructure) ## Schemas Migration - Moved all domain schemas from models/schema/ to their respective modules: - tenancy: company, vendor, admin, team, vendor_domain - cms: media, image, vendor_theme - messaging: email - models/schema/ now only contains base.py and auth.py (infrastructure) ## Routes Migration - Moved admin routes from app/api/v1/admin/ to modules: - menu_config.py -> core module - modules.py -> tenancy module - module_config.py -> tenancy module - app/api/v1/admin/ now only aggregates auto-discovered module routes ## Menu System - Implemented module-driven menu system with MenuDiscoveryService - Extended FrontendType enum: PLATFORM, ADMIN, VENDOR, STOREFRONT - Added MenuItemDefinition and MenuSectionDefinition dataclasses - Each module now defines its own menu items in definition.py - MenuService integrates with MenuDiscoveryService for template rendering ## Documentation - Updated docs/architecture/models-structure.md - Updated docs/architecture/menu-management.md - Updated architecture validation rules for new exceptions ## Architecture Validation - Updated MOD-019 rule to allow base.py in models/schema/ - Created core module exceptions.py and schemas/ directory - All validation errors resolved (only warnings remain) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
128 lines
4.1 KiB
Python
128 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Add I18n.loadModule() calls to JS files that use I18n.t().
|
|
This ensures module translations are loaded before use.
|
|
"""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
|
|
PROJECT_ROOT = Path(__file__).parent.parent
|
|
MODULES_DIR = PROJECT_ROOT / "app" / "modules"
|
|
|
|
# Pattern to find I18n.t('module.xxx') calls and extract module name
|
|
I18N_PATTERN = re.compile(r"I18n\.t\(['\"](\w+)\.")
|
|
|
|
|
|
def find_modules_used(content: str) -> set[str]:
|
|
"""Find all modules referenced in I18n.t() calls."""
|
|
return set(I18N_PATTERN.findall(content))
|
|
|
|
|
|
def add_module_loading(js_file: Path):
|
|
"""Add I18n.loadModule() calls to a JS file."""
|
|
content = js_file.read_text(encoding="utf-8")
|
|
|
|
# Find modules used in this file
|
|
modules = find_modules_used(content)
|
|
if not modules:
|
|
return False
|
|
|
|
# Check if already has module loading
|
|
if "I18n.loadModule(" in content:
|
|
return False
|
|
|
|
# Find the init() method and add loading there
|
|
# Look for common patterns:
|
|
# 1. init() { ... }
|
|
# 2. async init() { ... }
|
|
|
|
init_patterns = [
|
|
# Pattern for "init() {" or "async init() {"
|
|
(r"((?:async\s+)?init\s*\(\s*\)\s*\{)", "init"),
|
|
# Pattern for "mounted() {" (Vue style)
|
|
(r"(mounted\s*\(\s*\)\s*\{)", "mounted"),
|
|
]
|
|
|
|
for pattern, method_name in init_patterns:
|
|
match = re.search(pattern, content)
|
|
if match:
|
|
# Generate loading code
|
|
load_calls = "\n".join(f" await I18n.loadModule('{m}');" for m in sorted(modules))
|
|
|
|
# If init is not async, we need to make it async
|
|
full_match = match.group(1)
|
|
if "async" not in full_match:
|
|
# Make init async
|
|
new_init = full_match.replace(f"{method_name}()", f"async {method_name}()")
|
|
content = content.replace(full_match, new_init)
|
|
full_match = new_init
|
|
|
|
# Add loading after the opening brace
|
|
insert_code = f"\n // Load i18n translations\n{load_calls}\n"
|
|
content = content.replace(full_match, full_match + insert_code)
|
|
|
|
js_file.write_text(content, encoding="utf-8")
|
|
return True
|
|
|
|
# If no init found, add at file level (for simpler scripts)
|
|
# This handles files that don't use Alpine components
|
|
if "function " in content or "const " in content:
|
|
# Find first function definition
|
|
func_match = re.search(r"^(function\s+\w+\s*\([^)]*\)\s*\{)", content, re.MULTILINE)
|
|
if func_match:
|
|
load_calls = "\n".join(f" await I18n.loadModule('{m}');" for m in sorted(modules))
|
|
|
|
# Make function async if needed
|
|
full_match = func_match.group(1)
|
|
if "async" not in full_match:
|
|
new_func = full_match.replace("function ", "async function ")
|
|
content = content.replace(full_match, new_func)
|
|
full_match = new_func
|
|
|
|
insert_code = f"\n // Load i18n translations\n{load_calls}\n"
|
|
content = content.replace(full_match, full_match + insert_code)
|
|
|
|
js_file.write_text(content, encoding="utf-8")
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def main():
|
|
print("=" * 70)
|
|
print("Adding I18n.loadModule() to JS files")
|
|
print("=" * 70)
|
|
|
|
updated = 0
|
|
skipped = 0
|
|
|
|
# Find all JS files with I18n.t() calls
|
|
for js_file in MODULES_DIR.rglob("*.js"):
|
|
content = js_file.read_text(encoding="utf-8")
|
|
if "I18n.t(" not in content:
|
|
continue
|
|
|
|
modules = find_modules_used(content)
|
|
if not modules:
|
|
continue
|
|
|
|
rel_path = js_file.relative_to(PROJECT_ROOT)
|
|
|
|
if add_module_loading(js_file):
|
|
print(f" Updated: {rel_path} (modules: {', '.join(sorted(modules))})")
|
|
updated += 1
|
|
else:
|
|
print(f" Skipped: {rel_path} (already has loading or no init method)")
|
|
skipped += 1
|
|
|
|
print()
|
|
print(f"Updated: {updated} files")
|
|
print(f"Skipped: {skipped} files")
|
|
print("=" * 70)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|