Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion frontend/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BabelBridge</title>
<meta name="description" content="A modern web-based translation tool with live language identification and pluggable AI backends. Translate text seamlessly with intelligent context preservation." />
<meta name="color-scheme" content="light dark" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="manifest" href="/manifest.json" />
Expand All @@ -12,7 +13,32 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="BabelBridge" />
<link rel="apple-touch-icon" href="/favicon.svg" />
<script type="module" crossorigin src="/assets/index-CcXe80mT.js"></script>

<!-- Open Graph meta tags for social media previews -->
<meta property="og:title" content="BabelBridge - Modern Translation Tool" />
<meta property="og:description" content="A modern web-based translation tool with live language identification and pluggable AI backends. Translate text seamlessly with intelligent context preservation." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://babel-bridge.com" />
<meta property="og:image" content="/og-image.svg" />
<meta property="og:image:alt" content="BabelBridge - Modern Translation Tool with AI-powered features" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:site_name" content="BabelBridge" />
<meta property="og:locale" content="en_US" />

<!-- Twitter Card meta tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="BabelBridge - Modern Translation Tool" />
<meta name="twitter:description" content="A modern web-based translation tool with live language identification and pluggable AI backends. Translate text seamlessly with intelligent context preservation." />
<meta name="twitter:image" content="/og-image.svg" />
<meta name="twitter:image:alt" content="BabelBridge - Modern Translation Tool with AI-powered features" />

<!-- Additional meta tags for better SEO -->
<meta name="keywords" content="translation, AI, language, translate, babel, bridge, multilingual, context, preservation" />
<meta name="author" content="Daniel Sullivan" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="https://babel-bridge.com" />
<script type="module" crossorigin src="/assets/index-CD4TOx2g.js"></script>
</head>
<body>
<div id="root"></div>
Expand Down
26 changes: 26 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BabelBridge</title>
<meta name="description" content="A modern web-based translation tool with live language identification and pluggable AI backends. Translate text seamlessly with intelligent context preservation." />
<meta name="color-scheme" content="light dark" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="manifest" href="/manifest.json" />
Expand All @@ -12,6 +13,31 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="BabelBridge" />
<link rel="apple-touch-icon" href="/favicon.svg" />

<!-- Open Graph meta tags for social media previews -->
<meta property="og:title" content="BabelBridge - Modern Translation Tool" />
<meta property="og:description" content="A modern web-based translation tool with live language identification and pluggable AI backends. Translate text seamlessly with intelligent context preservation." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://babelbridge.world" />
<meta property="og:image" content="/og-image.svg" />
<meta property="og:image:alt" content="BabelBridge - Modern Translation Tool with AI-powered features" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:site_name" content="BabelBridge" />
<meta property="og:locale" content="en_US" />

<!-- Twitter Card meta tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="BabelBridge - Modern Translation Tool" />
<meta name="twitter:description" content="A modern web-based translation tool with live language identification and pluggable AI backends. Translate text seamlessly with intelligent context preservation." />
<meta name="twitter:image" content="/og-image.svg" />
<meta name="twitter:image:alt" content="BabelBridge - Modern Translation Tool with AI-powered features" />

<!-- Additional meta tags for better SEO -->
<meta name="keywords" content="translation, AI, language, translate, babel, bridge, multilingual, context, preservation" />
<meta name="author" content="Daniel Sullivan" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="https://babelbridge.world" />
</head>
<body>
<div id="root"></div>
Expand Down
29 changes: 24 additions & 5 deletions frontend/public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
{
"name": "BabelBridge",
"name": "BabelBridge - Modern Translation Tool",
"short_name": "BabelBridge",
"description": "A modern web-based translation tool with live language identification and pluggable AI backends",
"description": "A modern web-based translation tool with live language identification and pluggable AI backends. Translate text seamlessly with intelligent context preservation.",
"start_url": "/",
"display": "standalone",
"background_color": "#0f1220",
"background_color": "#0f172a",
"theme_color": "#6366F1",
"orientation": "portrait-primary",
"scope": "/",
"lang": "en",
"icons": [
{
"src": "/favicon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any maskable"
},
{
"src": "/og-image.svg",
"sizes": "1200x630",
"type": "image/svg+xml",
"purpose": "any"
}
],
"categories": ["productivity", "utilities", "education"],
"keywords": ["translation", "AI", "language", "translate", "babel", "bridge", "multilingual"],
"screenshots": [
{
"src": "/og-image.svg",
"sizes": "1200x630",
"type": "image/svg+xml",
"label": "BabelBridge main interface"
}
],
"categories": ["productivity", "utilities"],
"shortcuts": [
{
"name": "New Translation",
Expand All @@ -30,6 +47,8 @@
}
]
}
]
],
"related_applications": [],
"prefer_related_applications": false
}

