auto-claude: subtask-1-4 - Document content workflows (portfolio projects and blog posts)
- Added comprehensive Content Workflows section to CONTRIBUTING.md - Documented how to add portfolio projects with TypeScript data structure example - Documented how to add blog posts using MDX format - Documented i18n workflow for translations with JSON structure - Added file location conventions for all locales (de, en, sr) - Included step-by-step instructions and best practices Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+408
@@ -9,6 +9,7 @@ Thank you for your interest in contributing to this project! This guide will hel
|
||||
- [Tech Stack](#tech-stack)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Development Workflow](#development-workflow)
|
||||
- [Content Workflows](#content-workflows)
|
||||
- [Code Style & Standards](#code-style--standards)
|
||||
- [Testing](#testing)
|
||||
- [Commit Guidelines](#commit-guidelines)
|
||||
@@ -215,6 +216,413 @@ npm run build
|
||||
|
||||
Ensure the build completes without errors before submitting.
|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
### Custom Components (MDX Feature)
|
||||
|
||||
You can embed React components:
|
||||
|
||||
<CustomCallout type="info">
|
||||
This is a custom component in MDX!
|
||||
</CustomCallout>
|
||||
```
|
||||
|
||||
#### 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 (
|
||||
<form>
|
||||
<h2>{t('title')}</h2>
|
||||
<input placeholder={t('name')} />
|
||||
<input placeholder={t('email')} />
|
||||
<textarea placeholder={t('message')} />
|
||||
<button>{t('submit')}</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Translation Best Practices
|
||||
|
||||
1. **Maintain structure consistency** - Use the same JSON structure across all locale files
|
||||
2. **Use semantic keys** - Keys should describe the content, not the translation (e.g., `actions.submit` not `actions.submitButton`)
|
||||
3. **Organize by feature** - Group related translations under feature namespaces
|
||||
4. **Test all locales** - Always verify translations in all three languages (de, en, sr)
|
||||
5. **Avoid hardcoded text** - All user-facing text should come from translation files
|
||||
6. **Keep translations concise** - Match the tone and length of the original language when possible
|
||||
|
||||
#### Locale-Specific File Naming
|
||||
|
||||
When creating locale-specific content files, follow these conventions:
|
||||
|
||||
| Content Type | File Location | Naming Pattern | Example |
|
||||
|--------------|---------------|----------------|---------|
|
||||
| **Portfolio Projects** | `src/i18n/locales-old/[locale]/portfolio/projects/` | `project-slug.ts` | `ai-data-reader.ts` |
|
||||
| **Blog Posts** | `src/content/blog/[locale]/` | `post-slug.mdx` | `react-best-practices.mdx` |
|
||||
| **Translations** | `src/messages/` | `[locale].json` | `de.json` |
|
||||
| **Page Content** | `src/app/[locale]/[page]/` | Standard Next.js routing | `page.tsx` |
|
||||
|
||||
#### Switching Between Locales
|
||||
|
||||
The application automatically detects the user's locale based on:
|
||||
1. URL path segment (e.g., `/de/`, `/en/`, `/sr/`)
|
||||
2. Browser language settings
|
||||
3. User preference stored in cookies
|
||||
|
||||
Users can manually switch languages using the language selector in the navigation menu.
|
||||
|
||||
## Code Style & Standards
|
||||
|
||||
### ESLint & Code Linting
|
||||
|
||||
Reference in New Issue
Block a user