#!/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 collections import defaultdict from pathlib import Path 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("\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("\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()