From dc438ae4c107a0bc556467c0bfa8aeca09af7d07 Mon Sep 17 00:00:00 2001 From: Joan Date: Wed, 7 Jan 2026 15:12:01 +0100 Subject: [PATCH] WIP: i18n implementation - fix items.json syntax, add useTranslation hooks to components --- pwa/src/App.tsx | 6 +- pwa/src/components/Game.tsx | 1 - pwa/src/components/game/CombatView.tsx | 339 ----------------------- pwa/src/components/game/LocationView.tsx | 22 +- pwa/src/utils/assetPath.ts | 23 +- 5 files changed, 29 insertions(+), 362 deletions(-) delete mode 100644 pwa/src/components/game/CombatView.tsx diff --git a/pwa/src/App.tsx b/pwa/src/App.tsx index a0fd283..05444b7 100644 --- a/pwa/src/App.tsx +++ b/pwa/src/App.tsx @@ -1,4 +1,4 @@ -import { HashRouter as Router, Routes, Route, Navigate } from 'react-router-dom' +import { BrowserRouter, HashRouter, Routes, Route, Navigate } from 'react-router-dom' import { AuthProvider } from './contexts/AuthContext' import { useAuth } from './hooks/useAuth' import LandingPage from './components/LandingPage' @@ -13,6 +13,10 @@ import GameLayout from './components/GameLayout' import AccountPage from './components/AccountPage' import './App.css' +// Use HashRouter for Electron (file:// protocol), BrowserRouter for web +const isElectron = window.location.protocol === 'file:' +const Router = isElectron ? HashRouter : BrowserRouter + function PrivateRoute({ children }: { children: React.ReactNode }) { const { isAuthenticated, loading } = useAuth() diff --git a/pwa/src/components/Game.tsx b/pwa/src/components/Game.tsx index 748efcd..674516b 100644 --- a/pwa/src/components/Game.tsx +++ b/pwa/src/components/Game.tsx @@ -289,7 +289,6 @@ function Game() {
{/* Combat view (when in combat) */} {state.combatState && state.playerState && ( - console.log('Rendering Combat component', state.combatState), void - onFlee: () => void - onPvPAction: (action: string) => void - onExitCombat: () => void - onExitPvPCombat: () => void - flashEnemy?: boolean - buttonsDisabled?: boolean - floatingTexts?: { id: number, text: string, x: number, y: number, type: 'damage-player' | 'damage-enemy' | 'damage-player-dealt' | 'heal' }[] -} - -function CombatView({ - combatState, - combatLog, - profile: _profile, - playerState, - enemyName, - enemyImage, - enemyTurnMessage, - pvpTimeRemaining, - turnTimeRemaining, - onCombatAction, - onPvPAction, - onExitCombat, - onExitPvPCombat, - flashEnemy, - buttonsDisabled, - floatingTexts = [] -}: CombatViewProps) { - return ( -
-
-

- {combatState.is_pvp ? '⚔️ PvP Combat' : `⚔️ Combat - ${enemyName || combatState.combat?.npc_name || 'Enemy'}`} -

-
- - {combatState.is_pvp ? ( - /* PvP Combat UI - Unified Layout */ -
-
- {/* Opponent Display (using same structure as PvE Enemy) */} -
- {floatingTexts.map(ft => ( -
- {ft.text} -
- ))} - {(() => { - if (!combatState.pvp_combat) return null - const opponent = combatState.pvp_combat.is_attacker ? - combatState.pvp_combat.defender : - combatState.pvp_combat.attacker - - if (!opponent) return
- // Use a default avatar if no image, or maybe the class image if available? - // For now, let's use a placeholder or try to get it from profile if passed? - // The opponent object has: username, level, hp, max_hp. - // It might not have an image url. - return ( -
- 👤 -
{opponent.username} (Lv. {opponent.level})
-
- ) - })()} -
- -
- {/* Opponent HP Bar */} - {(() => { - if (!combatState.pvp_combat) return null - const opponent = combatState.pvp_combat.is_attacker ? - combatState.pvp_combat.defender : - combatState.pvp_combat.attacker - - if (!opponent) return null - - return ( -
-
-
- {opponent.username}: {opponent.hp} / {opponent.max_hp} -
-
-
-
- ) - })()} - - {/* Player HP Bar */} - {(() => { - if (!combatState.pvp_combat) return null - const you = combatState.pvp_combat.is_attacker ? - combatState.pvp_combat.attacker : - combatState.pvp_combat.defender - - if (!you) return null - - return ( -
-
-
- You: {you.hp} / {you.max_hp} -
-
-
-
- ) - })()} -
-
- -
- {combatState.pvp_combat.combat_over ? ( - - {combatState.pvp_combat.attacker_fled || combatState.pvp_combat.defender_fled ? "🏃 Combat Ended" : "💀 Combat Over"} - - ) : combatState.pvp_combat.your_turn ? ( - ✅ Your Turn ({pvpTimeRemaining ?? combatState.pvp_combat.time_remaining}s) - ) : ( - ⏳ Opponent's Turn ({pvpTimeRemaining ?? combatState.pvp_combat.time_remaining}s) - )} -
- -
- {!combatState.pvp_combat.combat_over ? ( - <> - - - - ) : ( - - )} -
- - {/* Combat Log */} -
-

Combat Log

-
-
- {combatLog.map((entry: any, i: number) => ( -
- [{entry.time}] - {entry.message} -
- ))} - {combatLog.length === 0 &&
PvP Combat started...
} -
-
-
-
- ) : ( - /* PvE Combat UI */ - <> -
-
- {/* Intent Bubble - Moved here to avoid overflow:hidden clipping */} - {combatState.combat?.npc_intent && !combatState.combat_over && ( -
- - {combatState.combat.npc_intent === 'attack' ? '⚔️' : - combatState.combat.npc_intent === 'defend' ? '🛡️' : - combatState.combat.npc_intent === 'special' ? '🔥' : '❓'} - - {combatState.combat.npc_intent} -
- )} - -
- {floatingTexts.map(ft => ( -
- {ft.text} -
- ))} - {enemyName -
-
-
-
-
- Enemy HP: {combatState.combat?.npc_hp || 0} / {combatState.combat?.npc_max_hp || 100} -
-
-
-
- {playerState && ( -
-
-
- Your HP: {playerState.health} / {playerState.max_health} -
-
-
-
- )} -
-
- -
- {!combatState.combat_over ? ( - enemyTurnMessage ? ( - 🗡️ Enemy's turn... - ) : combatState.combat?.turn === 'player' ? ( - <> - ✅ Your Turn - {turnTimeRemaining !== null && ( - - ⏱️ {Math.floor(turnTimeRemaining / 60)}:{String(Math.floor(turnTimeRemaining % 60)).padStart(2, '0')} - - )} - - ) : ( - ⚠️ Enemy Turn - ) - ) : ( - - {combatState.player_won ? "✅ Victory!" : combatState.player_fled ? "🏃 Escaped!" : "💀 Defeated"} - - )} -
- - {/* PvE Combat Actions */} - -
- {!combatState.combat_over ? ( - <> - - - - ) : ( - - )} -
- - {/* Combat Log */} -
-

Combat Log

-
-
- {combatLog.map((entry: any, i: number) => ( -
- [{entry.time}] - {entry.message} -
- ))} - {combatLog.length === 0 &&
Combat started...
} -
-
-
-
- - )} -
- ) -} - -export default CombatView diff --git a/pwa/src/components/game/LocationView.tsx b/pwa/src/components/game/LocationView.tsx index 826e178..18072f8 100644 --- a/pwa/src/components/game/LocationView.tsx +++ b/pwa/src/components/game/LocationView.tsx @@ -180,14 +180,14 @@ function LocationView({ {enemy.id && (
{enemy.name} { e.currentTarget.style.display = 'none' }} />
)}
-
{enemy.name}
+
{getTranslatedText(enemy.name)}
{enemy.level &&
Lv. {enemy.level}
}
@@ -286,7 +286,7 @@ function LocationView({
🧑
-
{npc.name}
+
{getTranslatedText(npc.name)}
{npc.level &&
Lv. {npc.level}
}
@@ -306,7 +306,7 @@ function LocationView({ {item.image_path ? ( {item.name} { (e.target as HTMLImageElement).style.display = 'none'; @@ -318,14 +318,14 @@ function LocationView({ {item.emoji || '📦'}
- {item.name || 'Unknown Item'} + {getTranslatedText(item.name) || 'Unknown Item'}
{item.quantity > 1 &&
×{item.quantity}
}
- {item.description &&
{item.description}
} + {item.description &&
{getTranslatedText(item.description)}
} {item.weight !== undefined && item.weight > 0 && (
⚖️ Weight: {item.weight}kg {item.quantity > 1 && `(Total: ${(item.weight * item.quantity).toFixed(2)}kg)`} diff --git a/pwa/src/utils/assetPath.ts b/pwa/src/utils/assetPath.ts index f3ef615..6376f1e 100644 --- a/pwa/src/utils/assetPath.ts +++ b/pwa/src/utils/assetPath.ts @@ -2,12 +2,15 @@ * Asset Path Utility * * Resolves asset paths based on runtime environment: - * - Electron: Returns local path (assets bundled with app) + * - Electron: Returns relative path (assets bundled with app, using ./) * - Browser: Returns full server URL */ -// Detect if running in Electron -const isElectron = !!(window as any).electronAPI?.isElectron +// Detect if running in Electron - check at runtime, not module load time +function checkIsElectron(): boolean { + return !!(window as any).electronAPI?.isElectron || + window.location.protocol === 'file:' +} // Base URL for remote assets (browser mode) const ASSET_BASE_URL = import.meta.env.VITE_ASSET_URL || @@ -21,21 +24,21 @@ const ASSET_BASE_URL = import.meta.env.VITE_ASSET_URL || export function getAssetPath(path: string): string { if (!path) return '' - // Normalize path (ensure leading slash) - const normalizedPath = path.startsWith('/') ? path : `/${path}` + // Normalize path (remove leading slash for Electron compatibility) + const cleanPath = path.startsWith('/') ? path.slice(1) : path - if (isElectron) { - // In Electron, assets are served relative to the app - return normalizedPath + if (checkIsElectron()) { + // In Electron with base: './', use relative paths + return `./${cleanPath}` } // In browser, prepend the server URL - return `${ASSET_BASE_URL}${normalizedPath}` + return `${ASSET_BASE_URL}/${cleanPath}` } /** * Check if we're running in Electron */ export function isElectronApp(): boolean { - return isElectron + return checkIsElectron() }