Skip to content

Commit b80c01e

Browse files
committed
feat: add i18, fix mobile view as intro, add aesthetics bento
1 parent a505529 commit b80c01e

11 files changed

Lines changed: 505 additions & 136 deletions

File tree

components/GridItems/Languages.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const Languages = ({ item }: { item: CombinedInterfaces }) => {
7777
</motion.div>
7878
</div>
7979
)}
80-
<div className="flex flex-col items-end justify-end w-full h-full overflow-hidden rounded-3xl p-1">
80+
<div className="relative flex flex-col items-end justify-end w-full h-full overflow-hidden rounded-3xl p-1">
8181
{/* Overlay */}
8282
<div className="absolute inset-0 z-10 bg-gradient-to-b from-transparent via-neutral-950/30 to-neutral-950/30" />
8383
{/* Image */}
@@ -86,11 +86,12 @@ const Languages = ({ item }: { item: CombinedInterfaces }) => {
8686
src={image ?? ""}
8787
alt="languages"
8888
fill
89+
priority={image === "/background.jpg"}
8990
/>
9091

9192
{/* Content */}
92-
<div className="relative z-20 w-full p-4 space-y-3 md:p-8">
93-
<div className="text-sm font-medium text-white mb-72">
93+
<div className="relative z-20 flex h-full w-full flex-col justify-between p-4 md:p-8">
94+
<div className="text-sm font-medium text-white drop-shadow">
9495
{item.title}
9596
</div>
9697
<div className="flex flex-wrap items-center gap-3">

components/I18n/index.tsx

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
"use client";
2+
3+
import { siteConfig as baseSiteConfig } from "@/config/site-config";
4+
import { createContext, ReactNode, useContext, useMemo, useState } from "react";
5+
6+
type Language = "en" | "ml" | "zh" | "ja" | "hi";
7+
type SiteConfig = typeof baseSiteConfig;
8+
9+
const translations: Record<Language, Record<string, string>> = {
10+
en: {},
11+
ml: {
12+
"Hi!": "ഹായ്!",
13+
"I am Bala": "ഞാൻ ബാലയാണ്",
14+
"Senior SWE - ELT, Web, OSS Dev": "സീനിയർ SWE - ELT, വെബ്, OSS ഡെവ്",
15+
"Remote, India": "റിമോട്ട്, ഇന്ത്യ",
16+
"Contact Me": "ബന്ധപ്പെടുക",
17+
"Before you go... Please reach out if you have any Research Software Engineer Positions available. Very much interested :)":
18+
"പോകുന്നതിന് മുൻപ്... Research Software Engineer ഒഴിവുകൾ ഉണ്ടെങ്കിൽ പറയണം. വളരെ താൽപ്പര്യം :)",
19+
"Portfolio website powered by Next.js, Tailwind CSS":
20+
"Next.js, Tailwind CSS ഉപയോഗിച്ച് നിർമ്മിച്ച പോർട്ട്ഫോളിയോ വെബ്‌സൈറ്റ്",
21+
Follow: "ഫോളോ",
22+
Subscribe: "സബ്സ്ക്രൈബ്",
23+
Join: "ചേരുക",
24+
"View profile": "പ്രൊഫൈൽ കാണുക",
25+
"See badges 🏅": "ബാഡ്ജുകൾ കാണുക 🏅",
26+
"Support me ☕": "സപ്പോർട്ട് ചെയ്യുക ☕",
27+
"See my profile": "പ്രൊഫൈൽ കാണുക",
28+
"See my posts": "പോസ്റ്റുകൾ കാണുക",
29+
"Open source contributions": "ഓപ്പൺ സോഴ്‌സ് സംഭാവനകൾ",
30+
"Things I Love": "എനിക്ക് ഇഷ്ടമുള്ള കാര്യങ്ങൾ",
31+
"Technical certificates": "ടെക്നിക്കൽ സർട്ടിഫിക്കറ്റുകൾ",
32+
"Community Workshops": "കമ്മ്യൂണിറ്റി വർക്ക്‌ഷോപ്പുകൾ",
33+
"Best Outgoing Student": "മികച്ച ഔട്ട്‌ഗോയിങ് വിദ്യാർത്ഥി",
34+
"Department Topper": "ഡിപ്പാർട്ട്മെന്റ് ടോപ്പർ",
35+
"Mathematics Topper": "മാത്തമാറ്റിക്സ് ടോപ്പർ",
36+
"Best Final Year Project": "മികച്ച ഫൈനൽ ഇയർ പ്രോജക്റ്റ്",
37+
"Nice to meet you": "കണ്ടതിൽ സന്തോഷം",
38+
About: "കുറിച്ച്",
39+
Experience: "പരിചയം",
40+
Achievements: "നേട്ടങ്ങൾ",
41+
Projects: "പ്രോജക്റ്റുകൾ",
42+
},
43+
zh: {
44+
"Hi!": "你好!",
45+
"I am Bala": "我是 Bala",
46+
"Senior SWE - ELT, Web, OSS Dev": "高级软件工程师 - ELT、Web、开源开发",
47+
"Remote, India": "远程,印度",
48+
"Contact Me": "联系我",
49+
"Before you go... Please reach out if you have any Research Software Engineer Positions available. Very much interested :)":
50+
"在你离开前... 如果有研究软件工程师职位,请联系我。我非常感兴趣 :)",
51+
"Portfolio website powered by Next.js, Tailwind CSS":
52+
"由 Next.js 和 Tailwind CSS 驱动的作品集网站",
53+
Follow: "关注",
54+
Subscribe: "订阅",
55+
Join: "加入",
56+
"View profile": "查看资料",
57+
"See badges 🏅": "查看徽章 🏅",
58+
"Support me ☕": "支持我 ☕",
59+
"See my profile": "查看我的资料",
60+
"See my posts": "查看我的文章",
61+
"Open source contributions": "开源贡献",
62+
"Things I Love": "我喜欢的事物",
63+
"Technical certificates": "技术证书",
64+
"Community Workshops": "社区工作坊",
65+
"Best Outgoing Student": "最佳毕业生",
66+
"Department Topper": "系级第一名",
67+
"Mathematics Topper": "数学第一名",
68+
"Best Final Year Project": "最佳毕业项目",
69+
"Nice to meet you": "很高兴认识你",
70+
About: "关于",
71+
Experience: "经历",
72+
Achievements: "成就",
73+
Projects: "项目",
74+
},
75+
ja: {
76+
"Hi!": "こんにちは!",
77+
"I am Bala": "Bala です",
78+
"Senior SWE - ELT, Web, OSS Dev": "シニアソフトウェアエンジニア - ELT、Web、OSS 開発",
79+
"Remote, India": "リモート、インド",
80+
"Contact Me": "連絡する",
81+
"Before you go... Please reach out if you have any Research Software Engineer Positions available. Very much interested :)":
82+
"行く前に... 研究ソフトウェアエンジニアの募集があればぜひ連絡してください。とても興味があります :)",
83+
"Portfolio website powered by Next.js, Tailwind CSS":
84+
"Next.js と Tailwind CSS で作られたポートフォリオサイト",
85+
Follow: "フォロー",
86+
Subscribe: "登録",
87+
Join: "参加",
88+
"View profile": "プロフィールを見る",
89+
"See badges 🏅": "バッジを見る 🏅",
90+
"Support me ☕": "支援する ☕",
91+
"See my profile": "プロフィールを見る",
92+
"See my posts": "投稿を見る",
93+
"Open source contributions": "オープンソース貢献",
94+
"Things I Love": "好きなもの",
95+
"Technical certificates": "技術証明書",
96+
"Community Workshops": "コミュニティワークショップ",
97+
"Best Outgoing Student": "最優秀卒業生",
98+
"Department Topper": "学科首席",
99+
"Mathematics Topper": "数学首席",
100+
"Best Final Year Project": "最優秀卒業制作",
101+
"Nice to meet you": "よろしくお願いします",
102+
About: "概要",
103+
Experience: "経験",
104+
Achievements: "実績",
105+
Projects: "プロジェクト",
106+
},
107+
hi: {
108+
"Hi!": "नमस्ते!",
109+
"I am Bala": "मैं बाला हूं",
110+
"Senior SWE - ELT, Web, OSS Dev": "सीनियर SWE - ELT, वेब, OSS डेवलपमेंट",
111+
"Remote, India": "रिमोट, भारत",
112+
"Contact Me": "संपर्क करें",
113+
"Before you go... Please reach out if you have any Research Software Engineer Positions available. Very much interested :)":
114+
"जाने से पहले... अगर कोई Research Software Engineer पद उपलब्ध हो तो कृपया संपर्क करें। बहुत रुचि है :)",
115+
"Portfolio website powered by Next.js, Tailwind CSS":
116+
"Next.js और Tailwind CSS से बना पोर्टफोलियो वेबसाइट",
117+
Follow: "फॉलो करें",
118+
Subscribe: "सब्सक्राइब",
119+
Join: "जुड़ें",
120+
"View profile": "प्रोफाइल देखें",
121+
"See badges 🏅": "बैज देखें 🏅",
122+
"Support me ☕": "सपोर्ट करें ☕",
123+
"See my profile": "मेरी प्रोफाइल देखें",
124+
"See my posts": "मेरी पोस्ट देखें",
125+
"Open source contributions": "ओपन सोर्स योगदान",
126+
"Things I Love": "मेरी पसंदीदा चीजें",
127+
"Technical certificates": "तकनीकी प्रमाणपत्र",
128+
"Community Workshops": "कम्युनिटी वर्कशॉप",
129+
"Best Outgoing Student": "सर्वश्रेष्ठ आउटगोइंग छात्र",
130+
"Department Topper": "विभाग टॉपर",
131+
"Mathematics Topper": "गणित टॉपर",
132+
"Best Final Year Project": "सर्वश्रेष्ठ अंतिम वर्ष प्रोजेक्ट",
133+
"Nice to meet you": "आपसे मिलकर खुशी हुई",
134+
About: "परिचय",
135+
Experience: "अनुभव",
136+
Achievements: "उपलब्धियां",
137+
Projects: "प्रोजेक्ट्स",
138+
},
139+
};
140+
141+
const translateText = (value: string, language: Language) =>
142+
translations[language][value] ?? value;
143+
144+
const translateValue = (value: unknown, language: Language): unknown => {
145+
if (typeof value === "string") {
146+
return translations[language][value] ?? value;
147+
}
148+
149+
if (Array.isArray(value)) {
150+
return value.map((item) => translateValue(item, language));
151+
}
152+
153+
if (value && typeof value === "object") {
154+
return Object.fromEntries(
155+
Object.entries(value).map(([key, entry]) => [key, translateValue(entry, language)])
156+
);
157+
}
158+
159+
return value;
160+
};
161+
162+
const I18nContext = createContext<{
163+
language: Language;
164+
setLanguage: (language: Language) => void;
165+
t: (value: string) => string;
166+
siteConfig: SiteConfig;
167+
} | null>(null);
168+
169+
export const I18nProvider = ({ children }: { children: ReactNode }) => {
170+
const [language, setLanguage] = useState<Language>("en");
171+
172+
const siteConfig = useMemo(
173+
() => translateValue(baseSiteConfig, language) as SiteConfig,
174+
[language]
175+
);
176+
const t = (value: string) => translateText(value, language);
177+
178+
return (
179+
<I18nContext.Provider value={{ language, setLanguage, t, siteConfig }}>
180+
{children}
181+
</I18nContext.Provider>
182+
);
183+
};
184+
185+
export const useI18n = () => {
186+
const context = useContext(I18nContext);
187+
if (!context) {
188+
return {
189+
language: "en" as Language,
190+
setLanguage: () => {},
191+
t: (value: string) => value,
192+
siteConfig: baseSiteConfig,
193+
};
194+
}
195+
196+
return context;
197+
};
198+
199+
export const LanguageToggle = () => {
200+
const { language, setLanguage } = useI18n();
201+
const languages: Language[] = ["en", "zh", "ja", "hi", "ml"];
202+
const nextLanguage = languages[(languages.indexOf(language) + 1) % languages.length];
203+
204+
return (
205+
<button
206+
type="button"
207+
onClick={() => setLanguage(nextLanguage)}
208+
className="mt-1 flex cursor-pointer rounded-lg bg-white px-2 py-2 text-sm font-semibold shadow-md transition-colors hover:bg-neutral-100 dark:bg-neutral-800 dark:hover:bg-neutral-700"
209+
aria-label="Change language"
210+
title={`Change language to ${nextLanguage.toUpperCase()}`}
211+
>
212+
{language.toUpperCase()}
213+
</button>
214+
);
215+
};

components/LeftPanel/Bio.tsx

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,96 @@
11
"use client";
2-
import { siteConfig } from "@/config/site-config";
32
import { Mail, MapPin } from "lucide-react";
43
import Image from "next/image";
54
import { ThemeToggle } from "@/components/Theme";
65
import RippleButton from "@/components/common/StyledLinkButton";
6+
import { motion } from "framer-motion";
7+
import { LanguageToggle, useI18n } from "@/components/I18n";
8+
9+
const Bio = ({ compact = false }: { compact?: boolean }) => {
10+
const { siteConfig, t } = useI18n();
11+
12+
if (compact) {
13+
return (
14+
<div className="shadow-md rounded-xl border border-neutral-200/70 dark:border-neutral-800 bg-white/70 dark:bg-neutral-950/40 p-5">
15+
<div className="grid grid-cols-2 gap-3">
16+
<div className="flex flex-col items-center">
17+
<Image
18+
priority
19+
loading="eager"
20+
alt="Avatar"
21+
placeholder="blur"
22+
src="/profile.jpg"
23+
width={132}
24+
height={132}
25+
blurDataURL="/profile.jpg"
26+
className="aspect-square h-auto w-full rounded-xl object-cover object-center shadow-lg"
27+
/>
28+
</div>
29+
30+
<div className="flex min-w-0 flex-col justify-center">
31+
<p className="text-lg font-semibold leading-none bg-gradient-to-r from-orange-400 to-blue-500 bg-clip-text text-transparent">
32+
{siteConfig.title}
33+
</p>
34+
<h1 className="mt-3 text-3xl font-bold leading-tight">
35+
{siteConfig.creator}!
36+
</h1>
37+
<p className="mt-2 text-base font-light leading-snug text-neutral-500">
38+
{siteConfig.bio}
39+
</p>
40+
</div>
41+
</div>
42+
43+
<div className="mt-4 grid grid-cols-2 gap-3">
44+
<div className="flex items-center justify-center gap-2">
45+
<ThemeToggle />
46+
<LanguageToggle />
47+
</div>
48+
49+
<motion.div
50+
className="mt-1 min-w-0 rounded-[15%] border border-blue-500/25 bg-blue-500/10 px-3 py-2 text-center text-sm font-medium shadow-[0_0_18px_rgba(59,130,246,0.12)]"
51+
animate={{
52+
opacity: [0.78, 1, 0.78],
53+
y: [0, -3, 0],
54+
scale: [1, 1.03, 1],
55+
boxShadow: [
56+
"0 0 12px rgba(59, 130, 246, 0.12)",
57+
"0 0 26px rgba(168, 85, 247, 0.32)",
58+
"0 0 12px rgba(59, 130, 246, 0.12)",
59+
],
60+
}}
61+
whileHover={{ rotate: -1, scale: 1.05 }}
62+
transition={{ duration: 2.1, repeat: Infinity, ease: "easeInOut" }}
63+
>
64+
<span className="block min-w-0 animate-pulse truncate bg-gradient-to-r from-blue-500 via-purple-500 to-orange-400 bg-clip-text text-transparent">
65+
{t("Nice to meet you")}
66+
</span>
67+
</motion.div>
68+
</div>
69+
70+
<div className="grid grid-cols-2 gap-3 mt-5">
71+
<RippleButton
72+
element={<MapPin size="14" />}
73+
buttonText={siteConfig.location}
74+
style={{
75+
backgroundImage: "linear-gradient(to right, #FFA07A, #FF4500)",
76+
transition: "background 0.5s ease",
77+
}}
78+
href={siteConfig.locationLink}
79+
/>
80+
<RippleButton
81+
element={<Mail size="14" />}
82+
buttonText={siteConfig.contactLabel}
83+
style={{
84+
backgroundImage: "linear-gradient(to right, #87CEEB, #4682B4)",
85+
transition: "background 0.5s ease",
86+
}}
87+
href={`mailto:${siteConfig.email}`}
88+
/>
89+
</div>
90+
</div>
91+
);
92+
}
793

8-
const Bio = () => {
994
return (
1095
<div className="shadow-md rounded-xl backdrop-filter backdrop-blur-lg bg-opacity-20 bg-white dark:bg-neutral-900 bg-opacity-10/80 border-opacity-50 p-6">
1196
<div>
@@ -19,9 +104,10 @@ const Bio = () => {
19104
height={120}
20105
blurDataURL="/profile.jpg"
21106
style={{
22-
borderRadius: "50%",
107+
borderRadius: "15%",
23108
border: "0.1px solid transparent",
24109
}}
110+
className="h-32 w-32 rounded-xl object-cover object-center shadow-lg"
25111
/>
26112
</div>
27113

@@ -56,7 +142,7 @@ const Bio = () => {
56142
/>
57143
<RippleButton
58144
element={<Mail size="14" />}
59-
buttonText={`Contact Me`}
145+
buttonText={siteConfig.contactLabel}
60146
style={{
61147
backgroundImage: "linear-gradient(to right, #87CEEB, #4682B4)",
62148
transition: "background 0.5s ease",

0 commit comments

Comments
 (0)