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,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"lint": "eslint . --fix",
"format": "prettier --write src/"
"dev": "nuxt dev",
"build": "nuxt build",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"lint": "eslint .",
"typecheck": "nuxt typecheck"
},
"dependencies": {
"@vueuse/head": "^2.0.0",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-i18n": "^9.14.4",
"vue-router": "^4.5.0"
"@nuxt/eslint": "^1.15.2",
"@nuxt/image": "^2.0.0",
"@nuxt/ui": "^3.0.0",
"@nuxtjs/i18n": "^10.2.4",
"@nuxtjs/sitemap": "^8.0.12",
"nuxt": "^4.0.0",
"nuxt-gtag": "^4.1.0",
"vue": "latest",
"vue-router": "latest"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"@tsconfig/node22": "^22.0.1",
"@types/node": "^22.14.0",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.0",
"@vue/tsconfig": "^0.7.0",
"autoprefixer": "^10.4.21",
"eslint": "^9.22.0",
"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"
"typescript": "~5.8.0"
},
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"esbuild",
"sharp",
"unrs-resolver",
"vue-demi"
]
}
}
+3
View File
@@ -5,6 +5,9 @@ export interface ProjectButton {
export interface Project {
id: string
title: string
description: string
longDescription?: string
image: string
technologies: string[]
category: string