73 changes: 73 additions & 0 deletions frontend/public/og-image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 52 additions & 5 deletions frontend/src/components/AddToAppButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,51 @@ export function AddToAppButton() {
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null)
const [showIOSInstructions, setShowIOSInstructions] = useState(false)
const [isInstallable, setIsInstallable] = useState(false)
const [showDebugInfo, setShowDebugInfo] = useState(false)

useEffect(() => {
const handleBeforeInstallPrompt = (e: Event) => {
// Prevent the mini-infobar from appearing on mobile
console.log('PWA: beforeinstallprompt event fired')
e.preventDefault()
setDeferredPrompt(e as BeforeInstallPromptEvent)
setIsInstallable(true)
}

const handleAppInstalled = () => {
console.log('PWA: app installed')
setIsInstallable(false)
setDeferredPrompt(null)
}

window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
window.addEventListener('appinstalled', handleAppInstalled)

// Debug logging
console.log('PWA: Component mounted, checking conditions...')
console.log('PWA: isIOS =', isIOS())
console.log('PWA: isInStandaloneMode =', isInStandaloneMode())
console.log('PWA: hasBeforeInstallPrompt =', 'onbeforeinstallprompt' in window)
console.log('PWA: hostname =', window.location.hostname)

// For localhost development: if no beforeinstallprompt after 2 seconds,
// assume it's available for testing (but only on localhost)
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
const isDev = process.env.NODE_ENV === 'development'

let timeoutId: NodeJS.Timeout | null = null
if (isDev && isLocalhost) {
timeoutId = setTimeout(() => {
if (!isInstallable && !isIOS()) {
console.log('PWA: No beforeinstallprompt after 2s, enabling for localhost testing')
setIsInstallable(true)
}
}, 2000)
}

return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
window.removeEventListener('appinstalled', handleAppInstalled)
if (timeoutId) clearTimeout(timeoutId)
}
}, [])

Expand All @@ -39,8 +64,14 @@ export function AddToAppButton() {
}

const isInStandaloneMode = () => {
return window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone === true
try {
const matchMediaResult = window.matchMedia('(display-mode: standalone)')
return (matchMediaResult && matchMediaResult.matches) ||
(window.navigator as any).standalone === true
} catch (e) {
// Fallback for test environments or browsers without matchMedia
return (window.navigator as any).standalone === true
}
}

const handleInstallClick = async () => {
Expand All @@ -59,11 +90,27 @@ export function AddToAppButton() {
}
}

// Don't show the button if already installed or not installable
if (isInStandaloneMode() || (!isInstallable && !isIOS())) {
// Don't show the button if already installed
if (isInStandaloneMode()) {
console.log('PWA: Button hidden - already in standalone mode')
return null
}

// Show button if:
// 1. Actually installable (beforeinstallprompt fired), OR
// 2. iOS device (can always install via Safari), OR
// 3. Development mode AND localhost (for testing)
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
const isDev = process.env.NODE_ENV === 'development'
const shouldShowButton = isInstallable || isIOS() || (isDev && isLocalhost)

if (!shouldShowButton) {
console.log('PWA: Button hidden - not installable, not iOS, not dev+localhost')
return null
}

console.log('PWA: Button will show - installable:', isInstallable, 'iOS:', isIOS(), 'dev+localhost:', isDev && isLocalhost)

return (
<>
<Box
Expand Down
Loading