Files
orion/scripts/migrate_js_i18n.py
Samir Boulahtit d7a0ff8818 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>
2026-02-01 21:02:56 +01:00

197 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""
Migrate hardcoded toast messages in JS files to use I18n.t().
Extracts messages, creates translation keys, and updates both JS and locale files.
"""
import json
import re
from pathlib import Path
from collections import defaultdict
PROJECT_ROOT = Path(__file__).parent.parent
MODULES_DIR = PROJECT_ROOT / "app" / "modules"
# Languages to update
LANGUAGES = ["en", "fr", "de", "lb"]
# Pattern to match Utils.showToast('message', 'type') or Utils.showToast("message", "type")
TOAST_PATTERN = re.compile(r"Utils\.showToast\(['\"]([^'\"]+)['\"],\s*['\"](\w+)['\"]\)")
# Map module names to their message namespace
MODULE_MESSAGE_NS = {
"catalog": "catalog",
"orders": "orders",
"customers": "customers",
"inventory": "inventory",
"marketplace": "marketplace",
"tenancy": "tenancy",
"core": "core",
"messaging": "messaging",
"billing": "billing",
"cms": "cms",
"checkout": "checkout",
"cart": "cart",
"dev_tools": "dev_tools",
"monitoring": "monitoring",
"analytics": "analytics",
}
def message_to_key(message: str) -> str:
"""Convert a message string to a translation key."""
# Remove special characters and convert to snake_case
key = message.lower()
key = re.sub(r'[^\w\s]', '', key)
key = re.sub(r'\s+', '_', key)
# Truncate if too long
if len(key) > 40:
key = key[:40].rstrip('_')
return key
def find_js_files_with_toasts() -> dict[str, list[Path]]:
"""Find all JS files with toast messages, grouped by module."""
files_by_module = defaultdict(list)
for module_dir in sorted(MODULES_DIR.iterdir()):
if not module_dir.is_dir():
continue
module_name = module_dir.name
# Find all JS files in this module
for js_file in module_dir.rglob("*.js"):
content = js_file.read_text(encoding="utf-8")
if "Utils.showToast(" in content:
files_by_module[module_name].append(js_file)
return dict(files_by_module)
def extract_messages_from_file(js_file: Path) -> list[tuple[str, str]]:
"""Extract all toast messages from a JS file."""
content = js_file.read_text(encoding="utf-8")
return TOAST_PATTERN.findall(content)
def load_locale_file(module_path: Path, lang: str) -> dict:
"""Load a module's locale file."""
locale_file = module_path / "locales" / f"{lang}.json"
if locale_file.exists():
with open(locale_file, encoding="utf-8") as f:
return json.load(f)
return {}
def save_locale_file(module_path: Path, lang: str, data: dict):
"""Save a module's locale file."""
locale_file = module_path / "locales" / f"{lang}.json"
locale_file.parent.mkdir(parents=True, exist_ok=True)
with open(locale_file, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
f.write("\n")
def update_js_file(js_file: Path, module_name: str, message_keys: dict[str, str]):
"""Update a JS file to use I18n.t() for toast messages."""
content = js_file.read_text(encoding="utf-8")
original = content
for message, key in message_keys.items():
# Replace both single and double quoted versions
full_key = f"{module_name}.messages.{key}"
# Pattern to match the exact message in Utils.showToast
for quote in ["'", '"']:
old = f"Utils.showToast({quote}{message}{quote},"
new = f"Utils.showToast(I18n.t('{full_key}'),"
content = content.replace(old, new)
if content != original:
js_file.write_text(content, encoding="utf-8")
return True
return False
def process_module(module_name: str, js_files: list[Path]) -> dict[str, str]:
"""Process all JS files for a module and return message->key mapping."""
all_messages = {}
# Extract all unique messages
for js_file in js_files:
messages = extract_messages_from_file(js_file)
for message, msg_type in messages:
if message not in all_messages:
all_messages[message] = message_to_key(message)
return all_messages
def main():
print("=" * 70)
print("JS i18n Migration Script")
print("=" * 70)
# Find all JS files with toasts
files_by_module = find_js_files_with_toasts()
total_files = sum(len(files) for files in files_by_module.values())
print(f"Found {total_files} JS files with toast messages across {len(files_by_module)} modules")
print()
for module_name, js_files in sorted(files_by_module.items()):
print(f"\n{'='*70}")
print(f"Module: {module_name} ({len(js_files)} files)")
print("=" * 70)
module_path = MODULES_DIR / module_name
# Process all JS files and get message mappings
message_keys = process_module(module_name, js_files)
if not message_keys:
print(" No messages found")
continue
print(f" Found {len(message_keys)} unique messages:")
for msg, key in sorted(message_keys.items(), key=lambda x: x[1]):
print(f" {key}: {msg[:50]}{'...' if len(msg) > 50 else ''}")
# Update locale files for all languages
print(f"\n Updating locale files...")
for lang in LANGUAGES:
locale_data = load_locale_file(module_path, lang)
# Add messages section if not exists
if "messages" not in locale_data:
locale_data["messages"] = {}
# Add each message (only add if not already present)
for message, key in message_keys.items():
if key not in locale_data["messages"]:
# For English, use the original message
# For other languages, we'll use the English as placeholder
locale_data["messages"][key] = message
save_locale_file(module_path, lang, locale_data)
print(f" Updated: {lang}.json")
# Update JS files
print(f"\n Updating JS files...")
for js_file in js_files:
if update_js_file(js_file, module_name, message_keys):
rel_path = js_file.relative_to(PROJECT_ROOT)
print(f" Updated: {rel_path}")
print("\n" + "=" * 70)
print("Migration complete!")
print("\nNext steps:")
print("1. Review the generated message keys in locale files")
print("2. Translate non-English messages (currently using English as placeholder)")
print("3. Test the application to verify toast messages work")
print("=" * 70)
if __name__ == "__main__":
main()