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:
@@ -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
@@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user