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:
Yusuf İpek
2025-11-10 17:44:04 +03:00
parent bb854ea4d0
commit e82366e5e8
8 changed files with 563 additions and 39 deletions
+76
View File
@@ -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>
)
}
+14 -12
View File
@@ -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>
+16 -13
View File
@@ -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>
)}
+21 -7
View File
@@ -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>
)
}
+9 -7
View File
@@ -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>