mirror of
https://github.com/yusufipk/RepoHub.git
synced 2026-09-11 18:46:07 +00:00
feat: add internationalization support to package browser UI
- Integrated locale context throughout PackageBrowser, PlatformSelector, SelectionManager, and RepoHubApp components - Replaced all hardcoded UI strings with translation keys (t() function calls) - Updated RepoHubApp to wrap content in LocaleProvider for i18n support
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useTheme } from '@/hooks/useTheme'
|
||||
import { useLocale } from '@/contexts/LocaleContext'
|
||||
import { Sun, Moon, Monitor, Globe } from 'lucide-react'
|
||||
|
||||
export function Header() {
|
||||
const { theme, isDark, toggleTheme } = useTheme()
|
||||
const { locale, toggleLocale } = useLocale()
|
||||
|
||||
const getThemeIcon = () => {
|
||||
switch (theme) {
|
||||
case 'light':
|
||||
return <Sun className="h-4 w-4" />
|
||||
case 'dark':
|
||||
return <Moon className="h-4 w-4" />
|
||||
default:
|
||||
return <Monitor className="h-4 w-4" />
|
||||
}
|
||||
}
|
||||
|
||||
const getThemeLabel = () => {
|
||||
switch (theme) {
|
||||
case 'light':
|
||||
return locale === 'tr' ? 'Aydınlık' : 'Light'
|
||||
case 'dark':
|
||||
return locale === 'tr' ? 'Karanlık' : 'Dark'
|
||||
default:
|
||||
return locale === 'tr' ? 'Sistem' : 'System'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container flex h-14 items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
||||
<span className="text-sm font-bold">RH</span>
|
||||
</div>
|
||||
<span className="hidden font-bold sm:inline-block">
|
||||
RepoHub
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* Theme Toggle */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleTheme}
|
||||
className="w-full justify-start"
|
||||
>
|
||||
{getThemeIcon()}
|
||||
<span className="ml-2 hidden sm:inline">
|
||||
{getThemeLabel()}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
{/* Language Toggle */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleLocale}
|
||||
className="w-full justify-start"
|
||||
>
|
||||
<Globe className="h-4 w-4" />
|
||||
<span className="ml-2 hidden sm:inline">
|
||||
{locale === 'tr' ? 'TR' : 'EN'}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { mockPackages, categories, licenses } from '@/data/mockData'
|
||||
import { useLocale } from '@/contexts/LocaleContext'
|
||||
import { Package, FilterOptions, Platform } from '@/types'
|
||||
import { Search, Package as PackageIcon, Terminal, Monitor } from 'lucide-react'
|
||||
|
||||
@@ -22,6 +23,7 @@ export function PackageBrowser({
|
||||
onPackageToggle,
|
||||
onFiltersChange
|
||||
}: PackageBrowserProps) {
|
||||
const { t } = useLocale()
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [filters, setFilters] = useState<FilterOptions>({
|
||||
platforms: [],
|
||||
@@ -83,7 +85,7 @@ export function PackageBrowser({
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardContent className="flex items-center justify-center h-64">
|
||||
<p className="text-muted-foreground">Please select a platform first</p>
|
||||
<p className="text-muted-foreground">{t('platform.description')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
@@ -103,7 +105,7 @@ export function PackageBrowser({
|
||||
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search packages..."
|
||||
placeholder={t('packages.search')}
|
||||
className="w-full pl-10 pr-4 py-2 border border-input rounded-md bg-background"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
@@ -115,7 +117,7 @@ export function PackageBrowser({
|
||||
handleFilterChange('categories', value ? [value] : [])
|
||||
}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Category" />
|
||||
<SelectValue placeholder={t('packages.filters.category')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map(category => (
|
||||
@@ -131,11 +133,11 @@ export function PackageBrowser({
|
||||
handleFilterChange('types', value ? [value as 'gui' | 'cli'] : [])
|
||||
}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Type" />
|
||||
<SelectValue placeholder={t('packages.filters.type')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="gui">GUI Applications</SelectItem>
|
||||
<SelectItem value="cli">CLI Tools</SelectItem>
|
||||
<SelectItem value="gui">{t('packages.filters.gui')}</SelectItem>
|
||||
<SelectItem value="cli">{t('packages.filters.cli')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
@@ -144,11 +146,11 @@ export function PackageBrowser({
|
||||
handleFilterChange('repositories', value ? [value as 'official' | 'third-party'] : [])
|
||||
}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Repository" />
|
||||
<SelectValue placeholder={t('packages.filters.repository')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="official">Official Only</SelectItem>
|
||||
<SelectItem value="third-party">Third Party</SelectItem>
|
||||
<SelectItem value="official">{t('packages.filters.official')}</SelectItem>
|
||||
<SelectItem value="third_party">{t('packages.filters.third_party')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -158,9 +160,9 @@ export function PackageBrowser({
|
||||
{/* Package List */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Available Packages ({filteredPackages.length})</CardTitle>
|
||||
<CardTitle>{t('packages.browse')} ({filteredPackages.length})</CardTitle>
|
||||
<CardDescription>
|
||||
Select packages to include in your installation script
|
||||
{t('packages.description')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -219,7 +221,7 @@ export function PackageBrowser({
|
||||
{filteredPackages.length === 0 && (
|
||||
<div className="text-center py-8">
|
||||
<PackageIcon className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">No packages found matching your criteria</p>
|
||||
<p className="text-muted-foreground">{t('packages.no_packages')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState } from 'react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { platforms } from '@/data/mockData'
|
||||
import { useLocale } from '@/contexts/LocaleContext'
|
||||
import { Platform } from '@/types'
|
||||
|
||||
interface PlatformSelectorProps {
|
||||
@@ -12,35 +13,37 @@ interface PlatformSelectorProps {
|
||||
}
|
||||
|
||||
export function PlatformSelector({ selectedPlatform, onPlatformSelect }: PlatformSelectorProps) {
|
||||
const { t } = useLocale()
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle>Select Your Platform</CardTitle>
|
||||
<CardDescription>
|
||||
Choose your operating system and package manager to browse available packages
|
||||
<CardHeader className="pb-4">
|
||||
<CardTitle className="text-lg">{t('platform.select')}</CardTitle>
|
||||
<CardDescription className="text-sm">
|
||||
{t('platform.description')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<CardContent className="pt-0">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3">
|
||||
{platforms.map((platform) => (
|
||||
<Button
|
||||
key={platform.id}
|
||||
variant={selectedPlatform?.id === platform.id ? "default" : "outline"}
|
||||
className="h-auto p-4 flex flex-col items-center space-y-2"
|
||||
className="h-auto p-3 flex flex-col items-center space-y-1"
|
||||
onClick={() => onPlatformSelect(platform)}
|
||||
>
|
||||
<div className="text-2xl">{platform.icon}</div>
|
||||
<div className="text-xl">{platform.icon}</div>
|
||||
<div className="text-center">
|
||||
<div className="font-semibold">{platform.name}</div>
|
||||
<div className="text-sm text-muted-foreground">{platform.packageManager}</div>
|
||||
<div className="font-semibold text-sm">{platform.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{platform.packageManager}</div>
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
{selectedPlatform && (
|
||||
<div className="mt-4 p-4 bg-secondary rounded-md">
|
||||
<p className="text-sm">
|
||||
<strong>Selected:</strong> {selectedPlatform.name} ({selectedPlatform.packageManager})
|
||||
<div className="mt-3 p-3 bg-secondary rounded-md">
|
||||
<p className="text-xs">
|
||||
<strong>{t('platform.selected')}:</strong> {selectedPlatform.name} ({selectedPlatform.packageManager})
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import { LocaleProvider } from '@/contexts/LocaleContext'
|
||||
import { Header } from '@/components/Header'
|
||||
import { PlatformSelector } from '@/components/PlatformSelector'
|
||||
import { PackageBrowser } from '@/components/PackageBrowser'
|
||||
import { SelectionManager } from '@/components/SelectionManager'
|
||||
import { ScriptPreview } from '@/components/ScriptPreview'
|
||||
import { generateScript } from '@/lib/scriptGenerator'
|
||||
import { useLocale } from '@/contexts/LocaleContext'
|
||||
import { Platform, Package, SelectedPackage, FilterOptions, GeneratedScript } from '@/types'
|
||||
|
||||
export function RepoHubApp() {
|
||||
function RepoHubAppContent() {
|
||||
const { t, locale } = useLocale()
|
||||
const [selectedPlatform, setSelectedPlatform] = useState<Platform | null>(null)
|
||||
const [selectedPackages, setSelectedPackages] = useState<SelectedPackage[]>([])
|
||||
const [generatedScript, setGeneratedScript] = useState<GeneratedScript | null>(null)
|
||||
@@ -56,18 +60,20 @@ export function RepoHubApp() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
<div key={locale} className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800">
|
||||
<Header />
|
||||
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
RepoHub
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-2">
|
||||
Cross-Platform Package Manager
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300 mb-2">
|
||||
{t('common.subtitle')}
|
||||
</p>
|
||||
<p className="text-lg text-gray-500 max-w-2xl mx-auto">
|
||||
Simplify software installation across Linux, Windows, and macOS with official repositories
|
||||
<p className="text-lg text-gray-500 dark:text-gray-400 max-w-2xl mx-auto">
|
||||
{t('common.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -109,3 +115,11 @@ export function RepoHubApp() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function RepoHubApp() {
|
||||
return (
|
||||
<LocaleProvider>
|
||||
<RepoHubAppContent />
|
||||
</LocaleProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useLocale } from '@/contexts/LocaleContext'
|
||||
import { Package, SelectedPackage } from '@/types'
|
||||
import { X, PackageOpen, Download } from 'lucide-react'
|
||||
|
||||
@@ -18,13 +19,15 @@ export function SelectionManager({
|
||||
onClearAll,
|
||||
onGenerateScript
|
||||
}: SelectionManagerProps) {
|
||||
const { t } = useLocale()
|
||||
|
||||
if (selectedPackages.length === 0) {
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardContent className="flex items-center justify-center h-32">
|
||||
<div className="text-center">
|
||||
<PackageOpen className="h-8 w-8 text-muted-foreground mx-auto mb-2" />
|
||||
<p className="text-muted-foreground">No packages selected</p>
|
||||
<p className="text-muted-foreground">{t('selection.none')}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -36,18 +39,18 @@ export function SelectionManager({
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Selected Packages ({selectedPackages.length})</CardTitle>
|
||||
<CardTitle>{t('selection.title')} ({selectedPackages.length})</CardTitle>
|
||||
<CardDescription>
|
||||
These packages will be included in your installation script
|
||||
{t('selection.description')}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" size="sm" onClick={onClearAll}>
|
||||
Clear All
|
||||
{t('selection.clear_all')}
|
||||
</Button>
|
||||
<Button onClick={onGenerateScript}>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Generate Script
|
||||
{t('selection.generate_script')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,8 +93,7 @@ export function SelectionManager({
|
||||
</div>
|
||||
<div className="mt-4 p-3 bg-muted rounded-lg">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<strong>Note:</strong> The generated script will use official repositories only
|
||||
and will be idempotent (safe to run multiple times).
|
||||
<strong>Note:</strong> {t('selection.note')}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user