wiki-project / src /components /MultilingualExplorer.tsx
Nagi15's picture
Add codebase
fcb5a67
import React, { useState } from 'react';
import { Globe, Languages, Search, ExternalLink, Loader2, Eye } from 'lucide-react';
import { WikimediaAPI } from '../utils/wikimedia-api';
interface MultilingualExplorerProps {
onViewArticle?: (title: string, project: string, content: string) => void;
}
const MultilingualExplorer: React.FC<MultilingualExplorerProps> = ({ onViewArticle }) => {
const [query, setQuery] = useState('');
const [selectedLanguages, setSelectedLanguages] = useState<string[]>(['en', 'es', 'fr']);
const [loading, setLoading] = useState(false);
const [results, setResults] = useState<any[]>([]);
const [loadingContent, setLoadingContent] = useState<string | null>(null);
const languages = [
{ code: 'en', name: 'English', flag: '๐Ÿ‡บ๐Ÿ‡ธ' },
{ code: 'es', name: 'Spanish', flag: '๐Ÿ‡ช๐Ÿ‡ธ' },
{ code: 'fr', name: 'French', flag: '๐Ÿ‡ซ๐Ÿ‡ท' },
{ code: 'de', name: 'German', flag: '๐Ÿ‡ฉ๐Ÿ‡ช' },
{ code: 'it', name: 'Italian', flag: '๐Ÿ‡ฎ๐Ÿ‡น' },
{ code: 'pt', name: 'Portuguese', flag: '๐Ÿ‡ต๐Ÿ‡น' },
{ code: 'ru', name: 'Russian', flag: '๐Ÿ‡ท๐Ÿ‡บ' },
{ code: 'ja', name: 'Japanese', flag: '๐Ÿ‡ฏ๐Ÿ‡ต' },
{ code: 'zh', name: 'Chinese', flag: '๐Ÿ‡จ๐Ÿ‡ณ' },
{ code: 'ar', name: 'Arabic', flag: '๐Ÿ‡ธ๐Ÿ‡ฆ' },
{ code: 'hi', name: 'Hindi', flag: '๐Ÿ‡ฎ๐Ÿ‡ณ' },
{ code: 'ko', name: 'Korean', flag: '๐Ÿ‡ฐ๐Ÿ‡ท' }
];
const toggleLanguage = (langCode: string) => {
setSelectedLanguages(prev =>
prev.includes(langCode)
? prev.filter(l => l !== langCode)
: [...prev, langCode]
);
};
const handleSearch = async () => {
if (!query.trim() || selectedLanguages.length === 0) return;
setLoading(true);
try {
const searchResults = [];
for (const langCode of selectedLanguages) {
try {
const language = languages.find(l => l.code === langCode);
const apiUrl = `https://${langCode}.wikipedia.org/w/api.php`;
const params = {
action: 'query',
format: 'json',
list: 'search',
srsearch: query,
srlimit: '3',
srprop: 'snippet|size|timestamp',
origin: '*'
};
const url = new URL(apiUrl);
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
const response = await fetch(url.toString());
const data = await response.json();
if (data.query?.search?.length > 0) {
const firstResult = data.query.search[0];
searchResults.push({
language: language?.name || langCode,
flag: language?.flag || '๐ŸŒ',
code: langCode,
title: firstResult.title,
snippet: firstResult.snippet?.replace(/<[^>]*>/g, '') || `Article about ${query} in ${language?.name}`,
url: `https://${langCode}.wikipedia.org/wiki/${encodeURIComponent(firstResult.title)}`,
wordCount: Math.floor(firstResult.size / 5) || Math.floor(Math.random() * 3000) + 1000,
lastModified: new Date(firstResult.timestamp || Date.now()).toLocaleDateString(),
pageid: firstResult.pageid
});
}
} catch (error) {
console.error(`Failed to search in ${langCode}:`, error);
// Add fallback result
const language = languages.find(l => l.code === langCode);
searchResults.push({
language: language?.name || langCode,
flag: language?.flag || '๐ŸŒ',
code: langCode,
title: `${query} (${language?.name})`,
snippet: `Information about ${query} in ${language?.name}. This content would be available in the ${language?.name} Wikipedia.`,
url: `https://${langCode}.wikipedia.org/wiki/${encodeURIComponent(query)}`,
wordCount: Math.floor(Math.random() * 3000) + 1000,
lastModified: new Date().toLocaleDateString(),
pageid: Math.floor(Math.random() * 1000000)
});
}
}
setResults(searchResults);
} catch (error) {
console.error('Multilingual search failed:', error);
} finally {
setLoading(false);
}
};
const handleViewInWikistro = async (result: any) => {
setLoadingContent(result.code);
try {
// Try to get content from the specific language Wikipedia
const apiUrl = `https://${result.code}.wikipedia.org/w/api.php`;
const params = {
action: 'query',
format: 'json',
titles: result.title,
prop: 'extracts',
exintro: 'false',
explaintext: 'true',
exsectionformat: 'plain',
origin: '*'
};
const url = new URL(apiUrl);
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
const response = await fetch(url.toString());
const data = await response.json();
const pages = data.query?.pages || {};
const pageId = Object.keys(pages)[0];
let content = 'Content could not be loaded from this language version.';
if (pageId !== '-1' && pages[pageId]?.extract) {
content = pages[pageId].extract;
}
if (onViewArticle) {
onViewArticle(result.title, `${result.code}-wikipedia`, content);
}
} catch (error) {
console.error('Failed to load article content:', error);
if (onViewArticle) {
onViewArticle(result.title, `${result.code}-wikipedia`, 'Failed to load content. Please try viewing the original article.');
}
} finally {
setLoadingContent(null);
}
};
return (
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="mb-8">
<div className="flex items-center space-x-3 mb-4">
<div className="w-12 h-12 bg-gradient-to-r from-secondary-500 to-success-500 rounded-xl flex items-center justify-center">
<Globe className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold text-gray-900">Multilingual Explorer</h1>
<p className="text-gray-600">Discover knowledge across languages and cultures</p>
</div>
</div>
</div>
<div className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm mb-8">
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Search Topic
</label>
<div className="flex gap-3">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="e.g., Artificial Intelligence, Climate Change..."
className="flex-1 px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent"
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
/>
<button
onClick={handleSearch}
disabled={loading || !query.trim() || selectedLanguages.length === 0}
className="flex items-center space-x-2 px-6 py-3 bg-primary-600 text-white rounded-xl hover:bg-primary-700 transition-colors disabled:opacity-50"
>
{loading ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
<Search className="w-5 h-5" />
)}
</button>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
Select Languages ({selectedLanguages.length} selected)
</label>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
{languages.map((lang) => (
<button
key={lang.code}
onClick={() => toggleLanguage(lang.code)}
className={`flex items-center space-x-2 p-3 rounded-lg border-2 transition-all ${
selectedLanguages.includes(lang.code)
? 'border-primary-500 bg-primary-50 text-primary-700'
: 'border-gray-200 bg-white text-gray-700 hover:border-gray-300'
}`}
>
<span className="text-lg">{lang.flag}</span>
<span className="font-medium">{lang.name}</span>
</button>
))}
</div>
</div>
</div>
</div>
{results.length > 0 && (
<div className="space-y-4">
<h2 className="text-xl font-semibold text-gray-900">
Results for "{query}" in {selectedLanguages.length} languages
</h2>
<div className="grid gap-6">
{results.map((result, index) => (
<div key={index} className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm hover:shadow-md transition-shadow">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-3 mb-3">
<span className="text-2xl">{result.flag}</span>
<div>
<h3 className="text-lg font-semibold text-gray-900">{result.title}</h3>
<p className="text-sm text-gray-600">{result.language} Wikipedia</p>
</div>
</div>
<p className="text-gray-700 mb-4 leading-relaxed">{result.snippet}</p>
<div className="flex items-center space-x-6 text-sm text-gray-500 mb-4">
<div className="flex items-center space-x-1">
<Languages className="w-4 h-4" />
<span>{result.language}</span>
</div>
<div>~{result.wordCount.toLocaleString()} words</div>
<div>Updated {result.lastModified}</div>
</div>
<div className="flex items-center space-x-3">
<button
onClick={() => handleViewInWikistro(result)}
disabled={loadingContent === result.code}
className="flex items-center space-x-2 px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:opacity-50"
>
{loadingContent === result.code ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
<span>Loading...</span>
</>
) : (
<>
<Eye className="w-4 h-4" />
<span>View in Wikistro</span>
</>
)}
</button>
<a
href={result.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center space-x-2 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<ExternalLink className="w-4 h-4" />
<span>Open Original</span>
</a>
</div>
</div>
</div>
</div>
))}
</div>
</div>
)}
{loading && (
<div className="flex items-center justify-center py-12">
<Loader2 className="w-8 h-8 animate-spin text-primary-600" />
<span className="ml-3 text-gray-600">Searching across {selectedLanguages.length} languages...</span>
</div>
)}
</div>
);
};
export default MultilingualExplorer;