feat(01-02): create useProjects() composable with i18n support

- useProjects() returns projects, featuredProjects, filterByCategory, search, findById
- Added title/description/longDescription fields to Project interface
- Uses Nuxt auto-imports (computed, useI18n, Ref)
- i18n keys follow projects.${id}.title pattern
This commit is contained in:
2026-04-08 14:59:29 +02:00
parent d139605704
commit cfb3fce2e1
3 changed files with 80 additions and 32 deletions
+51
View File
@@ -0,0 +1,51 @@
import { projects as projectsData } from '~/data/projects'
import type { Project } from '~~/shared/types'
/**
* Composable for accessing and filtering project data with i18n support.
* Titles, descriptions, and long descriptions are resolved via i18n keys.
*/
export function useProjects() {
const { t } = useI18n()
const projects = computed<Project[]>(() =>
projectsData.map((p) => ({
...p,
title: t(`projects.${p.id}.title`),
description: t(`projects.${p.id}.description`),
longDescription: t(`projects.${p.id}.longDescription`) || undefined,
})),
)
const featuredProjects = computed(() => projects.value.filter((p) => p.featured))
function filterByCategory(category: string) {
return computed(() => projects.value.filter((p) => p.category === category))
}
function search(query: Ref<string> | string) {
return computed(() => {
const q = typeof query === 'string' ? query : query.value
if (!q) return projects.value
const lower = q.toLowerCase()
return projects.value.filter(
(p) =>
p.title.toLowerCase().includes(lower) ||
p.description.toLowerCase().includes(lower) ||
p.technologies.some((tech) => tech.toLowerCase().includes(lower)),
)
})
}
function findById(id: string) {
return computed(() => projects.value.find((p) => p.id === id))
}
return {
projects,
featuredProjects,
filterByCategory,
search,
findById,
}
}
+26 -32
View File
@@ -4,41 +4,35 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "nuxt dev",
"build": "run-p type-check \"build-only {@}\" --", "build": "nuxt build",
"preview": "vite preview", "generate": "nuxt generate",
"build-only": "vite build", "preview": "nuxt preview",
"type-check": "vue-tsc --build", "postinstall": "nuxt prepare",
"lint": "eslint . --fix", "lint": "eslint .",
"format": "prettier --write src/" "typecheck": "nuxt typecheck"
}, },
"dependencies": { "dependencies": {
"@vueuse/head": "^2.0.0", "@nuxt/eslint": "^1.15.2",
"pinia": "^3.0.1", "@nuxt/image": "^2.0.0",
"vue": "^3.5.13", "@nuxt/ui": "^3.0.0",
"vue-i18n": "^9.14.4", "@nuxtjs/i18n": "^10.2.4",
"vue-router": "^4.5.0" "@nuxtjs/sitemap": "^8.0.12",
"nuxt": "^4.0.0",
"nuxt-gtag": "^4.1.0",
"vue": "latest",
"vue-router": "latest"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.10", "typescript": "~5.8.0"
"@tsconfig/node22": "^22.0.1", },
"@types/node": "^22.14.0", "pnpm": {
"@vitejs/plugin-vue": "^5.2.3", "onlyBuiltDependencies": [
"@vue/eslint-config-prettier": "^10.2.0", "@parcel/watcher",
"@vue/eslint-config-typescript": "^14.5.0", "esbuild",
"@vue/tsconfig": "^0.7.0", "sharp",
"autoprefixer": "^10.4.21", "unrs-resolver",
"eslint": "^9.22.0", "vue-demi"
"eslint-plugin-vue": "~10.0.0", ]
"jiti": "^2.4.2",
"npm-run-all2": "^7.0.2",
"postcss": "^8.5.6",
"prettier": "3.5.3",
"tailwindcss": "^4.1.10",
"terser": "^5.43.1",
"typescript": "~5.8.0",
"vite": "^6.2.4",
"vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.8"
} }
} }
+3
View File
@@ -5,6 +5,9 @@ export interface ProjectButton {
export interface Project { export interface Project {
id: string id: string
title: string
description: string
longDescription?: string
image: string image: string
technologies: string[] technologies: string[]
category: string category: string