feat: implement internationalization with next-intl and add core portfolio sections

This commit is contained in:
Yolando
2026-03-28 19:59:39 +07:00
parent 5b0254d71b
commit 39f8567519
17 changed files with 1304 additions and 253 deletions

143
messages/en.json Normal file
View File

@@ -0,0 +1,143 @@
{
"Navigation": {
"experience": "Experience",
"techStack": "Tech Stack",
"projects": "Projects",
"contact": "Contact"
},
"Hero": {
"badge": "Available for opportunities",
"titlePart1": "Building",
"titleHighlight": "Secure, Scalable",
"titlePart2": "Enterprise-Grade Systems",
"yearsExp": "3+ Years",
"subtitle": "in Banking Technology. Backend Developer specializing in Java Spring Boot, Microservices Architecture, and Enterprise Security.",
"ctaContact": "Get in Touch",
"ctaProjects": "View Projects",
"scroll": "Scroll"
},
"Experience": {
"badge": "Career Journey",
"title": "Experience & Evolution",
"subtitle": "A timeline of building enterprise-grade systems in the banking technology industry.",
"jobs": {
"enterprise": {
"year": "2024 — Present",
"title": "Senior Backend Developer",
"company": "Enterprise Banking Corp",
"description": "Leading microservices architecture design and implementation for core banking platform.",
"achievements": [
"Architected event-driven system processing 500K+ transactions/day",
"Reduced API response time by 40% through caching optimization",
"Mentored team of 4 junior developers"
]
},
"digital": {
"year": "2023 — 2024",
"title": "Backend Developer",
"company": "Digital Banking Solutions",
"description": "Developed and maintained Spring Boot microservices for payment processing and customer management.",
"achievements": [
"Built real-time notification service with Apache Kafka",
"Implemented OAuth2 + JWT authentication system",
"Achieved 99.9% uptime on production services"
]
},
"fintech": {
"year": "2021 — 2023",
"title": "Junior Backend Developer",
"company": "FinTech Startup",
"description": "Started career building REST APIs and database management for financial applications.",
"achievements": [
"Designed PostgreSQL schema handling 1M+ records",
"Created automated CI/CD pipeline with Jenkins",
"Developed unit & integration test coverage to 85%"
]
}
}
},
"TechStack": {
"badge": "Tech Arsenal",
"title": "Technology Stack",
"subtitle": "A comprehensive toolkit forged through years of enterprise development, from backend infrastructure to mobile interfaces.",
"categories": {
"backend": {
"title": "Enterprise Backend",
"description": "Core systems & microservices"
},
"infra": {
"title": "Database & Infrastructure",
"description": "Data management & DevOps"
},
"frontend": {
"title": "Frontend Development",
"description": "Modern web interfaces"
},
"mobile": {
"title": "Mobile Development",
"description": "Cross-platform apps"
}
}
},
"Projects": {
"badge": "Portfolio",
"title": "Projects & Case Studies",
"subtitle": "Real-world enterprise solutions built for scale, security, and reliability.",
"filters": {
"all": "All Projects",
"backend": "Backend",
"frontend": "Frontend",
"mobile": "Mobile"
},
"items": {
"apiGateway": {
"title": "Core Banking API Gateway",
"description": "High-performance API Gateway handling 500K+ daily transactions with rate limiting, circuit breaker pattern, and distributed tracing across 12 microservices.",
"metrics": "Increased throughput by 40%, 99.9% uptime"
},
"paymentEngine": {
"title": "Payment Processing Engine",
"description": "Event-driven payment system with Saga pattern for distributed transactions, supporting real-time transfers, bill payments, and batch processing.",
"metrics": "Processing 200K+ payments/day"
},
"onboarding": {
"title": "Customer Onboarding Portal",
"description": "Modern onboarding portal with multi-step KYC verification, document upload, and real-time status tracking for banking customers."
},
"dashboard": {
"title": "Internal Dashboard",
"description": "Admin dashboard for monitoring API performance, user analytics, and system health with real-time WebSocket updates."
},
"mobileApp": {
"title": "Mobile Banking App",
"description": "Cross-platform mobile banking application with biometric authentication, push notifications, and offline transaction history."
},
"authService": {
"title": "Authentication Microservice",
"description": "Centralized auth service with OAuth2, JWT, MFA, and session management. Supports SSO across 8 internal applications.",
"metrics": "Securing 50K+ active users"
}
}
},
"Contact": {
"badge": "Let's Connect",
"title": "Get in Touch",
"subtitle": "Interested in working together? Whether you're a recruiter, hiring manager, or potential collaborator — I'd love to hear from you.",
"form": {
"nameLabel": "Full Name",
"namePlaceholder": "John Doe",
"emailLabel": "Email Address",
"emailPlaceholder": "john@company.com",
"messageLabel": "Message",
"messagePlaceholder": "Tell me about the opportunity or project you have in mind...",
"submit": "Send Message",
"submitting": "Sending...",
"successTitle": "Message Sent!",
"successDesc": "Thank you for reaching out. I'll get back to you soon."
}
},
"Footer": {
"backToTop": "Back to top",
"copyright": "Yolando. Built with Next.js & crafted with purpose."
}
}

143
messages/id.json Normal file
View File

