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 @@
-
-
-
-
-
Hello World!
-
{{ myLayer }}
-
-
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 @@
+
+
+
+ locale {{ locale }}
+ locales {{ locales }}
+ variant {{ variant }}
+ variants {{ variants }}
+
+
+
+ {{ c.cookies.title }}
+
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 @@
+
+
+
+
+ {{ candidate.toUpperCase() }}
+
+
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 @@
+
+
+
+
+ {{ candidate.toUpperCase() }}
+
+
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: {}