From 2a1ba854ba0cb9f07f808e4abb0492f7259483ba Mon Sep 17 00:00:00 2001 From: Dominik Milacher Date: Sun, 5 Oct 2025 15:09:19 +0200 Subject: [PATCH] Set up content layer --- apps/panoramablick-saalbach.at/app/app.vue | 4 +- .../app/pages/[locale]/[variant]/[...all].vue | 9 + apps/panoramablick-saalbach.at/nuxt.config.ts | 2 +- apps/panoramablick-saalbach.at/package.json | 2 +- packages/layers/base/app.config.ts | 14 -- .../layers/base/components/HelloWorld.vue | 10 - .../layers/{base => content}/.editorconfig | 0 packages/layers/{base => content}/.gitignore | 0 packages/layers/{base => content}/.npmrc | 0 packages/layers/{base => content}/.nuxtrc | 0 .../.playground/app.config.ts | 0 .../{base => content/.playground}/app.vue | 2 +- .../.playground/nuxt.config.ts | 0 .../pages/[locale]/[variant]/[...all].vue | 14 ++ packages/layers/{base => content}/README.md | 0 packages/layers/content/app.config.ts | 24 +++ packages/layers/content/assets/content.yaml | 7 + .../content/components/LocaleSwitcher.vue | 10 + .../content/components/VariantSwitcher.vue | 10 + .../layers/content/composables/useContent.ts | 171 ++++++++++++++++++ .../layers/{base => content}/eslint.config.js | 0 .../layers/{base => content}/nuxt.config.ts | 6 + .../layers/{base => content}/package.json | 6 +- pnpm-lock.yaml | 44 ++++- 24 files changed, 297 insertions(+), 38 deletions(-) create mode 100644 apps/panoramablick-saalbach.at/app/pages/[locale]/[variant]/[...all].vue delete mode 100644 packages/layers/base/app.config.ts delete mode 100644 packages/layers/base/components/HelloWorld.vue rename packages/layers/{base => content}/.editorconfig (100%) rename packages/layers/{base => content}/.gitignore (100%) rename packages/layers/{base => content}/.npmrc (100%) rename packages/layers/{base => content}/.nuxtrc (100%) rename packages/layers/{base => content}/.playground/app.config.ts (100%) rename packages/layers/{base => content/.playground}/app.vue (57%) rename packages/layers/{base => content}/.playground/nuxt.config.ts (100%) create mode 100644 packages/layers/content/.playground/pages/[locale]/[variant]/[...all].vue rename packages/layers/{base => content}/README.md (100%) create mode 100644 packages/layers/content/app.config.ts create mode 100644 packages/layers/content/assets/content.yaml create mode 100644 packages/layers/content/components/LocaleSwitcher.vue create mode 100644 packages/layers/content/components/VariantSwitcher.vue create mode 100644 packages/layers/content/composables/useContent.ts rename packages/layers/{base => content}/eslint.config.js (100%) rename packages/layers/{base => content}/nuxt.config.ts (60%) rename packages/layers/{base => content}/package.json (80%) diff --git a/apps/panoramablick-saalbach.at/app/app.vue b/apps/panoramablick-saalbach.at/app/app.vue index c5d7320..7ca1c07 100644 --- a/apps/panoramablick-saalbach.at/app/app.vue +++ b/apps/panoramablick-saalbach.at/app/app.vue @@ -1,5 +1,3 @@ diff --git a/apps/panoramablick-saalbach.at/app/pages/[locale]/[variant]/[...all].vue b/apps/panoramablick-saalbach.at/app/pages/[locale]/[variant]/[...all].vue new file mode 100644 index 0000000..ce984d5 --- /dev/null +++ b/apps/panoramablick-saalbach.at/app/pages/[locale]/[variant]/[...all].vue @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/apps/panoramablick-saalbach.at/nuxt.config.ts b/apps/panoramablick-saalbach.at/nuxt.config.ts index ced2eb9..7d18ce2 100644 --- a/apps/panoramablick-saalbach.at/nuxt.config.ts +++ b/apps/panoramablick-saalbach.at/nuxt.config.ts @@ -2,5 +2,5 @@ export default defineNuxtConfig({ compatibilityDate: '2025-07-15', devtools: { enabled: true }, - extends: [ '@layers/base' ] + extends: [ '@layers/content' ] }) diff --git a/apps/panoramablick-saalbach.at/package.json b/apps/panoramablick-saalbach.at/package.json index de244bf..1faa9d6 100644 --- a/apps/panoramablick-saalbach.at/package.json +++ b/apps/panoramablick-saalbach.at/package.json @@ -13,6 +13,6 @@ "nuxt": "^4.1.2", "vue": "^3.5.22", "vue-router": "^4.5.1", - "@layers/base": "workspace:0.0.0" + "@layers/content": "workspace:0.0.0" } } diff --git a/packages/layers/base/app.config.ts b/packages/layers/base/app.config.ts deleted file mode 100644 index e3276b7..0000000 --- a/packages/layers/base/app.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -export default defineAppConfig({ - myLayer: { - name: 'Hello from Nuxt layer' - } -}) - -declare module '@nuxt/schema' { - interface AppConfigInput { - myLayer?: { - /** Project name */ - name?: string - } - } -} diff --git a/packages/layers/base/components/HelloWorld.vue b/packages/layers/base/components/HelloWorld.vue deleted file mode 100644 index 937eedb..0000000 --- a/packages/layers/base/components/HelloWorld.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/packages/layers/base/.editorconfig b/packages/layers/content/.editorconfig similarity index 100% rename from packages/layers/base/.editorconfig rename to packages/layers/content/.editorconfig diff --git a/packages/layers/base/.gitignore b/packages/layers/content/.gitignore similarity index 100% rename from packages/layers/base/.gitignore rename to packages/layers/content/.gitignore diff --git a/packages/layers/base/.npmrc b/packages/layers/content/.npmrc similarity index 100% rename from packages/layers/base/.npmrc rename to packages/layers/content/.npmrc diff --git a/packages/layers/base/.nuxtrc b/packages/layers/content/.nuxtrc similarity index 100% rename from packages/layers/base/.nuxtrc rename to packages/layers/content/.nuxtrc diff --git a/packages/layers/base/.playground/app.config.ts b/packages/layers/content/.playground/app.config.ts similarity index 100% rename from packages/layers/base/.playground/app.config.ts rename to packages/layers/content/.playground/app.config.ts diff --git a/packages/layers/base/app.vue b/packages/layers/content/.playground/app.vue similarity index 57% rename from packages/layers/base/app.vue rename to packages/layers/content/.playground/app.vue index f9b5dfc..7ca1c07 100644 --- a/packages/layers/base/app.vue +++ b/packages/layers/content/.playground/app.vue @@ -1,3 +1,3 @@ diff --git a/packages/layers/base/.playground/nuxt.config.ts b/packages/layers/content/.playground/nuxt.config.ts similarity index 100% rename from packages/layers/base/.playground/nuxt.config.ts rename to packages/layers/content/.playground/nuxt.config.ts diff --git a/packages/layers/content/.playground/pages/[locale]/[variant]/[...all].vue b/packages/layers/content/.playground/pages/[locale]/[variant]/[...all].vue new file mode 100644 index 0000000..bcc4fe6 --- /dev/null +++ b/packages/layers/content/.playground/pages/[locale]/[variant]/[...all].vue @@ -0,0 +1,14 @@ + + + diff --git a/packages/layers/base/README.md b/packages/layers/content/README.md similarity index 100% rename from packages/layers/base/README.md rename to packages/layers/content/README.md diff --git a/packages/layers/content/app.config.ts b/packages/layers/content/app.config.ts new file mode 100644 index 0000000..a8e6c0f --- /dev/null +++ b/packages/layers/content/app.config.ts @@ -0,0 +1,24 @@ +export default defineAppConfig({ + content: { + defaultLocale: 'de', + locales: [ + {code: 'de', name: {'de': 'Deutsch', 'en': 'German'}}, + {code: 'en', name: {'de': 'Englisch', 'en': 'English'}}, + ], + defaultVariant: 'summer', + variants: [ + {code: 'summer', name: {'de': 'Sommer', 'en': 'Summer'}, icon: 'i-lucide-sun'}, + {code: 'winter', name: {'de': 'Winter', 'en': 'Winter'}, icon: 'i-lucide-snowflake'} + ], + cookieMaxAge: 60 * 60 * 24 * 365 + } +}) + +declare module '@nuxt/schema' { + interface AppConfigInput { + myLayer?: { + /** Project name */ + name?: string + } + } +} diff --git a/packages/layers/content/assets/content.yaml b/packages/layers/content/assets/content.yaml new file mode 100644 index 0000000..80f7853 --- /dev/null +++ b/packages/layers/content/assets/content.yaml @@ -0,0 +1,7 @@ +cookies: + title: + de: "Ihre Privatsphäre ist uns wichtig" + en: "We value your privacy" + description: + de: "Wir verwenden keine Tracking-Cookies. Genießen Sie Ihren Aufenthalt auf unserer Website!" + en: "We do not use any tracking cookies. Enjoy your stay on our website!" diff --git a/packages/layers/content/components/LocaleSwitcher.vue b/packages/layers/content/components/LocaleSwitcher.vue new file mode 100644 index 0000000..ce5c273 --- /dev/null +++ b/packages/layers/content/components/LocaleSwitcher.vue @@ -0,0 +1,10 @@ + + + diff --git a/packages/layers/content/components/VariantSwitcher.vue b/packages/layers/content/components/VariantSwitcher.vue new file mode 100644 index 0000000..57f6907 --- /dev/null +++ b/packages/layers/content/components/VariantSwitcher.vue @@ -0,0 +1,10 @@ + + + diff --git a/packages/layers/content/composables/useContent.ts b/packages/layers/content/composables/useContent.ts new file mode 100644 index 0000000..c937aaf --- /dev/null +++ b/packages/layers/content/composables/useContent.ts @@ -0,0 +1,171 @@ +import { computed } from 'vue' +import { useRoute } from '#app' +import content from '~/assets/content.yaml' + +const specializations: Record = {} + +function buildSpecialization(locale: string, variant: string): object { + const config = useAppConfig() + const locales = config.content.locales.map(l => l.code) + const variants = config.content.variants.map(v => v.code) + const allTags = new Set([...locales, ...variants]) + + function split(key: string): {base?: string; tags: string[]} { + const parts = key.split('@') + + if (allTags.has(parts[0])) { + return {base: undefined, tags: parts} + } + + return {base: parts[0], tags: parts.slice(1)} + } + + function relevant(tags: string[]): boolean { + if (tags.length < 1) { + return true + } + + for (const tag of tags) { + if (tag !== locale && tag !== variant) { + return false + } + } + + return true + } + + function filterObject(object: any): any { + if (Array.isArray(object)) { + return object.map(e => filterObject(e)).filter(e => e !== undefined) + } + + if (typeof object !== 'object') { + return object + } + + const copy: Record = {} + let anonymous: [number, any][] = [] + + for (const key in object) { + const {base, tags} = split(key) + + if (!relevant(tags)) { + continue + } + + const value = filterObject(object[key]) + + if (base === undefined) { + anonymous.push([tags.length, value]) + } else { + copy[base] = value + } + } + + if (Object.keys(copy).length > 0 && anonymous.length > 0) { + console.warn(`Invalid mixing of keys in object ${object}`) + } + + if (Object.keys(copy).length > 0) { + return copy + } + + anonymous.sort((a, b) => b[0] - a[0]) + + if (anonymous[0][0] > 2) { + console.warn(`More than two tags per key in object ${object}`) + } + + return anonymous[0][1] + } + + return filterObject(content) +} + +function getSpecialization(locale: string, variant: string): object { + const key = locale + '/' + variant + + if (!(key in specializations)) { + specializations[key] = buildSpecialization(locale, variant) + } + + return specializations[key] +} + +export function useContent() { + const config = useAppConfig() + const route = useRoute() + console.log("miau") + console.log(route) + + const locales = config.content.locales.map(l => l.code) + const variants = config.content.variants.map(v => v.code) + + const localeCookie = useCookie('locale', { + sameSite: 'lax', + maxAge: config.content.cookieMaxAge + }) + + const variantCookie = useCookie('variant', { + sameSite: 'lax', + maxAge: config.content.cookieMaxAge + }) + + const currentLocale = computed(() => route.params.locale || localeCookie.value || config.content.defaultLocale) + const currentVariant = computed(() => route.params.variant || variantCookie.value || config.content.defaultVariant) + + function preferLocale(locale: string) { + if (locale in locales) { + localeCookie.value = locale + } + } + + function preferVariant(variant: string) { + if (variant in variants) { + variantCookie.value = variant + } + } + + function matrixPath(options: { page?: string; anchor?: string; locale?: string; variant?: string }): string { + let prefix: string[] = [] + + const showLocale = locales.length > 1 + const showVariant = variants.length > 1 + + if (showLocale) { + prefix.push(options.locale ?? currentLocale.value) + } + + if (showVariant) { + prefix.push(options.variant ?? currentVariant.value) + } + + // TODO preserve anchor when taking route.path + let page = (options.page ?? route.path).split('/').filter(Boolean) + + if (!options.page) { + page = page.slice([showLocale, showVariant].filter(Boolean).length) + } + + const base = '/' + [...prefix, ...page].join('/') + return options.anchor ? `${base}#${options.anchor}` : base + } + + function p(page: string, anchor?: string): string { + return matrixPath({page: page, anchor: anchor}) + } + + const specialization = computed(() => getSpecialization(currentLocale.value, currentVariant.value)) + + return { + locale: currentLocale, + locales: locales, + preferLocale: preferLocale, + variant: currentVariant, + variants: variants, + preferVariant: preferVariant, + matrixPath: matrixPath, + p: p, + c: specialization, + } +} diff --git a/packages/layers/base/eslint.config.js b/packages/layers/content/eslint.config.js similarity index 100% rename from packages/layers/base/eslint.config.js rename to packages/layers/content/eslint.config.js diff --git a/packages/layers/base/nuxt.config.ts b/packages/layers/content/nuxt.config.ts similarity index 60% rename from packages/layers/base/nuxt.config.ts rename to packages/layers/content/nuxt.config.ts index 9d825c4..0aae826 100644 --- a/packages/layers/base/nuxt.config.ts +++ b/packages/layers/content/nuxt.config.ts @@ -1,4 +1,10 @@ // https://nuxt.com/docs/api/configuration/nuxt-config + +import yaml from '@rollup/plugin-yaml' + export default defineNuxtConfig({ devtools: { enabled: true }, + vite: { + plugins: [ yaml() ] + } }) diff --git a/packages/layers/base/package.json b/packages/layers/content/package.json similarity index 80% rename from packages/layers/base/package.json rename to packages/layers/content/package.json index 55bb209..0747e24 100644 --- a/packages/layers/base/package.json +++ b/packages/layers/content/package.json @@ -1,5 +1,5 @@ { - "name": "@layers/base", + "name": "@layers/content", "type": "module", "version": "0.0.0", "main": "./nuxt.config.ts", @@ -11,8 +11,12 @@ "preview": "nuxt preview .playground", "lint": "eslint ." }, + "dependencies": { + "vue-router": "^4.5.1" + }, "devDependencies": { "@nuxt/eslint": "latest", + "@rollup/plugin-yaml": "^4.1.2", "eslint": "^9.36.0", "nuxt": "^4.1.2", "typescript": "^5.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b06d66d..bd172b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,9 +20,9 @@ importers: apps/panoramablick-saalbach.at: dependencies: - '@layers/base': + '@layers/content': specifier: workspace:0.0.0 - version: link:../../packages/layers/base + version: link:../../packages/layers/content nuxt: specifier: ^4.1.2 version: 4.1.2(@parcel/watcher@2.5.1)(@types/node@22.15.3)(@vue/compiler-sfc@3.5.22)(db0@0.3.4)(eslint@9.37.0(jiti@2.6.1))(ioredis@5.8.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.52.4)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.9(@types/node@22.15.3)(jiti@2.6.1)(terser@5.44.0)(yaml@2.8.1))(yaml@2.8.1) @@ -33,11 +33,18 @@ importers: specifier: ^4.5.1 version: 4.5.1(vue@3.5.22(typescript@5.9.2)) - packages/layers/base: + packages/layers/content: + dependencies: + vue-router: + specifier: ^4.5.1 + version: 4.5.1(vue@3.5.22(typescript@5.9.2)) devDependencies: '@nuxt/eslint': specifier: latest version: 1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.22)(eslint@9.37.0(jiti@2.6.1))(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.9(@types/node@22.15.3)(jiti@2.6.1)(terser@5.44.0)(yaml@2.8.1)) + '@rollup/plugin-yaml': + specifier: ^4.1.2 + version: 4.1.2(rollup@4.52.4) eslint: specifier: ^9.36.0 version: 9.37.0(jiti@2.6.1) @@ -1025,6 +1032,15 @@ packages: rollup: optional: true + '@rollup/plugin-yaml@4.1.2': + resolution: {integrity: sha512-RpupciIeZMUqhgFE97ba0s98mOFS7CWzN3EJNhJkqSv9XLlWYtwVdtE6cDw6ASOF/sZVFS7kRJXftaqM2Vakdw==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -3464,6 +3480,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + tosource@2.0.0-alpha.3: + resolution: {integrity: sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==} + engines: {node: '>=10'} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -4516,7 +4536,7 @@ snapshots: '@nuxt/eslint-plugin@1.9.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/types': 8.45.0 '@typescript-eslint/utils': 8.40.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.2) eslint: 9.37.0(jiti@2.6.1) transitivePeerDependencies: @@ -4974,6 +4994,14 @@ snapshots: optionalDependencies: rollup: 4.52.4 + '@rollup/plugin-yaml@4.1.2(rollup@4.52.4)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.52.4) + js-yaml: 4.1.0 + tosource: 2.0.0-alpha.3 + optionalDependencies: + rollup: 4.52.4 + '@rollup/pluginutils@5.3.0(rollup@4.52.4)': dependencies: '@types/estree': 1.0.8 @@ -5116,7 +5144,7 @@ snapshots: '@typescript-eslint/project-service@8.40.0(typescript@5.9.2)': dependencies: '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2) - '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/types': 8.45.0 debug: 4.4.1 typescript: 5.9.2 transitivePeerDependencies: @@ -5936,14 +5964,14 @@ snapshots: eslint-plugin-import-lite@0.3.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.2): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) - '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/types': 8.45.0 eslint: 9.37.0(jiti@2.6.1) optionalDependencies: typescript: 5.9.2 eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.37.0(jiti@2.6.1)): dependencies: - '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/types': 8.45.0 comment-parser: 1.4.1 debug: 4.4.1 eslint: 9.37.0(jiti@2.6.1) @@ -7647,6 +7675,8 @@ snapshots: toidentifier@1.0.1: {} + tosource@2.0.0-alpha.3: {} + totalist@3.0.1: {} tr46@0.0.3: {}