@@ -0,0 +1,143 @@
{
"Navigation": {
"experience": "Pengalaman",
"techStack": "Tech Stack",
"projects": "Proyek",
"contact": "Kontak"
},
"Hero": {
"badge": "Tersedia untuk peluang baru",
"titlePart1": "Membangun Sistem",
"titleHighlight": "Aman & Skalabel",
"titlePart2": "Skala Enterprise",
"yearsExp": "3+ Tahun",
"subtitle": "di Teknologi Perbankan. Backend Developer dengan spesialisasi Java Spring Boot, Arsitektur Microservices, dan Keamanan Enterprise.",
"ctaContact": "Hubungi Saya",
"ctaProjects": "Lihat Proyek",
"scroll": "Scroll"
},
"Experience": {
"badge": "Perjalanan Karir",
"title": "Pengalaman & Evolusi",
"subtitle": "Garis waktu (timeline) membangun sistem skala enterprise di industri teknologi perbankan.",
"jobs": {
"enterprise": {
"year": "2024 — Sekarang",
"title": "Senior Backend Developer",
"company": "Enterprise Banking Corp",
"description": "Memimpin desain dan implementasi arsitektur microservices untuk platform perbankan inti.",
"achievements": [
"Merancang sistem event-driven yang memproses 500Ribu+ transaksi/hari",
"Meningkatkan kecepatan respons API hingga 40% melalui optimasi caching",
"Mementori 4 junior developer"
]
},
"digital": {
"year": "2023 — 2024",
"title": "Backend Developer",
"company": "Digital Banking Solutions",
"description": "Mengembangkan dan memelihara microservices Spring Boot untuk pemrosesan pembayaran dan manajemen pelanggan.",
"achievements": [
"Membangun layanan notifikasi real-time dengan Apache Kafka",
"Menerapkan sistem autentikasi OAuth2 + JWT",
"Mencapai 99.9% uptime pada layanan produksi"
]
},
"fintech": {
"year": "2021 — 2023",
"title": "Junior Backend Developer",
"company": "FinTech Startup",
"description": "Memulai karir membangun REST API dan manajemen database untuk aplikasi finansial.",
"achievements": [
"Mendesain skema PostgreSQL yang menangani 1Juta+ data",
"Membuat pipeline CI/CD otomatis dengan Jenkins",
"Meningkatkan cakupan unit & integration test hingga 85%"
]
}
}
},
"TechStack": {
"badge": "Arsenal Teknologi",
"title": "Tech Stack",
"subtitle": "Perangkat komprehensif yang ditempa dari pengalaman pengembangan enterprise, mulai dari infrastruktur backend hingga antarmuka mobile.",
"categories": {
"backend": {
"title": "Enterprise Backend",
"description": "Sistem inti & microservices"
},
"infra": {
"title": "Database & Infrastruktur",
"description": "Manajemen data & DevOps"
},
"frontend": {
"title": "Frontend Development",
"description": "Antarmuka web modern"
},
"mobile": {
"title": "Mobile Development",
"description": "Aplikasi cross-platform"
}
}
},
"Projects": {
"badge": "Portofolio",
"title": "Proyek & Studi Kasus",
"subtitle": "Solusi enterprise dunia nyata yang dibangun untuk skalabilitas, keamanan, dan keandalan.",
"filters": {
"all": "Semua Proyek",
"backend": "Backend",
"frontend": "Frontend",
"mobile": "Mobile"
},
"items": {
"apiGateway": {
"title": "Core Banking API Gateway",
"description": "API Gateway performa tinggi yang menangani 500Ribu+ transaksi harian dengan rate limiting, pola circuit breaker, dan distributed tracing pada 12 microservices.",
"metrics": "Meningkatkan throughput 40%, 99.9% uptime"
},
"paymentEngine": {
"title": "Mesin Pemroses Pembayaran",
"description": "Sistem pembayaran event-driven dengan pola Saga untuk transaksi terdistribusi, mendukung transfer real-time, pembayaran tagihan, dan pemrosesan batch.",
"metrics": "Memproses 200Ribu+ pembayaran/hari"
},
"onboarding": {
"title": "Portal Onboarding Pelanggan",
"description": "Portal onboarding modern dengan verifikasi KYC multi-langkah, unggah dokumen, dan pelacakan status real-time untuk pelanggan perbankan."
},
"dashboard": {
"title": "Dashboard Internal",
"description": "Dashboard admin untuk memantau performa API, analitik pengguna, dan kesehatan sistem dengan update WebSocket real-time."
},
"mobileApp": {
"title": "Aplikasi Mobile Banking",
"description": "Aplikasi mobile banking cross-platform dengan autentikasi biometrik, notifikasi push, dan riwayat transaksi offline."
},
"authService": {
"title": "Microservice Autentikasi",
"description": "Layanan autentikasi terpusat dengan OAuth2, JWT, MFA, dan manajemen sesi. Mendukung SSO di 8 aplikasi internal.",
"metrics": "Mengamankan 50Ribu+ pengguna aktif"
}
}
},
"Contact": {
"badge": "Mari Terhubung",
"title": "Hubungi Saya",
"subtitle": "Tertarik bekerja sama? Baik Anda seorang recruiter, hiring manager, atau kolaborator potensial — saya sangat ingin mendengarnya.",
"form": {
"nameLabel": "Nama Lengkap",
"namePlaceholder": "Budi Santoso",
"emailLabel": "Alamat Email",
"emailPlaceholder": "budi@perusahaan.com",
"messageLabel": "Pesan",
"messagePlaceholder": "Ceritakan tentang peluang atau proyek yang Anda pikirkan...",
"submit": "Kirim Pesan",
"submitting": "Mengirim...",
"successTitle": "Pesan Terkirim!",
"successDesc": "Terima kasih telah menghubungi. Saya akan segera membalasnya."
}
},
"Footer": {
"backToTop": "Kembali ke atas",
"copyright": "Yolando. Dibangun dengan Next.js & dibuat dengan penuh tujuan."
}
}

View File

@@ -1,7 +1,10 @@
import type { NextConfig } from "next";
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;
export default withNextIntl(nextConfig);

