mirror of
https://github.com/yusufipk/RepoHub.git
synced 2026-09-11 10:36: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>
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
"use client"
|
||||
|
||||
import { createContext, useContext, useState, useEffect, ReactNode } from 'react'
|
||||
|
||||
type Locale = 'en' | 'tr'
|
||||
|
||||
const translations = {
|
||||
en: {
|
||||
common: {
|
||||
title: "RepoHub - Cross-Platform Package Manager",
|
||||
subtitle: "Cross-Platform Package Manager",
|
||||
description: "Simplify software installation across Linux, Windows, and macOS with official repositories"
|
||||
},
|
||||
platform: {
|
||||
select: "Select Your Platform",
|
||||
description: "Choose your operating system and package manager to browse available packages",
|
||||
selected: "Selected"
|
||||
},
|
||||
packages: {
|
||||
browse: "Available Packages",
|
||||
description: "Select packages to include in your installation script",
|
||||
search: "Search packages...",
|
||||
no_packages: "No packages found matching your criteria",
|
||||
filters: {
|
||||
category: "Category",
|
||||
type: "Type",
|
||||
repository: "Repository",
|
||||
gui: "GUI Applications",
|
||||
cli: "CLI Tools",
|
||||
official: "Official Only",
|
||||
third_party: "Third Party"
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
title: "Selected Packages",
|
||||
description: "These packages will be included in your installation script",
|
||||
none: "No packages selected",
|
||||
clear_all: "Clear All",
|
||||
generate_script: "Generate Script",
|
||||
note: "The generated script will use official repositories only and will be idempotent (safe to run multiple times)."
|
||||
},
|
||||
script: {
|
||||
title: "Installation Script",
|
||||
description: "Idempotent script for",
|
||||
official_repos: "Official Repositories",
|
||||
official_repos_desc: "All packages from trusted sources",
|
||||
idempotent: "Idempotent",
|
||||
idempotent_desc: "Safe to run multiple times",
|
||||
packages_count: "Packages",
|
||||
packages_count_desc: "Ready for installation",
|
||||
included_packages: "Included Packages:",
|
||||
script_content: "Script Content:",
|
||||
copy: "Copy",
|
||||
copied: "Copied!",
|
||||
download: "Download",
|
||||
usage: "How to use:",
|
||||
usage_steps: [
|
||||
"Download the script file to your target machine",
|
||||
"Make it executable (for Linux/macOS):",
|
||||
"Run the script with appropriate permissions",
|
||||
"The script will automatically handle repository setup and package installation"
|
||||
]
|
||||
}
|
||||
},
|
||||
tr: {
|
||||
common: {
|
||||
title: "RepoHub - Çok Platformlu Paket Yöneticisi",
|
||||
subtitle: "Çok Platformlu Paket Yöneticisi",
|
||||
description: "Linux, Windows ve macOS'te resmi depoları kullanarak yazılım kurulumunu basitleştirin"
|
||||
},
|
||||
platform: {
|
||||
select: "Platformunuzu Seçin",
|
||||
description: "Mevcut paketlere göz atmak için işletim sisteminizi ve paket yöneticinizi seçin",
|
||||
selected: "Seçildi"
|
||||
},
|
||||
packages: {
|
||||
browse: "Mevcut Paketler",
|
||||
description: "Kurulum scriptinize dahil edilecek paketleri seçin",
|
||||
search: "Paket ara...",
|
||||
no_packages: "Kriterlerinize uyan paket bulunamadı",
|
||||
filters: {
|
||||
category: "Kategori",
|
||||
type: "Tür",
|
||||
repository: "Depo",
|
||||
gui: "GUI Uygulamaları",
|
||||
cli: "CLI Araçları",
|
||||
official: "Sadece Resmi",
|
||||
third_party: "Üçüncü Parti"
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
title: "Seçilen Paketler",
|
||||
description: "Bu paketler kurulum scriptinize dahil edilecek",
|
||||
none: "Seçilen paket yok",
|
||||
clear_all: "Tümünü Temizle",
|
||||
generate_script: "Script Oluştur",
|
||||
note: "Oluşturulan script yalnızca resmi depoları kullanacak ve idempotent olacak (birden çok kez çalıştırılması güvenli)."
|
||||
},
|
||||
script: {
|
||||
title: "Kurulum Scripti",
|
||||
description: "için idempotent script",
|
||||
official_repos: "Resmi Depolar",
|
||||
official_repos_desc: "Tüm paketler güvenilir kaynaklardan",
|
||||
idempotent: "Idempotent",
|
||||
idempotent_desc: "Birden çok kez çalıştırılması güvenli",
|
||||
packages_count: "Paket",
|
||||
packages_count_desc: "Kurulum için hazır",
|
||||
included_packages: "Dahil Edilen Paketler:",
|
||||
script_content: "Script İçeriği:",
|
||||
copy: "Kopyala",
|
||||
copied: "Kopyalandı!",
|
||||
download: "İndir",
|
||||
usage: "Nasıl kullanılır:",
|
||||
usage_steps: [
|
||||
"Script dosyasını hedef makinenize indirin",
|
||||
"Çalıştırılabilir yapın (Linux/macOS için):",
|
||||
"Scripti uygun izinlerle çalıştırın",
|
||||
"Script otomatik olarak depo kurulumunu ve paket kurulumunu yönetecektir"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface LocaleContextType {
|
||||
locale: Locale
|
||||
changeLocale: (locale: Locale) => void
|
||||
toggleLocale: () => void
|
||||
t: (key: string) => string
|
||||
}
|
||||
|
||||
const LocaleContext = createContext<LocaleContextType | undefined>(undefined)
|
||||
|
||||
export function LocaleProvider({ children }: { children: ReactNode }) {
|
||||
const [locale, setLocale] = useState<Locale>('en')
|
||||
|
||||
useEffect(() => {
|
||||
// Detect user's preferred locale
|
||||
const detectLocale = (): Locale => {
|
||||
const stored = localStorage.getItem('locale') as Locale
|
||||
if (stored && (stored === 'en' || stored === 'tr')) {
|
||||
return stored
|
||||
}
|
||||
|
||||
// Get browser language
|
||||
const browserLang = navigator.language.toLowerCase()
|
||||
if (browserLang.startsWith('tr')) {
|
||||
return 'tr'
|
||||
}
|
||||
|
||||
return 'en'
|
||||
}
|
||||
|
||||
setLocale(detectLocale())
|
||||
}, [])
|
||||
|
||||
const changeLocale = (newLocale: Locale) => {
|
||||
setLocale(newLocale)
|
||||
localStorage.setItem('locale', newLocale)
|
||||
}
|
||||
|
||||
const toggleLocale = () => {
|
||||
changeLocale(locale === 'en' ? 'tr' : 'en')
|
||||
}
|
||||
|
||||
const t = (key: string) => {
|
||||
const keys = key.split('.')
|
||||
let value: any = translations[locale]
|
||||
|
||||
for (const k of keys) {
|
||||
value = value?.[k]
|
||||
}
|
||||
|
||||
return value || key
|
||||
}
|
||||
|
||||
return (
|
||||
<LocaleContext.Provider value={{ locale, changeLocale, toggleLocale, t }}>
|
||||
{children}
|
||||
</LocaleContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useLocale() {
|
||||
const context = useContext(LocaleContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useLocale must be used within a LocaleProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
type Locale = 'en' | 'tr'
|
||||
|
||||
const translations = {
|
||||
en: {
|
||||
common: {
|
||||
title: "RepoHub - Cross-Platform Package Manager",
|
||||
subtitle: "Cross-Platform Package Manager",
|
||||
description: "Simplify software installation across Linux, Windows, and macOS with official repositories"
|
||||
},
|
||||
platform: {
|
||||
select: "Select Your Platform",
|
||||
description: "Choose your operating system and package manager to browse available packages",
|
||||
selected: "Selected"
|
||||
},
|
||||
packages: {
|
||||
browse: "Available Packages",
|
||||
description: "Select packages to include in your installation script",
|
||||
search: "Search packages...",
|
||||
no_packages: "No packages found matching your criteria",
|
||||
filters: {
|
||||
category: "Category",
|
||||
type: "Type",
|
||||
repository: "Repository",
|
||||
gui: "GUI Applications",
|
||||
cli: "CLI Tools",
|
||||
official: "Official Only",
|
||||
third_party: "Third Party"
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
title: "Selected Packages",
|
||||
description: "These packages will be included in your installation script",
|
||||
none: "No packages selected",
|
||||
clear_all: "Clear All",
|
||||
generate_script: "Generate Script",
|
||||
note: "The generated script will use official repositories only and will be idempotent (safe to run multiple times)."
|
||||
},
|
||||
script: {
|
||||
title: "Installation Script",
|
||||
description: "Idempotent script for",
|
||||
official_repos: "Official Repositories",
|
||||
official_repos_desc: "All packages from trusted sources",
|
||||
idempotent: "Idempotent",
|
||||
idempotent_desc: "Safe to run multiple times",
|
||||
packages_count: "Packages",
|
||||
packages_count_desc: "Ready for installation",
|
||||
included_packages: "Included Packages:",
|
||||
script_content: "Script Content:",
|
||||
copy: "Copy",
|
||||
copied: "Copied!",
|
||||
download: "Download",
|
||||
usage: "How to use:",
|
||||
usage_steps: [
|
||||
"Download the script file to your target machine",
|
||||
"Make it executable (for Linux/macOS):",
|
||||
"Run the script with appropriate permissions",
|
||||
"The script will automatically handle repository setup and package installation"
|
||||
]
|
||||
}
|
||||
},
|
||||
tr: {
|
||||
common: {
|
||||
title: "RepoHub - Çok Platformlu Paket Yöneticisi",
|
||||
subtitle: "Çok Platformlu Paket Yöneticisi",
|
||||
description: "Linux, Windows ve macOS'te resmi depoları kullanarak yazılım kurulumunu basitleştirin"
|
||||
},
|
||||
platform: {
|
||||
select: "Platformunuzu Seçin",
|
||||
description: "Mevcut paketlere göz atmak için işletim sisteminizi ve paket yöneticinizi seçin",
|
||||
selected: "Seçildi"
|
||||
},
|
||||
packages: {
|
||||
browse: "Mevcut Paketler",
|
||||
description: "Kurulum scriptinize dahil edilecek paketleri seçin",
|
||||
search: "Paket ara...",
|
||||
no_packages: "Kriterlerinize uyan paket bulunamadı",
|
||||
filters: {
|
||||
category: "Kategori",
|
||||
type: "Tür",
|
||||
repository: "Depo",
|
||||
gui: "GUI Uygulamaları",
|
||||
cli: "CLI Araçları",
|
||||
official: "Sadece Resmi",
|
||||
third_party: "Üçüncü Parti"
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
title: "Seçilen Paketler",
|
||||
description: "Bu paketler kurulum scriptinize dahil edilecek",
|
||||
none: "Seçilen paket yok",
|
||||
clear_all: "Tümünü Temizle",
|
||||
generate_script: "Script Oluştur",
|
||||
note: "Oluşturulan script yalnızca resmi depoları kullanacak ve idempotent olacak (birden çok kez çalıştırılması güvenli)."
|
||||
},
|
||||
script: {
|
||||
title: "Kurulum Scripti",
|
||||
description: "için idempotent script",
|
||||
official_repos: "Resmi Depolar",
|
||||
official_repos_desc: "Tüm paketler güvenilir kaynaklardan",
|
||||
idempotent: "Idempotent",
|
||||
idempotent_desc: "Birden çok kez çalıştırılması güvenli",
|
||||
packages_count: "Paket",
|
||||
packages_count_desc: "Kurulum için hazır",
|
||||
included_packages: "Dahil Edilen Paketler:",
|
||||
script_content: "Script İçeriği:",
|
||||
copy: "Kopyala",
|
||||
copied: "Kopyalandı!",
|
||||
download: "İndir",
|
||||
usage: "Nasıl kullanılır:",
|
||||
usage_steps: [
|
||||
"Script dosyasını hedef makinenize indirin",
|
||||
"Çalıştırılabilir yapın (Linux/macOS için):",
|
||||
"Scripti uygun izinlerle çalıştırın",
|
||||
"Script otomatik olarak depo kurulumunu ve paket kurulumunu yönetecektir"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function useLocale() {
|
||||
const [locale, setLocale] = useState<Locale>('en')
|
||||
|
||||
useEffect(() => {
|
||||
// Detect user's preferred locale
|
||||
const detectLocale = (): Locale => {
|
||||
const stored = localStorage.getItem('locale') as Locale
|
||||
if (stored && (stored === 'en' || stored === 'tr')) {
|
||||
return stored
|
||||
}
|
||||
|
||||
// Get browser language
|
||||
const browserLang = navigator.language.toLowerCase()
|
||||
if (browserLang.startsWith('tr')) {
|
||||
return 'tr'
|
||||
}
|
||||
|
||||
return 'en'
|
||||
}
|
||||
|
||||
setLocale(detectLocale())
|
||||
}, [])
|
||||
|
||||
const changeLocale = (newLocale: Locale) => {
|
||||
setLocale(newLocale)
|
||||
localStorage.setItem('locale', newLocale)
|
||||
}
|
||||
|
||||
const toggleLocale = () => {
|
||||
changeLocale(locale === 'en' ? 'tr' : 'en')
|
||||
}
|
||||
|
||||
const t = (key: string) => {
|
||||
const keys = key.split('.')
|
||||
let value: any = translations[locale]
|
||||
|
||||
for (const k of keys) {
|
||||
value = value?.[k]
|
||||
}
|
||||
|
||||
return value || key
|
||||
}
|
||||
|
||||
return {
|
||||
locale,
|
||||
changeLocale,
|
||||
toggleLocale,
|
||||
t
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
type Theme = 'light' | 'dark' | 'system'
|
||||
|
||||
export function useTheme() {
|
||||
const [theme, setTheme] = useState<Theme>('system')
|
||||
const [isDark, setIsDark] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// Get stored theme or default to system
|
||||
const stored = localStorage.getItem('theme') as Theme
|
||||
setTheme(stored || 'system')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const updateTheme = () => {
|
||||
if (theme === 'system') {
|
||||
setIsDark(window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
} else {
|
||||
setIsDark(theme === 'dark')
|
||||
}
|
||||
}
|
||||
|
||||
updateTheme()
|
||||
|
||||
if (theme === 'system') {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
mediaQuery.addEventListener('change', updateTheme)
|
||||
return () => mediaQuery.removeEventListener('change', updateTheme)
|
||||
}
|
||||
}, [theme])
|
||||
|
||||
useEffect(() => {
|
||||
// Apply theme to document
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
}, [isDark])
|
||||
|
||||
const setThemeMode = (newTheme: Theme) => {
|
||||
setTheme(newTheme)
|
||||
localStorage.setItem('theme', newTheme)
|
||||
}
|
||||
|
||||
const toggleTheme = () => {
|
||||
if (theme === 'light') {
|
||||
setThemeMode('dark')
|
||||
} else if (theme === 'dark') {
|
||||
setThemeMode('light')
|
||||
} else {
|
||||
setThemeMode(isDark ? 'light' : 'dark')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
theme,
|
||||
isDark,
|
||||
setThemeMode,
|
||||
toggleTheme
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user