diff --git a/.auto-claude-status b/.auto-claude-status index 0dd51e5..5e2a149 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -1,17 +1,17 @@ { "active": true, - "spec": "032-split-large-jsonld-tsx-component-759-lines-into-se", + "spec": "030-add-unit-tests-vitest-configured-but-no-tests-exis", "state": "building", "subtasks": { - "completed": 16, - "total": 19, + "completed": 2, + "total": 8, "in_progress": 1, "failed": 0 }, "phase": { - "current": "Remove Old File", + "current": "Utility Functions Tests", "id": null, - "total": 1 + "total": 2 }, "workers": { "active": 0, @@ -19,7 +19,7 @@ }, "session": { "number": 4, - "started_at": "2026-01-25T11:52:09.599134" + "started_at": "2026-01-25T06:18:19.433683" }, - "last_update": "2026-01-25T11:58:46.607945" + "last_update": "2026-01-25T06:32:23.146357" } \ No newline at end of file diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 870f3db..f35b6c6 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -50,7 +50,24 @@ "Bash(npm init -y)", "Bash(npx create-next-app@latest:*)", "Bash(echo \"npm install failed with exit code $?\")", - "Bash(echo:*)" + "Bash(echo:*)", + "Bash(npm run lint)", + "Bash(npx --yes next build:*)", + "Bash(pnpm next build:*)", + "Bash(timeout /t 2 /nobreak)", + "Bash(pnpm list:*)", + "WebSearch", + "Bash(npx eslint:*)", + "WebFetch(domain:platform.openai.com)", + "WebFetch(domain:cookbook.openai.com)", + "Bash(findstr:*)", + "Bash(npm i:*)", + "Bash(npm view:*)", + "WebFetch(domain:ai.google.dev)", + "Bash(copy \"C:\\\\Users\\\\damja\\\\WebstormProjects\\\\Portfolio\\\\Portfolio.png\" \"C:\\\\Users\\\\damja\\\\WebstormProjects\\\\Portfolio\\\\public\\\\hero-portrait.png\")", + "Bash(npx playwright:*)", + "Bash(npx:*)", + "Bash(git reset:*)" ], "deny": [] } diff --git a/.claude_settings.json b/.claude_settings.json index a6f4d43..f2da165 100644 --- a/.claude_settings.json +++ b/.claude_settings.json @@ -11,14 +11,14 @@ "Edit(./**)", "Glob(./**)", "Grep(./**)", - "Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\032-split-large-jsonld-tsx-component-759-lines-into-se/**)", - "Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\032-split-large-jsonld-tsx-component-759-lines-into-se/**)", - "Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\032-split-large-jsonld-tsx-component-759-lines-into-se/**)", - "Glob(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\032-split-large-jsonld-tsx-component-759-lines-into-se/**)", - "Grep(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\032-split-large-jsonld-tsx-component-759-lines-into-se/**)", - "Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\032-split-large-jsonld-tsx-component-759-lines-into-se\\.auto-claude\\specs\\032-split-large-jsonld-tsx-component-759-lines-into-se/**)", - "Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\032-split-large-jsonld-tsx-component-759-lines-into-se\\.auto-claude\\specs\\032-split-large-jsonld-tsx-component-759-lines-into-se/**)", - "Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\032-split-large-jsonld-tsx-component-759-lines-into-se\\.auto-claude\\specs\\032-split-large-jsonld-tsx-component-759-lines-into-se/**)", + "Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)", + "Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)", + "Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)", + "Glob(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)", + "Grep(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)", + "Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis\\.auto-claude\\specs\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)", + "Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis\\.auto-claude\\specs\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)", + "Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude\\worktrees\\tasks\\030-add-unit-tests-vitest-configured-but-no-tests-exis\\.auto-claude\\specs\\030-add-unit-tests-vitest-configured-but-no-tests-exis/**)", "Read(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)", "Write(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)", "Edit(C:\\Users\\damja\\WebstormProjects\\Portfolio\\.auto-claude/**)", diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e6f84c8 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# Supabase Configuration +NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key + +# Google Analytics (optional) +NEXT_PUBLIC_GA_TRACKING_ID=G-XXXXXXXXXX + +# Site URL +NEXT_PUBLIC_SITE_URL=https://damjan-savic.com + +# OpenAI API Key (for image generation) +OPENAI_API_KEY=sk-your-api-key-here diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0cf30a7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,1221 @@ +# Contributing to Portfolio + +Thank you for your interest in contributing to this project! This guide will help you get started with development. + +## Table of Contents + +- [Getting Started](#getting-started) +- [Project Overview](#project-overview) +- [Tech Stack](#tech-stack) +- [Project Structure](#project-structure) +- [Development Workflow](#development-workflow) +- [Pull Request Guidelines](#pull-request-guidelines) +- [Content Workflows](#content-workflows) +- [Code Style & Standards](#code-style--standards) +- [Testing](#testing) +- [Legacy Code & Migration](#legacy-code--migration) +- [Commit Guidelines](#commit-guidelines) + +## Getting Started + +### Prerequisites + +- **Node.js** 18.x or higher +- **npm** 9.x or higher +- **Git** for version control + +### Installation + +1. **Clone the repository** + ```bash + git clone + cd portfolio + ``` + +2. **Install dependencies** + ```bash + npm install + ``` + +3. **Set up environment variables** + + Create a `.env.local` file in the root directory with the required environment variables: + ```bash + # Supabase Configuration + NEXT_PUBLIC_SUPABASE_URL=your_supabase_url + NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key + + # Optional: OpenAI (for image generation scripts) + OPENAI_API_KEY=your_openai_api_key + ``` + +4. **Start the development server** + ```bash + npm run dev + ``` + + The application will be available at `http://localhost:3000` + +### Available Scripts + +| Command | Description | +|---------|-------------| +| `npm run dev` | Start development server with hot reload | +| `npm run build` | Build production bundle | +| `npm start` | Start production server (after build) | +| `npm run lint` | Run ESLint with strict mode | +| `npm test` | Run unit tests with Vitest | +| `npm run test:coverage` | Run tests with coverage report | +| `npm run build:images` | Optimize images using Sharp | +| `npm run generate:images` | Generate blog images using OpenAI | +| `npm run generate:images:dry` | Preview image generation without creating files | + +## Project Overview + +This is a personal portfolio website for Damjan Savić, showcasing work as an AI & Automation Specialist. The site features: + +- **Multi-language support** - German, English, and Serbian with automatic language detection +- **Progressive Web App** - Installable and offline-capable +- **SEO optimized** - Structured data (Schema.org), sitemap generation +- **Responsive Design** - Mobile-first approach with Tailwind CSS +- **Blog System** - MDX-based content with frontmatter support +- **Portfolio Gallery** - Dynamic project showcase with filtering +- **Contact Form** - Integrated with Supabase backend + +## Tech Stack + +### Core Framework +- **Next.js 15** - React framework with App Router +- **React 19** - UI library with latest features +- **TypeScript 5.5** - Type-safe development + +### Styling & UI +- **Tailwind CSS 3.4** - Utility-first CSS framework +- **Framer Motion 11** - Animation library +- **Lucide React** - Icon library +- **Tailwind Merge** - Utility for merging Tailwind classes +- **CVA (Class Variance Authority)** - Component variants management + +### Content & Internationalization +- **MDX** - Markdown + JSX for content pages + - `@mdx-js/loader`, `@mdx-js/mdx`, `@mdx-js/react` + - `@next/mdx`, `next-mdx-remote` +- **next-intl 3.26** - Internationalization (i18n) +- **gray-matter** - Frontmatter parsing + +### Backend & Services +- **Supabase** - Database, authentication, and backend services + - `@supabase/supabase-js` + - `@supabase/ssr` +- **OpenAI 4.77** - AI integration for content generation + +### Development Tools +- **Vitest** - Unit testing framework +- **ESLint 9** - Code linting with Next.js config +- **PostCSS** - CSS processing +- **Autoprefixer** - CSS vendor prefixes +- **Sharp** - Image optimization + +### Performance & Analytics +- **web-vitals** - Performance metrics +- **react-intersection-observer** - Lazy loading and scroll animations + +## Project Structure + +``` +portfolio/ +├── public/ # Static assets (images, fonts, etc.) +├── scripts/ # Build and utility scripts +│ ├── optimize-images.js # Image optimization +│ └── generate-blog-images.mjs # AI-powered image generation +├── src/ +│ ├── app/ # Next.js App Router +│ │ ├── [locale]/ # Internationalized routes +│ │ │ ├── about/ # About page +│ │ │ ├── blog/ # Blog listing & posts +│ │ │ ├── contact/ # Contact form +│ │ │ ├── dashboard/ # Admin dashboard +│ │ │ ├── imprint/ # Legal imprint +│ │ │ ├── leistungen/ # Services page +│ │ │ ├── login/ # Authentication +│ │ │ ├── portfolio/ # Portfolio gallery & projects +│ │ │ ├── privacy/ # Privacy policy +│ │ │ ├── terms/ # Terms of service +│ │ │ ├── layout.tsx # Locale-specific layout +│ │ │ └── page.tsx # Homepage +│ │ ├── globals.css # Global styles +│ │ ├── layout.tsx # Root layout +│ │ ├── not-found.tsx # 404 page +│ │ ├── robots.ts # Robots.txt generation +│ │ └── sitemap.ts # Sitemap generation +│ ├── components/ # Reusable React components +│ ├── components-vite/ # Legacy Vite components +│ ├── data/ # Static data and content +│ ├── hooks/ # Custom React hooks +│ ├── i18n/ # Internationalization config +│ ├── lib/ # Utility libraries and helpers +│ ├── messages/ # Translation files (de, en, sr) +│ ├── middleware.ts # Next.js middleware (i18n routing) +│ ├── pages-vite/ # Legacy Vite pages +│ ├── services/ # API and service integrations +│ ├── styles/ # Additional styles +│ ├── types/ # TypeScript type definitions +│ └── utils/ # Helper functions +├── .env.local # Environment variables (not in git) +├── .eslintrc.json # ESLint configuration +├── docker-compose.yml # Docker Compose setup +├── Dockerfile # Docker container config +├── next.config.ts # Next.js configuration +├── package.json # Dependencies and scripts +├── postcss.config.js # PostCSS configuration +├── tailwind.config.js # Tailwind CSS configuration +└── tsconfig.json # TypeScript configuration +``` + +### Key Directories Explained + +- **`src/app/`** - Next.js App Router pages and layouts using the new file-based routing system +- **`src/app/[locale]/`** - All pages are nested under locale for multi-language support (de, en, sr) +- **`src/components/`** - Reusable UI components (buttons, cards, forms, navigation, etc.) +- **`src/hooks/`** - Custom React hooks for shared logic +- **`src/lib/`** - Utility libraries (Supabase client, helpers, etc.) +- **`src/messages/`** - Translation JSON files for each language +- **`src/services/`** - API integrations and external service wrappers +- **`src/types/`** - TypeScript interfaces and type definitions +- **`src/utils/`** - Helper functions and utilities +- **`public/`** - Static files served directly (images, fonts, favicons) +- **`scripts/`** - Build automation and utility scripts + +## Development Workflow + +### Branch Naming Conventions + +Use descriptive branch names that follow these patterns: + +| Branch Type | Pattern | Example | Description | +|-------------|---------|---------|-------------| +| **Feature** | `feature/` | `feature/add-dark-mode` | New features or enhancements | +| **Bug Fix** | `fix/` | `fix/mobile-nav-bug` | Bug fixes and error corrections | +| **Documentation** | `docs/` | `docs/update-readme` | Documentation updates | +| **Refactoring** | `refactor/` | `refactor/contact-form` | Code restructuring without behavior changes | +| **Testing** | `test/` | `test/add-unit-tests` | Adding or updating tests | +| **Chore** | `chore/` | `chore/update-dependencies` | Maintenance tasks, dependency updates | + +**Guidelines:** +- Use lowercase with hyphens (kebab-case) +- Keep branch names concise but descriptive (3-5 words max) +- Avoid special characters except hyphens +- Delete branches after they've been merged + +**Examples:** +```bash +# ✅ Good +git checkout -b feature/cookie-consent-banner +git checkout -b fix/portfolio-filter-crash +git checkout -b docs/api-integration-guide + +# ❌ Bad +git checkout -b myFeature +git checkout -b fix_bug +git checkout -b john-working-branch +``` + +### 1. Create a New Feature Branch +```bash +git checkout -b feature/your-feature-name +``` + +### 2. Make Your Changes +- Follow the existing code patterns and structure +- Use TypeScript for type safety +- Write clean, readable code with appropriate comments +- Test your changes locally + +### 3. Test Your Changes +```bash +# Run development server +npm run dev + +# Run linter +npm run lint + +# Run tests +npm test +``` + +### 4. Build for Production +```bash +npm run build +``` + +Ensure the build completes without errors before submitting. + +### 5. Submit a Pull Request + +See the [Pull Request Guidelines](#pull-request-guidelines) section below for detailed instructions. + +## Pull Request Guidelines + +When you're ready to submit your changes, create a pull request (PR) following these guidelines to ensure a smooth review process. + +### Before Creating a PR + +**Pre-submission Checklist:** +- [ ] All tests pass (`npm test`) +- [ ] Linter runs without errors (`npm run lint`) +- [ ] Production build succeeds (`npm run build`) +- [ ] Changes have been tested locally +- [ ] Code follows project style guidelines +- [ ] No debugging statements left in code (console.log, debugger, etc.) +- [ ] Translation files updated for all locales (de, en, sr) if UI text changed +- [ ] Documentation updated if necessary + +### PR Title Format + +Use the same format as commit messages: + +``` +: +``` + +**Examples:** +``` +feat: add cookie consent banner +fix: resolve mobile navigation menu bug +docs: update API integration guide +refactor: simplify contact form validation +``` + +### PR Description Template + +Provide a clear description of your changes: + +```markdown +## Summary +Brief overview of what this PR does and why. + +## Changes Made +- Added new component for cookie consent +- Updated privacy policy page +- Added tests for consent logic + +## Testing +- [ ] Tested on desktop browsers (Chrome, Firefox, Safari) +- [ ] Tested on mobile devices +- [ ] Verified all three languages (de, en, sr) +- [ ] Checked accessibility with screen reader + +## Screenshots (if applicable) +[Add screenshots for UI changes] + +## Related Issues +Closes #123 +Related to #456 +``` + +### PR Best Practices + +1. **Keep PRs Focused** + - One feature or fix per PR + - Avoid mixing unrelated changes + - Break large features into smaller, reviewable PRs + +2. **Write Descriptive Commits** + - Follow the [Commit Guidelines](#commit-guidelines) + - Each commit should be a logical unit of work + - Avoid "WIP" or "Fix" commit messages + +3. **Update Documentation** + - Update README.md if adding new features + - Update CONTRIBUTING.md if changing development workflow + - Add JSDoc comments for new public functions + +4. **Request Review** + - Tag relevant reviewers + - Respond to review comments promptly + - Make requested changes in new commits (don't force push) + +5. **CI/CD Checks** + - Ensure all automated checks pass + - Fix any linting or build errors + - Address test failures before requesting review + +### PR Review Process + +1. **Automated Checks** - All CI/CD checks must pass +2. **Code Review** - At least one approval required +3. **Testing** - Reviewer may test changes locally +4. **Merge** - Squash and merge into main branch +5. **Cleanup** - Delete branch after merge + +### Handling Review Feedback + +- **Be responsive** - Address feedback within 1-2 days +- **Ask questions** - If feedback is unclear, ask for clarification +- **Make changes** - Commit review suggestions as new commits +- **Re-request review** - After addressing all comments +- **Be respectful** - Code reviews are about the code, not the person + +### What Gets Rejected + +PRs will be rejected or require changes if: +- ❌ Tests are failing +- ❌ Linter errors exist +- ❌ Build fails +- ❌ Breaking changes without discussion +- ❌ Missing translations for new UI text +- ❌ No description or context provided +- ❌ Touches legacy code without coordination (see [Legacy Code](#legacy-code--migration)) + +## Content Workflows + +This section covers how to add and manage content for portfolio projects, blog posts, and translations. The project supports multi-language content in German (de), English (en), and Serbian (sr). + +### Adding Portfolio Projects + +Portfolio projects are defined as TypeScript files with structured metadata and content. Each project must be created for all supported locales. + +#### File Location Conventions + +Portfolio project files follow this structure: +``` +src/i18n/locales-old/ +├── de/portfolio/projects/ # German portfolio projects +│ └── my-project.ts +├── en/portfolio/projects/ # English portfolio projects +│ └── my-project.ts +└── sr/portfolio/projects/ # Serbian portfolio projects + └── my-project.ts +``` + +**Important:** Use the same filename (slug) across all locales to maintain consistency. + +#### TypeScript Data Structure + +Each portfolio project file must export an object with the following structure: + +```typescript +// src/i18n/locales-old/de/portfolio/projects/my-project.ts +export const myProject = { + meta: { + slug: 'my-project', // Unique identifier (same across locales) + title: 'Project Title | Damjan Savić', // SEO-optimized title + description: 'Brief project description for meta tags', + excerpt: 'Short excerpt for project cards', + date: '2025-01', // Publication date (YYYY-MM format) + category: 'Category Name', // e.g., "Web Development", "Data Processing" + client: 'Client Name', // Client or organization + duration: '2 Monate', // Project duration + url: 'https://project-url.com', // Live project URL (optional) + repository: '', // GitHub repository URL (optional) + documentation: '', // Documentation URL (optional) + published: true, // Visibility flag + featured: true, // Featured on homepage + technologies: [ // Technology stack + 'TypeScript', + 'React', + 'Next.js' + ], + tags: [ // Project tags for filtering + 'Web Development', + 'Full Stack', + 'SEO' + ] + }, + content: { + intro: 'Introduction paragraph describing the project overview.', + + challenge: { + title: 'The Challenge', + description: 'Brief description of the problem to solve.', + points: [ + 'Challenge point 1', + 'Challenge point 2', + 'Challenge point 3' + ] + }, + + solution: { + title: 'The Solution', + description: 'How the problem was approached.', + content: 'Detailed explanation of the solution.', + points: [ + 'Solution feature 1', + 'Solution feature 2', + 'Solution feature 3' + ] + }, + + technical: { + title: 'Technical Implementation', + description: 'Technical approach and architecture.', + points: [ + 'Technical detail 1', + 'Technical detail 2', + 'Technical detail 3' + ], + code: `// Optional code snippet to showcase implementation +export function exampleFunction() { + return 'Example code'; +}` + }, + + implementation: { + title: 'Implementation Process', + description: 'How the project was built.', + points: [ + 'Implementation step 1', + 'Implementation step 2', + 'Implementation step 3' + ] + }, + + results: { + title: 'Results and Impact', + description: 'Measurable outcomes and business impact.', + points: [ + 'Result metric 1', + 'Result metric 2', + 'Result metric 3' + ] + }, + + conclusion: 'Final thoughts and project summary.' + } +}; + +// Re-export for compatibility with the project import system +export default myProject; +``` + +#### Step-by-Step: Adding a New Portfolio Project + +1. **Create the TypeScript file** for each locale: + ```bash + # German version + touch src/i18n/locales-old/de/portfolio/projects/my-project.ts + + # English version + touch src/i18n/locales-old/en/portfolio/projects/my-project.ts + + # Serbian version + touch src/i18n/locales-old/sr/portfolio/projects/my-project.ts + ``` + +2. **Copy the template structure** above into each file + +3. **Translate the content** for each locale while keeping the same structure + +4. **Add project images** (if any) to `public/images/portfolio/my-project/` + +5. **Test the project** by navigating to: + - German: `http://localhost:3000/de/portfolio/my-project` + - English: `http://localhost:3000/en/portfolio/my-project` + - Serbian: `http://localhost:3000/sr/portfolio/my-project` + +6. **Verify SEO metadata** using browser DevTools to check meta tags + +### Adding Blog Posts + +Blog posts use MDX (Markdown + JSX) format with frontmatter metadata. MDX allows you to embed React components directly in your content. + +#### File Location Conventions + +Blog posts are organized by locale: +``` +src/content/blog/ +├── de/ # German blog posts +│ └── my-blog-post.mdx +├── en/ # English blog posts +│ └── my-blog-post.mdx +└── sr/ # Serbian blog posts + └── my-blog-post.mdx +``` + +#### MDX File Structure + +Each blog post must include frontmatter metadata at the top: + +```mdx +--- +title: "Blog Post Title" +description: "Brief description for SEO and post previews" +date: "2025-01-20" +author: "Damjan Savić" +category: "Technology" +tags: ["React", "Next.js", "TypeScript"] +image: "/images/blog/my-blog-post/hero.jpg" +published: true +featured: false +excerpt: "A short excerpt that appears in post listings" +--- + +# Blog Post Title + +Your content starts here. You can use standard Markdown syntax: + +## Headings + +Regular **bold** and *italic* text. + +### Code Blocks + +```typescript +// Code examples with syntax highlighting +const greeting = "Hello, World!"; +console.log(greeting); +``` + +### Lists + +- Bullet point 1 +- Bullet point 2 +- Bullet point 3 + +### Images + +![Alt text](/images/blog/my-blog-post/diagram.png) + +### Custom Components (MDX Feature) + +You can embed React components: + + + This is a custom component in MDX! + +``` + +#### Step-by-Step: Adding a New Blog Post + +1. **Create the MDX file** for each locale: + ```bash + # German version + touch src/content/blog/de/my-blog-post.mdx + + # English version + touch src/content/blog/en/my-blog-post.mdx + + # Serbian version + touch src/content/blog/sr/my-blog-post.mdx + ``` + +2. **Add frontmatter metadata** with all required fields + +3. **Write the content** using Markdown/MDX syntax + +4. **Add images** to `public/images/blog/my-blog-post/` + +5. **Generate blog images** (optional) using the AI-powered script: + ```bash + # Preview image generation + npm run generate:images:dry + + # Generate actual images + npm run generate:images + ``` + +6. **Test the blog post** by navigating to: + - German: `http://localhost:3000/de/blog/my-blog-post` + - English: `http://localhost:3000/en/blog/my-blog-post` + - Serbian: `http://localhost:3000/sr/blog/my-blog-post` + +### Internationalization (i18n) Workflow + +The project uses **next-intl** for internationalization with JSON-based translation files. + +#### Translation File Structure + +Translation files are located in `src/messages/` and organized by locale: + +``` +src/messages/ +├── de.json # German translations +├── en.json # English translations +└── sr.json # Serbian translations +``` + +#### Translation File Format + +Each translation file is a nested JSON object organized by page or section: + +```json +{ + "meta": { + "site": { + "name": "Damjan Savić", + "title": "Damjan Savić | Fullstack Developer", + "description": "Portfolio website description" + } + }, + "common": { + "nav": { + "home": "Home", + "about": "About", + "portfolio": "Portfolio", + "blog": "Blog", + "contact": "Contact" + }, + "actions": { + "readMore": "Read More", + "viewMore": "View More", + "back": "Back", + "close": "Close" + } + }, + "pages": { + "home": { + "hero": { + "title": "FULLSTACK DEVELOPER", + "subtitle": "AI Agents | Voice AI | Process Automation" + } + } + } +} +``` + +#### Step-by-Step: Adding Translations + +1. **Add the translation key** to `src/messages/de.json` (German - primary language): + ```json + { + "pages": { + "contact": { + "form": { + "title": "Kontakt aufnehmen", + "name": "Name", + "email": "E-Mail", + "message": "Nachricht", + "submit": "Absenden" + } + } + } + } + ``` + +2. **Add the same structure** to `src/messages/en.json` (English): + ```json + { + "pages": { + "contact": { + "form": { + "title": "Get in Touch", + "name": "Name", + "email": "Email", + "message": "Message", + "submit": "Submit" + } + } + } + } + ``` + +3. **Add the same structure** to `src/messages/sr.json` (Serbian): + ```json + { + "pages": { + "contact": { + "form": { + "title": "Kontaktirajte nas", + "name": "Ime", + "email": "Email", + "message": "Poruka", + "submit": "Pošalji" + } + } + } + } + ``` + +4. **Use the translation** in your component: + ```typescript + import { useTranslations } from 'next-intl'; + + export function ContactForm() { + const t = useTranslations('pages.contact.form'); + + return ( +
+

{t('title')}

+ + +