688
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"framer-motion": "^12.38.0",
"lucide-react": "^1.7.0",
"next": "^15.5.14",
"next-intl": "^4.8.3",
"next-themes": "^0.4.6",
"react": "19.2.4",
"react-dom": "19.2.4"
@@ -203,6 +204,51 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@formatjs/bigdecimal": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@formatjs/bigdecimal/-/bigdecimal-0.2.0.tgz",
"integrity": "sha512-GeaxHZbUoYvHL9tC5eltHLs+1zU70aPw0s7LwqgktIzF5oMhNY4o4deEtusJMsq7WFJF3Ye2zQEzdG8beVk73w=="
},
"node_modules/@formatjs/ecma402-abstract": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.2.0.tgz",
"integrity": "sha512-dHnqHgBo6GXYGRsepaE1wmsC2etaivOWd5VaJstZd+HI2zR3DCUjbDVZRtoPGkkXZmyHvBwrdEUuqfvzhF/DtQ==",
"dependencies": {
"@formatjs/bigdecimal": "0.2.0",
"@formatjs/fast-memoize": "3.1.1",
"@formatjs/intl-localematcher": "0.8.2"
}
},
"node_modules/@formatjs/fast-memoize": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.1.tgz",
"integrity": "sha512-CbNbf+tlJn1baRnPkNePnBqTLxGliG6DDgNa/UtV66abwIjwsliPMOt0172tzxABYzSuxZBZfcp//qI8AvBWPg=="
},
"node_modules/@formatjs/icu-messageformat-parser": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.3.tgz",
"integrity": "sha512-HJWZ9S6JWey6iY5+YXE3Kd0ofWU1sC2KTTp56e1168g/xxWvVvr8k9G4fexIgwYV9wbtjY7kGYK5FjoWB3B2OQ==",
"dependencies": {
"@formatjs/ecma402-abstract": "3.2.0",
"@formatjs/icu-skeleton-parser": "2.1.3"
}
},
"node_modules/@formatjs/icu-skeleton-parser": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.3.tgz",
"integrity": "sha512-9mFp8TJ166ZM2pcjKwsBWXrDnOJGT7vMEScVgLygUODPOsE8S6f/FHoacvrlHK1B4dYZk8vSCNruyPU64AfgJQ==",
"dependencies": {
"@formatjs/ecma402-abstract": "3.2.0"
}
},
"node_modules/@formatjs/intl-localematcher": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.2.tgz",
"integrity": "sha512-q05KMYGJLyqFNFtIb8NhWLF5X3aK/k0wYt7dnRFuy6aLQL+vUwQ1cg5cO4qawEiINybeCPXAWlprY2mSBjSXAQ==",
"dependencies": {
"@formatjs/fast-memoize": "3.1.1"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -917,6 +963,298 @@
"node": ">=12.4.0"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
"hasInstallScript": true,
"dependencies": {
"detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
"node-addon-api": "^7.0.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.6",
"@parcel/watcher-darwin-arm64": "2.5.6",
"@parcel/watcher-darwin-x64": "2.5.6",
"@parcel/watcher-freebsd-x64": "2.5.6",
"@parcel/watcher-linux-arm-glibc": "2.5.6",
"@parcel/watcher-linux-arm-musl": "2.5.6",
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
"@parcel/watcher-linux-arm64-musl": "2.5.6",
"@parcel/watcher-linux-x64-glibc": "2.5.6",
"@parcel/watcher-linux-x64-musl": "2.5.6",
"@parcel/watcher-win32-arm64": "2.5.6",
"@parcel/watcher-win32-ia32": "2.5.6",
"@parcel/watcher-win32-x64": "2.5.6"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -929,6 +1267,196 @@
"integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==",
"dev": true
},
"node_modules/@schummar/icu-type-parser": {
"version": "1.21.5",
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw=="
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.21.tgz",
"integrity": "sha512-SA8SFg9dp0qKRH8goWsax6bptFE2EdmPf2YRAQW9WoHGf3XKM1bX0nd5UdwxmC5hXsBUZAYf7xSciCler6/oyA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.21.tgz",
"integrity": "sha512-//fOVntgowz9+V90lVsNCtyyrtbHp3jWH6Rch7MXHXbcvbLmbCTmssl5DeedUWLLGiAAW1wksBdqdGYOTjaNLw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.21.tgz",
"integrity": "sha512-meNI4Sh6h9h8DvIfEc0l5URabYMSuNvyisLmG6vnoYAS43s8ON3NJR8sDHvdP7NJTrLe0q/x2XCn6yL/BeHcZg==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.21.tgz",
"integrity": "sha512-QrXlNQnHeXqU2EzLlnsPoWEh8/GtNJLvfMiPsDhk+ht6Xv8+vhvZ5YZ/BokNWSIZiWPKLAqR0M7T92YF5tmD3g==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.21.tgz",
"integrity": "sha512-8/yGCMO333ultDaMQivE5CjO6oXDPeeg1IV4sphojPkb0Pv0i6zvcRIkgp60xDB+UxLr6VgHgt+BBgqS959E9g==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-ppc64-gnu": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.21.tgz",
"integrity": "sha512-ucW0HzPx0s1dgRvcvuLSPSA/2Kk/VYTv9st8qe1Kc22Gu0Q0rH9+6TcBTmMuNIp0Xs4BPr1uBttmbO1wEGI49Q==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-s390x-gnu": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.21.tgz",
"integrity": "sha512-ulTnOGc5I7YRObE/9NreAhQg94QkiR5qNhhcUZ1iFAYjzg/JGAi1ch+s/Ixe61pMIr8bfVrF0NOaB0f8wjaAfA==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.21.tgz",
"integrity": "sha512-D0RokxtM+cPvSqJIKR6uja4hbD+scI9ezo95mBhfSyLUs9wnPPl26sLp1ZPR/EXRdYm3F3S6RUtVi+8QXhT24Q==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.21.tgz",
"integrity": "sha512-nER8u7VeRfmU6fMDzl1NQAbbB/G7O2avmvCOwIul1uGkZ2/acbPH+DCL9h5+0yd/coNcxMBTL6NGepIew+7C2w==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.21.tgz",
"integrity": "sha512-+/AgNBnjYugUA8C0Do4YzymgvnGbztv7j8HKSQLvR/DQgZPoXQ2B3PqB2mTtGh/X5DhlJWiqnunN35JUgWcAeQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.21.tgz",
"integrity": "sha512-IkSZj8PX/N4HcaFhMQtzmkV8YSnuNoJ0E6OvMwFiOfejPhiKXvl7CdDsn1f4/emYEIDO3fpgZW9DTaCRMDxaDA==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.21.tgz",
"integrity": "sha512-zUyWso7OOENB6e1N1hNuNn8vbvLsTdKQ5WKLgt/JcBNfJhKy/6jmBmqI3GXk/MyvQKd5SLvP7A0F36p7TeDqvw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
},
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -937,6 +1465,14 @@
"tslib": "^2.8.0"
}
},
"node_modules/@swc/types": {
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz",
"integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==",
"dependencies": {
"@swc/counter": "^0.1.3"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@@ -2246,7 +2782,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"optional": true,
"engines": {
"node": ">=8"
}
@@ -3335,6 +3870,20 @@
"node": ">= 0.4"
}
},
"node_modules/icu-minify": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.8.3.tgz",
"integrity": "sha512-65Av7FLosNk7bPbmQx5z5XG2Y3T2GFppcjiXh4z1idHeVgQxlDpAmkGoYI0eFzAvrOnjpWTL5FmPDhsdfRMPEA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"dependencies": {
"@formatjs/icu-messageformat-parser": "^3.4.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3383,6 +3932,16 @@
"node": ">= 0.4"
}
},
"node_modules/intl-messageformat": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.0.tgz",
"integrity": "sha512-IhghAA8n4KSlXuWKzYsWyWb82JoYTzShfyvdSF85oJPnNOjvv4kAo7S7Jtkm3/vJ53C7dQNRO+Gpnj3iWgTjBQ==",
"dependencies": {
"@formatjs/ecma402-abstract": "3.2.0",
"@formatjs/fast-memoize": "3.1.1",
"@formatjs/icu-messageformat-parser": "3.5.3"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -3535,7 +4094,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3578,7 +4136,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -4091,6 +4648,14 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/next": {
"version": "15.5.14",
"resolved": "https://registry.npmjs.org/next/-/next-15.5.14.tgz",
@@ -4142,6 +4707,91 @@
}
}
},
"node_modules/next-intl": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.8.3.tgz",
"integrity": "sha512-PvdBDWg+Leh7BR7GJUQbCDVVaBRn37GwDBWc9sv0rVQOJDQ5JU1rVzx9EEGuOGYo0DHAl70++9LQ7HxTawdL7w==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"dependencies": {
"@formatjs/intl-localematcher": "^0.8.1",
"@parcel/watcher": "^2.4.1",
"@swc/core": "^1.15.2",
"icu-minify": "^4.8.3",
"negotiator": "^1.0.0",
"next-intl-swc-plugin-extractor": "^4.8.3",
"po-parser": "^2.1.1",
"use-intl": "^4.8.3"
},
"peerDependencies": {
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0",
"typescript": "^5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/next-intl-swc-plugin-extractor": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.8.3.tgz",
"integrity": "sha512-YcaT+R9z69XkGhpDarVFWUprrCMbxgIQYPUaXoE6LGVnLjGdo8hu3gL6bramDVjNKViYY8a/pXPy7Bna0mXORg=="
},
"node_modules/next-intl/node_modules/@swc/core": {
"version": "1.15.21",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.21.tgz",
"integrity": "sha512-fkk7NJcBscrR3/F8jiqlMptRHP650NxqDnspBMrRe5d8xOoCy9MLL5kOBLFXjFLfMo3KQQHhk+/jUULOMlR1uQ==",
"hasInstallScript": true,
"dependencies": {
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.25"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.15.21",
"@swc/core-darwin-x64": "1.15.21",
"@swc/core-linux-arm-gnueabihf": "1.15.21",
"@swc/core-linux-arm64-gnu": "1.15.21",
"@swc/core-linux-arm64-musl": "1.15.21",
"@swc/core-linux-ppc64-gnu": "1.15.21",
"@swc/core-linux-s390x-gnu": "1.15.21",
"@swc/core-linux-x64-gnu": "1.15.21",
"@swc/core-linux-x64-musl": "1.15.21",
"@swc/core-win32-arm64-msvc": "1.15.21",
"@swc/core-win32-ia32-msvc": "1.15.21",
"@swc/core-win32-x64-msvc": "1.15.21"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.17"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/next-intl/node_modules/@swc/helpers": {
"version": "0.5.20",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.20.tgz",
"integrity": "sha512-2egEBHUMasdypIzrprsu8g+OEVd7Vp2MM3a2eVlM/cyFYto0nGz5BX5BTgh/ShZZI9ed+ozEq+Ngt+rgmUs8tw==",
"optional": true,
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/next-themes": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
@@ -4178,6 +4828,11 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
},
"node_modules/node-exports-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
@@ -4479,6 +5134,11 @@
"node": ">= 6"
}
},
"node_modules/po-parser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz",
"integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ=="
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -5595,7 +6255,7 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5701,6 +6361,26 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-intl": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.8.3.tgz",
"integrity": "sha512-nLxlC/RH+le6g3amA508Itnn/00mE+J22ui21QhOWo5V9hCEC43+WtnRAITbJW0ztVZphev5X9gvOf2/Dk9PLA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"dependencies": {
"@formatjs/fast-memoize": "^3.1.0",
"@schummar/icu-type-parser": "1.21.5",
"icu-minify": "^4.8.3",
"intl-messageformat": "^11.1.0"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@@ -12,6 +12,7 @@
"framer-motion": "^12.38.0",
"lucide-react": "^1.7.0",
"next": "^15.5.14",
"next-intl": "^4.8.3",
"next-themes": "^0.4.6",
"react": "19.2.4",
"react-dom": "19.2.4"

