From 087bccbb13b6c9db0f7c2b8498030cdcf912aeb8 Mon Sep 17 00:00:00 2001 From: RJ Date: Thu, 4 Dec 2025 14:56:00 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=93=9D=20add=20locale=20on=20blog=20s?= =?UTF-8?q?sr,=20add=20translations=20for=20other=20webpage=20content?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 46 ++++++++++++++ app/[locale]/about/page.tsx | 100 +++++++++++++----------------- app/[locale]/blog/blog-client.tsx | 5 +- app/[locale]/blog/page.tsx | 8 ++- app/[locale]/page.tsx | 41 +++++++----- content/blog/en/why-this-page.md | 2 +- content/blog/ro/why-this-page.md | 4 +- messages/en.json | 68 +++++++++++++++++++- messages/ro.json | 68 +++++++++++++++++++- 9 files changed, 255 insertions(+), 87 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..09ff259 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,46 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Next.js: Full Stack", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev", + "serverReadyAction": { + "pattern": "started server on .+, url: (https?://.+)", + "uriFormat": "%s", + "action": "debugWithChrome" + }, + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Next.js: Frontend", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3030", + "webRoot": "${workspaceFolder}", + "sourceMapPathOverrides": { + "webpack://_N_E/*": "${webRoot}/*" + } + }, + { + "name": "Next.js: Backend", + "type": "node", + "request": "attach", + "port": 9229, + "restart": true, + "sourceMaps": true, + "sourceMapPathOverrides": { + "webpack:///*": "${workspaceFolder}/*" + } + } + ], + "compounds": [ + { + "name": "Next.js: Debug Full Stack", + "configurations": ["Next.js: Backend", "Next.js: Frontend"], + "stopAll": true + } + ] +} diff --git a/app/[locale]/about/page.tsx b/app/[locale]/about/page.tsx index 7b0e750..059a0cd 100644 --- a/app/[locale]/about/page.tsx +++ b/app/[locale]/about/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next' import { Navbar } from '@/components/blog/navbar' -import {setRequestLocale} from 'next-intl/server' +import {setRequestLocale, getTranslations} from 'next-intl/server' export const metadata: Metadata = { title: 'About', @@ -14,6 +14,7 @@ type Props = { export default async function AboutPage({params}: Props) { const {locale} = await params setRequestLocale(locale) + const t = await getTranslations('About') return ( <> @@ -22,10 +23,10 @@ export default async function AboutPage({params}: Props) { {/* Classification Header */}

- >> _DOC://PUBLIC_ACCESS + {t('classificationHeader')}

- ABOUT ME_ + {t('mainTitle')}

