diff --git a/app/modules/catalog/locales/en.json b/app/modules/catalog/locales/en.json index d6654518..2738a620 100644 --- a/app/modules/catalog/locales/en.json +++ b/app/modules/catalog/locales/en.json @@ -1,49 +1,82 @@ { - "title": "Product Catalog", - "description": "Product catalog management for vendors", "products": { "title": "Products", - "subtitle": "Manage your product catalog", - "create": "Create Product", - "edit": "Edit Product", - "delete": "Delete Product", - "empty": "No products found", - "empty_search": "No products match your search" - }, - "product": { - "name": "Product Name", - "description": "Description", + "product": "Product", + "add_product": "Add Product", + "edit_product": "Edit Product", + "delete_product": "Delete Product", + "product_name": "Product Name", + "product_code": "Product Code", "sku": "SKU", "price": "Price", + "sale_price": "Sale Price", + "cost": "Cost", "stock": "Stock", - "status": "Status", - "active": "Active", - "inactive": "Inactive" - }, - "media": { - "title": "Product Media", - "upload": "Upload Image", - "delete": "Delete Image", - "primary": "Set as Primary", - "error": "Media upload failed" - }, - "validation": { - "name_required": "Product name is required", - "price_required": "Price is required", - "invalid_sku": "Invalid SKU format", - "duplicate_sku": "SKU already exists" + "in_stock": "In Stock", + "out_of_stock": "Out of Stock", + "low_stock": "Low Stock", + "availability": "Availability", + "available": "Available", + "unavailable": "Unavailable", + "brand": "Brand", + "category": "Category", + "categories": "Categories", + "image": "Image", + "images": "Images", + "main_image": "Main Image", + "gallery": "Gallery", + "weight": "Weight", + "dimensions": "Dimensions", + "color": "Color", + "size": "Size", + "material": "Material", + "condition": "Condition", + "new": "New", + "used": "Used", + "refurbished": "Refurbished", + "no_products": "No products found", + "search_products": "Search products...", + "filter_by_category": "Filter by category", + "filter_by_status": "Filter by status", + "sort_by": "Sort by", + "sort_newest": "Newest", + "sort_oldest": "Oldest", + "sort_price_low": "Price: Low to High", + "sort_price_high": "Price: High to Low", + "sort_name_az": "Name: A-Z", + "sort_name_za": "Name: Z-A" }, "messages": { - "created": "Product created successfully", - "updated": "Product updated successfully", - "deleted": "Product deleted successfully", - "not_found": "Product not found", - "cannot_delete": "Cannot delete product", - "error_loading": "Error loading products" - }, - "filters": { - "all_products": "All Products", - "active_only": "Active Only", - "search_placeholder": "Search products..." + "product_deleted_successfully": "Product deleted successfully", + "product_created_successfully": "Product created successfully", + "product_updated_successfully": "Product updated successfully", + "product_activated": "Product activated", + "product_deactivated": "Product deactivated", + "product_marked_as_featured": "Product marked as featured", + "product_unmarked_as_featured": "Product unmarked as featured", + "products_activated": "{count} product(s) activated", + "products_deactivated": "{count} product(s) deactivated", + "products_marked_as_featured": "{count} product(s) marked as featured", + "products_unmarked_as_featured": "{count} product(s) unmarked as featured", + "products_deleted": "{count} product(s) deleted", + "failed_to_save_product": "Failed to save product", + "failed_to_create_product": "Failed to create product", + "failed_to_update_product": "Failed to update product", + "failed_to_delete_product": "Failed to delete product", + "failed_to_load_products": "Failed to load products", + "failed_to_activate_products": "Failed to activate products", + "failed_to_deactivate_products": "Failed to deactivate products", + "failed_to_upload_image": "Failed to upload image", + "product_removed_from_vendor_catalog": "Product removed from vendor catalog.", + "please_fill_in_all_required_fields": "Please fill in all required fields", + "failed_to_load_media_library": "Failed to load media library", + "no_vendor_associated_with_this_product": "No vendor associated with this product", + "please_select_an_image_file": "Please select an image file", + "image_must_be_less_than_10mb": "Image must be less than 10MB", + "image_uploaded_successfully": "Image uploaded successfully", + "please_select_a_vendor": "Please select a vendor", + "please_enter_a_product_title_english": "Please enter a product title (English)", + "please_select_a_vendor_first": "Please select a vendor first", + "title_and_price_required": "Title and price are required" } } diff --git a/app/modules/catalog/static/vendor/js/product-create.js b/app/modules/catalog/static/vendor/js/product-create.js index 278c8b64..a2d35be4 100644 --- a/app/modules/catalog/static/vendor/js/product-create.js +++ b/app/modules/catalog/static/vendor/js/product-create.js @@ -44,6 +44,9 @@ function vendorProductCreate() { }, async init() { + // Load i18n translations + await I18n.loadModule('catalog'); + // Guard against duplicate initialization if (window._vendorProductCreateInitialized) return; window._vendorProductCreateInitialized = true; @@ -66,7 +69,7 @@ function vendorProductCreate() { async createProduct() { if (!this.form.title || !this.form.price) { - this.showToast('Title and price are required', 'error'); + Utils.showToast(I18n.t('catalog.messages.title_and_price_required'), 'error'); return; } @@ -93,7 +96,7 @@ function vendorProductCreate() { } vendorProductCreateLog.info('Product created:', response.data); - this.showToast('Product created successfully', 'success'); + Utils.showToast(I18n.t('catalog.messages.product_created_successfully'), 'success'); // Navigate back to products list setTimeout(() => { @@ -102,8 +105,8 @@ function vendorProductCreate() { } catch (err) { vendorProductCreateLog.error('Failed to create product:', err); - this.error = err.message || 'Failed to create product'; - this.showToast(this.error, 'error'); + this.error = err.message || I18n.t('catalog.messages.failed_to_create_product'); + Utils.showToast(this.error, 'error'); } finally { this.saving = false; } diff --git a/app/modules/cms/locales/en.json b/app/modules/cms/locales/en.json index fe0bc0d4..b9919d73 100644 --- a/app/modules/cms/locales/en.json +++ b/app/modules/cms/locales/en.json @@ -1,126 +1,213 @@ { - "title": "Content Management", - "description": "Manage content pages, media library, and vendor themes", - "pages": { - "title": "Content Pages", - "subtitle": "Manage platform and vendor content pages", - "create": "Create Page", - "edit": "Edit Page", - "delete": "Delete Page", - "list": "All Pages", - "empty": "No pages found", - "empty_search": "No pages match your search", - "create_first": "Create First Page" - }, - "page": { - "title": "Page Title", - "slug": "Slug", - "slug_help": "URL-safe identifier (lowercase, numbers, hyphens only)", - "content": "Content", - "content_format": "Content Format", - "format_html": "HTML", - "format_markdown": "Markdown", - "platform": "Platform", - "vendor_override": "Vendor Override", - "vendor_override_none": "None (Platform Default)", - "vendor_override_help_default": "This is a platform-wide default page", - "vendor_override_help_vendor": "This page overrides the default for selected vendor only" - }, - "tiers": { - "platform": "Platform Marketing", - "vendor_default": "Vendor Default", - "vendor_override": "Vendor Override" - }, - "seo": { - "title": "SEO & Metadata", - "meta_description": "Meta Description", - "meta_description_help": "characters (150-160 recommended)", - "meta_keywords": "Meta Keywords", - "meta_keywords_placeholder": "keyword1, keyword2, keyword3" - }, - "navigation": { - "title": "Navigation & Display", - "display_order": "Display Order", - "display_order_help": "Lower = first", - "show_in_header": "Show in Header", - "show_in_footer": "Show in Footer", - "show_in_legal": "Show in Legal", - "show_in_legal_help": "Bottom bar next to copyright" - }, - "publishing": { - "published": "Published", - "draft": "Draft", - "publish_help": "Make this page visible to the public" - }, - "homepage": { - "title": "Homepage Sections", - "subtitle": "Multi-language content", - "loading": "Loading sections...", - "hero": { - "title": "Hero Section", - "badge_text": "Badge Text", - "main_title": "Title", - "subtitle": "Subtitle", - "buttons": "Buttons", - "add_button": "Add Button" + "platform": { + "nav": { + "pricing": "Pricing", + "find_shop": "Find Your Shop", + "start_trial": "Start Free Trial", + "admin_login": "Admin Login", + "vendor_login": "Vendor Login", + "toggle_menu": "Toggle menu", + "toggle_dark_mode": "Toggle dark mode" }, - "features": { - "title": "Features Section", - "section_title": "Section Title", - "cards": "Feature Cards", - "add_card": "Add Feature Card", - "icon": "Icon name", - "feature_title": "Title", - "feature_description": "Description" + "hero": { + "badge": "{trial_days}-Day Free Trial - No Credit Card Required to Start", + "title": "Lightweight OMS for Letzshop Sellers", + "subtitle": "Order management, inventory, and invoicing built for Luxembourg e-commerce. Stop juggling spreadsheets. Start running your business.", + "cta_trial": "Start Free Trial", + "cta_find_shop": "Find Your Letzshop Shop" }, "pricing": { - "title": "Pricing Section", - "section_title": "Section Title", - "use_tiers": "Use subscription tiers from database", - "use_tiers_help": "When enabled, pricing cards are dynamically pulled from your subscription tier configuration." + "title": "Simple, Transparent Pricing", + "subtitle": "Choose the plan that fits your business. All plans include a {trial_days}-day free trial.", + "monthly": "Monthly", + "annual": "Annual", + "save_months": "Save 2 months!", + "most_popular": "MOST POPULAR", + "recommended": "RECOMMENDED", + "contact_sales": "Contact Sales", + "start_trial": "Start Free Trial", + "per_month": "/month", + "per_year": "/year", + "custom": "Custom", + "orders_per_month": "{count} orders/month", + "unlimited_orders": "Unlimited orders", + "products_limit": "{count} products", + "unlimited_products": "Unlimited products", + "team_members": "{count} team members", + "unlimited_team": "Unlimited team", + "letzshop_sync": "Letzshop order sync", + "eu_vat_invoicing": "EU VAT invoicing", + "analytics_dashboard": "Analytics dashboard", + "api_access": "API access", + "multi_channel": "Multi-channel integration", + "products": "products", + "team_member": "team member", + "unlimited": "Unlimited", + "order_history": "months order history", + "trial_note": "All plans include a {trial_days}-day free trial. No credit card required.", + "back_home": "Back to Home" + }, + "features": { + "letzshop_sync": "Letzshop order sync", + "inventory_basic": "Basic inventory management", + "inventory_locations": "Warehouse locations", + "inventory_purchase_orders": "Purchase orders", + "invoice_lu": "Luxembourg VAT invoicing", + "invoice_eu_vat": "EU VAT invoicing", + "invoice_bulk": "Bulk invoicing", + "customer_view": "Customer list", + "customer_export": "Customer export", + "analytics_dashboard": "Analytics dashboard", + "accounting_export": "Accounting export", + "api_access": "API access", + "automation_rules": "Automation rules", + "team_roles": "Team roles & permissions", + "white_label": "White-label option", + "multi_vendor": "Multi-vendor support", + "custom_integrations": "Custom integrations", + "sla_guarantee": "SLA guarantee", + "dedicated_support": "Dedicated account manager" + }, + "addons": { + "title": "Enhance Your Platform", + "subtitle": "Add custom branding, professional email, and enhanced security.", + "per_year": "/year", + "per_month": "/month", + "custom_domain": "Custom Domain", + "custom_domain_desc": "Use your own domain (mydomain.com)", + "premium_ssl": "Premium SSL", + "premium_ssl_desc": "EV certificate for trust badges", + "email_package": "Email Package", + "email_package_desc": "Professional email addresses" + }, + "find_shop": { + "title": "Find Your Letzshop Shop", + "subtitle": "Already selling on Letzshop? Enter your shop URL to get started.", + "placeholder": "Enter your Letzshop URL (e.g., letzshop.lu/vendors/my-shop)", + "button": "Find My Shop", + "claim_shop": "Claim This Shop", + "already_claimed": "Already Claimed", + "no_account": "Don't have a Letzshop account?", + "signup_letzshop": "Sign up with Letzshop first", + "then_connect": ", then come back to connect your shop.", + "search_placeholder": "Enter Letzshop URL or shop name...", + "search_button": "Search", + "examples": "Examples:", + "claim_button": "Claim This Shop & Start Free Trial", + "not_found": "We could not find a Letzshop shop with that URL. Please check and try again.", + "or_signup": "Or sign up without a Letzshop connection", + "need_help": "Need Help?", + "no_account_yet": "Don't have a Letzshop account yet? No problem!", + "create_letzshop": "Create a Letzshop Account", + "signup_without": "Sign Up Without Letzshop", + "looking_up": "Looking up your shop...", + "found": "Found:", + "claimed_badge": "Already Claimed" + }, + "signup": { + "step_plan": "Select Plan", + "step_shop": "Claim Shop", + "step_account": "Account", + "step_payment": "Payment", + "choose_plan": "Choose Your Plan", + "save_percent": "Save {percent}%", + "trial_info": "We'll collect your payment info, but you won't be charged until the trial ends.", + "connect_shop": "Connect Your Letzshop Shop", + "connect_optional": "Optional: Link your Letzshop account to sync orders automatically.", + "connect_continue": "Connect & Continue", + "skip_step": "Skip This Step", + "create_account": "Create Your Account", + "first_name": "First Name", + "last_name": "Last Name", + "company_name": "Company Name", + "email": "Email", + "password": "Password", + "password_hint": "Minimum 8 characters", + "continue": "Continue", + "continue_payment": "Continue to Payment", + "back": "Back", + "add_payment": "Add Payment Method", + "no_charge_note": "You won't be charged until your {trial_days}-day trial ends.", + "processing": "Processing...", + "start_trial": "Start Free Trial", + "creating_account": "Creating your account..." + }, + "success": { + "title": "Welcome to Wizamart!", + "subtitle": "Your account has been created and your {trial_days}-day free trial has started.", + "what_next": "What's Next?", + "step_connect": "Connect Letzshop:", + "step_connect_desc": "Add your API key to start syncing orders automatically.", + "step_invoicing": "Set Up Invoicing:", + "step_invoicing_desc": "Configure your invoice settings for Luxembourg compliance.", + "step_products": "Import Products:", + "step_products_desc": "Sync your product catalog from Letzshop.", + "go_to_dashboard": "Go to Dashboard", + "login_dashboard": "Login to Dashboard", + "need_help": "Need help getting started?", + "contact_support": "Contact our support team" }, "cta": { - "title": "Call to Action Section", - "main_title": "Title", - "subtitle": "Subtitle", - "buttons": "Buttons", - "add_button": "Add Button" + "title": "Ready to Streamline Your Orders?", + "subtitle": "Join Letzshop vendors who trust Wizamart for their order management. Start your {trial_days}-day free trial today.", + "button": "Start Free Trial" + }, + "footer": { + "tagline": "Lightweight OMS for Letzshop sellers. Manage orders, inventory, and invoicing.", + "quick_links": "Quick Links", + "platform": "Platform", + "contact": "Contact", + "copyright": "© {year} Wizamart. Built for Luxembourg e-commerce.", + "privacy": "Privacy Policy", + "terms": "Terms of Service", + "about": "About Us", + "faq": "FAQ", + "contact_us": "Contact Us" + }, + "modern": { + "badge_integration": "Official Integration", + "badge_connect": "Connect in 2 minutes", + "hero_title_1": "Built for Luxembourg E-Commerce", + "hero_title_2": "The Back-Office Letzshop Doesn't Give You", + "hero_subtitle": "Sync orders, manage inventory, generate invoices with correct VAT, and own your customer data. All in one place.", + "cta_trial": "Start {trial_days}-Day Free Trial", + "cta_how": "See How It Works", + "hero_note": "No credit card required. Setup in 5 minutes. Cancel anytime.", + "pain_title": "Sound Familiar?", + "pain_subtitle": "These are the daily frustrations of Letzshop sellers", + "pain_manual": "Manual Order Entry", + "pain_manual_desc": "Copy-pasting orders from Letzshop to spreadsheets. Every. Single. Day.", + "pain_inventory": "Inventory Chaos", + "pain_inventory_desc": "Stock in Letzshop doesn't match reality. Overselling happens.", + "pain_vat": "Wrong VAT Invoices", + "pain_vat_desc": "EU customers need correct VAT. Your accountant keeps complaining.", + "pain_customers": "Lost Customers", + "pain_customers_desc": "Letzshop owns your customer data. You can't retarget or build loyalty.", + "how_title": "How It Works", + "how_subtitle": "From Chaos to Control in 4 Steps", + "how_step1": "Connect Letzshop", + "how_step1_desc": "Enter your Letzshop API credentials. Done in 2 minutes, no technical skills needed.", + "how_step2": "Orders Flow In", + "how_step2_desc": "Orders sync automatically. Confirm and add tracking directly from Wizamart.", + "how_step3": "Generate Invoices", + "how_step3_desc": "One click to create compliant PDF invoices with correct VAT for any EU country.", + "how_step4": "Grow Your Business", + "how_step4_desc": "Export customers for marketing. Track inventory. Focus on selling, not spreadsheets.", + "features_title": "Everything a Letzshop Seller Needs", + "features_subtitle": "The operational tools Letzshop doesn't provide", + "cta_final_title": "Ready to Take Control of Your Letzshop Business?", + "cta_final_subtitle": "Join Luxembourg vendors who've stopped fighting spreadsheets and started growing their business.", + "cta_final_note": "No credit card required. Setup in 5 minutes. Full Professional features during trial." } }, - "media": { - "title": "Media Library", - "upload": "Upload", - "upload_file": "Upload File", - "delete": "Delete", - "empty": "No media files", - "upload_first": "Upload your first file" - }, - "themes": { - "title": "Vendor Themes", - "subtitle": "Manage vendor theme customizations" - }, - "actions": { - "save": "Save", - "saving": "Saving...", - "update": "Update Page", - "create": "Create Page", - "cancel": "Cancel", - "back_to_list": "Back to List", - "preview": "Preview", - "revert_to_default": "Revert to Default" - }, "messages": { - "created": "Page created successfully", - "updated": "Page updated successfully", - "deleted": "Page deleted successfully", - "reverted": "Reverted to default page", - "error_loading": "Error loading page", - "error_saving": "Error saving page", - "confirm_delete": "Are you sure you want to delete this page?" + "failed_to_delete_page": "Failed to delete page: {error}", + "media_updated_successfully": "Media updated successfully", + "media_deleted_successfully": "Media deleted successfully", + "url_copied_to_clipboard": "URL copied to clipboard", + "failed_to_copy_url": "Failed to copy URL" }, - "filters": { - "all_pages": "All Pages", - "all_platforms": "All Platforms", - "search_placeholder": "Search pages..." + "confirmations": { + "delete_file": "Are you sure you want to delete this file? This cannot be undone." } } diff --git a/app/modules/cms/static/vendor/js/media.js b/app/modules/cms/static/vendor/js/media.js index 764becc7..dc693779 100644 --- a/app/modules/cms/static/vendor/js/media.js +++ b/app/modules/cms/static/vendor/js/media.js @@ -119,6 +119,9 @@ function vendorMedia() { uploadingFiles: [], async init() { + // Load i18n translations + await I18n.loadModule('cms'); + // Guard against duplicate initialization if (window._vendorMediaInitialized) return; window._vendorMediaInitialized = true; @@ -233,7 +236,7 @@ function vendorMedia() { }); if (response.ok) { - this.showToast('Media updated successfully', 'success'); + Utils.showToast(I18n.t('cms.messages.media_updated_successfully'), 'success'); this.showDetailModal = false; await this.loadMedia(); } else { @@ -250,7 +253,7 @@ function vendorMedia() { async deleteMedia() { if (!this.selectedMedia) return; - if (!confirm('Are you sure you want to delete this file? This cannot be undone.')) { + if (!confirm(I18n.t('cms.confirmations.delete_file'))) { return; } @@ -260,7 +263,7 @@ function vendorMedia() { const response = await apiClient.delete(`/vendor/media/${this.selectedMedia.id}`); if (response.ok) { - this.showToast('Media deleted successfully', 'success'); + Utils.showToast(I18n.t('cms.messages.media_deleted_successfully'), 'success'); this.showDetailModal = false; this.selectedMedia = null; await this.loadMedia(); @@ -351,9 +354,9 @@ function vendorMedia() { copyToClipboard(text) { if (!text) return; navigator.clipboard.writeText(text).then(() => { - this.showToast('URL copied to clipboard', 'success'); + Utils.showToast(I18n.t('cms.messages.url_copied_to_clipboard'), 'success'); }).catch(() => { - this.showToast('Failed to copy URL', 'error'); + Utils.showToast(I18n.t('cms.messages.failed_to_copy_url'), 'error'); }); } }; diff --git a/app/modules/core/locales/en.json b/app/modules/core/locales/en.json new file mode 100644 index 00000000..3b07122b --- /dev/null +++ b/app/modules/core/locales/en.json @@ -0,0 +1,79 @@ +{ + "dashboard": { + "title": "Dashboard", + "welcome": "Welcome back", + "overview": "Overview", + "quick_stats": "Quick Stats", + "recent_activity": "Recent Activity", + "total_products": "Total Products", + "total_orders": "Total Orders", + "total_customers": "Total Customers", + "total_revenue": "Total Revenue", + "active_products": "Active Products", + "pending_orders": "Pending Orders", + "new_customers": "New Customers", + "today": "Today", + "this_week": "This Week", + "this_month": "This Month", + "this_year": "This Year", + "error_loading": "Error loading dashboard", + "no_data": "No data available" + }, + "settings": { + "title": "Settings", + "general": "General", + "store": "Store", + "store_name": "Store Name", + "store_description": "Store Description", + "contact_email": "Contact Email", + "contact_phone": "Contact Phone", + "business_address": "Business Address", + "tax_number": "Tax Number", + "currency": "Currency", + "timezone": "Timezone", + "language": "Language", + "language_settings": "Language Settings", + "default_language": "Default Language", + "dashboard_language": "Dashboard Language", + "storefront_language": "Storefront Language", + "enabled_languages": "Enabled Languages", + "notifications": "Notifications", + "email_notifications": "Email Notifications", + "integrations": "Integrations", + "api_keys": "API Keys", + "webhooks": "Webhooks", + "save_settings": "Save Settings", + "settings_saved": "Settings saved successfully" + }, + "profile": { + "title": "Profile", + "my_profile": "My Profile", + "edit_profile": "Edit Profile", + "personal_info": "Personal Information", + "first_name": "First Name", + "last_name": "Last Name", + "email": "Email", + "phone": "Phone", + "avatar": "Avatar", + "change_avatar": "Change Avatar", + "security": "Security", + "two_factor": "Two-Factor Authentication", + "sessions": "Active Sessions", + "preferences": "Preferences", + "language_preference": "Language Preference", + "save_profile": "Save Profile", + "profile_updated": "Profile updated successfully" + }, + "messages": { + "failed_to_load_dashboard_data": "Failed to load dashboard data", + "dashboard_refreshed": "Dashboard refreshed", + "item_removed_from_cart": "Item removed from cart", + "cart_cleared": "Cart cleared" + }, + "confirmations": { + "show_all_menu_items": "This will show all menu items. Continue?", + "hide_all_menu_items": "This will hide all menu items (except mandatory ones). You can then enable the ones you want. Continue?", + "reset_email_settings": "This will reset all email settings to use .env defaults. Continue?", + "cleanup_logs": "This will delete all logs older than {days} days. Continue?" + } +} diff --git a/app/modules/core/static/admin/js/my-menu-config.js b/app/modules/core/static/admin/js/my-menu-config.js index 973ebc4a..dc7749b6 100644 --- a/app/modules/core/static/admin/js/my-menu-config.js +++ b/app/modules/core/static/admin/js/my-menu-config.js @@ -57,6 +57,9 @@ function adminMyMenuConfig() { }, async init() { + // Load i18n translations + await I18n.loadModule('core'); + // Guard against multiple initialization if (window._adminMyMenuConfigInitialized) { myMenuConfigLog.warn('Already initialized, skipping'); @@ -141,7 +144,7 @@ function adminMyMenuConfig() { }, async showAll() { - if (!confirm('This will show all menu items. Continue?')) { + if (!confirm(I18n.t('core.confirmations.show_all_menu_items'))) { return; } @@ -163,7 +166,7 @@ function adminMyMenuConfig() { }, async resetToDefaults() { - if (!confirm('This will hide all menu items (except mandatory ones). You can then enable the ones you want. Continue?')) { + if (!confirm(I18n.t('core.confirmations.hide_all_menu_items'))) { return; } diff --git a/app/modules/core/static/admin/js/settings.js b/app/modules/core/static/admin/js/settings.js index e758880c..8739fad2 100644 --- a/app/modules/core/static/admin/js/settings.js +++ b/app/modules/core/static/admin/js/settings.js @@ -82,6 +82,9 @@ function adminSettings() { testEmailSuccess: null, async init() { + // Load i18n translations + await I18n.loadModule('core'); + // Guard against multiple initialization if (window._adminSettingsInitialized) return; window._adminSettingsInitialized = true; @@ -434,7 +437,7 @@ function adminSettings() { }, async resetEmailSettings() { - if (!confirm('This will reset all email settings to use .env defaults. Continue?')) { + if (!confirm(I18n.t('core.confirmations.reset_email_settings'))) { return; } diff --git a/app/modules/core/static/storefront/js/storefront-layout.js b/app/modules/core/static/storefront/js/storefront-layout.js index fcb3d271..bc445e63 100644 --- a/app/modules/core/static/storefront/js/storefront-layout.js +++ b/app/modules/core/static/storefront/js/storefront-layout.js @@ -153,13 +153,13 @@ function shopLayoutData() { removeFromCart(productId) { this.cart = this.cart.filter(item => item.id !== productId); this.saveCart(); - this.showToast('Item removed from cart', 'info'); + this.showToast(I18n.t('core.messages.item_removed_from_cart'), 'info'); }, clearCart() { this.cart = []; this.saveCart(); - this.showToast('Cart cleared', 'info'); + this.showToast(I18n.t('core.messages.cart_cleared'), 'info'); }, saveCart() { diff --git a/app/modules/marketplace/locales/en.json b/app/modules/marketplace/locales/en.json index 96470ed4..3ef75f53 100644 --- a/app/modules/marketplace/locales/en.json +++ b/app/modules/marketplace/locales/en.json @@ -1,122 +1,75 @@ { - "title": "Marketplace Integration", - "description": "Letzshop product and order synchronization", - "products": { - "title": "Marketplace Products", - "subtitle": "Products imported from marketplaces", - "empty": "No products found", - "empty_search": "No products match your search", - "import": "Import Products" - }, - "import": { - "title": "Import Products", - "subtitle": "Import products from marketplace feeds", - "source_url": "Feed URL", - "source_url_help": "URL to the marketplace CSV feed", - "marketplace": "Marketplace", - "language": "Language", - "language_help": "Language for product translations", - "batch_size": "Batch Size", + "marketplace": { + "title": "Marketplace", + "import": "Import", + "export": "Export", + "sync": "Sync", + "source": "Source", + "source_url": "Source URL", + "import_products": "Import Products", "start_import": "Start Import", - "cancel": "Cancel" - }, - "import_jobs": { - "title": "Import History", - "subtitle": "Past and current import jobs", - "empty": "No import jobs", + "importing": "Importing...", + "import_complete": "Import Complete", + "import_failed": "Import Failed", + "import_history": "Import History", "job_id": "Job ID", - "marketplace": "Marketplace", - "vendor": "Vendor", - "status": "Status", - "imported": "Imported", - "updated": "Updated", - "errors": "Errors", - "created": "Created", - "completed": "Completed", - "statuses": { - "pending": "Pending", - "processing": "Processing", - "completed": "Completed", - "completed_with_errors": "Completed with Errors", - "failed": "Failed" - } + "started_at": "Started At", + "completed_at": "Completed At", + "duration": "Duration", + "imported_count": "Imported", + "error_count": "Errors", + "total_processed": "Total Processed", + "progress": "Progress", + "no_import_jobs": "No import jobs yet", + "start_first_import": "Start your first import using the form above" }, "letzshop": { "title": "Letzshop Integration", - "subtitle": "Manage Letzshop connection and sync", - "credentials": { - "title": "API Credentials", - "api_key": "API Key", - "api_key_help": "Your Letzshop API key", - "endpoint": "API Endpoint", - "test_mode": "Test Mode", - "test_mode_help": "When enabled, no changes are made to Letzshop" - }, - "sync": { - "title": "Synchronization", - "auto_sync": "Auto Sync", - "auto_sync_help": "Automatically sync orders from Letzshop", - "interval": "Sync Interval", - "interval_help": "Minutes between syncs", - "last_sync": "Last Sync", - "last_status": "Last Status", - "sync_now": "Sync Now" - }, - "carrier": { - "title": "Carrier Settings", - "default_carrier": "Default Carrier", - "greco": "Greco", - "colissimo": "Colissimo", - "xpresslogistics": "XpressLogistics", - "label_url": "Label URL Prefix" - }, - "historical": { - "title": "Historical Import", - "subtitle": "Import past orders from Letzshop", - "start_import": "Start Historical Import", - "phase": "Phase", - "confirmed": "Confirmed Orders", - "unconfirmed": "Unconfirmed Orders", - "fetching": "Fetching...", - "processing": "Processing...", - "page": "Page", - "fetched": "Fetched", - "processed": "Processed", - "imported": "Imported", - "updated": "Updated", - "skipped": "Skipped" - }, - "vendors": { - "title": "Vendor Directory", - "subtitle": "Browse Letzshop vendors", - "claim": "Claim", - "claimed": "Claimed", - "unclaimed": "Unclaimed", - "last_synced": "Last synced" + "connection": "Connection", + "credentials": "Credentials", + "api_key": "API Key", + "api_endpoint": "API Endpoint", + "auto_sync": "Auto Sync", + "sync_interval": "Sync Interval", + "every_hour": "Every hour", + "every_day": "Every day", + "test_connection": "Test Connection", + "save_credentials": "Save Credentials", + "connection_success": "Connection successful", + "connection_failed": "Connection failed", + "last_sync": "Last Sync", + "sync_status": "Sync Status", + "import_orders": "Import Orders", + "export_products": "Export Products", + "no_credentials": "Configure your API key in Settings to get started", + "carriers": { + "dhl": "DHL", + "ups": "UPS", + "fedex": "FedEx", + "dpd": "DPD", + "gls": "GLS", + "post_luxembourg": "Post Luxembourg", + "other": "Other" } }, - "export": { - "title": "Export Products", - "subtitle": "Export products to marketplace format", - "format": "Format", - "format_csv": "CSV", - "format_xml": "XML", - "download": "Download Export" - }, "messages": { - "import_started": "Import started successfully", - "import_completed": "Import completed", - "import_failed": "Import failed", - "credentials_saved": "Credentials saved successfully", - "sync_started": "Sync started", - "sync_completed": "Sync completed", - "sync_failed": "Sync failed", - "export_ready": "Export ready for download", - "error_loading": "Error loading data" + "no_error_details_available": "No error details available", + "failed_to_load_error_details": "Failed to load error details", + "copied_to_clipboard": "Copied to clipboard", + "failed_to_copy_to_clipboard": "Failed to copy to clipboard", + "please_configure_api_key_first": "Please configure your API key first", + "please_enter_api_key": "Please enter an API key", + "please_fill_in_all_fields": "Please fill in all fields" }, - "filters": { - "all_marketplaces": "All Marketplaces", - "all_vendors": "All Vendors", - "search_placeholder": "Search products..." + "confirmations": { + "remove_letzshop_credentials": "Are you sure you want to remove your Letzshop credentials?", + "confirm_order": "Confirm this order?", + "reject_order": "Reject this order? This action cannot be undone.", + "remove_letzshop_config_vendor": "Are you sure you want to remove Letzshop configuration for this vendor?", + "decline_order": "Are you sure you want to decline this order? All items will be marked as unavailable.", + "confirm_all_items": "Are you sure you want to confirm all items in this order?", + "decline_all_items": "Are you sure you want to decline all items in this order?", + "remove_letzshop_config": "Are you sure you want to remove the Letzshop configuration? This will disable all Letzshop features for this vendor.", + "ignore_exception": "Are you sure you want to ignore this exception? The order will still be blocked from confirmation." } } diff --git a/app/modules/marketplace/static/admin/js/letzshop.js b/app/modules/marketplace/static/admin/js/letzshop.js index 4a10adef..0b5ae3f3 100644 --- a/app/modules/marketplace/static/admin/js/letzshop.js +++ b/app/modules/marketplace/static/admin/js/letzshop.js @@ -64,6 +64,9 @@ function adminLetzshop() { vendorOrders: [], async init() { + // Load i18n translations + await I18n.loadModule('marketplace'); + // Guard against multiple initialization if (window._adminLetzshopInitialized) { return; @@ -188,7 +191,7 @@ function adminLetzshop() { * Delete vendor configuration */ async deleteVendorConfig() { - if (!confirm('Are you sure you want to remove Letzshop configuration for this vendor?')) { + if (!confirm(I18n.t('marketplace.confirmations.remove_letzshop_config_vendor'))) { return; } diff --git a/app/modules/marketplace/static/admin/js/marketplace-letzshop.js b/app/modules/marketplace/static/admin/js/marketplace-letzshop.js index 97f70dc9..e65bdc51 100644 --- a/app/modules/marketplace/static/admin/js/marketplace-letzshop.js +++ b/app/modules/marketplace/static/admin/js/marketplace-letzshop.js @@ -227,6 +227,9 @@ function adminMarketplaceLetzshop() { }, async init() { + // Load i18n translations + await I18n.loadModule('marketplace'); + marketplaceLetzshopLog.info('init() called'); // Guard against multiple initialization @@ -1007,7 +1010,7 @@ function adminMarketplaceLetzshop() { async declineOrder(order) { if (!this.selectedVendor) return; - if (!confirm('Are you sure you want to decline this order? All items will be marked as unavailable.')) return; + if (!confirm(I18n.t('marketplace.confirmations.decline_order'))) return; try { await apiClient.post(`/admin/letzshop/vendors/${this.selectedVendor.id}/orders/${order.id}/reject`); @@ -1125,7 +1128,7 @@ function adminMarketplaceLetzshop() { async confirmAllItems(order) { if (!this.selectedVendor) return; - if (!confirm('Are you sure you want to confirm all items in this order?')) return; + if (!confirm(I18n.t('marketplace.confirmations.confirm_all_items'))) return; try { await apiClient.post( @@ -1146,7 +1149,7 @@ function adminMarketplaceLetzshop() { async declineAllItems(order) { if (!this.selectedVendor) return; - if (!confirm('Are you sure you want to decline all items in this order?')) return; + if (!confirm(I18n.t('marketplace.confirmations.decline_all_items'))) return; try { await apiClient.post( @@ -1238,7 +1241,7 @@ function adminMarketplaceLetzshop() { async deleteCredentials() { if (!this.selectedVendor) return; - if (!confirm('Are you sure you want to remove the Letzshop configuration? This will disable all Letzshop features for this vendor.')) { + if (!confirm(I18n.t('marketplace.confirmations.remove_letzshop_config'))) { return; } @@ -1456,7 +1459,7 @@ function adminMarketplaceLetzshop() { * Ignore an exception */ async ignoreException(exception) { - if (!confirm('Are you sure you want to ignore this exception? The order will still be blocked from confirmation.')) { + if (!confirm(I18n.t('marketplace.confirmations.ignore_exception'))) { return; } @@ -1540,7 +1543,7 @@ function adminMarketplaceLetzshop() { if (job.type === 'import') { const errors = response.errors || []; if (errors.length === 0) { - Utils.showToast('No error details available', 'info'); + Utils.showToast(I18n.t('marketplace.messages.no_error_details_available'), 'info'); return; } // Store errors and show in job details modal @@ -1553,7 +1556,7 @@ function adminMarketplaceLetzshop() { } } catch (error) { marketplaceLetzshopLog.error('Failed to load job errors:', error); - Utils.showToast('Failed to load error details', 'error'); + Utils.showToast(I18n.t('marketplace.messages.failed_to_load_error_details'), 'error'); } }, diff --git a/app/modules/marketplace/static/vendor/js/letzshop.js b/app/modules/marketplace/static/vendor/js/letzshop.js index 016164f2..e1a3e5cd 100644 --- a/app/modules/marketplace/static/vendor/js/letzshop.js +++ b/app/modules/marketplace/static/vendor/js/letzshop.js @@ -81,6 +81,9 @@ function vendorLetzshop() { exporting: false, async init() { + // Load i18n translations + await I18n.loadModule('marketplace'); + // Guard against multiple initialization if (window._vendorLetzshopInitialized) { return; @@ -196,7 +199,7 @@ function vendorLetzshop() { */ async importOrders() { if (!this.status.is_configured) { - this.error = 'Please configure your API key first'; + this.error = I18n.t('marketplace.messages.please_configure_api_key_first'); this.activeTab = 'settings'; return; } @@ -229,7 +232,7 @@ function vendorLetzshop() { */ async saveCredentials() { if (!this.credentialsForm.api_key && !this.credentials) { - this.error = 'Please enter an API key'; + this.error = I18n.t('marketplace.messages.please_enter_api_key'); return; } @@ -288,7 +291,7 @@ function vendorLetzshop() { * Delete credentials */ async deleteCredentials() { - if (!confirm('Are you sure you want to remove your Letzshop credentials?')) { + if (!confirm(I18n.t('marketplace.confirmations.remove_letzshop_credentials'))) { return; } @@ -313,7 +316,7 @@ function vendorLetzshop() { * Confirm order */ async confirmOrder(order) { - if (!confirm('Confirm this order?')) { + if (!confirm(I18n.t('marketplace.confirmations.confirm_order'))) { return; } @@ -337,7 +340,7 @@ function vendorLetzshop() { * Reject order */ async rejectOrder(order) { - if (!confirm('Reject this order? This action cannot be undone.')) { + if (!confirm(I18n.t('marketplace.confirmations.reject_order'))) { return; } @@ -374,7 +377,7 @@ function vendorLetzshop() { */ async submitTracking() { if (!this.trackingForm.tracking_number || !this.trackingForm.tracking_carrier) { - this.error = 'Please fill in all fields'; + this.error = I18n.t('marketplace.messages.please_fill_in_all_fields'); return; } diff --git a/app/modules/messaging/locales/en.json b/app/modules/messaging/locales/en.json index 0967ef42..a9f8ee6d 100644 --- a/app/modules/messaging/locales/en.json +++ b/app/modules/messaging/locales/en.json @@ -1 +1,49 @@ -{} +{ + "notifications": { + "title": "Notifications", + "mark_read": "Mark as Read", + "mark_all_read": "Mark All as Read", + "no_notifications": "No notifications", + "new_order": "New Order", + "order_updated": "Order Updated", + "low_stock": "Low Stock Alert", + "import_complete": "Import Complete", + "import_failed": "Import Failed" + }, + "messages": { + "failed_to_load_template": "Failed to load template", + "template_saved_successfully": "Template saved successfully", + "reverted_to_platform_default": "Reverted to platform default", + "failed_to_load_preview": "Failed to load preview", + "failed_to_send_test_email": "Failed to send test email", + "failed_to_load_conversations": "Failed to load conversations", + "failed_to_load_conversation": "Failed to load conversation", + "conversation_closed": "Conversation closed", + "failed_to_close_conversation": "Failed to close conversation", + "conversation_reopened": "Conversation reopened", + "failed_to_reopen_conversation": "Failed to reopen conversation", + "conversation_created": "Conversation created", + "notification_marked_as_read": "Notification marked as read", + "all_notifications_marked_as_read": "All notifications marked as read", + "notification_deleted": "Notification deleted", + "notification_settings_saved": "Notification settings saved", + "failed_to_load_templates": "Failed to load templates", + "failed_to_load_recipients": "Failed to load recipients", + "failed_to_load_notifications": "Failed to load notifications", + "failed_to_mark_notification_as_read": "Failed to mark notification as read", + "failed_to_mark_all_as_read": "Failed to mark all as read", + "failed_to_delete_notification": "Failed to delete notification", + "failed_to_load_alerts": "Failed to load alerts", + "alert_resolved_successfully": "Alert resolved successfully", + "failed_to_resolve_alert": "Failed to resolve alert", + "no_template_for_language": "No template for {language} - create one by saving", + "failed_to_save_template": "Failed to save template", + "test_email_sent": "Test email sent to {email}" + }, + "confirmations": { + "delete_notification": "Are you sure you want to delete this notification?", + "close_conversation": "Close this conversation?", + "close_conversation_admin": "Are you sure you want to close this conversation?", + "delete_customization": "Are you sure you want to delete your customization and revert to the platform default?" + } +} diff --git a/app/modules/messaging/static/admin/js/messages.js b/app/modules/messaging/static/admin/js/messages.js index 539fead4..f3980e9e 100644 --- a/app/modules/messaging/static/admin/js/messages.js +++ b/app/modules/messaging/static/admin/js/messages.js @@ -67,6 +67,9 @@ function adminMessages(initialConversationId = null) { * Initialize component */ async init() { + // Load i18n translations + await I18n.loadModule('messaging'); + // Guard against multiple initialization if (window._adminMessagesInitialized) return; window._adminMessagesInitialized = true; @@ -141,7 +144,7 @@ function adminMessages(initialConversationId = null) { messagesLog.debug(`Loaded ${this.conversations.length} conversations`); } catch (error) { messagesLog.error('Failed to load conversations:', error); - Utils.showToast('Failed to load conversations', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_load_conversations'), 'error'); } finally { this.loadingConversations = false; } @@ -197,7 +200,7 @@ function adminMessages(initialConversationId = null) { this.scrollToBottom(); } catch (error) { messagesLog.error('Failed to load conversation:', error); - Utils.showToast('Failed to load conversation', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_load_conversation'), 'error'); } finally { this.loadingMessages = false; } @@ -301,7 +304,7 @@ function adminMessages(initialConversationId = null) { * Close conversation */ async closeConversation() { - if (!confirm('Are you sure you want to close this conversation?')) return; + if (!confirm(I18n.t('messaging.confirmations.close_conversation_admin'))) return; try { await apiClient.post(`/admin/messages/${this.selectedConversationId}/close`); @@ -316,10 +319,10 @@ function adminMessages(initialConversationId = null) { conv.is_closed = true; } - Utils.showToast('Conversation closed', 'success'); + Utils.showToast(I18n.t('messaging.messages.conversation_closed'), 'success'); } catch (error) { messagesLog.error('Failed to close conversation:', error); - Utils.showToast('Failed to close conversation', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_close_conversation'), 'error'); } }, @@ -340,10 +343,10 @@ function adminMessages(initialConversationId = null) { conv.is_closed = false; } - Utils.showToast('Conversation reopened', 'success'); + Utils.showToast(I18n.t('messaging.messages.conversation_reopened'), 'success'); } catch (error) { messagesLog.error('Failed to reopen conversation:', error); - Utils.showToast('Failed to reopen conversation', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_reopen_conversation'), 'error'); } }, @@ -367,7 +370,7 @@ function adminMessages(initialConversationId = null) { messagesLog.debug(`Loaded ${this.recipients.length} recipients`); } catch (error) { messagesLog.error('Failed to load recipients:', error); - Utils.showToast('Failed to load recipients', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_load_recipients'), 'error'); } finally { this.loadingRecipients = false; } @@ -416,7 +419,7 @@ function adminMessages(initialConversationId = null) { await this.loadConversations(); await this.selectConversation(response.id); - Utils.showToast('Conversation created', 'success'); + Utils.showToast(I18n.t('messaging.messages.conversation_created'), 'success'); } catch (error) { messagesLog.error('Failed to create conversation:', error); Utils.showToast(error.message || 'Failed to create conversation', 'error'); diff --git a/app/modules/messaging/static/admin/js/notifications.js b/app/modules/messaging/static/admin/js/notifications.js index 12e742da..9e60101d 100644 --- a/app/modules/messaging/static/admin/js/notifications.js +++ b/app/modules/messaging/static/admin/js/notifications.js @@ -71,6 +71,9 @@ function adminNotifications() { * Initialize component */ async init() { + // Load i18n translations + await I18n.loadModule('messaging'); + // Guard against multiple initialization if (window._adminNotificationsInitialized) return; window._adminNotificationsInitialized = true; @@ -114,7 +117,7 @@ function adminNotifications() { notificationsLog.debug(`Loaded ${this.notifications.length} notifications`); } catch (error) { notificationsLog.error('Failed to load notifications:', error); - Utils.showToast('Failed to load notifications', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_load_notifications'), 'error'); } finally { this.loadingNotifications = false; } @@ -131,10 +134,10 @@ function adminNotifications() { notification.is_read = true; this.stats.unread_count = Math.max(0, this.stats.unread_count - 1); - Utils.showToast('Notification marked as read', 'success'); + Utils.showToast(I18n.t('messaging.messages.notification_marked_as_read'), 'success'); } catch (error) { notificationsLog.error('Failed to mark as read:', error); - Utils.showToast('Failed to mark notification as read', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_mark_notification_as_read'), 'error'); } }, @@ -149,10 +152,10 @@ function adminNotifications() { this.notifications.forEach(n => n.is_read = true); this.stats.unread_count = 0; - Utils.showToast('All notifications marked as read', 'success'); + Utils.showToast(I18n.t('messaging.messages.all_notifications_marked_as_read'), 'success'); } catch (error) { notificationsLog.error('Failed to mark all as read:', error); - Utils.showToast('Failed to mark all as read', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_mark_all_as_read'), 'error'); } }, @@ -160,7 +163,7 @@ function adminNotifications() { * Delete notification */ async deleteNotification(notificationId) { - if (!confirm('Are you sure you want to delete this notification?')) { + if (!confirm(I18n.t('messaging.confirmations.delete_notification'))) { return; } @@ -175,10 +178,10 @@ function adminNotifications() { this.stats.unread_count = Math.max(0, this.stats.unread_count - 1); } - Utils.showToast('Notification deleted', 'success'); + Utils.showToast(I18n.t('messaging.messages.notification_deleted'), 'success'); } catch (error) { notificationsLog.error('Failed to delete notification:', error); - Utils.showToast('Failed to delete notification', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_delete_notification'), 'error'); } }, @@ -225,7 +228,7 @@ function adminNotifications() { notificationsLog.debug(`Loaded ${this.alerts.length} alerts`); } catch (error) { notificationsLog.error('Failed to load alerts:', error); - Utils.showToast('Failed to load alerts', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_load_alerts'), 'error'); } finally { this.loadingAlerts = false; } @@ -272,10 +275,10 @@ function adminNotifications() { } this.alertStats.resolved_today++; - Utils.showToast('Alert resolved successfully', 'success'); + Utils.showToast(I18n.t('messaging.messages.alert_resolved_successfully'), 'success'); } catch (error) { notificationsLog.error('Failed to resolve alert:', error); - Utils.showToast('Failed to resolve alert', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_resolve_alert'), 'error'); } }, diff --git a/app/modules/messaging/static/vendor/js/email-templates.js b/app/modules/messaging/static/vendor/js/email-templates.js index 0295d07d..26b4d8c9 100644 --- a/app/modules/messaging/static/vendor/js/email-templates.js +++ b/app/modules/messaging/static/vendor/js/email-templates.js @@ -54,6 +54,9 @@ function vendorEmailTemplates() { // Lifecycle async init() { + // Load i18n translations + await I18n.loadModule('messaging'); + if (window._vendorEmailTemplatesInitialized) return; window._vendorEmailTemplatesInitialized = true; @@ -134,7 +137,7 @@ function vendorEmailTemplates() { Utils.showToast(`No template available for ${this.editLanguage.toUpperCase()}`, 'info'); } else { vendorEmailTemplatesLog.error('Failed to load template:', error); - Utils.showToast('Failed to load template', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_load_template'), 'error'); } } finally { this.loadingTemplate = false; @@ -166,7 +169,7 @@ function vendorEmailTemplates() { } ); - Utils.showToast('Template saved successfully', 'success'); + Utils.showToast(I18n.t('messaging.messages.template_saved_successfully'), 'success'); this.templateSource = 'vendor_override'; // Refresh list to show updated status await this.loadData(); @@ -181,7 +184,7 @@ function vendorEmailTemplates() { async revertToDefault() { if (!this.editingTemplate) return; - if (!confirm('Are you sure you want to delete your customization and revert to the platform default?')) { + if (!confirm(I18n.t('messaging.confirmations.delete_customization'))) { return; } @@ -192,7 +195,7 @@ function vendorEmailTemplates() { `/vendor/email-templates/${this.editingTemplate.code}/${this.editLanguage}` ); - Utils.showToast('Reverted to platform default', 'success'); + Utils.showToast(I18n.t('messaging.messages.reverted_to_platform_default'), 'success'); // Reload the template to show platform version await this.loadTemplateLanguage(); // Refresh list @@ -222,7 +225,7 @@ function vendorEmailTemplates() { this.showPreviewModal = true; } catch (error) { vendorEmailTemplatesLog.error('Failed to preview template:', error); - Utils.showToast('Failed to load preview', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_load_preview'), 'error'); } }, @@ -255,7 +258,7 @@ function vendorEmailTemplates() { } } catch (error) { vendorEmailTemplatesLog.error('Failed to send test email:', error); - Utils.showToast('Failed to send test email', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_send_test_email'), 'error'); } finally { this.sendingTest = false; } diff --git a/app/modules/messaging/static/vendor/js/messages.js b/app/modules/messaging/static/vendor/js/messages.js index 7f5d8934..a8cf1321 100644 --- a/app/modules/messaging/static/vendor/js/messages.js +++ b/app/modules/messaging/static/vendor/js/messages.js @@ -62,6 +62,9 @@ function vendorMessages(initialConversationId = null) { * Initialize component */ async init() { + // Load i18n translations + await I18n.loadModule('messaging'); + // Guard against multiple initialization if (window._vendorMessagesInitialized) return; window._vendorMessagesInitialized = true; @@ -143,7 +146,7 @@ function vendorMessages(initialConversationId = null) { messagesLog.debug(`Loaded ${this.conversations.length} conversations`); } catch (error) { messagesLog.error('Failed to load conversations:', error); - Utils.showToast('Failed to load conversations', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_load_conversations'), 'error'); } finally { this.loadingConversations = false; } @@ -192,7 +195,7 @@ function vendorMessages(initialConversationId = null) { this.scrollToBottom(); } catch (error) { messagesLog.error('Failed to load conversation:', error); - Utils.showToast('Failed to load conversation', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_load_conversation'), 'error'); } finally { this.loadingMessages = false; } @@ -276,7 +279,7 @@ function vendorMessages(initialConversationId = null) { * Close conversation */ async closeConversation() { - if (!confirm('Close this conversation?')) return; + if (!confirm(I18n.t('messaging.confirmations.close_conversation'))) return; try { await apiClient.post(`/vendor/messages/${this.selectedConversationId}/close`); @@ -288,10 +291,10 @@ function vendorMessages(initialConversationId = null) { const conv = this.conversations.find(c => c.id === this.selectedConversationId); if (conv) conv.is_closed = true; - Utils.showToast('Conversation closed', 'success'); + Utils.showToast(I18n.t('messaging.messages.conversation_closed'), 'success'); } catch (error) { messagesLog.error('Failed to close conversation:', error); - Utils.showToast('Failed to close conversation', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_close_conversation'), 'error'); } }, @@ -309,10 +312,10 @@ function vendorMessages(initialConversationId = null) { const conv = this.conversations.find(c => c.id === this.selectedConversationId); if (conv) conv.is_closed = false; - Utils.showToast('Conversation reopened', 'success'); + Utils.showToast(I18n.t('messaging.messages.conversation_reopened'), 'success'); } catch (error) { messagesLog.error('Failed to reopen conversation:', error); - Utils.showToast('Failed to reopen conversation', 'error'); + Utils.showToast(I18n.t('messaging.messages.failed_to_reopen_conversation'), 'error'); } }, @@ -356,7 +359,7 @@ function vendorMessages(initialConversationId = null) { await this.loadConversations(); await this.selectConversation(response.id); - Utils.showToast('Conversation created', 'success'); + Utils.showToast(I18n.t('messaging.messages.conversation_created'), 'success'); } catch (error) { messagesLog.error('Failed to create conversation:', error); Utils.showToast(error.message || 'Failed to create conversation', 'error'); diff --git a/app/modules/messaging/static/vendor/js/notifications.js b/app/modules/messaging/static/vendor/js/notifications.js index 626e9499..22e7c927 100644 --- a/app/modules/messaging/static/vendor/js/notifications.js +++ b/app/modules/messaging/static/vendor/js/notifications.js @@ -51,6 +51,9 @@ function vendorNotifications() { }, async init() { + // Load i18n translations + await I18n.loadModule('messaging'); + vendorNotificationsLog.info('Notifications init() called'); // Guard against multiple initialization @@ -121,7 +124,7 @@ function vendorNotifications() { notification.is_read = true; this.stats.unread_count = Math.max(0, this.stats.unread_count - 1); - Utils.showToast('Notification marked as read', 'success'); + Utils.showToast(I18n.t('messaging.messages.notification_marked_as_read'), 'success'); } catch (error) { vendorNotificationsLog.error('Failed to mark as read:', error); Utils.showToast(error.message || 'Failed to mark notification as read', 'error'); @@ -139,7 +142,7 @@ function vendorNotifications() { this.notifications.forEach(n => n.is_read = true); this.stats.unread_count = 0; - Utils.showToast('All notifications marked as read', 'success'); + Utils.showToast(I18n.t('messaging.messages.all_notifications_marked_as_read'), 'success'); } catch (error) { vendorNotificationsLog.error('Failed to mark all as read:', error); Utils.showToast(error.message || 'Failed to mark all as read', 'error'); @@ -150,7 +153,7 @@ function vendorNotifications() { * Delete notification */ async deleteNotification(notificationId) { - if (!confirm('Are you sure you want to delete this notification?')) { + if (!confirm(I18n.t('messaging.confirmations.delete_notification'))) { return; } @@ -165,7 +168,7 @@ function vendorNotifications() { this.stats.unread_count = Math.max(0, this.stats.unread_count - 1); } - Utils.showToast('Notification deleted', 'success'); + Utils.showToast(I18n.t('messaging.messages.notification_deleted'), 'success'); } catch (error) { vendorNotificationsLog.error('Failed to delete notification:', error); Utils.showToast(error.message || 'Failed to delete notification', 'error'); @@ -195,7 +198,7 @@ function vendorNotifications() { async saveSettings() { try { await apiClient.put(`/vendor/notifications/settings`, this.settingsForm); - Utils.showToast('Notification settings saved', 'success'); + Utils.showToast(I18n.t('messaging.messages.notification_settings_saved'), 'success'); this.showSettingsModal = false; } catch (error) { vendorNotificationsLog.error('Failed to save settings:', error); diff --git a/app/modules/tenancy/locales/en.json b/app/modules/tenancy/locales/en.json new file mode 100644 index 00000000..5255ed7a --- /dev/null +++ b/app/modules/tenancy/locales/en.json @@ -0,0 +1,88 @@ +{ + "team": { + "title": "Team", + "members": "Members", + "add_member": "Add Member", + "invite_member": "Invite Member", + "remove_member": "Remove Member", + "role": "Role", + "owner": "Owner", + "manager": "Manager", + "editor": "Editor", + "viewer": "Viewer", + "permissions": "Permissions", + "pending_invitations": "Pending Invitations", + "invitation_sent": "Invitation Sent", + "invitation_accepted": "Invitation Accepted" + }, + "messages": { + "business_info_saved": "Business info saved", + "marketplace_settings_saved": "Marketplace settings saved", + "please_enter_a_url_first": "Please enter a URL first", + "could_not_validate_url_it_may_still_work": "Could not validate URL - it may still work", + "localization_settings_saved": "Localization settings saved", + "failed_to_load_email_settings": "Failed to load email settings", + "from_email_and_from_name_are_required": "From Email and From Name are required", + "email_settings_saved": "Email settings saved", + "please_enter_a_test_email_address": "Please enter a test email address", + "please_save_your_email_settings_first": "Please save your email settings first", + "test_email_sent_check_your_inbox": "Test email sent! Check your inbox.", + "please_fix_the_errors_before_saving": "Please fix the errors before saving", + "profile_updated_successfully": "Profile updated successfully", + "email_is_required": "Email is required", + "invitation_sent_successfully": "Invitation sent successfully", + "team_member_updated": "Team member updated", + "team_member_removed": "Team member removed", + "invalid_company_url": "Invalid company URL", + "failed_to_load_company_details": "Failed to load company details", + "company_deleted_successfully": "Company deleted successfully", + "company_details_refreshed": "Company details refreshed", + "invalid_admin_user_url": "Invalid admin user URL", + "failed_to_load_admin_user_details": "Failed to load admin user details", + "you_cannot_deactivate_your_own_account": "You cannot deactivate your own account", + "you_cannot_delete_your_own_account": "You cannot delete your own account", + "admin_user_deleted_successfully": "Admin user deleted successfully", + "admin_user_details_refreshed": "Admin user details refreshed", + "failed_to_initialize_page": "Failed to initialize page", + "failed_to_load_company": "Failed to load company", + "company_updated_successfully": "Company updated successfully", + "ownership_transferred_successfully": "Ownership transferred successfully", + "theme_saved_successfully": "Theme saved successfully", + "failed_to_apply_preset": "Failed to apply preset", + "theme_reset_to_default": "Theme reset to default", + "failed_to_reset_theme": "Failed to reset theme", + "failed_to_load_vendors": "Failed to load vendors", + "vendor_deleted_successfully": "Vendor deleted successfully", + "vendors_list_refreshed": "Vendors list refreshed", + "invalid_user_url": "Invalid user URL", + "failed_to_load_user_details": "Failed to load user details", + "user_deleted_successfully": "User deleted successfully", + "user_details_refreshed": "User details refreshed", + "invalid_vendor_url": "Invalid vendor URL", + "failed_to_load_vendor_details": "Failed to load vendor details", + "no_vendor_loaded": "No vendor loaded", + "subscription_created_successfully": "Subscription created successfully", + "vendor_details_refreshed": "Vendor details refreshed", + "failed_to_load_users": "Failed to load users", + "failed_to_delete_user": "Failed to delete user", + "failed_to_load_admin_users": "Failed to load admin users", + "failed_to_load_admin_user": "Failed to load admin user", + "you_cannot_demote_yourself_from_super_ad": "You cannot demote yourself from super admin", + "platform_assigned_successfully": "Platform assigned successfully", + "platform_admin_must_be_assigned_to_at_le": "Platform admin must be assigned to at least one platform", + "platform_removed_successfully": "Platform removed successfully", + "please_fix_the_errors_before_submitting": "Please fix the errors before submitting", + "failed_to_load_vendor": "Failed to load vendor", + "vendor_updated_successfully": "Vendor updated successfully", + "all_contact_fields_reset_to_company_defa": "All contact fields reset to company defaults", + "failed_to_load_user": "Failed to load user", + "user_updated_successfully": "User updated successfully" + }, + "confirmations": { + "enable_all_modules": "This will enable all modules. Continue?", + "disable_optional_modules": "This will disable all optional modules, keeping only core modules. Continue?", + "reset_theme": "Reset theme to default? This cannot be undone.", + "show_all_menu_items": "This will show all menu items. Continue?", + "hide_all_menu_items": "This will hide all menu items (except mandatory ones). You can then enable the ones you want. Continue?" + } +} diff --git a/app/modules/tenancy/static/admin/js/platform-menu-config.js b/app/modules/tenancy/static/admin/js/platform-menu-config.js index 7a59a938..d202cd9b 100644 --- a/app/modules/tenancy/static/admin/js/platform-menu-config.js +++ b/app/modules/tenancy/static/admin/js/platform-menu-config.js @@ -52,6 +52,9 @@ function adminPlatformMenuConfig(platformCode) { }, async init() { + // Load i18n translations + await I18n.loadModule('tenancy'); + // Guard against duplicate initialization if (window._platformMenuConfigInitialized) { menuConfigLog.warn('Already initialized, skipping'); @@ -158,7 +161,7 @@ function adminPlatformMenuConfig(platformCode) { }, async showAll() { - if (!confirm('This will show all menu items. Continue?')) { + if (!confirm(I18n.t('tenancy.confirmations.show_all_menu_items'))) { return; } @@ -184,7 +187,7 @@ function adminPlatformMenuConfig(platformCode) { }, async resetToDefaults() { - if (!confirm('This will hide all menu items (except mandatory ones). You can then enable the ones you want. Continue?')) { + if (!confirm(I18n.t('tenancy.confirmations.hide_all_menu_items'))) { return; } diff --git a/app/modules/tenancy/static/admin/js/platform-modules.js b/app/modules/tenancy/static/admin/js/platform-modules.js index 999db5db..e876283c 100644 --- a/app/modules/tenancy/static/admin/js/platform-modules.js +++ b/app/modules/tenancy/static/admin/js/platform-modules.js @@ -65,6 +65,9 @@ function adminPlatformModules(platformCode) { }, async init() { + // Load i18n translations + await I18n.loadModule('tenancy'); + // Guard against duplicate initialization if (window._platformModulesInitialized) { moduleConfigLog.warn('Already initialized, skipping'); @@ -173,7 +176,7 @@ function adminPlatformModules(platformCode) { }, async enableAll() { - if (!confirm('This will enable all modules. Continue?')) { + if (!confirm(I18n.t('tenancy.confirmations.enable_all_modules'))) { return; } @@ -204,7 +207,7 @@ function adminPlatformModules(platformCode) { }, async disableOptional() { - if (!confirm('This will disable all optional modules, keeping only core modules. Continue?')) { + if (!confirm(I18n.t('tenancy.confirmations.disable_optional_modules'))) { return; } diff --git a/app/modules/tenancy/static/admin/js/vendor-theme.js b/app/modules/tenancy/static/admin/js/vendor-theme.js index 45c6ba87..b6d29a2c 100644 --- a/app/modules/tenancy/static/admin/js/vendor-theme.js +++ b/app/modules/tenancy/static/admin/js/vendor-theme.js @@ -78,6 +78,9 @@ function adminVendorTheme() { // ==================================================================== async init() { + // Load i18n translations + await I18n.loadModule('tenancy'); + // Guard against multiple initialization if (window._adminVendorThemeInitialized) return; window._adminVendorThemeInitialized = true; @@ -225,7 +228,7 @@ function adminVendorTheme() { window.LogConfig.logPerformance('Save Theme', duration); themeLog.info('Theme saved successfully'); - Utils.showToast('Theme saved successfully', 'success'); + Utils.showToast(I18n.t('tenancy.messages.theme_saved_successfully'), 'success'); } catch (error) { window.LogConfig.logError(error, 'Save Theme'); @@ -262,14 +265,14 @@ function adminVendorTheme() { } catch (error) { window.LogConfig.logError(error, 'Apply Preset'); - Utils.showToast('Failed to apply preset', 'error'); + Utils.showToast(I18n.t('tenancy.messages.failed_to_apply_preset'), 'error'); } finally { this.saving = false; } }, async resetTheme() { - if (!confirm('Reset theme to default? This cannot be undone.')) { + if (!confirm(I18n.t('tenancy.confirmations.reset_theme'))) { return; } @@ -288,11 +291,11 @@ function adminVendorTheme() { await this.loadTheme(); themeLog.info('Theme reset successfully'); - Utils.showToast('Theme reset to default', 'success'); + Utils.showToast(I18n.t('tenancy.messages.theme_reset_to_default'), 'success'); } catch (error) { window.LogConfig.logError(error, 'Reset Theme'); - Utils.showToast('Failed to reset theme', 'error'); + Utils.showToast(I18n.t('tenancy.messages.failed_to_reset_theme'), 'error'); } finally { this.saving = false; } diff --git a/static/locales/en.json b/static/locales/en.json index 9366a7b1..6bb63908 100644 --- a/static/locales/en.json +++ b/static/locales/en.json @@ -135,257 +135,6 @@ "account": "Account", "wishlist": "Wishlist" }, - "dashboard": { - "title": "Dashboard", - "welcome": "Welcome back", - "overview": "Overview", - "quick_stats": "Quick Stats", - "recent_activity": "Recent Activity", - "total_products": "Total Products", - "total_orders": "Total Orders", - "total_customers": "Total Customers", - "total_revenue": "Total Revenue", - "active_products": "Active Products", - "pending_orders": "Pending Orders", - "new_customers": "New Customers", - "today": "Today", - "this_week": "This Week", - "this_month": "This Month", - "this_year": "This Year", - "error_loading": "Error loading dashboard", - "no_data": "No data available" - }, - "products": { - "title": "Products", - "product": "Product", - "add_product": "Add Product", - "edit_product": "Edit Product", - "delete_product": "Delete Product", - "product_name": "Product Name", - "product_code": "Product Code", - "sku": "SKU", - "price": "Price", - "sale_price": "Sale Price", - "cost": "Cost", - "stock": "Stock", - "in_stock": "In Stock", - "out_of_stock": "Out of Stock", - "low_stock": "Low Stock", - "availability": "Availability", - "available": "Available", - "unavailable": "Unavailable", - "brand": "Brand", - "category": "Category", - "categories": "Categories", - "image": "Image", - "images": "Images", - "main_image": "Main Image", - "gallery": "Gallery", - "weight": "Weight", - "dimensions": "Dimensions", - "color": "Color", - "size": "Size", - "material": "Material", - "condition": "Condition", - "new": "New", - "used": "Used", - "refurbished": "Refurbished", - "no_products": "No products found", - "search_products": "Search products...", - "filter_by_category": "Filter by category", - "filter_by_status": "Filter by status", - "sort_by": "Sort by", - "sort_newest": "Newest", - "sort_oldest": "Oldest", - "sort_price_low": "Price: Low to High", - "sort_price_high": "Price: High to Low", - "sort_name_az": "Name: A-Z", - "sort_name_za": "Name: Z-A" - }, - "orders": { - "title": "Orders", - "order": "Order", - "order_id": "Order ID", - "order_number": "Order Number", - "order_date": "Order Date", - "order_status": "Order Status", - "order_details": "Order Details", - "order_items": "Order Items", - "order_total": "Order Total", - "subtotal": "Subtotal", - "shipping": "Shipping", - "tax": "Tax", - "discount": "Discount", - "customer": "Customer", - "shipping_address": "Shipping Address", - "billing_address": "Billing Address", - "payment_method": "Payment Method", - "payment_status": "Payment Status", - "tracking": "Tracking", - "tracking_number": "Tracking Number", - "carrier": "Carrier", - "no_orders": "No orders found", - "search_orders": "Search orders...", - "filter_by_status": "Filter by status", - "status_pending": "Pending", - "status_processing": "Processing", - "status_shipped": "Shipped", - "status_delivered": "Delivered", - "status_cancelled": "Cancelled", - "status_refunded": "Refunded", - "status_confirmed": "Confirmed", - "status_rejected": "Rejected", - "confirm_order": "Confirm Order", - "reject_order": "Reject Order", - "set_tracking": "Set Tracking", - "view_details": "View Details" - }, - "customers": { - "title": "Customers", - "customer": "Customer", - "add_customer": "Add Customer", - "edit_customer": "Edit Customer", - "customer_name": "Customer Name", - "customer_email": "Customer Email", - "customer_phone": "Customer Phone", - "customer_number": "Customer Number", - "first_name": "First Name", - "last_name": "Last Name", - "company": "Company", - "total_orders": "Total Orders", - "total_spent": "Total Spent", - "last_order": "Last Order", - "registered": "Registered", - "no_customers": "No customers found", - "search_customers": "Search customers..." - }, - "inventory": { - "title": "Inventory", - "stock_level": "Stock Level", - "quantity": "Quantity", - "reorder_point": "Reorder Point", - "adjust_stock": "Adjust Stock", - "stock_in": "Stock In", - "stock_out": "Stock Out", - "transfer": "Transfer", - "history": "History", - "low_stock_alert": "Low Stock Alert", - "out_of_stock_alert": "Out of Stock Alert" - }, - "marketplace": { - "title": "Marketplace", - "import": "Import", - "export": "Export", - "sync": "Sync", - "source": "Source", - "source_url": "Source URL", - "import_products": "Import Products", - "start_import": "Start Import", - "importing": "Importing...", - "import_complete": "Import Complete", - "import_failed": "Import Failed", - "import_history": "Import History", - "job_id": "Job ID", - "started_at": "Started At", - "completed_at": "Completed At", - "duration": "Duration", - "imported_count": "Imported", - "error_count": "Errors", - "total_processed": "Total Processed", - "progress": "Progress", - "no_import_jobs": "No import jobs yet", - "start_first_import": "Start your first import using the form above" - }, - "letzshop": { - "title": "Letzshop Integration", - "connection": "Connection", - "credentials": "Credentials", - "api_key": "API Key", - "api_endpoint": "API Endpoint", - "auto_sync": "Auto Sync", - "sync_interval": "Sync Interval", - "every_hour": "Every hour", - "every_day": "Every day", - "test_connection": "Test Connection", - "save_credentials": "Save Credentials", - "connection_success": "Connection successful", - "connection_failed": "Connection failed", - "last_sync": "Last Sync", - "sync_status": "Sync Status", - "import_orders": "Import Orders", - "export_products": "Export Products", - "no_credentials": "Configure your API key in Settings to get started", - "carriers": { - "dhl": "DHL", - "ups": "UPS", - "fedex": "FedEx", - "dpd": "DPD", - "gls": "GLS", - "post_luxembourg": "Post Luxembourg", - "other": "Other" - } - }, - "team": { - "title": "Team", - "members": "Members", - "add_member": "Add Member", - "invite_member": "Invite Member", - "remove_member": "Remove Member", - "role": "Role", - "owner": "Owner", - "manager": "Manager", - "editor": "Editor", - "viewer": "Viewer", - "permissions": "Permissions", - "pending_invitations": "Pending Invitations", - "invitation_sent": "Invitation Sent", - "invitation_accepted": "Invitation Accepted" - }, - "settings": { - "title": "Settings", - "general": "General", - "store": "Store", - "store_name": "Store Name", - "store_description": "Store Description", - "contact_email": "Contact Email", - "contact_phone": "Contact Phone", - "business_address": "Business Address", - "tax_number": "Tax Number", - "currency": "Currency", - "timezone": "Timezone", - "language": "Language", - "language_settings": "Language Settings", - "default_language": "Default Language", - "dashboard_language": "Dashboard Language", - "storefront_language": "Storefront Language", - "enabled_languages": "Enabled Languages", - "notifications": "Notifications", - "email_notifications": "Email Notifications", - "integrations": "Integrations", - "api_keys": "API Keys", - "webhooks": "Webhooks", - "save_settings": "Save Settings", - "settings_saved": "Settings saved successfully" - }, - "profile": { - "title": "Profile", - "my_profile": "My Profile", - "edit_profile": "Edit Profile", - "personal_info": "Personal Information", - "first_name": "First Name", - "last_name": "Last Name", - "email": "Email", - "phone": "Phone", - "avatar": "Avatar", - "change_avatar": "Change Avatar", - "security": "Security", - "two_factor": "Two-Factor Authentication", - "sessions": "Active Sessions", - "preferences": "Preferences", - "language_preference": "Language Preference", - "save_profile": "Save Profile", - "profile_updated": "Profile updated successfully" - }, "errors": { "generic": "An error occurred", "not_found": "Not Found", @@ -414,35 +163,6 @@ "logout_title": "Confirm Logout", "logout_message": "Are you sure you want to log out?" }, - "notifications": { - "title": "Notifications", - "mark_read": "Mark as Read", - "mark_all_read": "Mark All as Read", - "no_notifications": "No notifications", - "new_order": "New Order", - "order_updated": "Order Updated", - "low_stock": "Low Stock Alert", - "import_complete": "Import Complete", - "import_failed": "Import Failed" - }, - "shop": { - "welcome": "Welcome to our store", - "browse_products": "Browse Products", - "add_to_cart": "Add to Cart", - "buy_now": "Buy Now", - "view_cart": "View Cart", - "checkout": "Checkout", - "continue_shopping": "Continue Shopping", - "start_shopping": "Start Shopping", - "empty_cart": "Your cart is empty", - "cart_total": "Cart Total", - "proceed_checkout": "Proceed to Checkout", - "payment": "Payment", - "place_order": "Place Order", - "order_placed": "Order Placed Successfully", - "thank_you": "Thank you for your order", - "order_confirmation": "Order Confirmation" - }, "footer": { "all_rights_reserved": "All rights reserved", "powered_by": "Powered by" @@ -472,205 +192,8 @@ "datetime": "MM/DD/YYYY HH:mm", "currency": "{symbol}{amount}" }, - "platform": { - "nav": { - "pricing": "Pricing", - "find_shop": "Find Your Shop", - "start_trial": "Start Free Trial", - "admin_login": "Admin Login", - "vendor_login": "Vendor Login", - "toggle_menu": "Toggle menu", - "toggle_dark_mode": "Toggle dark mode" - }, - "hero": { - "badge": "{trial_days}-Day Free Trial - No Credit Card Required to Start", - "title": "Lightweight OMS for Letzshop Sellers", - "subtitle": "Order management, inventory, and invoicing built for Luxembourg e-commerce. Stop juggling spreadsheets. Start running your business.", - "cta_trial": "Start Free Trial", - "cta_find_shop": "Find Your Letzshop Shop" - }, - "pricing": { - "title": "Simple, Transparent Pricing", - "subtitle": "Choose the plan that fits your business. All plans include a {trial_days}-day free trial.", - "monthly": "Monthly", - "annual": "Annual", - "save_months": "Save 2 months!", - "most_popular": "MOST POPULAR", - "recommended": "RECOMMENDED", - "contact_sales": "Contact Sales", - "start_trial": "Start Free Trial", - "per_month": "/month", - "per_year": "/year", - "custom": "Custom", - "orders_per_month": "{count} orders/month", - "unlimited_orders": "Unlimited orders", - "products_limit": "{count} products", - "unlimited_products": "Unlimited products", - "team_members": "{count} team members", - "unlimited_team": "Unlimited team", - "letzshop_sync": "Letzshop order sync", - "eu_vat_invoicing": "EU VAT invoicing", - "analytics_dashboard": "Analytics dashboard", - "api_access": "API access", - "multi_channel": "Multi-channel integration", - "products": "products", - "team_member": "team member", - "unlimited": "Unlimited", - "order_history": "months order history", - "trial_note": "All plans include a {trial_days}-day free trial. No credit card required.", - "back_home": "Back to Home" - }, - "features": { - "letzshop_sync": "Letzshop order sync", - "inventory_basic": "Basic inventory management", - "inventory_locations": "Warehouse locations", - "inventory_purchase_orders": "Purchase orders", - "invoice_lu": "Luxembourg VAT invoicing", - "invoice_eu_vat": "EU VAT invoicing", - "invoice_bulk": "Bulk invoicing", - "customer_view": "Customer list", - "customer_export": "Customer export", - "analytics_dashboard": "Analytics dashboard", - "accounting_export": "Accounting export", - "api_access": "API access", - "automation_rules": "Automation rules", - "team_roles": "Team roles & permissions", - "white_label": "White-label option", - "multi_vendor": "Multi-vendor support", - "custom_integrations": "Custom integrations", - "sla_guarantee": "SLA guarantee", - "dedicated_support": "Dedicated account manager" - }, - "addons": { - "title": "Enhance Your Platform", - "subtitle": "Add custom branding, professional email, and enhanced security.", - "per_year": "/year", - "per_month": "/month", - "custom_domain": "Custom Domain", - "custom_domain_desc": "Use your own domain (mydomain.com)", - "premium_ssl": "Premium SSL", - "premium_ssl_desc": "EV certificate for trust badges", - "email_package": "Email Package", - "email_package_desc": "Professional email addresses" - }, - "find_shop": { - "title": "Find Your Letzshop Shop", - "subtitle": "Already selling on Letzshop? Enter your shop URL to get started.", - "placeholder": "Enter your Letzshop URL (e.g., letzshop.lu/vendors/my-shop)", - "button": "Find My Shop", - "claim_shop": "Claim This Shop", - "already_claimed": "Already Claimed", - "no_account": "Don't have a Letzshop account?", - "signup_letzshop": "Sign up with Letzshop first", - "then_connect": ", then come back to connect your shop.", - "search_placeholder": "Enter Letzshop URL or shop name...", - "search_button": "Search", - "examples": "Examples:", - "claim_button": "Claim This Shop & Start Free Trial", - "not_found": "We could not find a Letzshop shop with that URL. Please check and try again.", - "or_signup": "Or sign up without a Letzshop connection", - "need_help": "Need Help?", - "no_account_yet": "Don't have a Letzshop account yet? No problem!", - "create_letzshop": "Create a Letzshop Account", - "signup_without": "Sign Up Without Letzshop", - "looking_up": "Looking up your shop...", - "found": "Found:", - "claimed_badge": "Already Claimed" - }, - "signup": { - "step_plan": "Select Plan", - "step_shop": "Claim Shop", - "step_account": "Account", - "step_payment": "Payment", - "choose_plan": "Choose Your Plan", - "save_percent": "Save {percent}%", - "trial_info": "We'll collect your payment info, but you won't be charged until the trial ends.", - "connect_shop": "Connect Your Letzshop Shop", - "connect_optional": "Optional: Link your Letzshop account to sync orders automatically.", - "connect_continue": "Connect & Continue", - "skip_step": "Skip This Step", - "create_account": "Create Your Account", - "first_name": "First Name", - "last_name": "Last Name", - "company_name": "Company Name", - "email": "Email", - "password": "Password", - "password_hint": "Minimum 8 characters", - "continue": "Continue", - "continue_payment": "Continue to Payment", - "back": "Back", - "add_payment": "Add Payment Method", - "no_charge_note": "You won't be charged until your {trial_days}-day trial ends.", - "processing": "Processing...", - "start_trial": "Start Free Trial", - "creating_account": "Creating your account..." - }, - "success": { - "title": "Welcome to Wizamart!", - "subtitle": "Your account has been created and your {trial_days}-day free trial has started.", - "what_next": "What's Next?", - "step_connect": "Connect Letzshop:", - "step_connect_desc": "Add your API key to start syncing orders automatically.", - "step_invoicing": "Set Up Invoicing:", - "step_invoicing_desc": "Configure your invoice settings for Luxembourg compliance.", - "step_products": "Import Products:", - "step_products_desc": "Sync your product catalog from Letzshop.", - "go_to_dashboard": "Go to Dashboard", - "login_dashboard": "Login to Dashboard", - "need_help": "Need help getting started?", - "contact_support": "Contact our support team" - }, - "cta": { - "title": "Ready to Streamline Your Orders?", - "subtitle": "Join Letzshop vendors who trust Wizamart for their order management. Start your {trial_days}-day free trial today.", - "button": "Start Free Trial" - }, - "footer": { - "tagline": "Lightweight OMS for Letzshop sellers. Manage orders, inventory, and invoicing.", - "quick_links": "Quick Links", - "platform": "Platform", - "contact": "Contact", - "copyright": "© {year} Wizamart. Built for Luxembourg e-commerce.", - "privacy": "Privacy Policy", - "terms": "Terms of Service", - "about": "About Us", - "faq": "FAQ", - "contact_us": "Contact Us" - }, - "modern": { - "badge_integration": "Official Integration", - "badge_connect": "Connect in 2 minutes", - "hero_title_1": "Built for Luxembourg E-Commerce", - "hero_title_2": "The Back-Office Letzshop Doesn't Give You", - "hero_subtitle": "Sync orders, manage inventory, generate invoices with correct VAT, and own your customer data. All in one place.", - "cta_trial": "Start {trial_days}-Day Free Trial", - "cta_how": "See How It Works", - "hero_note": "No credit card required. Setup in 5 minutes. Cancel anytime.", - "pain_title": "Sound Familiar?", - "pain_subtitle": "These are the daily frustrations of Letzshop sellers", - "pain_manual": "Manual Order Entry", - "pain_manual_desc": "Copy-pasting orders from Letzshop to spreadsheets. Every. Single. Day.", - "pain_inventory": "Inventory Chaos", - "pain_inventory_desc": "Stock in Letzshop doesn't match reality. Overselling happens.", - "pain_vat": "Wrong VAT Invoices", - "pain_vat_desc": "EU customers need correct VAT. Your accountant keeps complaining.", - "pain_customers": "Lost Customers", - "pain_customers_desc": "Letzshop owns your customer data. You can't retarget or build loyalty.", - "how_title": "How It Works", - "how_subtitle": "From Chaos to Control in 4 Steps", - "how_step1": "Connect Letzshop", - "how_step1_desc": "Enter your Letzshop API credentials. Done in 2 minutes, no technical skills needed.", - "how_step2": "Orders Flow In", - "how_step2_desc": "Orders sync automatically. Confirm and add tracking directly from Wizamart.", - "how_step3": "Generate Invoices", - "how_step3_desc": "One click to create compliant PDF invoices with correct VAT for any EU country.", - "how_step4": "Grow Your Business", - "how_step4_desc": "Export customers for marketing. Track inventory. Focus on selling, not spreadsheets.", - "features_title": "Everything a Letzshop Seller Needs", - "features_subtitle": "The operational tools Letzshop doesn't provide", - "cta_final_title": "Ready to Take Control of Your Letzshop Business?", - "cta_final_subtitle": "Join Luxembourg vendors who've stopped fighting spreadsheets and started growing their business.", - "cta_final_note": "No credit card required. Setup in 5 minutes. Full Professional features during trial." - } + "clipboard": { + "copied": "Copied to clipboard", + "failed": "Failed to copy" } } diff --git a/static/shared/js/utils.js b/static/shared/js/utils.js index 9bb1a31a..8c1c52ab 100644 --- a/static/shared/js/utils.js +++ b/static/shared/js/utils.js @@ -140,10 +140,13 @@ const Utils = { async copyToClipboard(text) { try { await navigator.clipboard.writeText(text); - this.showToast('Copied to clipboard', 'success'); + // Use I18n if available, fallback to hardcoded string + const message = typeof I18n !== 'undefined' ? I18n.t('clipboard.copied') : 'Copied to clipboard'; + this.showToast(message, 'success'); } catch (error) { console.error('Failed to copy:', error); - this.showToast('Failed to copy', 'error'); + const message = typeof I18n !== 'undefined' ? I18n.t('clipboard.failed') : 'Failed to copy'; + this.showToast(message, 'error'); } },