View File

@@ -1,7 +1,11 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { ThemeProvider } from "@/shared/components/theme-provider";
import "./globals.css";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
import { notFound } from "next/navigation";
import { routing } from "@/i18n/routing";
import "../globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -35,19 +39,34 @@ export const metadata: Metadata = {
},
};
export default function RootLayout({
export default async function RootLayout({
children,
params,
}: Readonly<{
children: React.ReactNode;
params: Promise<{ locale: string }>;
}>) {
const { locale } = await params;
// Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale as any)) {
notFound();
}
// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();
return (
<html
lang="en"
lang={locale}
suppressHydrationWarning
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">
<NextIntlClientProvider messages={messages}>
<ThemeProvider>{children}</ThemeProvider>
</NextIntlClientProvider>
</body>
</html>
);

View File

@@ -3,67 +3,58 @@
import { AnimatedSection } from "@/shared/components/animated-section";
import { SectionHeading } from "@/shared/components/section-heading";
import { Briefcase, Award, Rocket, Code2, Building2 } from "lucide-react";
import { useTranslations } from "next-intl";
interface TimelineItem {
year: string;
title: string;
company: string;
description: string;
achievements: string[];
icon: React.ReactNode;
}
export function ExperienceSection() {
const t = useTranslations("Experience");
const timelineData: TimelineItem[] = [
const timelineData = [
{
year: "2024 — Present",
title: "Senior Backend Developer",
company: "Enterprise Banking Corp",
description:
"Leading microservices architecture design and implementation for core banking platform.",
year: t("jobs.enterprise.year"),
title: t("jobs.enterprise.title"),
company: t("jobs.enterprise.company"),
description: t("jobs.enterprise.description"),
achievements: [
"Architected event-driven system processing 500K+ transactions/day",
"Reduced API response time by 40% through caching optimization",
"Mentored team of 4 junior developers",
t("jobs.enterprise.achievements.0"),
t("jobs.enterprise.achievements.1"),
t("jobs.enterprise.achievements.2"),
],
icon: <Rocket size={20} />,
},
{
year: "2023 — 2024",
title: "Backend Developer",
company: "Digital Banking Solutions",
description:
"Developed and maintained Spring Boot microservices for payment processing and customer management.",
year: t("jobs.digital.year"),
title: t("jobs.digital.title"),
company: t("jobs.digital.company"),
description: t("jobs.digital.description"),
achievements: [
"Built real-time notification service with Apache Kafka",
"Implemented OAuth2 + JWT authentication system",
"Achieved 99.9% uptime on production services",
t("jobs.digital.achievements.0"),
t("jobs.digital.achievements.1"),
t("jobs.digital.achievements.2"),
],
icon: <Code2 size={20} />,
},
{
year: "2021 — 2023",
title: "Junior Backend Developer",
company: "FinTech Startup",
description:
"Started career building REST APIs and database management for financial applications.",
year: t("jobs.fintech.year"),
title: t("jobs.fintech.title"),
company: t("jobs.fintech.company"),
description: t("jobs.fintech.description"),
achievements: [
"Designed PostgreSQL schema handling 1M+ records",
"Created automated CI/CD pipeline with Jenkins",
"Developed unit & integration test coverage to 85%",
t("jobs.fintech.achievements.0"),
t("jobs.fintech.achievements.1"),
t("jobs.fintech.achievements.2"),
],
icon: <Building2 size={20} />,
},
];
export function ExperienceSection() {
return (
<section id="experience" className="section-padding relative">
<div className="max-w-5xl mx-auto px-6">
<AnimatedSection>
<SectionHeading
badge="Career Journey"
title="Experience & Evolution"
subtitle="A timeline of building enterprise-grade systems in the banking technology industry."
badge={t("badge")}
title={t("title")}
subtitle={t("subtitle")}
/>
</AnimatedSection>

View File

@@ -2,23 +2,23 @@
import { motion } from "framer-motion";
import { ArrowDown, FileText, Send } from "lucide-react";
import { useTranslations } from "next-intl";
export function HeroSection() {
const t = useTranslations("Hero");
return (
<section
id="hero"
className="relative min-h-screen flex items-center justify-center overflow-hidden"
>
{/* Background effects */}
<div className="absolute inset-0 grid-pattern opacity-30" />
{/* Gradient orbs */}
<div className="absolute top-1/4 -left-32 w-96 h-96 rounded-full bg-accent/20 blur-[120px] animate-pulse-glow" />
<div className="absolute bottom-1/4 -right-32 w-96 h-96 rounded-full bg-purple-500/15 blur-[120px] animate-pulse-glow" style={{ animationDelay: "2s" }} />
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full bg-indigo-500/5 blur-[100px]" />
<div className="relative z-10 max-w-5xl mx-auto px-6 text-center">
{/* Status badge */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
@@ -29,37 +29,32 @@ export function HeroSection() {
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-success opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-success" />
</span>
Available for opportunities
{t("badge")}
</span>
</motion.div>
{/* Main heading */}
<motion.h1
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.3 }}
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight leading-[1.1] mb-6"
>
Building{" "}
<span className="gradient-text">Secure, Scalable</span>
{t("titlePart1")}{" "}
<span className="gradient-text">{t("titleHighlight")}</span>
<br />
Enterprise-Grade Systems
{t("titlePart2")}
</motion.h1>
{/* Subtitle */}
<motion.p
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.5 }}
className="text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto mb-10 leading-relaxed"
>
<span className="font-mono text-accent font-semibold">3+ Years</span>{" "}
in Banking Technology.{" "}
<span className="text-foreground">Backend Developer</span> specializing in
Java Spring Boot, Microservices Architecture, and Enterprise Security.
<span className="font-mono text-accent font-semibold">{t("yearsExp")}</span>{" "}
{t("subtitle")}
</motion.p>
{/* CTA buttons */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
@@ -71,18 +66,17 @@ export function HeroSection() {
className="group inline-flex items-center gap-2 px-7 py-3.5 rounded-xl bg-gradient-to-r from-accent to-purple-500 text-white font-semibold text-sm shadow-lg shadow-accent/25 hover:shadow-accent/40 hover:scale-105 transition-all duration-300"
>
<Send size={16} />
Get in Touch
{t("ctaContact")}
</a>
<a
href="#projects"
className="inline-flex items-center gap-2 px-7 py-3.5 rounded-xl font-semibold text-sm border border-border hover:bg-muted/50 transition-all duration-300 hover:scale-105"
>
<FileText size={16} />
View Projects
{t("ctaProjects")}
</a>
</motion.div>
{/* Tech badges */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
@@ -100,7 +94,6 @@ export function HeroSection() {
</motion.div>
</div>
{/* Scroll indicator */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
@@ -108,7 +101,7 @@ export function HeroSection() {
className="absolute bottom-8 left-1/2 -translate-x-1/2"
>
<a href="#experience" className="flex flex-col items-center gap-2 text-muted-foreground hover:text-accent transition-colors">
<span className="text-xs font-mono">Scroll</span>
<span className="text-xs font-mono">{t("scroll")}</span>
<motion.div
animate={{ y: [0, 8, 0] }}
transition={{ duration: 1.5, repeat: Infinity }}

View File

@@ -4,8 +4,10 @@ import { useState } from "react";
import { AnimatedSection } from "@/shared/components/animated-section";
import { SectionHeading } from "@/shared/components/section-heading";
import { Send, Mail, User, MessageSquare, CheckCircle, Loader2 } from "lucide-react";
import { useTranslations } from "next-intl";
export function ContactSection() {
const t = useTranslations("Contact");
const [formState, setFormState] = useState<"idle" | "loading" | "success">("idle");
const [formData, setFormData] = useState({
name: "",
@@ -34,9 +36,9 @@ export function ContactSection() {
<div className="relative max-w-4xl mx-auto px-6">
<AnimatedSection>
<SectionHeading
badge="Let's Connect"
title="Get in Touch"
subtitle="Interested in working together? Whether you're a recruiter, hiring manager, or potential collaborator — I'd love to hear from you."
badge={t("badge")}
title={t("title")}
subtitle={t("subtitle")}
/>
</AnimatedSection>
@@ -49,9 +51,9 @@ export function ContactSection() {
<div className="w-16 h-16 rounded-full bg-success/10 flex items-center justify-center mx-auto mb-4">
<CheckCircle size={32} className="text-success" />
</div>
<h3 className="text-xl font-bold mb-2">Message Sent!</h3>
<h3 className="text-xl font-bold mb-2">{t("form.successTitle")}</h3>
<p className="text-muted-foreground text-sm">
Thank you for reaching out. I&apos;ll get back to you soon.
{t("form.successDesc")}
</p>
</div>
</div>
@@ -66,7 +68,7 @@ export function ContactSection() {
className="flex items-center gap-2 text-sm font-medium"
>
<User size={14} className="text-accent" />
Full Name
{t("form.nameLabel")}
</label>
<input
id="contact-name"
@@ -76,7 +78,7 @@ export function ContactSection() {
onChange={(e) =>
setFormData((prev) => ({ ...prev, name: e.target.value }))
}
placeholder="John Doe"
placeholder={t("form.namePlaceholder")}
className="w-full px-4 py-3 rounded-xl bg-muted/50 border border-border/50 text-sm placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-accent/50 focus:border-accent/50 transition-all"
/>
</div>
@@ -88,7 +90,7 @@ export function ContactSection() {
className="flex items-center gap-2 text-sm font-medium"
>
<Mail size={14} className="text-accent" />
Email Address
{t("form.emailLabel")}
</label>
<input
id="contact-email"
@@ -98,7 +100,7 @@ export function ContactSection() {
onChange={(e) =>
setFormData((prev) => ({ ...prev, email: e.target.value }))
}
placeholder="john@company.com"
placeholder={t("form.emailPlaceholder")}
className="w-full px-4 py-3 rounded-xl bg-muted/50 border border-border/50 text-sm placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-accent/50 focus:border-accent/50 transition-all"
/>
</div>
@@ -111,7 +113,7 @@ export function ContactSection() {
className="flex items-center gap-2 text-sm font-medium"
>
<MessageSquare size={14} className="text-accent" />
Message
{t("form.messageLabel")}
</label>
<textarea
id="contact-message"
@@ -121,7 +123,7 @@ export function ContactSection() {
onChange={(e) =>
setFormData((prev) => ({ ...prev, message: e.target.value }))
}
placeholder="Tell me about the opportunity or project you have in mind..."
placeholder={t("form.messagePlaceholder")}
className="w-full px-4 py-3 rounded-xl bg-muted/50 border border-border/50 text-sm placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-accent/50 focus:border-accent/50 transition-all resize-none"
/>
</div>
@@ -135,12 +137,12 @@ export function ContactSection() {
{formState === "loading" ? (
<>
<Loader2 size={16} className="animate-spin" />
Sending...
{t("form.submitting")}
</>
) : (
<>
<Send size={16} />
Send Message
{t("form.submit")}
</>
)}
</button>

View File

@@ -13,42 +13,33 @@ import {
Globe,
Layers,
} from "lucide-react";
import { useTranslations } from "next-intl";
interface Project {
id: string;
title: string;
description: string;
category: "backend" | "frontend" | "mobile";
tags: string[];
metrics?: string;
repoUrl?: string;
liveUrl?: string;
}
export function ProjectsSection() {
const t = useTranslations("Projects");
const [activeFilter, setActiveFilter] = useState("all");
const projects: Project[] = [
const projects = [
{
id: "1",
title: "Core Banking API Gateway",
description:
"High-performance API Gateway handling 500K+ daily transactions with rate limiting, circuit breaker pattern, and distributed tracing across 12 microservices.",
title: t("items.apiGateway.title"),
description: t("items.apiGateway.description"),
category: "backend",
tags: ["Spring Boot", "Kafka", "Redis", "Docker"],
metrics: "Increased throughput by 40%, 99.9% uptime",
metrics: t.has("items.apiGateway.metrics") ? t("items.apiGateway.metrics") : undefined,
},
{
id: "2",
title: "Payment Processing Engine",
description:
"Event-driven payment system with Saga pattern for distributed transactions, supporting real-time transfers, bill payments, and batch processing.",
title: t("items.paymentEngine.title"),
description: t("items.paymentEngine.description"),
category: "backend",
tags: ["Java", "Kafka", "PostgreSQL", "gRPC"],
metrics: "Processing 200K+ payments/day",
metrics: t.has("items.paymentEngine.metrics") ? t("items.paymentEngine.metrics") : undefined,
},
{
id: "3",
title: "Customer Onboarding Portal",
description:
"Modern onboarding portal with multi-step KYC verification, document upload, and real-time status tracking for banking customers.",
title: t("items.onboarding.title"),
description: t("items.onboarding.description"),
category: "frontend",
tags: ["React", "Next.js", "TypeScript", "Tailwind"],
repoUrl: "#",
@@ -56,42 +47,36 @@ const projects: Project[] = [
},
{
id: "4",
title: "Internal Dashboard",
description:
"Admin dashboard for monitoring API performance, user analytics, and system health with real-time WebSocket updates.",
title: t("items.dashboard.title"),
description: t("items.dashboard.description"),
category: "frontend",
tags: ["React", "Chart.js", "WebSocket", "REST"],
repoUrl: "#",
},
{
id: "5",
title: "Mobile Banking App",
description:
"Cross-platform mobile banking application with biometric authentication, push notifications, and offline transaction history.",
title: t("items.mobileApp.title"),
description: t("items.mobileApp.description"),
category: "mobile",
tags: ["React Native", "TypeScript", "Redux"],
},
{
id: "6",
title: "Authentication Microservice",
description:
"Centralized auth service with OAuth2, JWT, MFA, and session management. Supports SSO across 8 internal applications.",
title: t("items.authService.title"),
description: t("items.authService.description"),
category: "backend",
tags: ["Spring Security", "OAuth2", "JWT", "Redis"],
metrics: "Securing 50K+ active users",
metrics: t.has("items.authService.metrics") ? t("items.authService.metrics") : undefined,
},
];
const filters = [
{ value: "all", label: "All Projects", icon: <Layers size={16} /> },
{ value: "backend", label: "Backend", icon: <Server size={16} /> },
{ value: "frontend", label: "Frontend", icon: <Globe size={16} /> },
{ value: "mobile", label: "Mobile", icon: <Smartphone size={16} /> },
{ value: "all", label: t("filters.all"), icon: <Layers size={16} /> },
{ value: "backend", label: t("filters.backend"), icon: <Server size={16} /> },
{ value: "frontend", label: t("filters.frontend"), icon: <Globe size={16} /> },
{ value: "mobile", label: t("filters.mobile"), icon: <Smartphone size={16} /> },
];
export function ProjectsSection() {
const [activeFilter, setActiveFilter] = useState("all");
const filteredProjects =
activeFilter === "all"
? projects
@@ -102,9 +87,9 @@ export function ProjectsSection() {
<div className="max-w-6xl mx-auto px-6">
<AnimatedSection>
<SectionHeading
badge="Portfolio"
title="Projects & Case Studies"
subtitle="Real-world enterprise solutions built for scale, security, and reliability."
badge={t("badge")}
title={t("title")}
subtitle={t("subtitle")}
/>
</AnimatedSection>

View File

@@ -16,23 +16,15 @@ import {
Cloud,
Workflow,
} from "lucide-react";
import { useTranslations } from "next-intl";
interface TechItem {
name: string;
icon: React.ReactNode;
}
export function TechStackSection() {
const t = useTranslations("TechStack");
interface TechCategory {
title: string;
description: string;
items: TechItem[];
accent: string;
}
const techCategories: TechCategory[] = [
const techCategories = [
{
title: "Enterprise Backend",
description: "Core systems & microservices",
title: t("categories.backend.title"),
description: t("categories.backend.description"),
accent: "from-blue-500 to-cyan-500",
items: [
{ name: "Java", icon: <Cpu size={22} /> },
@@ -44,8 +36,8 @@ const techCategories: TechCategory[] = [
],
},
{
title: "Database & Infrastructure",
description: "Data management & DevOps",
title: t("categories.infra.title"),
description: t("categories.infra.description"),
accent: "from-emerald-500 to-teal-500",
items: [
{ name: "PostgreSQL", icon: <Database size={22} /> },
@@ -57,8 +49,8 @@ const techCategories: TechCategory[] = [
],
},
{
title: "Frontend Development",
description: "Modern web interfaces",
title: t("categories.frontend.title"),
description: t("categories.frontend.description"),
accent: "from-violet-500 to-purple-500",
items: [
{ name: "React / Next.js", icon: <Globe size={22} /> },
@@ -68,8 +60,8 @@ const techCategories: TechCategory[] = [
],
},
{
title: "Mobile Development",
description: "Cross-platform apps",
title: t("categories.mobile.title"),
description: t("categories.mobile.description"),
accent: "from-orange-500 to-rose-500",
items: [
{ name: "React Native", icon: <Smartphone size={22} /> },
@@ -78,21 +70,19 @@ const techCategories: TechCategory[] = [
},
];
export function TechStackSection() {
return (
<section
id="tech-stack"
className="section-padding relative bg-muted/30"
>
{/* Subtle background */}
<div className="absolute inset-0 grid-pattern opacity-20" />
<div className="relative max-w-6xl mx-auto px-6">
<AnimatedSection>
<SectionHeading
badge="Tech Arsenal"
title="Technology Stack"
subtitle="A comprehensive toolkit forged through years of enterprise development, from backend infrastructure to mobile interfaces."
badge={t("badge")}
title={t("title")}
subtitle={t("subtitle")}
/>
</AnimatedSection>
@@ -100,7 +90,6 @@ export function TechStackSection() {
{techCategories.map((category, catIndex) => (
<AnimatedSection key={category.title} delay={catIndex * 0.1}>
<div className="group relative h-full p-6 rounded-2xl bg-card border border-border/50 hover:border-accent/30 transition-all duration-500 hover:shadow-lg">
{/* Category header */}
<div className="flex items-center gap-3 mb-4">
<div
className={`w-10 h-10 rounded-xl bg-gradient-to-br ${category.accent} flex items-center justify-center text-white shadow-lg`}
@@ -115,7 +104,6 @@ export function TechStackSection() {
</div>
</div>
{/* Tech items grid */}
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
{category.items.map((tech) => (
<div

15
src/i18n/request.ts Normal file
View File

@@ -0,0 +1,15 @@
import {getRequestConfig} from 'next-intl/server';
import {routing} from './routing';
export default getRequestConfig(async ({requestLocale}) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default
};
});

10
src/i18n/routing.ts Normal file
View File

@@ -0,0 +1,10 @@
import {defineRouting} from 'next-intl/routing';
import {createNavigation} from 'next-intl/navigation';
export const routing = defineRouting({
locales: ['id', 'en'],
defaultLocale: 'id',
localePrefix: 'as-needed' // Don't show /id for the default locale
});
export const {Link, redirect, usePathname, useRouter, getPathname} = createNavigation(routing);

9
src/middleware.ts Normal file
View File

@@ -0,0 +1,9 @@
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
export default createMiddleware(routing);
export const config = {
// Match only internationalized pathnames
matcher: ['/', '/(id|en)/:path*']
};

View File

@@ -1,6 +1,9 @@
import { GitFork, Link2, Mail, ArrowUp } from "lucide-react";
import { useTranslations } from "next-intl";
export function Footer() {
const t = useTranslations("Footer");
return (
<footer className="relative border-t border-border/50 bg-card/50">
<div className="max-w-6xl mx-auto px-6 py-12">
@@ -49,7 +52,7 @@ export function Footer() {
href="#"
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-accent transition-colors group"
>
Back to top
{t("backToTop")}
<ArrowUp
size={14}
className="transition-transform group-hover:-translate-y-0.5"
@@ -59,8 +62,7 @@ export function Footer() {
<div className="mt-8 pt-6 border-t border-border/30 text-center">
<p className="text-sm text-muted-foreground font-mono">
© {new Date().getFullYear()} Yolando. Built with Next.js & crafted
with purpose.
© {new Date().getFullYear()} {t("copyright")}
</p>
</div>
</div>

View File

@@ -1,20 +1,20 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useTransition } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Menu, X } from "lucide-react";
import { Menu, X, Languages } from "lucide-react";
import { ThemeToggle } from "@/shared/components/theme-toggle";
const navLinks = [
{ href: "#experience", label: "Experience" },
{ href: "#tech-stack", label: "Tech Stack" },
{ href: "#projects", label: "Projects" },
{ href: "#contact", label: "Contact" },
];
import { useTranslations, useLocale } from "next-intl";
import { usePathname, useRouter } from "@/i18n/routing";
export function Navbar() {
const [isScrolled, setIsScrolled] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const t = useTranslations("Navigation");
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const [isPending, startTransition] = useTransition();
useEffect(() => {
const handleScroll = () => setIsScrolled(window.scrollY > 20);
@@ -22,6 +22,20 @@ export function Navbar() {
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const navLinks = [
{ href: "#experience", label: t("experience") },
{ href: "#tech-stack", label: t("techStack") },
{ href: "#projects", label: t("projects") },
{ href: "#contact", label: t("contact") },
];
const switchLocale = (newLocale: string) => {
startTransition(() => {
router.replace(pathname, { locale: newLocale });
setIsOpen(false);
});
};
return (
<motion.header
initial={{ y: -100 }}
@@ -56,7 +70,33 @@ export function Navbar() {
<span className="absolute bottom-0 left-1/2 -translate-x-1/2 w-0 h-0.5 bg-accent rounded-full transition-all duration-300 group-hover:w-6" />
</a>
))}
<div className="ml-4 pl-4 border-l border-border/50">
<div className="ml-4 pl-4 border-l border-border/50 flex items-center gap-2">
{/* Language Switcher */}
<div className="flex items-center gap-1 bg-muted/50 p-1 rounded-xl border border-border/50">
<button
disabled={isPending}
onClick={() => switchLocale("id")}
className={`px-2 py-1 text-xs font-bold rounded-lg transition-colors ${
locale === "id"
? "bg-background shadow-sm text-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
>
ID
</button>
<button
disabled={isPending}
onClick={() => switchLocale("en")}
className={`px-2 py-1 text-xs font-bold rounded-lg transition-colors ${
locale === "en"
? "bg-background shadow-sm text-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
>
EN
</button>
</div>
<ThemeToggle />
</div>
</div>
@@ -94,6 +134,33 @@ export function Navbar() {
{link.label}
</a>
))}
<div className="px-4 py-3 mt-2 border-t border-border/30 flex items-center gap-4">
<span className="text-sm font-medium flex items-center gap-2 text-muted-foreground">
<Languages size={16} /> Language
</span>
<div className="flex items-center gap-2">
<button
onClick={() => switchLocale("id")}
className={`px-3 py-1 text-xs font-bold rounded-lg border ${
locale === "id"
? "bg-accent/10 border-accent/20 text-accent"
: "bg-muted/50 border-border/50 text-muted-foreground"
}`}
>
ID
</button>
<button
onClick={() => switchLocale("en")}
className={`px-3 py-1 text-xs font-bold rounded-lg border ${
locale === "en"
? "bg-accent/10 border-accent/20 text-accent"
: "bg-muted/50 border-border/50 text-muted-foreground"
}`}
>
EN
</button>
</div>
</div>
</div>
</motion.div>
)}