@@ -4,28 +4,39 @@ import { Navbar } from './components/Navbar';
44import { VersionCard } from './components/VersionCard' ;
55import { WallpaperGrid } from './components/WallpaperGrid' ;
66import { Settings } from './components/Settings' ;
7- import { AppSection , ReleaseInfo , ThemeId } from './types' ;
7+ import { Gallery } from './components/Gallery' ;
8+ import { Team } from './components/Team' ;
9+ import { AppSection , ReleaseInfo , ThemeId , Language } from './types' ;
810import { RELEASE_INFO_URL } from './constants' ;
911import { parseHackerReleaseFile } from './utils/parser' ;
1012import { Loader2 , WifiOff , Terminal } from 'lucide-react' ;
1113import { applyTheme } from './utils/theme' ;
14+ import { TRANSLATIONS } from './utils/translations' ;
1215
1316export default function App ( ) {
1417 const [ currentSection , setCurrentSection ] = useState < AppSection > ( AppSection . RELEASES ) ;
1518 const [ releases , setReleases ] = useState < ReleaseInfo [ ] > ( [ ] ) ;
1619 const [ loading , setLoading ] = useState ( true ) ;
1720 const [ error , setError ] = useState < string | null > ( null ) ;
1821 const [ currentTheme , setCurrentTheme ] = useState < ThemeId > ( 'hacker' ) ;
22+ const [ currentLanguage , setCurrentLanguage ] = useState < Language > ( 'pl' ) ;
1923
20- // Load saved theme on boot
24+ // Load saved settings on boot
2125 useEffect ( ( ) => {
22- const saved = localStorage . getItem ( 'hackeros_theme' ) as ThemeId ;
23- if ( saved ) {
24- setCurrentTheme ( saved ) ;
25- applyTheme ( saved ) ;
26+ // Theme
27+ const savedTheme = localStorage . getItem ( 'hackeros_theme' ) as ThemeId ;
28+ if ( savedTheme ) {
29+ setCurrentTheme ( savedTheme ) ;
30+ applyTheme ( savedTheme ) ;
2631 } else {
2732 applyTheme ( 'hacker' ) ;
2833 }
34+
35+ // Language
36+ const savedLang = localStorage . getItem ( 'hackeros_lang' ) as Language ;
37+ if ( savedLang ) {
38+ setCurrentLanguage ( savedLang ) ;
39+ }
2940 } , [ ] ) ;
3041
3142 const handleThemeChange = ( id : ThemeId ) => {
@@ -34,13 +45,20 @@ export default function App() {
3445 localStorage . setItem ( 'hackeros_theme' , id ) ;
3546 } ;
3647
48+ const handleLanguageChange = ( lang : Language ) => {
49+ setCurrentLanguage ( lang ) ;
50+ localStorage . setItem ( 'hackeros_lang' , lang ) ;
51+ } ;
52+
53+ const t = TRANSLATIONS [ currentLanguage ] ;
54+
3755 useEffect ( ( ) => {
3856 const fetchData = async ( ) => {
3957 try {
4058 setLoading ( true ) ;
4159 // Add cache busting
4260 const response = await fetch ( `${ RELEASE_INFO_URL } ?t=${ Date . now ( ) } ` ) ;
43-
61+
4462 if ( ! response . ok ) throw new Error ( 'Network error' ) ;
4563
4664 const text = await response . text ( ) ;
@@ -62,93 +80,103 @@ export default function App() {
6280 case AppSection . RELEASES :
6381 return (
6482 < div className = "px-4 pb-24 pt-2 max-w-lg mx-auto" >
65- < header className = "px-2 mb-6 flex flex-col justify-center" >
66- < h1 className = "text-3xl font-mono font-bold text-white flex items-center gap-3" >
67- HackerOS
68- < span className = "px-2 py-0.5 rounded text-[10px] font-bold bg-primary text-background border border-primary/50" >
69- MOBILE
70- </ span >
71- </ h1 >
72- < p className = "text-muted text-sm flex items-center gap-2 mt-1" >
73- < span className = "w-2 h-2 rounded-full bg-green-500 animate-pulse" > </ span >
74- System Systems Online
75- </ p >
76- </ header >
77-
78- { loading ? (
79- < div className = "flex flex-col items-center justify-center h-[50vh] text-muted space-y-4" >
80- < div className = "relative" >
81- < div className = "absolute inset-0 bg-primary/20 blur-xl rounded-full" > </ div >
82- < Loader2 className = "animate-spin relative z-10 text-primary" size = { 48 } />
83- </ div >
84- < p className = "font-mono text-xs animate-pulse" > DECRYPTING_PACKETS...</ p >
85- </ div >
86- ) : error ? (
87- < div className = "flex flex-col items-center justify-center h-[50vh] text-red-400 text-center px-6" >
88- < div className = "bg-red-500/10 p-4 rounded-full mb-4 ring-1 ring-red-500/20" >
89- < WifiOff size = { 40 } />
90- </ div >
91- < p className = "font-bold mb-2" > SIGNAL_LOST</ p >
92- < p className = "text-xs text-muted mb-6" > { error } </ p >
93- < button
94- onClick = { ( ) => window . location . reload ( ) }
95- className = "px-6 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg text-sm font-bold shadow-lg transition-colors"
96- >
97- RETRY_CONNECTION
98- </ button >
99- </ div >
100- ) : (
101- < div className = "space-y-4" >
102- { releases . map ( ( release , index ) => (
103- < motion . div
104- key = { index }
105- initial = { { opacity : 0 , y : 20 } }
106- animate = { { opacity : 1 , y : 0 } }
107- transition = { { delay : index * 0.1 } }
108- >
109- < VersionCard
110- release = { release }
111- isLatest = { index === 0 }
112- />
113- </ motion . div >
114- ) ) }
83+ < header className = "px-2 mb-6 flex flex-col justify-center" >
84+ < h1 className = "text-3xl font-mono font-bold text-white flex items-center gap-3" >
85+ HackerOS
86+ < span className = "px-2 py-0.5 rounded text-[10px] font-bold bg-primary text-background border border-primary/50" >
87+ { t . sub_releases }
88+ </ span >
89+ </ h1 >
90+ </ header >
11591
116- < div className = "text-center py-8 opacity-50" >
117- < Terminal size = { 24 } className = "mx-auto mb-2 text-muted" />
118- < p className = "text-[10px] font-mono text-muted uppercase" > End of Log</ p >
119- </ div >
120- </ div >
121- ) }
92+ { loading ? (
93+ < div className = "flex flex-col items-center justify-center h-[50vh] text-muted space-y-4" >
94+ < div className = "relative" >
95+ < div className = "absolute inset-0 bg-primary/20 blur-xl rounded-full" > </ div >
96+ < Loader2 className = "animate-spin relative z-10 text-primary" size = { 48 } />
97+ </ div >
98+ < p className = "font-mono text-xs animate-pulse" > { t . decrypting } </ p >
99+ </ div >
100+ ) : error ? (
101+ < div className = "flex flex-col items-center justify-center h-[50vh] text-red-400 text-center px-6" >
102+ < div className = "bg-red-500/10 p-4 rounded-full mb-4 ring-1 ring-red-500/20" >
103+ < WifiOff size = { 40 } />
104+ </ div >
105+ < p className = "font-bold mb-2" > { t . error_signal } </ p >
106+ < p className = "text-xs text-muted mb-6" > { t . error_network } </ p >
107+ < button
108+ onClick = { ( ) => window . location . reload ( ) }
109+ className = "px-6 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg text-sm font-bold shadow-lg transition-colors"
110+ >
111+ { t . retry }
112+ </ button >
113+ </ div >
114+ ) : (
115+ < div className = "space-y-4" >
116+ { releases . map ( ( release , index ) => (
117+ < motion . div
118+ key = { index }
119+ initial = { { opacity : 0 , y : 20 } }
120+ animate = { { opacity : 1 , y : 0 } }
121+ transition = { { delay : index * 0.1 } }
122+ >
123+ < VersionCard
124+ release = { release }
125+ isLatest = { index === 0 }
126+ language = { currentLanguage }
127+ />
128+ </ motion . div >
129+ ) ) }
130+
131+ < div className = "text-center py-8 opacity-50" >
132+ < Terminal size = { 24 } className = "mx-auto mb-2 text-muted" />
133+ < p className = "text-[10px] font-mono text-muted uppercase" > { t . end_of_log } </ p >
134+ </ div >
135+ </ div >
136+ ) }
122137 </ div >
123138 ) ;
124- case AppSection . WALLPAPERS :
125- return < WallpaperGrid /> ;
126- case AppSection . SETTINGS :
127- return < Settings currentTheme = { currentTheme } setTheme = { handleThemeChange } /> ;
128- default :
129- return null ;
139+ case AppSection . WALLPAPERS :
140+ return < WallpaperGrid language = { currentLanguage } /> ;
141+ case AppSection . GALLERY :
142+ return < Gallery language = { currentLanguage } /> ;
143+ case AppSection . TEAM :
144+ return < Team language = { currentLanguage } /> ;
145+ case AppSection . SETTINGS :
146+ return < Settings
147+ currentTheme = { currentTheme }
148+ setTheme = { handleThemeChange }
149+ language = { currentLanguage }
150+ setLanguage = { handleLanguageChange }
151+ /> ;
152+ default :
153+ return null ;
130154 }
131155 } ;
132156
133157 return (
134158 < div className = "min-h-screen bg-background text-text selection:bg-primary/30 safe-area-top" >
135- { /* Background ambient glow */ }
136- < div className = "fixed top-0 left-0 w-full h-96 bg-primary/5 blur-[100px] pointer-events-none" />
137-
138- < AnimatePresence mode = "wait" >
139- < motion . main
140- key = { currentSection }
141- initial = { { opacity : 0 , x : 10 } }
142- animate = { { opacity : 1 , x : 0 } }
143- exit = { { opacity : 0 , x : - 10 } }
144- transition = { { duration : 0.2 } }
145- className = "relative z-10"
146- >
147- { renderContent ( ) }
148- </ motion . main >
149- </ AnimatePresence >
150-
151- < Navbar currentSection = { currentSection } onSectionChange = { setCurrentSection } />
159+ { /* Background ambient glow */ }
160+ < div className = "fixed top-0 left-0 w-full h-96 bg-primary/5 blur-[100px] pointer-events-none" />
161+
162+ < AnimatePresence mode = "wait" >
163+ < motion . main
164+ key = { currentSection }
165+ initial = { { opacity : 0 , x : 10 } }
166+ animate = { { opacity : 1 , x : 0 } }
167+ exit = { { opacity : 0 , x : - 10 } }
168+ transition = { { duration : 0.2 } }
169+ className = "relative z-10"
170+ >
171+ { renderContent ( ) }
172+ </ motion . main >
173+ </ AnimatePresence >
174+
175+ < Navbar
176+ currentSection = { currentSection }
177+ onSectionChange = { setCurrentSection }
178+ language = { currentLanguage }
179+ />
152180 </ div >
153181 ) ;
154182}
0 commit comments