@@ -35,12 +36,10 @@ export default async function AboutPage({params}: Props) {

- Welcome to my corner of the internet! This is where I share my thoughts, opinions, - and experiences - from tech adventures to life as a family man. Yes, I love - technology, but there's so much more to life than just code and servers. + {t('introParagraph1')}

- STATUS: ACTIVE // ROLE: DAD + DEV + LIFE ENTHUSIAST + {t('introLabel')}

@@ -48,46 +47,39 @@ export default async function AboutPage({params}: Props) { {/* Life & Values Section */}

- > LIFE & VALUES + {t('lifeValuesTitle')}

- [FAMILY FIRST] + {t('familyFirstTitle')}

- Being a dad to an amazing toddler is my most important role. Family time is - sacred - whether it's building block towers, exploring parks, or just - enjoying the chaos of everyday life together. Tech can wait; these moments - can't. + {t('familyFirstText')}

- [ACTIVE LIFESTYLE] + {t('activeLifestyleTitle')}

- I believe in keeping the body active. Whether it's hitting the gym, playing - sports, or just staying on the move - physical activity keeps me sharp, - balanced, and ready for whatever life throws my way. + {t('activeLifestyleText')}

- [ENJOYING THE SIMPLE THINGS] + {t('simpleThingsTitle')}

- Life's too short not to enjoy it. A good drink, a relaxing - evening after a long day, or just not doing anything a blowing some steam off. + {t('simpleThingsText')}

- [TECH WITH PURPOSE] + {t('techPurposeTitle')}

- Yes, I love tech - self-hosting, privacy, tinkering with hardware. But it's - a tool, not a lifestyle. Tech should serve life, not the other way around. + {t('techPurposeText')}

@@ -96,51 +88,46 @@ export default async function AboutPage({params}: Props) { {/* Content Section */}

- > WHAT YOU'LL FIND HERE + {t('contentTitle')}

- CONTENT SCOPE // EVERYTHING FROM TECH TO LIFE + {t('contentSubtitle')}

  • > - Thoughts & Opinions - My take on life, work, and everything in - between + {t('contentThoughts')} - {t('contentThoughtsDesc')}
  • > - Life & Family - Adventures in parenting, sports, and enjoying - the simple things + {t('contentLifeFamily')} - {t('contentLifeFamilyDesc')}
  • > - Tech Research - When I dive into interesting technologies and - experiments + {t('contentTechResearch')} - {t('contentTechResearchDesc')}
  • > - System Administration - Self-hosting, infrastructure, and - DevOps adventures + {t('contentSysAdmin')} - {t('contentSysAdminDesc')}
  • > - Development Insights - Lessons learned from building software + {t('contentDevelopment')} - {t('contentDevelopmentDesc')}
  • > - Random Stuff - Because life doesn't fit into neat - categories! + {t('contentRandom')} - {t('contentRandomDesc')}
@@ -149,40 +136,39 @@ export default async function AboutPage({params}: Props) { {/* Areas of Focus Section */}

- > AREAS OF FOCUS + {t('focusTitle')}

- [BEING A DAD] + {t('focusBeingDadTitle')}

- Playing with my boy, teaching moments, watching him grow, building memories - together + {t('focusBeingDadText')}

- [STAYING ACTIVE] + {t('focusStayingActiveTitle')}

- Gym sessions, sports, keeping fit, maintaining energy for life's demands + {t('focusStayingActiveText')}

- [TECHNOLOGY & SYSTEMS] + {t('focusTechnologyTitle')}

- Software development, infrastructure, DevOps, self-hosting adventures + {t('focusTechnologyText')}

- [LIFE BALANCE] + {t('focusLifeBalanceTitle')}

- Relaxing with good company, enjoying downtime, appreciating the simple moments + {t('focusLifeBalanceText')}

@@ -190,42 +176,42 @@ export default async function AboutPage({params}: Props) { {/* Tech Stack Section */}

- > TECH STACK + {t('techStackTitle')}

- TOOLS I USE // WHEN NEEDED + {t('techStackSubtitle')}

- [DEVELOPMENT] + {t('techStackDevelopmentTitle')}

- .NET, Golang, TypeScript, Next.js, React + {t('techStackDevelopmentText')}

- [INFRASTRUCTURE] + {t('techStackInfrastructureTitle')}

- Windows Server, Linux, Docker, Hyper-V + {t('techStackInfrastructureText')}

- [DESIGN] + {t('techStackDesignTitle')}

- Tailwind CSS, Markdown, Terminal aesthetics + {t('techStackDesignText')}

- [SELF-HOSTING] + {t('techStackSelfHostingTitle')}

- Home lab, privacy-focused services, full control, Git server + {t('techStackSelfHostingText')}

@@ -233,7 +219,7 @@ export default async function AboutPage({params}: Props) { {/* Contact Section */}

- > CONTACT + {t('contactTitle')}

{/*

diff --git a/app/[locale]/blog/blog-client.tsx b/app/[locale]/blog/blog-client.tsx index 43ad88d..5b67834 100644 --- a/app/[locale]/blog/blog-client.tsx +++ b/app/[locale]/blog/blog-client.tsx @@ -7,7 +7,6 @@ import { BlogCard } from '@/components/blog/blog-card' import { SearchBar } from '@/components/blog/search-bar' import { SortDropdown } from '@/components/blog/sort-dropdown' import { TagFilter } from '@/components/blog/tag-filter' -import { Navbar } from '@/components/blog/navbar' interface BlogPageClientProps { posts: Post[] @@ -142,7 +141,7 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps) disabled={currentPage === 1} className="px-6 py-3 font-mono text-sm uppercase border border-[rgb(var(--border-primary))] text-[rgb(var(--text-primary))] disabled:opacity-30 disabled:cursor-not-allowed hover:border-[var(--neon-cyan)] hover:text-[var(--neon-cyan)] transition-colors cursor-pointer" > - < PREV + {t('prev')}

{Array.from({ length: totalPages }, (_, i) => i + 1).map(page => ( @@ -164,7 +163,7 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps) disabled={currentPage === totalPages} className="px-6 py-3 font-mono text-sm uppercase border border-[rgb(var(--border-primary))] text-[rgb(var(--text-primary))] disabled:opacity-30 disabled:cursor-not-allowed hover:border-[var(--neon-cyan)] hover:text-[var(--neon-cyan)] transition-colors cursor-pointer" > - NEXT > + {t('next')}
diff --git a/app/[locale]/blog/page.tsx b/app/[locale]/blog/page.tsx index 485362e..c80bdd4 100644 --- a/app/[locale]/blog/page.tsx +++ b/app/[locale]/blog/page.tsx @@ -1,9 +1,11 @@ import { getAllPosts } from '@/lib/markdown' import BlogPageClient from './blog-client' -import {setRequestLocale} from 'next-intl/server' +import { setRequestLocale } from 'next-intl/server' -export default async function BlogPage() { - const posts = await getAllPosts() +export default async function BlogPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + await setRequestLocale(locale) + const posts = await getAllPosts(locale) const allTags = Array.from(new Set(posts.flatMap(post => post.frontmatter.tags))).sort() return diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 58ab27e..4d59ec2 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -3,9 +3,18 @@ import Image from 'next/image' import { getAllPosts } from '@/lib/markdown' import { formatDate } from '@/lib/utils' import { ThemeToggle } from '@/components/theme-toggle' -import {setRequestLocale} from 'next-intl/server' +import {setRequestLocale, getTranslations} from 'next-intl/server' + +type Props = { + params: Promise<{locale: string}> +} + +export default async function HomePage({params}: Props) { + const {locale} = await params + setRequestLocale(locale) + const t = await getTranslations('Home') + const tNav = await getTranslations('Navigation') -export default async function HomePage() { const allPosts = await getAllPosts() const featuredPosts = allPosts.slice(0, 6) @@ -24,7 +33,7 @@ export default async function HomePage() {
Logo - TERMINAL:// V2.0 + {t('terminalVersion')}
@@ -32,13 +41,13 @@ export default async function HomePage() { href="/blog" className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400" > - [BLOG] + [{tNav('blog')}] - [ABOUT] + [{tNav('about')}]
@@ -46,15 +55,13 @@ export default async function HomePage() {

- DOCUMENT LEVEL-1 // + {t('documentLevel')}

- BUILD. WRITE. -
- SHARE. + {t('heroTitle')}

- > Explore ideas + {t('heroSubtitle')}

@@ -63,13 +70,13 @@ export default async function HomePage() { href="/blog" className="px-6 md:px-8 py-3 md:py-4 bg-cyan-700 dark:bg-cyan-900 text-white dark:text-slate-100 border-2 border-cyan-600 dark:border-cyan-700 font-mono font-bold uppercase text-xs md:text-sm tracking-wider hover:bg-cyan-600 dark:hover:bg-cyan-800 hover:border-cyan-500 dark:hover:border-cyan-600 rounded-none transition-colors duration-200" > - [CHECK POSTS] + {t('checkPostsButton')} - [ABOUT ME] + {t('aboutMeButton')} @@ -81,10 +88,10 @@ export default async function HomePage() {

- ARCHIVE ACCESS // RECENT ENTRIES + {t('recentEntriesLabel')}

- > RECENT ENTRIES + {t('recentEntriesTitle')}

@@ -134,7 +141,7 @@ export default async function HomePage() { href={`/blog/${post.slug}`} className="inline-flex items-center text-cyan-600 dark:text-cyan-400 font-mono text-xs font-bold uppercase tracking-wider hover:text-cyan-500 dark:hover:text-cyan-300 border-2 border-slate-400 dark:border-slate-700 px-4 py-2 hover:border-cyan-700 dark:hover:border-cyan-900 transition-colors duration-200" > - [ACCESEAZĂ] >> + {t('accessButton')}
@@ -147,14 +154,14 @@ export default async function HomePage() { href="/blog" className="inline-flex items-center px-8 py-4 bg-transparent text-slate-700 dark:text-slate-300 border-2 border-slate-400 dark:border-slate-700 font-mono font-bold uppercase text-sm tracking-wider hover:bg-slate-200 dark:hover:bg-slate-800 hover:border-slate-500 dark:hover:border-slate-600 transition-colors duration-200" > - [SEE POSTS] >> + {t('seePostsButton')} )} - [SEE ALL TAGS] >> + {t('seeAllTagsButton')} diff --git a/content/blog/en/why-this-page.md b/content/blog/en/why-this-page.md index dc12dff..614dfc5 100644 --- a/content/blog/en/why-this-page.md +++ b/content/blog/en/why-this-page.md @@ -1,5 +1,5 @@ --- -title: 'Why I created this page' +title: 'First post' description: 'First post' date: '2025-12-02' author: 'Rares' diff --git a/content/blog/ro/why-this-page.md b/content/blog/ro/why-this-page.md index c040ebd..454216b 100644 --- a/content/blog/ro/why-this-page.md +++ b/content/blog/ro/why-this-page.md @@ -1,6 +1,6 @@ --- -title: 'Why I created this page' -description: 'First post' +title: 'Primul post' +description: 'Primul post' date: '2025-12-02' author: 'Rares' category: 'Opinion' diff --git a/messages/en.json b/messages/en.json index af3d382..d354bb9 100644 --- a/messages/en.json +++ b/messages/en.json @@ -18,6 +18,21 @@ "about": "About" }, + "Home": { + "terminalVersion": "TERMINAL:// V2.0", + "documentLevel": "DOCUMENT LEVEL-1 //", + "heroTitle": "BUILD. WRITE. SHARE.", + "heroSubtitle": "> Explore ideas", + "checkPostsButton": "[CHECK POSTS]", + "aboutMeButton": "[ABOUT ME]", + "recentEntriesLabel": "ARCHIVE ACCESS // RECENT ENTRIES", + "recentEntriesTitle": "> RECENT ENTRIES", + "fileLabel": "FILE#{number} // {category}", + "accessButton": "[ACCESS] >>", + "seePostsButton": "[SEE POSTS] >>", + "seeAllTagsButton": "[SEE ALL TAGS] >>" + }, + "BlogListing": { "title": "Blog", "subtitle": "Latest articles and thoughts", @@ -29,7 +44,9 @@ "filterByTag": "Filter by tag", "clearFilters": "Clear filters", "foundPosts": "Found {count} posts", - "noPosts": "No posts found" + "noPosts": "No posts found", + "prev": "< PREV", + "next": "NEXT >" }, "BlogPost": { @@ -53,7 +70,54 @@ "About": { "title": "About", - "subtitle": "Learn more about me" + "subtitle": "Learn more about me", + "classificationHeader": ">> _DOC://PUBLIC_ACCESS", + "mainTitle": "ABOUT ME_", + "introLabel": "STATUS: ACTIVE // ROLE: DAD + DEV + LIFE ENTHUSIAST", + "introParagraph1": "Welcome to my corner of the internet! This is where I share my thoughts, opinions, and experiences - from tech adventures to life as a family man. Yes, I love technology, but there's so much more to life than just code and servers.", + "lifeValuesTitle": "> LIFE & VALUES", + "familyFirstTitle": "[FAMILY FIRST]", + "familyFirstText": "Being a dad to an amazing toddler is my most important role. Family time is sacred - whether it's building block towers, exploring parks, or just enjoying the chaos of everyday life together. Tech can wait; these moments can't.", + "activeLifestyleTitle": "[ACTIVE LIFESTYLE]", + "activeLifestyleText": "I believe in keeping the body active. Whether it's hitting the gym, playing sports, or just staying on the move - physical activity keeps me sharp, balanced, and ready for whatever life throws my way.", + "simpleThingsTitle": "[ENJOYING THE SIMPLE THINGS]", + "simpleThingsText": "Life's too short not to enjoy it. A good drink, a relaxing evening after a long day, or just not doing anything a blowing some steam off.", + "techPurposeTitle": "[TECH WITH PURPOSE]", + "techPurposeText": "Yes, I love tech - self-hosting, privacy, tinkering with hardware. But it's a tool, not a lifestyle. Tech should serve life, not the other way around.", + "contentTitle": "> WHAT YOU'LL FIND HERE", + "contentSubtitle": "CONTENT SCOPE // EVERYTHING FROM TECH TO LIFE", + "contentThoughts": "Thoughts & Opinions", + "contentThoughtsDesc": "My take on life, work, and everything in between", + "contentLifeFamily": "Life & Family", + "contentLifeFamilyDesc": "Adventures in parenting, sports, and enjoying the simple things", + "contentTechResearch": "Tech Research", + "contentTechResearchDesc": "When I dive into interesting technologies and experiments", + "contentSysAdmin": "System Administration", + "contentSysAdminDesc": "Self-hosting, infrastructure, and DevOps adventures", + "contentDevelopment": "Development Insights", + "contentDevelopmentDesc": "Lessons learned from building software", + "contentRandom": "Random Stuff", + "contentRandomDesc": "Because life doesn't fit into neat categories!", + "focusTitle": "> AREAS OF FOCUS", + "focusBeingDadTitle": "[BEING A DAD]", + "focusBeingDadText": "Playing with my boy, teaching moments, watching him grow, building memories together", + "focusStayingActiveTitle": "[STAYING ACTIVE]", + "focusStayingActiveText": "Gym sessions, sports, keeping fit, maintaining energy for life's demands", + "focusTechnologyTitle": "[TECHNOLOGY & SYSTEMS]", + "focusTechnologyText": "Software development, infrastructure, DevOps, self-hosting adventures", + "focusLifeBalanceTitle": "[LIFE BALANCE]", + "focusLifeBalanceText": "Relaxing with good company, enjoying downtime, appreciating the simple moments", + "techStackTitle": "> TECH STACK", + "techStackSubtitle": "TOOLS I USE // WHEN NEEDED", + "techStackDevelopmentTitle": "[DEVELOPMENT]", + "techStackDevelopmentText": ".NET, Golang, TypeScript, Next.js, React", + "techStackInfrastructureTitle": "[INFRASTRUCTURE]", + "techStackInfrastructureText": "Windows Server, Linux, Docker, Hyper-V", + "techStackDesignTitle": "[DESIGN]", + "techStackDesignText": "Tailwind CSS, Markdown, Terminal aesthetics", + "techStackSelfHostingTitle": "[SELF-HOSTING]", + "techStackSelfHostingText": "Home lab, privacy-focused services, full control, Git server", + "contactTitle": "> CONTACT" }, "NotFound": { diff --git a/messages/ro.json b/messages/ro.json index d0077f2..0536108 100644 --- a/messages/ro.json +++ b/messages/ro.json @@ -18,6 +18,21 @@ "about": "Despre" }, + "Home": { + "terminalVersion": "TERMINAL:// V2.0", + "documentLevel": "DOCUMENT LEVEL-1 //", + "heroTitle": "BUILD. WRITE. SHARE.", + "heroSubtitle": "> Explore ideas", + "checkPostsButton": "[CHECK POSTS]", + "aboutMeButton": "[ABOUT ME]", + "recentEntriesLabel": "ARCHIVE ACCESS // RECENT ENTRIES", + "recentEntriesTitle": "> RECENT ENTRIES", + "fileLabel": "FILE#{number} // {category}", + "accessButton": "[ACCESS] >>", + "seePostsButton": "[SEE POSTS] >>", + "seeAllTagsButton": "[SEE ALL TAGS] >>" + }, + "BlogListing": { "title": "Blog", "subtitle": "Ultimele articole și gânduri", @@ -29,7 +44,9 @@ "filterByTag": "Filtrează după etichetă", "clearFilters": "Șterge filtrele", "foundPosts": "{count} articole găsite", - "noPosts": "Niciun articol găsit" + "noPosts": "Niciun articol găsit", + "prev": "< PREV", + "next": "NEXT >" }, "BlogPost": { @@ -53,7 +70,54 @@ "About": { "title": "Despre", - "subtitle": "Află mai multe despre mine" + "subtitle": "Află mai multe despre mine", + "classificationHeader": ">> _DOC://PUBLIC_ACCESS", + "mainTitle": "ABOUT ME_", + "introLabel": "STATUS: ACTIVE // ROLE: DAD + DEV + LIFE ENTHUSIAST", + "introParagraph1": "Welcome to my corner of the internet! This is where I share my thoughts, opinions, and experiences - from tech adventures to life as a family man. Yes, I love technology, but there's so much more to life than just code and servers.", + "lifeValuesTitle": "> LIFE & VALUES", + "familyFirstTitle": "[FAMILY FIRST]", + "familyFirstText": "Being a dad to an amazing toddler is my most important role. Family time is sacred - whether it's building block towers, exploring parks, or just enjoying the chaos of everyday life together. Tech can wait; these moments can't.", + "activeLifestyleTitle": "[ACTIVE LIFESTYLE]", + "activeLifestyleText": "I believe in keeping the body active. Whether it's hitting the gym, playing sports, or just staying on the move - physical activity keeps me sharp, balanced, and ready for whatever life throws my way.", + "simpleThingsTitle": "[ENJOYING THE SIMPLE THINGS]", + "simpleThingsText": "Life's too short not to enjoy it. A good drink, a relaxing evening after a long day, or just not doing anything a blowing some steam off.", + "techPurposeTitle": "[TECH WITH PURPOSE]", + "techPurposeText": "Yes, I love tech - self-hosting, privacy, tinkering with hardware. But it's a tool, not a lifestyle. Tech should serve life, not the other way around.", + "contentTitle": "> WHAT YOU'LL FIND HERE", + "contentSubtitle": "CONTENT SCOPE // EVERYTHING FROM TECH TO LIFE", + "contentThoughts": "Thoughts & Opinions", + "contentThoughtsDesc": "My take on life, work, and everything in between", + "contentLifeFamily": "Life & Family", + "contentLifeFamilyDesc": "Adventures in parenting, sports, and enjoying the simple things", + "contentTechResearch": "Tech Research", + "contentTechResearchDesc": "When I dive into interesting technologies and experiments", + "contentSysAdmin": "System Administration", + "contentSysAdminDesc": "Self-hosting, infrastructure, and DevOps adventures", + "contentDevelopment": "Development Insights", + "contentDevelopmentDesc": "Lessons learned from building software", + "contentRandom": "Random Stuff", + "contentRandomDesc": "Because life doesn't fit into neat categories!", + "focusTitle": "> AREAS OF FOCUS", + "focusBeingDadTitle": "[BEING A DAD]", + "focusBeingDadText": "Playing with my boy, teaching moments, watching him grow, building memories together", + "focusStayingActiveTitle": "[STAYING ACTIVE]", + "focusStayingActiveText": "Gym sessions, sports, keeping fit, maintaining energy for life's demands", + "focusTechnologyTitle": "[TECHNOLOGY & SYSTEMS]", + "focusTechnologyText": "Software development, infrastructure, DevOps, self-hosting adventures", + "focusLifeBalanceTitle": "[LIFE BALANCE]", + "focusLifeBalanceText": "Relaxing with good company, enjoying downtime, appreciating the simple moments", + "techStackTitle": "> TECH STACK", + "techStackSubtitle": "TOOLS I USE // WHEN NEEDED", + "techStackDevelopmentTitle": "[DEVELOPMENT]", + "techStackDevelopmentText": ".NET, Golang, TypeScript, Next.js, React", + "techStackInfrastructureTitle": "[INFRASTRUCTURE]", + "techStackInfrastructureText": "Windows Server, Linux, Docker, Hyper-V", + "techStackDesignTitle": "[DESIGN]", + "techStackDesignText": "Tailwind CSS, Markdown, Terminal aesthetics", + "techStackSelfHostingTitle": "[SELF-HOSTING]", + "techStackSelfHostingText": "Home lab, privacy-focused services, full control, Git server", + "contactTitle": "> CONTACT" }, "NotFound": { -- 2.49.1 From b68325123b9674f1f05118c9db0329621973a494 Mon Sep 17 00:00:00 2001 From: RJ Date: Thu, 4 Dec 2025 15:55:36 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9D=20update=20copy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- messages/en.json | 2 +- messages/ro.json | 89 ++++++++++++++++++++++-------------------------- 2 files changed, 41 insertions(+), 50 deletions(-) diff --git a/messages/en.json b/messages/en.json index d354bb9..a585bd5 100644 --- a/messages/en.json +++ b/messages/en.json @@ -73,7 +73,7 @@ "subtitle": "Learn more about me", "classificationHeader": ">> _DOC://PUBLIC_ACCESS", "mainTitle": "ABOUT ME_", - "introLabel": "STATUS: ACTIVE // ROLE: DAD + DEV + LIFE ENTHUSIAST", + "introLabel": "STATUS: ACTIVE // ROLE: DAD + DEV", "introParagraph1": "Welcome to my corner of the internet! This is where I share my thoughts, opinions, and experiences - from tech adventures to life as a family man. Yes, I love technology, but there's so much more to life than just code and servers.", "lifeValuesTitle": "> LIFE & VALUES", "familyFirstTitle": "[FAMILY FIRST]", diff --git a/messages/ro.json b/messages/ro.json index 0536108..e2ddba4 100644 --- a/messages/ro.json +++ b/messages/ro.json @@ -3,22 +3,19 @@ "siteTitle": "Blog Personal", "siteDescription": "Gânduri despre tehnologie și dezvoltare" }, - "Navigation": { "home": "Acasă", "blog": "Blog", "tags": "Etichete", "about": "Despre" }, - "Breadcrumbs": { "home": "Acasă", "blog": "Blog", "tags": "Etichete", "about": "Despre" }, - - "Home": { + "Home": { "terminalVersion": "TERMINAL:// V2.0", "documentLevel": "DOCUMENT LEVEL-1 //", "heroTitle": "BUILD. WRITE. SHARE.", @@ -32,7 +29,6 @@ "seePostsButton": "[SEE POSTS] >>", "seeAllTagsButton": "[SEE ALL TAGS] >>" }, - "BlogListing": { "title": "Blog", "subtitle": "Ultimele articole și gânduri", @@ -48,7 +44,6 @@ "prev": "< PREV", "next": "NEXT >" }, - "BlogPost": { "readMore": "Citește mai mult", "readingTime": "{minutes} min citire", @@ -58,7 +53,6 @@ "relatedPosts": "Articole similare", "sharePost": "Distribuie acest articol" }, - "Tags": { "title": "Etichete", "subtitle": "Navighează după subiect", @@ -67,67 +61,64 @@ "relatedTags": "Etichete similare", "quickNav": "Navigare rapidă" }, - "About": { "title": "Despre", "subtitle": "Află mai multe despre mine", "classificationHeader": ">> _DOC://PUBLIC_ACCESS", - "mainTitle": "ABOUT ME_", - "introLabel": "STATUS: ACTIVE // ROLE: DAD + DEV + LIFE ENTHUSIAST", - "introParagraph1": "Welcome to my corner of the internet! This is where I share my thoughts, opinions, and experiences - from tech adventures to life as a family man. Yes, I love technology, but there's so much more to life than just code and servers.", - "lifeValuesTitle": "> LIFE & VALUES", - "familyFirstTitle": "[FAMILY FIRST]", - "familyFirstText": "Being a dad to an amazing toddler is my most important role. Family time is sacred - whether it's building block towers, exploring parks, or just enjoying the chaos of everyday life together. Tech can wait; these moments can't.", - "activeLifestyleTitle": "[ACTIVE LIFESTYLE]", - "activeLifestyleText": "I believe in keeping the body active. Whether it's hitting the gym, playing sports, or just staying on the move - physical activity keeps me sharp, balanced, and ready for whatever life throws my way.", - "simpleThingsTitle": "[ENJOYING THE SIMPLE THINGS]", - "simpleThingsText": "Life's too short not to enjoy it. A good drink, a relaxing evening after a long day, or just not doing anything a blowing some steam off.", - "techPurposeTitle": "[TECH WITH PURPOSE]", - "techPurposeText": "Yes, I love tech - self-hosting, privacy, tinkering with hardware. But it's a tool, not a lifestyle. Tech should serve life, not the other way around.", - "contentTitle": "> WHAT YOU'LL FIND HERE", - "contentSubtitle": "CONTENT SCOPE // EVERYTHING FROM TECH TO LIFE", - "contentThoughts": "Thoughts & Opinions", - "contentThoughtsDesc": "My take on life, work, and everything in between", - "contentLifeFamily": "Life & Family", - "contentLifeFamilyDesc": "Adventures in parenting, sports, and enjoying the simple things", - "contentTechResearch": "Tech Research", - "contentTechResearchDesc": "When I dive into interesting technologies and experiments", - "contentSysAdmin": "System Administration", - "contentSysAdminDesc": "Self-hosting, infrastructure, and DevOps adventures", + "mainTitle": "DESPRE_", + "introLabel": "STATUS: ACTIV // ROL: TATĂ + DEV", + "introParagraph1": "Mi-am făcut un colțișor pe internet unde pot să împărtășesc cam tot ce vreau. O să găsești aici și tech, și viață, și haosul de zi cu zi.", + "lifeValuesTitle": "> VIAȚĂ & VALORI", + "familyFirstTitle": "[FAMILIA PE PRIMUL LOC]", + "familyFirstText": "Să fiu tată pentru un puști genial e cel mai important rol al meu. Timpul cu familia e sfânt – fie că construim turnuri din cuburi, explorăm parcuri sau doar trăim frumos haosul de zi cu zi. Tech-ul poate să aștepte, momentele astea nu.", + "activeLifestyleTitle": "[STIL DE VIAȚĂ ACTIV]", + "activeLifestyleText": "Încerc să-mi țin corpul în mișcare. Sală, sport, orice mă scoate din scaun. Mă ajută să fiu mai clar la minte, mai echilibrat și pregătit de ce aruncă viața în mine.", + "simpleThingsTitle": "[BUCURIA LUCRURILOR SIMPLE]", + "simpleThingsText": "Viața e prea scurtă să n-o savurezi. O băutură bună, o seară liniștită după o zi grea sau pur și simplu să nu faci nimic și să lași aburii să iasă… și e perfect așa.", + "techPurposeTitle": "[TECH CU SENS]", + "techPurposeText": "Da, îmi place tehnologia – self-hosting, privacy, joacă cu hardware. Dar pentru mine e o unealtă, nu un stil de viață. Tech-ul ar trebui să lucreze pentru tine, nu tu pentru el.", + "contentTitle": "> CE GĂSEȘTI AICI", + "contentSubtitle": "CONTENT SCOPE // DE LA TECH LA VIAȚĂ", + "contentThoughts": "Gânduri & Opinii", + "contentThoughtsDesc": "Cum văd eu viața, munca și tot ce e între ele", + "contentLifeFamily": "Viață & Familie", + "contentLifeFamilyDesc": "Aventuri de părinte, sport și bucuria lucrurilor mici", + "contentTechResearch": "Experimente Tech", + "contentTechResearchDesc": "Când mă afund în tehnologii interesante și experimente ciudate", + "contentSysAdmin": "Administrare Sisteme", + "contentSysAdminDesc": "Self-hosting, infrastructură și aventuri de tip DevOps", "contentDevelopment": "Development Insights", - "contentDevelopmentDesc": "Lessons learned from building software", - "contentRandom": "Random Stuff", - "contentRandomDesc": "Because life doesn't fit into neat categories!", - "focusTitle": "> AREAS OF FOCUS", - "focusBeingDadTitle": "[BEING A DAD]", - "focusBeingDadText": "Playing with my boy, teaching moments, watching him grow, building memories together", - "focusStayingActiveTitle": "[STAYING ACTIVE]", - "focusStayingActiveText": "Gym sessions, sports, keeping fit, maintaining energy for life's demands", - "focusTechnologyTitle": "[TECHNOLOGY & SYSTEMS]", - "focusTechnologyText": "Software development, infrastructure, DevOps, self-hosting adventures", - "focusLifeBalanceTitle": "[LIFE BALANCE]", - "focusLifeBalanceText": "Relaxing with good company, enjoying downtime, appreciating the simple moments", + "contentDevelopmentDesc": "Lecții învățate din proiectele pe care le construiesc", + "contentRandom": "Chestii Random", + "contentRandomDesc": "Pentru că viața nu intră mereu frumos pe categorii!", + "focusTitle": "> ZONE DE FOCUS", + "focusBeingDadTitle": "[TATĂ ÎN PRIMUL RÂND]", + "focusBeingDadText": "Joacă cu băiatul meu, momente de învățat, să-l văd cum crește și să strângem amintiri împreună", + "focusStayingActiveTitle": "[SĂ RĂMÂN ACTIV]", + "focusStayingActiveText": "Sesiuni la sală, sport, să mă țin în formă și cu energie pentru tot ce am de dus", + "focusTechnologyTitle": "[TECH & SISTEME]", + "focusTechnologyText": "Dezvoltare software, infrastructură, DevOps, aventuri de self-hosting", + "focusLifeBalanceTitle": "[ECHILIBRU ÎN VIAȚĂ]", + "focusLifeBalanceText": "Relax cu prietenii, timp de respiro, apreciat momentele simple", "techStackTitle": "> TECH STACK", - "techStackSubtitle": "TOOLS I USE // WHEN NEEDED", + "techStackSubtitle": "UNELTELE PE CARE LE FOLOSESC // CÂND TREBUIE", "techStackDevelopmentTitle": "[DEVELOPMENT]", "techStackDevelopmentText": ".NET, Golang, TypeScript, Next.js, React", - "techStackInfrastructureTitle": "[INFRASTRUCTURE]", + "techStackInfrastructureTitle": "[INFRASTRUCTURĂ]", "techStackInfrastructureText": "Windows Server, Linux, Docker, Hyper-V", "techStackDesignTitle": "[DESIGN]", - "techStackDesignText": "Tailwind CSS, Markdown, Terminal aesthetics", + "techStackDesignText": "Tailwind CSS, Markdown", "techStackSelfHostingTitle": "[SELF-HOSTING]", - "techStackSelfHostingText": "Home lab, privacy-focused services, full control, Git server", + "techStackSelfHostingText": "Home lab, servicii cu focus pe privacy, control total, server Git", "contactTitle": "> CONTACT" }, - "NotFound": { "title": "Pagina nu a fost găsită", "description": "Pagina pe care o cauți nu există", "goHome": "Mergi la pagina principală" }, - "LanguageSwitcher": { "switchLanguage": "Schimbă limba", "currentLanguage": "Limba curentă" } -} +} \ No newline at end of file -- 2.49.1 From 101624c4d59ce603682cf03a408c8dba6f064b76 Mon Sep 17 00:00:00 2001 From: RJ Date: Thu, 4 Dec 2025 15:57:39 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=93=9D=20priettier=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[locale]/about/page.tsx | 8 ++--- app/[locale]/blog/[...slug]/not-found.tsx | 4 +-- app/[locale]/blog/blog-client.tsx | 9 +++-- app/[locale]/blog/page.tsx | 2 +- app/[locale]/layout.tsx | 22 +++++------- app/[locale]/page.tsx | 10 +++--- app/[locale]/tags/[tag]/page.tsx | 6 ++-- app/[locale]/tags/page.tsx | 10 +++--- app/feed.xml/route.ts | 2 +- app/layout.tsx | 10 ++---- app/robots.ts | 8 ++--- app/sitemap.ts | 10 +++--- components/blog/OptimizedImage.tsx | 4 +-- components/blog/blog-card.tsx | 6 ++-- components/blog/popular-tags.tsx | 2 +- components/blog/tag-cloud.tsx | 2 +- components/layout/Breadcrumbs.tsx | 2 +- components/layout/LanguageSwitcher.tsx | 40 +++++++++------------ content/blog/en/why-this-page.md | 2 +- content/blog/ro/why-this-page.md | 2 +- docs/ENV_CONFIG_GUIDE.md | 42 +++++++++++++++++------ docs/OPTIMIZATION_REPORT.md | 42 ++++++++++++++++++++--- lib/env-validation.ts | 11 ++---- lib/tags.ts | 10 ++++-- messages/ro.json | 2 +- middleware.ts | 18 ++++------ next.config.js | 10 ++---- src/i18n/navigation.ts | 7 ++-- src/i18n/request.ts | 22 ++++++------ src/i18n/routing.ts | 10 +++--- tsconfig.json | 20 +++-------- types/translations.d.ts | 2 +- 32 files changed, 185 insertions(+), 172 deletions(-) diff --git a/app/[locale]/about/page.tsx b/app/[locale]/about/page.tsx index 059a0cd..6553ac4 100644 --- a/app/[locale]/about/page.tsx +++ b/app/[locale]/about/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next' import { Navbar } from '@/components/blog/navbar' -import {setRequestLocale, getTranslations} from 'next-intl/server' +import { setRequestLocale, getTranslations } from 'next-intl/server' export const metadata: Metadata = { title: 'About', @@ -8,11 +8,11 @@ export const metadata: Metadata = { } type Props = { - params: Promise<{locale: string}> + params: Promise<{ locale: string }> } -export default async function AboutPage({params}: Props) { - const {locale} = await params +export default async function AboutPage({ params }: Props) { + const { locale } = await params setRequestLocale(locale) const t = await getTranslations('About') return ( diff --git a/app/[locale]/blog/[...slug]/not-found.tsx b/app/[locale]/blog/[...slug]/not-found.tsx index b0e9602..1bcc9e5 100644 --- a/app/[locale]/blog/[...slug]/not-found.tsx +++ b/app/[locale]/blog/[...slug]/not-found.tsx @@ -9,9 +9,7 @@ export default function NotFound() {

404

{t('title')}

-

- {t('description')} -

+

{t('description')}

- {t("subtitle")} + {t('subtitle')}

- > {t("title")}_ + > {t('title')}_

@@ -103,8 +103,7 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps) {/* Results Count */}

- {t("foundPosts", {count: filteredAndSortedPosts.length})}{' '} - + {t('foundPosts', { count: filteredAndSortedPosts.length })}{' '}

@@ -127,7 +126,7 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps) ) : (

- {t("noPosts")} + {t('noPosts')}

)} diff --git a/app/[locale]/blog/page.tsx b/app/[locale]/blog/page.tsx index c80bdd4..cde074b 100644 --- a/app/[locale]/blog/page.tsx +++ b/app/[locale]/blog/page.tsx @@ -3,7 +3,7 @@ import BlogPageClient from './blog-client' import { setRequestLocale } from 'next-intl/server' export default async function BlogPage({ params }: { params: Promise<{ locale: string }> }) { - const { locale } = await params; + const { locale } = await params await setRequestLocale(locale) const posts = await getAllPosts(locale) const allTags = Array.from(new Set(posts.flatMap(post => post.frontmatter.tags))).sort() diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index 009cb7c..55666f7 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -1,25 +1,21 @@ -import {notFound} from 'next/navigation' -import {setRequestLocale} from 'next-intl/server' -import {routing} from '@/src/i18n/routing' -import {ReactNode} from 'react' +import { notFound } from 'next/navigation' +import { setRequestLocale } from 'next-intl/server' +import { routing } from '@/src/i18n/routing' +import { ReactNode } from 'react' type Props = { children: ReactNode breadcrumbs: ReactNode - params: Promise<{locale: string}> + params: Promise<{ locale: string }> } export function generateStaticParams() { - return routing.locales.map((locale) => ({locale})) + return routing.locales.map(locale => ({ locale })) } -export default async function LocaleLayout({ - children, - breadcrumbs, - params -}: Props) { - const {locale} = await params - +export default async function LocaleLayout({ children, breadcrumbs, params }: Props) { + const { locale } = await params + if (!routing.locales.includes(locale as any)) { notFound() } diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 4d59ec2..72e2f9d 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -1,16 +1,16 @@ -import {Link} from '@/src/i18n/navigation' +import { Link } from '@/src/i18n/navigation' import Image from 'next/image' import { getAllPosts } from '@/lib/markdown' import { formatDate } from '@/lib/utils' import { ThemeToggle } from '@/components/theme-toggle' -import {setRequestLocale, getTranslations} from 'next-intl/server' +import { setRequestLocale, getTranslations } from 'next-intl/server' type Props = { - params: Promise<{locale: string}> + params: Promise<{ locale: string }> } -export default async function HomePage({params}: Props) { - const {locale} = await params +export default async function HomePage({ params }: Props) { + const { locale } = await params setRequestLocale(locale) const t = await getTranslations('Home') const tNav = await getTranslations('Navigation') diff --git a/app/[locale]/tags/[tag]/page.tsx b/app/[locale]/tags/[tag]/page.tsx index eb1c06d..0a4fbce 100644 --- a/app/[locale]/tags/[tag]/page.tsx +++ b/app/[locale]/tags/[tag]/page.tsx @@ -1,11 +1,11 @@ import { Metadata } from 'next' import { notFound } from 'next/navigation' -import {Link} from '@/src/i18n/navigation' +import { Link } from '@/src/i18n/navigation' import { getAllTags, getPostsByTag, getTagInfo, getRelatedTags } from '@/lib/tags' import { TagList } from '@/components/blog/tag-list' import { formatDate } from '@/lib/utils' -import {setRequestLocale} from 'next-intl/server' -import {routing} from '@/src/i18n/routing' +import { setRequestLocale } from 'next-intl/server' +import { routing } from '@/src/i18n/routing' export async function generateStaticParams() { const tags = await getAllTags() diff --git a/app/[locale]/tags/page.tsx b/app/[locale]/tags/page.tsx index 156dd73..7a84d93 100644 --- a/app/[locale]/tags/page.tsx +++ b/app/[locale]/tags/page.tsx @@ -1,9 +1,9 @@ import { Metadata } from 'next' -import {Link} from '@/src/i18n/navigation' +import { Link } from '@/src/i18n/navigation' import { getAllTags, getTagCloud } from '@/lib/tags' import { TagCloud } from '@/components/blog/tag-cloud' import { TagBadge } from '@/components/blog/tag-badge' -import {setRequestLocale} from 'next-intl/server' +import { setRequestLocale } from 'next-intl/server' export const metadata: Metadata = { title: 'Tag-uri', @@ -11,11 +11,11 @@ export const metadata: Metadata = { } type Props = { - params: Promise<{locale: string}> + params: Promise<{ locale: string }> } -export default async function TagsPage({params}: Props) { - const {locale} = await params +export default async function TagsPage({ params }: Props) { + const { locale } = await params setRequestLocale(locale) const allTags = await getAllTags() const tagCloud = await getTagCloud() diff --git a/app/feed.xml/route.ts b/app/feed.xml/route.ts index e1a42c1..7101786 100644 --- a/app/feed.xml/route.ts +++ b/app/feed.xml/route.ts @@ -3,7 +3,7 @@ import { NextResponse } from 'next/server' export async function GET() { const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3030' - const posts = await getAllPosts("en", false) + const posts = await getAllPosts('en', false) const rss = ` diff --git a/app/layout.tsx b/app/layout.tsx index 9de5d47..690aa98 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,8 +3,8 @@ import { JetBrains_Mono } from 'next/font/google' import './globals.css' import { ThemeProvider } from '@/providers/providers' import '@/lib/env-validation' -import {NextIntlClientProvider} from 'next-intl' -import {getMessages} from 'next-intl/server' +import { NextIntlClientProvider } from 'next-intl' +import { getMessages } from 'next-intl/server' const jetbrainsMono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-mono' }) @@ -30,11 +30,7 @@ export const metadata: Metadata = { }, } -export default async function RootLayout({ - children -}: { - children: React.ReactNode -}) { +export default async function RootLayout({ children }: { children: React.ReactNode }) { const messages = await getMessages() return ( diff --git a/app/robots.ts b/app/robots.ts index 01e3f32..a036ad8 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -2,15 +2,15 @@ import { MetadataRoute } from 'next' export default function robots(): MetadataRoute.Robots { const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3030' - + return { rules: { userAgent: '*', allow: '/', disallow: [ - '/api/', // Disallow API routes (if any) - '/_next/', // Disallow Next.js internals - '/admin/', // Disallow admin (if any) + '/api/', // Disallow API routes (if any) + '/_next/', // Disallow Next.js internals + '/admin/', // Disallow admin (if any) ], }, sitemap: `${baseUrl}/sitemap.xml`, diff --git a/app/sitemap.ts b/app/sitemap.ts index 9325927..d73e95f 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -3,10 +3,10 @@ import { getAllPosts } from '@/lib/markdown' export default async function sitemap(): Promise { const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3030' - + // Get all blog posts - const posts = await getAllPosts("en", false) - + const posts = await getAllPosts('en', false) + // Generate sitemap entries for blog posts const blogPosts: MetadataRoute.Sitemap = posts.map(post => ({ url: `${baseUrl}/blog/${post.slug}`, @@ -14,7 +14,7 @@ export default async function sitemap(): Promise { changeFrequency: 'monthly' as const, priority: 0.8, })) - + // Static pages const staticPages: MetadataRoute.Sitemap = [ { @@ -36,6 +36,6 @@ export default async function sitemap(): Promise { priority: 0.7, }, ] - + return [...staticPages, ...blogPosts] } diff --git a/components/blog/OptimizedImage.tsx b/components/blog/OptimizedImage.tsx index 500ab57..41ae046 100644 --- a/components/blog/OptimizedImage.tsx +++ b/components/blog/OptimizedImage.tsx @@ -62,9 +62,7 @@ export function OptimizedImage({ return ( {imageElement} - {caption && ( - {caption} - )} + {caption && {caption}} ) } diff --git a/components/blog/blog-card.tsx b/components/blog/blog-card.tsx index aaaa1b5..e39ed73 100644 --- a/components/blog/blog-card.tsx +++ b/components/blog/blog-card.tsx @@ -40,7 +40,7 @@ export function BlogCard({ post, variant }: BlogCardProps) { ))}
- > {t('readingTime', {minutes: post.readingTime})} + > {t('readingTime', { minutes: post.readingTime })} @@ -84,7 +84,7 @@ export function BlogCard({ post, variant }: BlogCardProps) { ))} - > {t('readingTime', {minutes: post.readingTime})} + > {t('readingTime', { minutes: post.readingTime })} @@ -129,7 +129,7 @@ export function BlogCard({ post, variant }: BlogCardProps) { ))} - > {t('readingTime', {minutes: post.readingTime})} + > {t('readingTime', { minutes: post.readingTime })} diff --git a/components/blog/popular-tags.tsx b/components/blog/popular-tags.tsx index 8ea1217..ff80305 100644 --- a/components/blog/popular-tags.tsx +++ b/components/blog/popular-tags.tsx @@ -3,7 +3,7 @@ import { getPopularTags } from '@/lib/tags' import { TagBadge } from './tag-badge' export async function PopularTags({ limit = 5 }: { limit?: number }) { - const tags = await getPopularTags("en", limit) + const tags = await getPopularTags('en', limit) if (tags.length === 0) return null diff --git a/components/blog/tag-cloud.tsx b/components/blog/tag-cloud.tsx index 26d258f..c5f31fa 100644 --- a/components/blog/tag-cloud.tsx +++ b/components/blog/tag-cloud.tsx @@ -28,7 +28,7 @@ export function TagCloud({ tags }: TagCloudProps) { hover:text-cyan-400 transition-colors `} - title={t('postsWithTag', {count: tag.count, tag: tag.name})} + title={t('postsWithTag', { count: tag.count, tag: tag.name })} > #{tag.name} diff --git a/components/layout/Breadcrumbs.tsx b/components/layout/Breadcrumbs.tsx index 57f9fa5..1005f20 100644 --- a/components/layout/Breadcrumbs.tsx +++ b/components/layout/Breadcrumbs.tsx @@ -1,6 +1,6 @@ 'use client' -import {Link} from '@/i18n/navigation' +import { Link } from '@/i18n/navigation' import { usePathname } from 'next/navigation' import { useLocale, useTranslations } from 'next-intl' import { Fragment } from 'react' diff --git a/components/layout/LanguageSwitcher.tsx b/components/layout/LanguageSwitcher.tsx index 078ea8f..2b89bd8 100644 --- a/components/layout/LanguageSwitcher.tsx +++ b/components/layout/LanguageSwitcher.tsx @@ -1,21 +1,21 @@ -'use client'; +'use client' -import {useLocale} from 'next-intl'; -import {useRouter, usePathname} from '@/i18n/navigation'; -import {routing} from '@/i18n/routing'; -import {useState} from 'react'; +import { useLocale } from 'next-intl' +import { useRouter, usePathname } from '@/i18n/navigation' +import { routing } from '@/i18n/routing' +import { useState } from 'react' export default function LanguageSwitcher() { - const locale = useLocale(); - const router = useRouter(); - const pathname = usePathname(); - const [isOpen, setIsOpen] = useState(false); + const locale = useLocale() + const router = useRouter() + const pathname = usePathname() + const [isOpen, setIsOpen] = useState(false) const handleLocaleChange = (newLocale: string) => { - router.replace(pathname, {locale: newLocale}); - router.refresh(); - setIsOpen(false); - }; + router.replace(pathname, { locale: newLocale }) + router.refresh() + setIsOpen(false) + } return (
@@ -36,9 +36,8 @@ export default function LanguageSwitcher() { className={` w-full text-left px-4 py-2 font-mono uppercase text-xs border-b border-slate-700 last:border-b-0 - ${locale === loc - ? 'bg-cyan-900 text-cyan-300' - : 'text-slate-400 hover:bg-slate-800' + ${ + locale === loc ? 'bg-cyan-900 text-cyan-300' : 'text-slate-400 hover:bg-slate-800' } `} > @@ -48,12 +47,7 @@ export default function LanguageSwitcher() {
)} - {isOpen && ( -
setIsOpen(false)} - /> - )} + {isOpen &&
setIsOpen(false)} />}
- ); + ) } diff --git a/content/blog/en/why-this-page.md b/content/blog/en/why-this-page.md index 614dfc5..ba3fbb5 100644 --- a/content/blog/en/why-this-page.md +++ b/content/blog/en/why-this-page.md @@ -24,7 +24,7 @@ Well, yes, there are. But I believe that sharing some of my opinions and experie ## Why self-host? -![Selfhosting rig](./whythispage/selfhostedrig.gif?w=400 "My self-hosting setup | A look at the hardware running this blog") +![Selfhosting rig](./whythispage/selfhostedrig.gif?w=400 'My self-hosting setup | A look at the hardware running this blog') Now, let's talk about why I chose to self-host this blog. In a nutshell, self-hosting gave me: diff --git a/content/blog/ro/why-this-page.md b/content/blog/ro/why-this-page.md index 454216b..23ed5df 100644 --- a/content/blog/ro/why-this-page.md +++ b/content/blog/ro/why-this-page.md @@ -23,7 +23,7 @@ Dacă te gândești de ce să mai creezi inca un blog cand sunt atea pe net, pai ## De ce selfhost? -![Selfhosting rig](./whythispage/selfhostedrig.gif?w=400 "Acesta este pc-ul | Hardware-ul pe care ruleaza cest webpage") +![Selfhosting rig](./whythispage/selfhostedrig.gif?w=400 'Acesta este pc-ul | Hardware-ul pe care ruleaza cest webpage') Am inceput sa fac hosting acasa din cateva motive: diff --git a/docs/ENV_CONFIG_GUIDE.md b/docs/ENV_CONFIG_GUIDE.md index d21b057..ebc26ea 100644 --- a/docs/ENV_CONFIG_GUIDE.md +++ b/docs/ENV_CONFIG_GUIDE.md @@ -5,6 +5,7 @@ This guide documents the configuration for build-time environment variables in the Next.js CI/CD pipeline. **Problem Solved:** Next.js 16 requires `NEXT_PUBLIC_*` variables available during `npm run build` for: + - SEO metadata (`metadataBase`) - Sitemap generation - OpenGraph URLs @@ -19,6 +20,7 @@ This guide documents the configuration for build-time environment variables in t ### 1. `.gitea/workflows/main.yml` **Changes:** + - Added step to create `.env` from Gitea secrets (after checkout) - Added cleanup step to remove `.env` after Docker push @@ -34,7 +36,7 @@ This guide documents the configuration for build-time environment variables in t NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 EOF - + echo "✅ .env file created successfully" echo "Preview (secrets masked):" cat .env | sed 's/=.*/=***MASKED***/g' @@ -44,7 +46,7 @@ This guide documents the configuration for build-time environment variables in t - name: 🚀 Push Docker image to registry run: | docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - + # Clean up sensitive files rm -f .env echo "✅ Cleaned up .env file" @@ -55,6 +57,7 @@ This guide documents the configuration for build-time environment variables in t ### 2. `Dockerfile.nextjs` **Changes:** + - Added `COPY .env* ./` in builder stage (after copying node_modules, before copying source code) **Added Section:** @@ -73,6 +76,7 @@ COPY .env* ./ ### 3. `.dockerignore` **Changes:** + - Modified to allow `.env` file (created by CI/CD) while excluding other `.env*` files **Updated Section:** @@ -85,6 +89,7 @@ COPY .env* ./ ``` **Explanation:** + - `.env*` excludes all environment files - `!.env` creates exception for main `.env` (from CI/CD) - `.env.local`, `.env.development`, `.env.production.local` remain excluded @@ -99,11 +104,12 @@ Navigate to: **Repository Settings → Secrets** Add the following secret: -| Secret Name | Value | Type | Description | -|------------|-------|------|-------------| +| Secret Name | Value | Type | Description | +| ---------------------- | ------------------------ | ------------------ | ------------------- | | `NEXT_PUBLIC_SITE_URL` | `https://yourdomain.com` | Secret or Variable | Production site URL | **Notes:** + - Can be configured as **Secret** (masked in logs) or **Variable** (visible in logs) - Recommended: Use **Variable** since it's a public URL - For sensitive values (API keys), always use **Secret** @@ -113,12 +119,14 @@ Add the following secret: To add more build-time variables: 1. **Add to Gitea Secrets/Variables:** + ``` NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX NEXT_PUBLIC_API_URL=https://api.example.com ``` 2. **Update workflow `.env` creation step:** + ```yaml cat > .env << EOF NEXT_PUBLIC_SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }} @@ -138,6 +146,7 @@ To add more build-time variables: ### Local Testing 1. **Create test `.env` file:** + ```bash cat > .env << EOF NEXT_PUBLIC_SITE_URL=http://localhost:3030 @@ -147,24 +156,27 @@ To add more build-time variables: ``` 2. **Build Docker image:** + ```bash docker build -t mypage:test -f Dockerfile.nextjs . ``` 3. **Verify variable is embedded (should show "NOT FOUND" - correct behavior):** + ```bash docker run --rm mypage:test node -e "console.log(process.env.NEXT_PUBLIC_SITE_URL || 'NOT FOUND')" ``` - + **Expected Output:** `NOT FOUND` - + **Why?** `NEXT_PUBLIC_*` variables are embedded in JavaScript bundle during build, NOT available as runtime environment variables. 4. **Test application starts:** + ```bash docker run --rm -p 3030:3030 mypage:test ``` - + Visit `http://localhost:3030` to verify. 5. **Cleanup:** @@ -214,10 +226,10 @@ To add more build-time variables: ### 🔒 Sensitive Data Guidelines -| Type | Use For | Access | -|------|---------|--------| -| `NEXT_PUBLIC_*` | Client-side config (URLs, feature flags) | Public (embedded in JS bundle) | -| `SECRET_*` | Server-side secrets (API keys, DB passwords) | Private (runtime only) | +| Type | Use For | Access | +| --------------- | -------------------------------------------- | ------------------------------ | +| `NEXT_PUBLIC_*` | Client-side config (URLs, feature flags) | Public (embedded in JS bundle) | +| `SECRET_*` | Server-side secrets (API keys, DB passwords) | Private (runtime only) | --- @@ -226,10 +238,12 @@ To add more build-time variables: ### Issue: Variables not available during build **Symptoms:** + - Next.js build errors about missing `NEXT_PUBLIC_SITE_URL` - Metadata/sitemap generation fails **Solution:** + - Verify `NEXT_PUBLIC_SITE_URL` secret exists in Gitea - Check workflow logs for `.env` creation step - Ensure `.env` file is created BEFORE Docker build @@ -237,9 +251,11 @@ To add more build-time variables: ### Issue: Variables not working in application **Symptoms:** + - URLs show as `undefined` or `null` in production **Diagnosis:** + ```bash # Check if variable is in bundle (should work): curl https://yourdomain.com | grep -o 'NEXT_PUBLIC_SITE_URL' @@ -249,6 +265,7 @@ docker exec mypage-prod node -e "console.log(process.env.NEXT_PUBLIC_SITE_URL)" ``` **Solution:** + - Verify `.env` was copied during Docker build - Check Dockerfile logs for `COPY .env* ./` step - Rebuild with `--no-cache` if needed @@ -256,9 +273,11 @@ docker exec mypage-prod node -e "console.log(process.env.NEXT_PUBLIC_SITE_URL)" ### Issue: `.env` file not found during Docker build **Symptoms:** + - Docker build warning: `COPY .env* ./` - no files matched **Solution:** + - Check `.dockerignore` allows `.env` file - Verify workflow creates `.env` BEFORE Docker build - Check file exists: `ls -la .env` in workflow @@ -289,6 +308,7 @@ After deploying changes: ## Support For issues or questions: + 1. Check workflow logs in Gitea Actions 2. Review Docker build logs 3. Verify Gitea secrets configuration diff --git a/docs/OPTIMIZATION_REPORT.md b/docs/OPTIMIZATION_REPORT.md index 0699a86..412db3a 100644 --- a/docs/OPTIMIZATION_REPORT.md +++ b/docs/OPTIMIZATION_REPORT.md @@ -1,4 +1,5 @@ # Production Optimizations Report + Date: 2025-11-24 Branch: feat/production-improvements @@ -7,6 +8,7 @@ Branch: feat/production-improvements Successfully implemented 7 categories of production optimizations for Next.js 16 blog application. ### Build Status: SUCCESS + - Build Time: ~3.9s compilation + ~1.5s static generation - Static Pages Generated: 19 pages - Bundle Size: 1.2MB (static assets) @@ -17,10 +19,12 @@ Successfully implemented 7 categories of production optimizations for Next.js 16 ## 1. Bundle Size Optimization - Remove Unused Dependencies ### Actions Taken: + - Removed `react-syntax-highlighter` (11 packages eliminated) - Removed `@types/react-syntax-highlighter` ### Impact: + - **11 packages removed** from dependency tree - Cleaner bundle, faster npm installs - All remaining dependencies verified as actively used @@ -30,11 +34,13 @@ Successfully implemented 7 categories of production optimizations for Next.js 16 ## 2. Lazy Loading for Heavy Components ### Status: + - Attempted to implement dynamic imports for CodeBlock component - Tool limitations prevented full implementation - Benefit would be minimal (CodeBlock already client-side rendered) ### Recommendation: + - Consider manual lazy loading in future if CodeBlock becomes heavier - Current implementation is already performant @@ -45,16 +51,19 @@ Successfully implemented 7 categories of production optimizations for Next.js 16 ### Security Enhancements Applied: **Dockerfile.nextjs:** + - Remove SUID/SGID binaries (prevent privilege escalation) - Remove apk package manager after dependencies installed - Create proper permissions for /tmp, /.next/cache, /app/logs directories **docker-compose.prod.yml:** + - Added `security_opt: no-new-privileges:true` - Added commented read-only filesystem option (optional hardening) - Documented tmpfs mounts for extra security ### Security Posture: + - Minimal attack surface in production container - Non-root user execution enforced - Package manager unavailable at runtime @@ -66,22 +75,26 @@ Successfully implemented 7 categories of production optimizations for Next.js 16 ### Files Created: **app/sitemap.ts:** + - Dynamic sitemap generation from markdown posts - Static pages included (/, /blog, /about) - Posts include lastModified date from frontmatter - Priority and changeFrequency configured **app/robots.ts:** + - Allows all search engines -- Disallows /api/, /_next/, /admin/ +- Disallows /api/, /\_next/, /admin/ - References sitemap.xml **app/feed.xml/route.ts:** + - RSS 2.0 feed for latest 20 posts - Includes title, description, author, pubDate - Proper content-type and cache headers ### SEO Impact: + - Search engines can discover all content via sitemap - RSS feed for blog subscribers - Proper robots.txt prevents indexing of internal routes @@ -93,16 +106,19 @@ Successfully implemented 7 categories of production optimizations for Next.js 16 ### Configuration Updates: **Sharp:** + - Already installed (production-grade image optimizer) - Faster than default Next.js image optimizer **next.config.js - Image Settings:** + - Cache optimized images for 30 days (`minimumCacheTTL`) - Support AVIF and WebP formats - SVG rendering enabled with security CSP - Responsive image sizes configured (640px to 3840px) ### Performance Impact: + - Faster image processing during builds - Smaller image file sizes (AVIF/WebP) - Better Core Web Vitals (LCP, CLS) @@ -113,21 +129,25 @@ Successfully implemented 7 categories of production optimizations for Next.js 16 ### Cache Headers Added: -**Static Assets (/_next/static/*):** +**Static Assets (/\_next/static/\*):** + - `Cache-Control: public, max-age=31536000, immutable` - 1 year cache for versioned assets -**Images (/images/*):** +**Images (/images/\*):** + - `Cache-Control: public, max-age=31536000, immutable` ### Experimental Features Enabled: **next.config.js - experimental:** + - `staleTimes.dynamic: 30s` (client-side cache for dynamic pages) - `staleTimes.static: 180s` (client-side cache for static pages) - `optimizePackageImports` for react-markdown ecosystem ### Performance Impact: + - Reduced bandwidth usage - Faster repeat visits (cached assets) - Improved navigation speed (stale-while-revalidate) @@ -137,18 +157,22 @@ Successfully implemented 7 categories of production optimizations for Next.js 16 ## 7. Bundle Analyzer Setup ### Tools Installed: + - `@next/bundle-analyzer` (16.0.3) ### NPM Scripts Added: + - `npm run analyze` - Full bundle analysis - `npm run analyze:server` - Server bundle only - `npm run analyze:browser` - Browser bundle only ### Configuration: + - `next.config.analyzer.js` created - Enabled with `ANALYZE=true` environment variable ### Usage: + ```bash npm run analyze # Opens browser with bundle visualization @@ -160,6 +184,7 @@ npm run analyze ## Bundle Size Analysis ### Static Assets: + ``` Total Static: 1.2MB - Largest chunks: @@ -170,10 +195,12 @@ Total Static: 1.2MB ``` ### Standalone Output: + - Total: 44MB (includes Node.js runtime, dependencies, server) - Expected Docker image size: ~150MB (Alpine + Node.js + app) ### Bundle Composition: + - React + React-DOM: Largest dependencies - react-markdown ecosystem: Second largest - Next.js framework: Optimized with tree-shaking @@ -183,6 +210,7 @@ Total Static: 1.2MB ## Build Verification ### Build Output: + ``` Creating an optimized production build ... ✓ Compiled successfully in 3.9s @@ -200,6 +228,7 @@ Route (app) ``` ### Pre-rendered Pages: + - 19 static pages generated - 3 blog posts - 7 tag pages @@ -210,6 +239,7 @@ Route (app) ## Files Modified/Created ### Modified: + - `Dockerfile.nextjs` (security hardening) - `docker-compose.prod.yml` (security options) - `next.config.js` (image optimization, caching headers) @@ -217,6 +247,7 @@ Route (app) - `package-lock.json` (dependency updates) ### Created: + - `app/sitemap.ts` (dynamic sitemap) - `app/robots.ts` (robots.txt) - `app/feed.xml/route.ts` (RSS feed) @@ -227,6 +258,7 @@ Route (app) ## Performance Recommendations ### Implemented: + 1. Bundle size reduced (11 packages removed) 2. Security hardened (Docker + CSP) 3. SEO optimized (sitemap + robots + RSS) @@ -235,7 +267,8 @@ Route (app) 6. Bundle analyzer ready for monitoring ### Future Optimizations: -1. Consider CDN for static assets (/images, /_next/static) + +1. Consider CDN for static assets (/images, /\_next/static) 2. Monitor bundle sizes with `npm run analyze` on each release 3. Add bundle size limits in CI/CD (fail if > threshold) 4. Consider Edge deployment for global performance @@ -246,6 +279,7 @@ Route (app) ## Production Deployment Checklist Before deploying: + - [ ] Set `NEXT_PUBLIC_SITE_URL` in production environment - [ ] Verify Caddy reverse proxy configuration - [ ] Test Docker build: `npm run docker:build` diff --git a/lib/env-validation.ts b/lib/env-validation.ts index 86219c2..28038d1 100644 --- a/lib/env-validation.ts +++ b/lib/env-validation.ts @@ -3,16 +3,9 @@ * Ensures all required environment variables are set before deployment */ -const requiredEnvVars = [ - 'NEXT_PUBLIC_SITE_URL', - 'NODE_ENV', -] as const +const requiredEnvVars = ['NEXT_PUBLIC_SITE_URL', 'NODE_ENV'] as const -const optionalEnvVars = [ - 'PORT', - 'HOSTNAME', - 'NEXT_PUBLIC_GA_ID', -] as const +const optionalEnvVars = ['PORT', 'HOSTNAME', 'NEXT_PUBLIC_GA_ID'] as const export function validateEnvironment() { const missingVars: string[] = [] diff --git a/lib/tags.ts b/lib/tags.ts index 392929a..aa75628 100644 --- a/lib/tags.ts +++ b/lib/tags.ts @@ -65,7 +65,11 @@ export async function getPopularTags(locale: string = 'en', limit = 10): Promise return allTags.slice(0, limit) } -export async function getRelatedTags(tagSlug: string, locale: string = 'en', limit = 5): Promise { +export async function getRelatedTags( + tagSlug: string, + locale: string = 'en', + limit = 5 +): Promise { const posts = await getPostsByTag(tagSlug, locale) const relatedTagMap = new Map() @@ -107,7 +111,9 @@ export function validateTags(tags: any): string[] { return validTags } -export async function getTagCloud(locale: string = 'en'): Promise> { +export async function getTagCloud( + locale: string = 'en' +): Promise> { const tags = await getAllTags(locale) if (tags.length === 0) return [] diff --git a/messages/ro.json b/messages/ro.json index e2ddba4..5f7f57a 100644 --- a/messages/ro.json +++ b/messages/ro.json @@ -121,4 +121,4 @@ "switchLanguage": "Schimbă limba", "currentLanguage": "Limba curentă" } -} \ No newline at end of file +} diff --git a/middleware.ts b/middleware.ts index 5d0d195..6ed5c63 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,5 +1,5 @@ -import createMiddleware from 'next-intl/middleware'; -import {routing} from './src/i18n/routing'; +import createMiddleware from 'next-intl/middleware' +import { routing } from './src/i18n/routing' export default createMiddleware({ ...routing, @@ -7,14 +7,10 @@ export default createMiddleware({ localeCookie: { name: 'NEXT_LOCALE', maxAge: 60 * 60 * 24 * 365, - sameSite: 'lax' - } -}); + sameSite: 'lax', + }, +}) export const config = { - matcher: [ - '/', - '/(en|ro)/:path*', - '/((?!api|_next|_vercel|.*\\..*).*)' - ] -}; + matcher: ['/', '/(en|ro)/:path*', '/((?!api|_next|_vercel|.*\\..*).*)'], +} diff --git a/next.config.js b/next.config.js index 492484c..28f0760 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,4 @@ -const withNextIntl = require('next-intl/plugin')(); +const withNextIntl = require('next-intl/plugin')() /** @type {import('next').NextConfig} */ // ============================================ @@ -8,7 +8,6 @@ const withNextIntl = require('next-intl/plugin')(); // Deprecated options have been removed (swcMinify, reactStrictMode) // SWC minification is now default in Next.js 16 - // Production-ready Next.js configuration with standalone output // This configuration is optimized for Docker deployment with minimal image size // @@ -123,12 +122,7 @@ const nextConfig = { }, // Optimize package imports for smaller bundles - optimizePackageImports: [ - 'react-markdown', - 'rehype-raw', - 'rehype-sanitize', - 'remark-gfm', - ], + optimizePackageImports: ['react-markdown', 'rehype-raw', 'rehype-sanitize', 'remark-gfm'], // Enable PPR (Partial Prerendering) - Next.js 16 feature // Uncomment to enable (currently in beta) diff --git a/src/i18n/navigation.ts b/src/i18n/navigation.ts index 8f5a5e2..a045d71 100644 --- a/src/i18n/navigation.ts +++ b/src/i18n/navigation.ts @@ -1,5 +1,4 @@ -import {createNavigation} from 'next-intl/navigation'; -import {routing} from './routing'; +import { createNavigation } from 'next-intl/navigation' +import { routing } from './routing' -export const {Link, redirect, usePathname, useRouter} = - createNavigation(routing); +export const { Link, redirect, usePathname, useRouter } = createNavigation(routing) diff --git a/src/i18n/request.ts b/src/i18n/request.ts index d00e5cb..0d5af21 100644 --- a/src/i18n/request.ts +++ b/src/i18n/request.ts @@ -1,15 +1,15 @@ -import {getRequestConfig} from 'next-intl/server'; -import {routing} from './routing'; - -export default getRequestConfig(async ({requestLocale}) => { - let locale = await requestLocale; - +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; + locale = routing.defaultLocale } - + return { locale, - messages: (await import(`../../messages/${locale}.json`)).default - }; -}); + messages: (await import(`../../messages/${locale}.json`)).default, + } +}) diff --git a/src/i18n/routing.ts b/src/i18n/routing.ts index 9d21ecb..1b94c0a 100644 --- a/src/i18n/routing.ts +++ b/src/i18n/routing.ts @@ -1,4 +1,4 @@ -import {defineRouting} from 'next-intl/routing'; +import { defineRouting } from 'next-intl/routing' export const routing = defineRouting({ locales: ['en', 'ro'], @@ -6,8 +6,8 @@ export const routing = defineRouting({ localePrefix: 'always', localeNames: { en: 'English', - ro: 'Română' - } -} as any); + ro: 'Română', + }, +} as any) -export type Locale = (typeof routing.locales)[number]; +export type Locale = (typeof routing.locales)[number] diff --git a/tsconfig.json b/tsconfig.json index 3900e3a..4441b4c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "ES2020", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -23,12 +19,8 @@ } ], "paths": { - "@/*": [ - "./*" - ], - "@/i18n/*": [ - "./src/i18n/*" - ] + "@/*": ["./*"], + "@/i18n/*": ["./src/i18n/*"] } }, "include": [ @@ -38,7 +30,5 @@ ".next/types/**/*.ts", ".next/dev/types/**/*.ts" ], - "exclude": [ - "node_modules" - ] -} \ No newline at end of file + "exclude": ["node_modules"] +} diff --git a/types/translations.d.ts b/types/translations.d.ts index fc4e94f..837db6e 100644 --- a/types/translations.d.ts +++ b/types/translations.d.ts @@ -1,4 +1,4 @@ -type Messages = typeof import('../messages/en.json'); +type Messages = typeof import('../messages/en.json') declare global { interface IntlMessages extends Messages {} -- 2.49.1 From bba507a7e837a6ff4ca994782fa61fe9fbd62974 Mon Sep 17 00:00:00 2001 From: RJ Date: Thu, 4 Dec 2025 16:21:34 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=9F=A2=20fix=20linting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[locale]/blog/[...slug]/page.tsx | 4 +--- app/[locale]/tags/[tag]/page.tsx | 2 -- components/blog/code-block.tsx | 4 ++-- components/blog/markdown-renderer.tsx | 2 +- eslint.config.mjs | 3 ++- fix.js | 11 ----------- lib/env-validation.ts | 2 +- lib/remark-copy-images.ts | 2 +- next-env.d.ts | 2 +- types/translations.d.ts | 1 + 10 files changed, 10 insertions(+), 23 deletions(-) delete mode 100644 fix.js diff --git a/app/[locale]/blog/[...slug]/page.tsx b/app/[locale]/blog/[...slug]/page.tsx index 4cb18c0..86be4ff 100644 --- a/app/[locale]/blog/[...slug]/page.tsx +++ b/app/[locale]/blog/[...slug]/page.tsx @@ -2,13 +2,11 @@ import { Metadata } from 'next' import { notFound } from 'next/navigation' import { Link } from '@/src/i18n/navigation' import { getAllPosts, getPostBySlug, getRelatedPosts } from '@/lib/markdown' -import { formatDate, formatRelativeDate } from '@/lib/utils' +import { formatDate } from '@/lib/utils' import { TableOfContents } from '@/components/blog/table-of-contents' import { ReadingProgress } from '@/components/blog/reading-progress' import { StickyFooter } from '@/components/blog/sticky-footer' import MarkdownRenderer from '@/components/blog/markdown-renderer' -import { setRequestLocale } from 'next-intl/server' -import { routing } from '@/src/i18n/routing' export async function generateStaticParams() { const locales = ['en', 'ro'] diff --git a/app/[locale]/tags/[tag]/page.tsx b/app/[locale]/tags/[tag]/page.tsx index 0a4fbce..f4a0ae9 100644 --- a/app/[locale]/tags/[tag]/page.tsx +++ b/app/[locale]/tags/[tag]/page.tsx @@ -4,8 +4,6 @@ import { Link } from '@/src/i18n/navigation' import { getAllTags, getPostsByTag, getTagInfo, getRelatedTags } from '@/lib/tags' import { TagList } from '@/components/blog/tag-list' import { formatDate } from '@/lib/utils' -import { setRequestLocale } from 'next-intl/server' -import { routing } from '@/src/i18n/routing' export async function generateStaticParams() { const tags = await getAllTags() diff --git a/components/blog/code-block.tsx b/components/blog/code-block.tsx index dcc2a94..c54cb93 100644 --- a/components/blog/code-block.tsx +++ b/components/blog/code-block.tsx @@ -6,10 +6,10 @@ interface CodeBlockProps { code: string language: string filename?: string - showLineNumbers?: boolean + _showLineNumbers?: boolean } -export function CodeBlock({ code, language, filename, showLineNumbers = true }: CodeBlockProps) { +export function CodeBlock({ code, language, filename, _showLineNumbers = true }: CodeBlockProps) { const [copied, setCopied] = useState(false) const handleCopy = async () => { diff --git a/components/blog/markdown-renderer.tsx b/components/blog/markdown-renderer.tsx index 6162475..b103e1d 100644 --- a/components/blog/markdown-renderer.tsx +++ b/components/blog/markdown-renderer.tsx @@ -15,7 +15,7 @@ interface MarkdownRendererProps { } export default function MarkdownRenderer({ content, className = '' }: MarkdownRendererProps) { - const locale = useLocale() + const _locale = useLocale() return (
/// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/types/translations.d.ts b/types/translations.d.ts index 837db6e..277982e 100644 --- a/types/translations.d.ts +++ b/types/translations.d.ts @@ -1,5 +1,6 @@ type Messages = typeof import('../messages/en.json') declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type interface IntlMessages extends Messages {} } -- 2.49.1