diff --git a/apps/panoramablick-saalbach.at/app.config.ts b/apps/panoramablick-saalbach.at/app.config.ts index 30380dc..72b69c3 100644 --- a/apps/panoramablick-saalbach.at/app.config.ts +++ b/apps/panoramablick-saalbach.at/app.config.ts @@ -9,7 +9,7 @@ const matrix: ContentMatrix = { { code: 'de', name: { de: 'Deutsch', en: 'German' }, icon: 'german' }, { code: 'en', name: { de: 'Englisch', en: 'English' }, icon: 'english' }, ], - cookieMaxAge: 60 * 60 * 24 * 365 + cookieMaxAge: 60 * 60 * 24 * 365 * 3 }, variant: { default: 'summer', @@ -17,7 +17,7 @@ const matrix: ContentMatrix = { { 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 + cookieMaxAge: 60 * 60 * 24 } } diff --git a/apps/panoramablick-saalbach.at/pages/[locale]/[variant]/[...all].vue b/apps/panoramablick-saalbach.at/pages/[locale]/[variant]/[...all].vue deleted file mode 100644 index d5d25b5..0000000 --- a/apps/panoramablick-saalbach.at/pages/[locale]/[variant]/[...all].vue +++ /dev/null @@ -1,39 +0,0 @@ - - - - diff --git a/packages/layers/content/components/LocaleSwitcher.vue b/packages/layers/content/components/LocaleSwitcher.vue index 9daec65..4f39d37 100644 --- a/packages/layers/content/components/LocaleSwitcher.vue +++ b/packages/layers/content/components/LocaleSwitcher.vue @@ -1,10 +1,12 @@ diff --git a/packages/layers/content/components/VariantSwitcher.vue b/packages/layers/content/components/VariantSwitcher.vue index 6177461..44b6a8e 100644 --- a/packages/layers/content/components/VariantSwitcher.vue +++ b/packages/layers/content/components/VariantSwitcher.vue @@ -1,5 +1,7 @@ @@ -8,7 +10,8 @@ const candidates = computed(() => variants.filter(v => v.code !== variant.value) v-for="candidate in candidates" :key="candidate.code" :to="{ path: matrixPath({variant: candidate.code}), query: { freeze: 'true' }}" - class="flex items-center justify-center"> + class="flex items-center justify-center" + @click="preferVariant(candidate.code)"> diff --git a/packages/layers/content/composables/useContent.ts b/packages/layers/content/composables/useContent.ts index f22c4da..d3ee9ee 100644 --- a/packages/layers/content/composables/useContent.ts +++ b/packages/layers/content/composables/useContent.ts @@ -70,6 +70,10 @@ function buildSpecialization(locale: string, variant: string): object { return copy } + if (anonymous.length < 1) { + console.warn("Unable to parse object", object) + } + anonymous.sort((a, b) => b[0] - a[0]) if (anonymous[0][0] > 2) { @@ -105,13 +109,6 @@ function buildSpecialization(locale: string, variant: string): object { return filtered } -function measureTime(fn: () => void): number { - const start = performance.now(); - fn(); - const end = performance.now(); - return end - start; // in milliseconds -} - function getSpecialization(locale: string, variant: string): object { const key = locale + '/' + variant @@ -125,57 +122,39 @@ function getSpecialization(locale: string, variant: string): object { export function useContent() { const config = useAppConfig() const route = useRoute() + const {getLocaleVariant, buildPrefix} = useContentPrefix() const locales = config.content.matrix.locale.list.map(l => l.code) const variants = config.content.matrix.variant.list.map(v => v.code) - const localeCookie = useCookie('locale', { - sameSite: 'lax', - maxAge: config.content.matrix.locale.cookieMaxAge - }) - - const variantCookie = useCookie('variant', { - sameSite: 'lax', - maxAge: config.content.matrix.locale.cookieMaxAge - }) - - const currentLocale = computed(() => route.params.locale || localeCookie.value || config.content.matrix.locale.default) - const currentVariant = computed(() => route.params.variant || variantCookie.value || config.content.matrix.locale.default) - - function preferLocale(locale: string) { - if (locale in locales) { - localeCookie.value = locale - } - } - - function preferVariant(variant: string) { - if (variant in variants) { - variantCookie.value = variant - } - } + const localeVariant = computed(() => getLocaleVariant(route.path)!) + const currentLocale = computed(() => localeVariant.value[0]) + const currentVariant = computed(() => localeVariant.value[1]) function matrixPath(options: { page?: string; anchor?: string; locale?: string; variant?: string }): string { - let prefix: string[] = [] + let page = options.page - const showLocale = locales.length > 1 - const showVariant = variants.length > 1 - - if (showLocale) { - prefix.push(options.locale ?? currentLocale.value) + if (page && !page.startsWith('/')) { + page = '/' + page } - if (showVariant) { - prefix.push(options.variant ?? currentVariant.value) + if (page === undefined) { + page = route.fullPath + + const [locale, variant] = getLocaleVariant(page)! + const length = buildPrefix(locale, variant).length + + page = page.slice(length) } - // TODO preserve anchor when taking route.path - let page = (options.page ?? route.path).split('/').filter(Boolean) + const prefix = buildPrefix(options.locale ?? currentLocale.value, options.variant ?? currentVariant.value) - if (!options.page) { - page = page.slice([showLocale, showVariant].filter(Boolean).length) + let base = prefix + page + + if (base.endsWith('/')) { + base = base.slice(base.length - 1) } - const base = '/' + [...prefix, ...page].join('/') return options.anchor ? `${base}#${options.anchor}` : base } @@ -189,11 +168,9 @@ export function useContent() { locale: currentLocale, locales: config.content.matrix.locale.list, localeCodes: locales, - preferLocale: preferLocale, variant: currentVariant, variants: config.content.matrix.variant.list, variantCodes: variants, - preferVariant: preferVariant, matrixPath: matrixPath, p: p, c: specialization, diff --git a/packages/layers/content/composables/useContentPreference.ts b/packages/layers/content/composables/useContentPreference.ts new file mode 100644 index 0000000..c117862 --- /dev/null +++ b/packages/layers/content/composables/useContentPreference.ts @@ -0,0 +1,45 @@ +import type {CookieRef} from "nuxt/app" + +export function useContentPreference() { + const config = useAppConfig() + + const locales = config.content.matrix.locale.list.map(l => l.code) + const variants = config.content.matrix.variant.list.map(v => v.code) + + const localeCookie = useCookie('locale', { + sameSite: 'lax', + maxAge: config.content.matrix.locale.cookieMaxAge + }) + + const variantCookie = useCookie('variant', { + sameSite: 'lax', + maxAge: config.content.matrix.variant.cookieMaxAge + }) + + function prefer(list: string[], cookie: CookieRef) { + return (item: string) => { + if (list.includes(item)) { + cookie.value = item + } + } + } + + function preferred(list: string[], cookie: CookieRef, fallback: string) { + return computed(() => { + if (cookie.value!==undefined && list.includes(cookie.value)) { + return cookie.value + } else if (cookie.value !== undefined) { + cookie.value = undefined + } + + return fallback + }) + } + + return { + preferredLocale: preferred(locales, localeCookie, config.content.matrix.locale.default), + preferLocale: prefer(locales, localeCookie), + preferredVariant: preferred(variants, variantCookie, config.content.matrix.variant.default), + preferVariant: prefer(variants, variantCookie) + } +} diff --git a/packages/layers/content/composables/useContentPrefix.ts b/packages/layers/content/composables/useContentPrefix.ts new file mode 100644 index 0000000..07a9126 --- /dev/null +++ b/packages/layers/content/composables/useContentPrefix.ts @@ -0,0 +1,49 @@ +export function useContentPrefix() { + const config = useAppConfig() + + const locales: string[] = config.content.matrix.locale.list.map(l => l.code) + const variants: string[] = config.content.matrix.variant.list.map(v => v.code) + + type Prefix = [string, string, string] + let prefixes: Prefix[] = [] + + if (locales.length>1 && variants.length>1) { + prefixes = locales.flatMap(l => variants.map(v => [`/${l}/${v}`, l, v] as Prefix)) + } else if (locales.length>1 && variants.length===1) { + prefixes = locales.map(l => [`/${l}`, l, variants[0]!]) + } else if (locales.length===1 && variants.length>1) { + prefixes = variants.map(v => [`/${v}`, locales[0]!, v]) + } else if (locales.length===1 && variants.length===1) { + prefixes = [['', locales[0]!, variants[0]!]] + } else { + console.warn('Missing locales and variants in AppConfig') + } + + const terminators = new Set(['/', '#', '?']) + + function getLocaleVariant(path: string): [string, string] | undefined { + for (const [p, l, v] of prefixes) { + if (path.startsWith(p) && (path.length===p.length || terminators.has(path[p.length]))) { + return [l, v] + } + } + + return undefined + } + + function buildPrefix(locale: string, variant: string): string { + let segments: string[] = [] + + if (locales.length > 1) { + segments.push(locale) + } + + if (variants.length > 1) { + segments.push(variant) + } + + return (segments.length ? '/' : '') + segments.join('/') + } + + return {getLocaleVariant, buildPrefix} +} diff --git a/packages/layers/content/middleware/content-routing.global.ts b/packages/layers/content/middleware/content-routing.global.ts new file mode 100644 index 0000000..2b0da60 --- /dev/null +++ b/packages/layers/content/middleware/content-routing.global.ts @@ -0,0 +1,16 @@ +export default defineNuxtRouteMiddleware((to, from) => { + // important: routes do not exist in middleware, never (in)directly use e.g. useRoute + + const {getLocaleVariant, buildPrefix} = useContentPrefix() + const localeVariant = getLocaleVariant(to.path) + + if (to.matched.length && localeVariant) return + + if (!to.matched.length && localeVariant) { + const [locale, variant] = localeVariant + return navigateTo(buildPrefix(locale, variant)) + } + + const {preferredLocale, preferredVariant} = useContentPreference() + return navigateTo(buildPrefix(preferredLocale.value, preferredVariant.value)) +})