refactor: complete module-driven architecture migration
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>
This commit is contained in:
127
scripts/add_i18n_module_loading.py
Normal file
127
scripts/add_i18n_module_loading.py
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user