diff --git a/frontend/src/stores/phone-locale-contract.test.ts b/frontend/src/stores/phone-locale-contract.test.ts index de5e679..e15f3b9 100644 --- a/frontend/src/stores/phone-locale-contract.test.ts +++ b/frontend/src/stores/phone-locale-contract.test.ts @@ -224,6 +224,24 @@ describe('phone locale contract', () => { expect(missing).toEqual([]) }) + it('keeps the VaultX browser fallback complete for auth and transfers', () => { + const fallbackPaths = collectDefaultLocalePaths(phoneStoreSource) + + for (const path of [ + 'Apps.crypto.auth.network', + 'Apps.crypto.auth.confirmPassword', + 'Apps.crypto.quick.send', + 'Apps.crypto.transfer.walletKey', + 'Apps.crypto.profile.copyKey', + 'Apps.crypto.profile.shareKey', + 'Apps.crypto.activityTypes.transfer_in', + 'Apps.crypto.activityTypes.transfer_out', + 'Apps.crypto.errors.invalid_wallet_key', + ]) { + expect(fallbackPaths.has(path), path).toBe(true) + } + }) + it('keeps German structurally aligned with English', () => { const germanPaths = collectLuaLocalePaths(germanLocaleSource) diff --git a/frontend/src/stores/phone.ts b/frontend/src/stores/phone.ts index 9f1ed49..1fdb876 100644 --- a/frontend/src/stores/phone.ts +++ b/frontend/src/stores/phone.ts @@ -427,17 +427,39 @@ const cryptoFallbackLocales = { eyebrow: 'Character-bound exchange', loginTitle: 'Welcome back', registerTitle: 'Create your VaultX profile', - body: "Trade fictional digital assets with your character's in-game funds.", + body: 'Trade and transfer fictional digital assets through a protected, server-controlled account.', + network: 'VAULT NETWORK ONLINE', + serverSecured: 'Server secured', + personalKey: 'Personal crypto key', + characterBound: 'Character bound', login: 'Log in', register: 'Register', + loginPanelTitle: 'Secure account access', + loginPanelBody: 'Your character identity is detected automatically.', + registerPanelTitle: 'Set up your private vault', + registerPanelBody: 'Choose a profile identity and secure access phrase.', handle: 'VaultX handle', handlePlaceholder: '3–20 letters or numbers', password: 'Password', passwordPlaceholder: '8–72 characters', + confirmPassword: 'Confirm password', + confirmPasswordPlaceholder: 'Enter the same password again', showPassword: 'Show password', hidePassword: 'Hide password', security: 'Use an in-character password. Never reuse a real password.', - loginAction: 'Open portfolio', + ruleLength: '8+ characters', + ruleMixed: 'Upper & lowercase', + ruleNumber: 'Contains a number', + ruleSpecial: 'Special character', + strength0: 'Start entering a secure password', + strength1: 'Weak protection', + strength2: 'Basic protection', + strength3: 'Strong protection', + strength4: 'Excellent protection', + acceptTitle: 'Create a fictional wallet', + acceptBody: 'I understand that VaultX assets have no real-world value.', + footer: 'Encrypted session · Rate-limited access · Memory-hard password', + loginAction: 'Unlock VaultX', registerAction: 'Create secure profile', }, tabs: { @@ -467,7 +489,7 @@ const cryptoFallbackLocales = { insightBody: 'Your holdings are valued against the latest synthetic server prices.', }, - quick: { trade: 'Trade', more: 'More' }, + quick: { trade: 'Trade', send: 'Send', more: 'More' }, actions: { deposit: 'Deposit', withdraw: 'Withdraw' }, markets: { title: 'Fictional markets', @@ -522,10 +544,29 @@ const cryptoFallbackLocales = { amount: 'Whole bank amount', confirm: 'Confirm securely', }, + transfer: { + title: 'Send crypto', + subtitle: 'VaultX transfer', + keyTitle: 'Public receiving key', + keyBody: + 'Verify the recipient before selecting an asset. Cash can never be sent here.', + walletKey: 'Recipient crypto key', + verify: 'Verify', + verifiedRecipient: 'Verified VaultX recipient', + asset: 'Choose asset', + quantity: 'Crypto amount', + available: 'Available balance', + password: 'Confirm with password', + protected: 'Atomic wallet transfer', + protectedBody: + 'VaultX rechecks recipient, ownership, quantity and balance on the server, then books both wallets together.', + confirm: 'Send crypto securely', + }, activity: { title: 'Financial activity', empty: 'No activity', - emptyBody: 'Deposits, withdrawals and trades will appear here.', + emptyBody: + 'Deposits, withdrawals, trades and crypto transfers will appear here.', volume: 'Lifetime trading volume', completedTrades: 'completed trades', cash: 'Cash', @@ -533,6 +574,14 @@ const cryptoFallbackLocales = { }, profile: { verified: 'Character ownership verified', + walletKey: 'Public crypto key', + walletKeyBody: + 'Share this key to receive crypto. It cannot access your account.', + copyKey: 'Copy', + copied: 'Copied', + shareKey: 'Share', + shareTitle: 'My VaultX crypto key', + shareText: 'Send crypto to my public VaultX key:', trades: 'Trades', volume: 'Volume', memberSince: 'Member since', @@ -567,6 +616,8 @@ const cryptoFallbackLocales = { sell: 'Asset sale', deposit: 'Bank deposit', withdrawal: 'Bank withdrawal', + transfer_in: 'Crypto received', + transfer_out: 'Crypto sent', }, statuses: { completed: 'Completed', @@ -579,11 +630,18 @@ const cryptoFallbackLocales = { handle_taken: 'That VaultX handle is already taken.', profile_exists: 'This character already owns a VaultX profile.', invalid_password: 'Password must be 8–72 characters.', + password_mismatch: 'The passwords do not match.', + accept_terms: 'Confirm that this is a fictional in-game wallet.', invalid_credentials: 'The password is incorrect.', locked: 'Too many attempts. Try again later.', not_authenticated: 'Sign in to VaultX first.', invalid_amount: 'Enter a positive whole amount.', invalid_quantity: 'Enter a quantity with up to six decimals.', + invalid_wallet_key: 'Enter a complete VX receiving key.', + recipient_not_found: 'No active VaultX account uses this key.', + self_transfer: 'You cannot send crypto to your own key.', + invalid_transfer: 'Verify a recipient, asset and valid crypto amount.', + recipient_limit_exceeded: 'The recipient wallet cannot hold that amount.', insufficient_funds: 'Your available balance is too low.', insufficient_liquidity: 'The exchange cannot fill that amount right now.', quote_expired: 'This quote expired. Request a new one.', diff --git a/frontend/src/views/apps/CryptoApp.vue b/frontend/src/views/apps/CryptoApp.vue index bfe7988..8e0484d 100644 --- a/frontend/src/views/apps/CryptoApp.vue +++ b/frontend/src/views/apps/CryptoApp.vue @@ -3043,6 +3043,7 @@ onMounted(() => void crypto.load()) } .quick button { align-content: center; + min-width: 0; min-height: 84px; gap: 8px; padding: 11px 5px; @@ -3074,7 +3075,11 @@ onMounted(() => void crypto.load()) box-shadow 180ms ease; } .quick button b { + max-width: 100%; + overflow: hidden; font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; } .quick button:focus-visible { border-color: rgba(101, 251, 210, 0.5); diff --git a/sky_phone/config/locales/de.lua b/sky_phone/config/locales/de.lua index a321aae..d49105e 100644 --- a/sky_phone/config/locales/de.lua +++ b/sky_phone/config/locales/de.lua @@ -1056,7 +1056,7 @@ Locales["de"] = { }, tabs = { portfolio = "Portfolio", markets = "Märkte", activity = "Aktivität", profile = "Profil" }, portfolio = { total = "Gesamtes Portfolio", performance = "Gewinn / Verlust", return = "Gesamtrendite", current = "Jetziger Stand", history = "Entwicklung", forecast = "Prognose", chartLabel = "Gewinn- und Verlustentwicklung mit gestrichelter Prognose", cash = "Verfügbares Guthaben", invested = "Investierte Assets", allocation = "Portfolio-Verteilung", inMarket = "in Märkten", assets = "Assets", holdings = "Deine Assets", empty = "Noch keine Assets", emptyBody = "Öffne Märkte und fordere ein geschütztes Angebot an.", avg = "Ø", insightTitle = "Portfolio-Einblick", insightBody = "Deine Bestände werden mit den neuesten synthetischen Serverkursen bewertet." }, - quick = { trade = "Handeln", send = "Krypto senden", more = "Mehr" }, + quick = { trade = "Handeln", send = "Senden", more = "Mehr" }, actions = { deposit = "Einzahlen", withdraw = "Auszahlen" }, markets = { title = "Fiktive Märkte", live = "Serverpreise", noticeTitle = "Ingame-Börse", notice = "Die Kurse sind synthetisch und haben keinen realen Wert. Jeder Handel wird gegen die begrenzte Liquidität der Börse geprüft.", eyebrow = "Marktpuls", movers = "Top-Beweger", today = "Heute" }, marketDetail = { back = "Zurück zu den Märkten", today = "Heute", description = "Der Referenzkurs wird vom Server erzeugt. Ausführbare Kurse verwenden ein geschütztes, kurzlebiges Angebot.", investment = "Deine Investition", totalValue = "Aktueller Wert", statistics = "Marktstatistik", high24h = "24h-Hoch", low24h = "24h-Tief", supply = "Ausgegebene Menge", liquidity = "Börsenliquidität" }, diff --git a/sky_phone/config/locales/en.lua b/sky_phone/config/locales/en.lua index 1014f6e..82ecd04 100644 --- a/sky_phone/config/locales/en.lua +++ b/sky_phone/config/locales/en.lua @@ -1056,7 +1056,7 @@ Locales["en"] = { }, tabs = { portfolio = "Portfolio", markets = "Markets", activity = "Activity", profile = "Profile" }, portfolio = { total = "Total portfolio", performance = "Profit / loss", return = "Total return", current = "Current position", history = "Performance", forecast = "Forecast", chartLabel = "Profit and loss performance with dashed forecast", cash = "Available cash", invested = "Invested assets", allocation = "Portfolio allocation", inMarket = "in markets", assets = "assets", holdings = "Your assets", empty = "No assets yet", emptyBody = "Open Markets to request a protected quote.", avg = "Avg.", insightTitle = "Portfolio insight", insightBody = "Your holdings are valued against the latest synthetic server prices." }, - quick = { trade = "Trade", send = "Send crypto", more = "More" }, + quick = { trade = "Trade", send = "Send", more = "More" }, actions = { deposit = "Deposit", withdraw = "Withdraw" }, markets = { title = "Fictional markets", live = "Server priced", noticeTitle = "In-game exchange", notice = "Prices are synthetic and have no real-world value. Every trade is checked against finite exchange liquidity.", eyebrow = "Market pulse", movers = "Top movers", today = "Today" }, marketDetail = { back = "Back to markets", today = "Today", description = "The reference price is generated by the server. Executable prices use a protected short-lived quote.", investment = "Your investment", totalValue = "Current value", statistics = "Market statistics", high24h = "24h high", low24h = "24h low", supply = "Issued supply", liquidity = "Exchange liquidity" },