feat: Add chat message translation feature

Enable translation of non-English chat messages to help users understand
international communications.

## Changes

### Translation Detection
- Added `mightNeedTranslation()` function to detect non-English text
- Checks for Cyrillic, Chinese, Japanese, Korean, and Arabic characters
- Uses Unicode range pattern matching for language detection

### Translation UI
- Added Languages icon from lucide-svelte
- Display translate button next to messages that contain non-English text
- Button shows icon only on mobile, "Translate" text on desktop
- Positioned using flexbox to prevent text wrapping issues

### Translation Functionality
- `translateMessage()` opens Google Translate in new popup window
- Auto-detects source language, translates to English
- Uses Google Translate's free web interface (no API key required)
- Opens in 800x600 popup for optimal translation viewing

## Implementation Details

The feature works by:
1. Scanning each message for non-ASCII character ranges
2. Showing translate button only for messages likely in foreign languages
3. Opening Google Translate web UI in popup when clicked
4. Preserving original message while providing translation access

**Why Google Translate Web Interface:**
- No API keys or authentication required
- Free and unlimited usage
- Familiar translation interface for users
- Supports all languages Google Translate offers
- Popup window keeps context while showing translation

This approach avoids the complexity and cost of translation APIs while
providing full-featured translation capabilities to users.

This completes Phase 2 Feature 4 and ALL Phase 2 features! 🎉

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-12 20:12:09 +01:00
parent d4d7015df6
commit b59eebcddb

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { MessageSquare, Filter, Search, AlertCircle } from 'lucide-svelte'; import { MessageSquare, Filter, Search, AlertCircle, Languages } from 'lucide-svelte';
import Card from '$lib/components/ui/Card.svelte'; import Card from '$lib/components/ui/Card.svelte';
import Badge from '$lib/components/ui/Badge.svelte'; import Badge from '$lib/components/ui/Badge.svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
@@ -36,6 +36,22 @@
}; };
}; };
// Check if text likely needs translation (contains non-ASCII or Cyrillic characters)
const mightNeedTranslation = (text: string): boolean => {
// Check for Cyrillic, Chinese, Japanese, Korean, Arabic, etc.
const nonEnglishPattern =
/[\u0400-\u04FF\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF\u0600-\u06FF]/;
return nonEnglishPattern.test(text);
};
// Open Google Translate for a message
const translateMessage = (text: string) => {
const encodedText = encodeURIComponent(text);
// Use Google Translate web interface (auto-detect language to English)
const translateUrl = `https://translate.google.com/?sl=auto&tl=en&text=${encodedText}&op=translate`;
window.open(translateUrl, '_blank', 'width=800,height=600,noopener,noreferrer');
};
if (chatData) { if (chatData) {
// Get unique players who sent messages // Get unique players who sent messages
messagePlayers = Array.from(new Set(chatData.messages.map((m) => m.player_id))) messagePlayers = Array.from(new Set(chatData.messages.map((m) => m.player_id)))
@@ -243,7 +259,20 @@
<Badge variant="default" size="sm">Team</Badge> <Badge variant="default" size="sm">Team</Badge>
{/if} {/if}
</div> </div>
<p class="mt-1 break-words text-base-content">{message.message}</p> <div class="mt-1 flex items-start gap-2">
<p class="break-words text-base-content">{message.message}</p>
{#if mightNeedTranslation(message.message)}
<button
onclick={() => translateMessage(message.message)}
class="btn btn-ghost btn-xs flex-shrink-0 gap-1"
title="Translate message"
aria-label="Translate to English"
>
<Languages class="h-3 w-3" />
<span class="hidden sm:inline">Translate</span>
</button>
{/if}
</div>
</div> </div>
</div> </div>
</div> </div>