feat: add i18n localization system (en/es/de) with admin panel

- Add i18n module with tForUser/tForLang/t functions and {{param}} interpolation
- Add 3 locale files: en.json, es.json, de.json (201 keys each)
- Add language selection on /start and /language command with flag emojis
- Localize all bot user-facing strings (handlers, keyboards, errors)
- Localize messageRouter keyboard matching via locale keys
- Add DB migrations 008 (language column) and 009 (language_set column)
- Add localization admin tab at /locales for editing translations
- Add userService.getUserLanguage/setUserLanguage methods
- Cache user object on msg.__user to avoid triple DB fetch
- Idempotent migrations with checkColumnExists guards
- Error boundary on i18n locale file loading
- Admin locales route uses AVAILABLE_LANGUAGES import
This commit is contained in:
NW
2026-06-25 21:22:32 +01:00
parent 41ff2b8769
commit a8bf50df24
29 changed files with 1606 additions and 365 deletions

219
src/i18n/locales/en.json Normal file
View File

@@ -0,0 +1,219 @@
{
"bot": {
"welcome": "Welcome to the shop! Choose an option:",
"language_select": "🌍 Please select your language:",
"language_changed": "✅ Language changed to {{language}}!",
"error_generic": "Error processing request. Please try again.",
"account_blocked": "⚠️ Your account has been blocked by administrator",
"account_deleted": "⚠️ Your account has been deleted by administrator",
"contact_support": "Contact support"
},
"profile": {
"title": "👤 *Your Profile*",
"not_found": "Profile not found. Please use /start to create one.",
"telegram_id": "📱 Telegram ID",
"location": "📍 Location",
"location_not_set": "Not set",
"stats": "📊 Statistics:",
"total_purchases": "Total Purchases",
"total_spent": "Total Spent",
"active_wallets": "Active Wallets",
"archived_wallets": "Archived Wallets",
"bonus_balance": "Bonus Balance",
"available_balance": "Available Balance",
"member_since": "📅 Member since",
"set_location": "📍 Set Location",
"delete_account": "❌ Delete Account",
"error_loading": "Error loading profile. Please try again."
},
"products": {
"select_country": "🌍 Select your country:",
"select_city": "🏙 Select city in {{country}}:",
"select_district": "📍 Select district in {{city}}:",
"select_category": "📦 Select category:",
"select_product": "Select a product:",
"no_products": "No products available at the moment.",
"no_products_category": "No products available in this category.",
"no_products_subcategory": "No products available in this subcategory.",
"back_to_countries": "« Back to Countries",
"back_to_cities": "« Back to Cities",
"back_to_subcategories": "« Back to Subcategories",
"back": "« Back",
"product_price": "💰 Price",
"product_description": "📝 Description",
"product_available": "📦 Available",
"product_category": "Category",
"buy_now": "🛒 Buy Now",
"increase": "",
"decrease": "",
"products_in": "📦 Products in {{name}}:",
"error_loading": "Error loading products. Please try again.",
"error_loading_cities": "Error loading cities. Please try again.",
"error_loading_districts": "Error loading districts. Please try again.",
"error_loading_categories": "Error loading categories. Please try again.",
"error_loading_product": "Error loading product details. Please try again.",
"not_found": "Location not found. Returning to previous menu."
},
"purchase": {
"summary": "🛒 Purchase Summary:",
"product": "Product",
"quantity": "Quantity",
"total": "Total",
"pay": "Pay",
"cancel": "« Cancel",
"insufficient_balance": "❌ Insufficient balance. Your current balance is ${{balance}}. You need ${{total}} to complete this purchase.",
"need_wallet": "You need to add a crypto wallet first to make purchases.",
"add_wallet": " Add Wallet",
"top_up_balance": "💰 Top Up Balance",
"not_enough_stock": "❌ Not enough items in stock. Only {{count}} available.",
"not_enough_money": "Not enough money",
"details": "📦 Purchase Details:",
"location": "Location",
"category": "Category",
"private_info": "🔒 Private Information:",
"hidden_location": "Hidden Location",
"coordinates": "Coordinates",
"view_purchase": "View new purchase",
"confirm_received": "I've got it!",
"back_to_list": "« Back to Purchase List",
"history_empty": "Your purchase history is empty.",
"browse_products": "🛍 Browse Products",
"select_purchase": "📦 Select purchase to view detailed information (Page {{page}} of {{total}}):",
"page_back": "« Back (Page {{page}})",
"page_next": "Next » (Page {{page}})",
"page_info": "Page {{current}} of {{total}}",
"no_such_purchase": "No such purchase",
"no_such_product": "No such product",
"purchase_received": "Thank you! Your purchase has been marked as received.",
"admin_notification": "User {{username}} has confirmed receiving purchase #{{purchaseId}}.",
"error_loading": "Error loading purchase history. Please try again.",
"error_loading_details": "Error loading purchase details. Please try again.",
"error_confirming": "Error confirming receipt. Please try again.",
"error_processing": "Error processing purchase. Please try again.",
"invalid_wallet": "Invalid wallet type.",
"invalid_product": "Invalid product.",
"invalid_quantity": "Invalid quantity."
},
"wallet": {
"your_wallets": "💰 *Your Wallets*",
"your_active_wallets": "💰 *Your Active Wallets:*",
"balance": "Balance",
"value": "Value",
"address": "Address",
"network": "Network",
"deposit": "💳 Deposit {{type}}",
"deposit_via_changenow": "💳 Deposit via ChangeNOW",
"select_crypto": "🔐 Select cryptocurrency to generate wallet:",
"wallet_generated": "✅ New wallet generated successfully!",
"wallet_type": "Type",
"previous_archived": " Your previous wallet has been archived.",
"recovery_stored": "⚠️ Important: Your recovery phrase has been securely stored.",
"error_generating": "❌ Error generating wallet. Please try again.",
"no_wallets": "You don't have any wallets yet. Create one first.",
"no_wallets_prefix": "❌ You don't have any wallets yet. Create one first.",
"no_active_wallets": "You don't have any active wallets yet.",
"back_to_balance": "« Back to Balance",
"back": "« Back",
"invalid_wallet_type": "Invalid wallet type.",
"user_not_found": "User not found.",
"profile_not_found": "Profile not found. Please use /start to create one.",
"profile_not_found_short": "Profile not found.",
"error_loading": "Error loading wallets. Please try again.",
"error_loading_balance": "Error loading balance. Please try again.",
"error_loading_archived": "Error loading archived wallets. Please try again.",
"error_loading_history": "Error loading transaction history. Please try again.",
"error_refreshing_balances": "❌ Error refreshing balances.",
"error_refreshing_balances_retry": "❌ Error refreshing balances. Please try again.",
"error_deposit_instructions": "Error creating deposit instructions. Please try again.",
"error_copying_address": "Error copying address.",
"add_crypto_wallet": " Add Crypto Wallet",
"top_up": "💸 Top Up",
"refresh_balance": "🔄 Refresh Balance",
"refreshing_balances": "🔄 Refreshing balances...",
"archived_wallets_count": "📁 Archived Wallets ({{count}})",
"archived_wallets_title": "📁 *Archived Wallets:*",
"no_archived_wallets": "No archived wallets found.",
"archived_date": "Archived",
"total_crypto_balance": "📊 *Total Crypto Balance:*",
"bonus_balance_label": "🎁 *Bonus Balance:*",
"available_balance_label": "💰 *Available Balance:*",
"total_type": "📊 *Total {{type}}:*",
"amount": "Amount",
"total_archived_value": "💰 *Total Value of Archived Wallets:*",
"transaction_history": "📊 Transaction History",
"transaction_history_title": "📊 *Transaction History:*",
"recent_transactions": "📊 *Recent Transactions:*",
"no_transactions": "No transactions found.",
"tx_amount": "💰 Amount",
"tx_hash": "🔗 TX Hash",
"tx_date": "🕒 Date",
"tx_wallet_type": "💼 Wallet Type",
"previous_page": "⬅️ Previous",
"next_page": "➡️ Next",
"wallet_not_found": "Wallet not found. Please try again.",
"wallet_not_found_short": "Wallet not found.",
"deposit_changenow_select": "💳 *Deposit via ChangeNOW*\n\nSelect the wallet you want to top up:",
"deposit_select_amount": "💳 *Deposit {{type}}*\n\nSelect the amount (USD) you want to deposit:",
"deposit_title": "💳 *Deposit {{type}} — €{{amount}}*",
"deposit_instructions_title": "📋 *Step\\-by\\-step instructions:*",
"deposit_step1": "1⃣ Tap *Copy Address* below to copy your wallet address",
"deposit_step2": "2⃣ Tap *Open ChangeNOW* \\— the amount €{{amount}} and currency {{type}} are already set",
"deposit_step3": "3⃣ On ChangeNOW\\, paste the copied address as the receiving wallet",
"deposit_step4": "4⃣ Enter your email and create a password when prompted \\— this is *required by law* for card payments \\(KYC verification\\)\\. Your data is protected by ChangeNOW\\'s security",
"deposit_step5": "5⃣ Pay with your bank card \\(Visa\\/Mastercard\\)",
"deposit_step6": "6⃣ Crypto will arrive in your wallet within 5\\-30 minutes",
"deposit_your_address": "🔐 *Your {{type}} wallet address:*",
"deposit_important_title": "⚠️ *Important:*",
"deposit_important1": "• Double\\-check the wallet address before confirming",
"deposit_important2": "• Email \\+ password on ChangeNOW is a standard verification step for card payments \\— don\\'t worry\\, it\\'s safe",
"deposit_important3": "• If crypto doesn\\'t arrive within 30 min \\— check the transaction status in your ChangeNOW email confirmation",
"deposit_open_changenow": "🌐 Open ChangeNOW — €{{amount}} → {{type}}",
"deposit_copy_address": "📋 Copy Address",
"deposit_change_amount": "🔄 Change Amount",
"deposit_choose_different": "💸 Choose Different Wallet",
"deposit_wallet_address": "{{type}} wallet address:",
"back_to_deposit": "« Back to Deposit",
"deposit_address_sent": "📋 {{type}} address sent! Copy it from the message below.",
"network_erc20": "Ethereum Network (ERC-20)",
"network_btc": "Bitcoin Network",
"network_ltc": "Litecoin Network",
"network_eth": "Ethereum Network",
"network_unknown": "Unknown Network"
},
"location": {
"select_country": "🌍 Select your country:",
"select_city": "🏙 Select city in {{country}}:",
"select_district": "📍 Select district in {{city}}:",
"no_locations": "No locations available yet.",
"back_to_profile": "« Back to Profile",
"back_to_countries": "« Back to Countries",
"location_updated": "✅ Location updated successfully!",
"country": "Country",
"city": "City",
"district": "District",
"error_loading_countries": "Error loading countries. Please try again.",
"error_loading_cities": "Error loading cities. Please try again.",
"error_loading_districts": "Error loading districts. Please try again.",
"error_updating": "Error updating location. Please try again."
},
"deletion": {
"confirm_title": "⚠️ Are you sure you want to delete your account?",
"confirm_body": "This action will:\n- Delete all user data\n- Remove all wallets\n- Erase purchase history\n\nThis action cannot be undone!",
"confirm_button": "✅ Confirm Delete",
"cancel_button": "❌ Cancel",
"deleted": "⚠️ Your account has been successfully deleted",
"error_processing": "Error processing delete request. Please try again.",
"error_deleting": "Error deleting user. Please try again."
},
"keyboard": {
"products": "📦 Products",
"profile": "👤 Profile",
"purchases": "🛍 Purchases",
"wallets": "💰 Wallets",
"manage_products": "📦 Manage Products",
"manage_users": "👥 Manage Users",
"manage_locations": "📍 Manage Locations",
"database_backup": "💾 Database Backup",
"manage_wallets": "💰 Manage Wallets"
}
}