Improve locale and variant handling
This commit is contained in:
parent
93674b23b7
commit
cfeda865ab
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
<!-- /error.vue (Nuxt automatically serves this for 404 routes) -->
|
||||
<template>
|
||||
<section
|
||||
class="flex min-h-screen flex-col items-center justify-center
|
||||
bg-neutral-100 px-4 text-center"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<UIcon
|
||||
name="i-heroicons-face-frown"
|
||||
class="h-16 w-16 text-primary-600 mb-6"
|
||||
/>
|
||||
|
||||
<!-- Headline -->
|
||||
<h1 class="text-4xl font-bold text-neutral-900 mb-2">
|
||||
Seite nicht gefunden
|
||||
</h1>
|
||||
|
||||
<!-- Sub-copy -->
|
||||
<p class="max-w-md text-neutral-600 mb-8">
|
||||
Die gewünschte Seite existiert leider nicht (mehr) oder der Link war
|
||||
fehlerhaft. Versuchen Sie es über die Startseite noch einmal.
|
||||
</p>
|
||||
|
||||
<!-- CTA -->
|
||||
<UButton
|
||||
to="/"
|
||||
size="lg"
|
||||
color="primary"
|
||||
variant="solid"
|
||||
trailing-icon="i-heroicons-arrow-uturn-left"
|
||||
>
|
||||
Zur Startseite
|
||||
</UButton>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({layout: false, statusCode: 404})
|
||||
</script>
|
||||
@ -1,10 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
const { locale, localeCodes, matrixPath } = useContent()
|
||||
const { preferLocale } = useContentPreference()
|
||||
|
||||
const candidates = computed(() => localeCodes.filter(l => l !== locale.value))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink v-for="candidate in candidates" :key="candidate" :to="{ path: matrixPath({locale: candidate}), query: { freeze: 'true' } }">
|
||||
<NuxtLink v-for="candidate in candidates" :key="candidate" :to="{ path: matrixPath({locale: candidate}), query: { freeze: 'true' } }" @click="preferLocale(candidate)">
|
||||
{{ candidate.toUpperCase() }}
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const { variant, variants, matrixPath } = useContent()
|
||||
const { preferVariant } = useContentPreference()
|
||||
|
||||
const candidates = computed(() => variants.filter(v => v.code !== variant.value))
|
||||
</script>
|
||||
|
||||
@ -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)">
|
||||
<Icon :name="candidate.icon"/>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
@ -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<string>('locale', {
|
||||
sameSite: 'lax',
|
||||
maxAge: config.content.matrix.locale.cookieMaxAge
|
||||
})
|
||||
|
||||
const variantCookie = useCookie<string>('variant', {
|
||||
sameSite: 'lax',
|
||||
maxAge: config.content.matrix.locale.cookieMaxAge
|
||||
})
|
||||
|
||||
const currentLocale = computed<string>(() => route.params.locale || localeCookie.value || config.content.matrix.locale.default)
|
||||
const currentVariant = computed<string>(() => 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,
|
||||
|
||||
45
packages/layers/content/composables/useContentPreference.ts
Normal file
45
packages/layers/content/composables/useContentPreference.ts
Normal file
@ -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<string>('locale', {
|
||||
sameSite: 'lax',
|
||||
maxAge: config.content.matrix.locale.cookieMaxAge
|
||||
})
|
||||
|
||||
const variantCookie = useCookie<string>('variant', {
|
||||
sameSite: 'lax',
|
||||
maxAge: config.content.matrix.variant.cookieMaxAge
|
||||
})
|
||||
|
||||
function prefer(list: string[], cookie: CookieRef<string>) {
|
||||
return (item: string) => {
|
||||
if (list.includes(item)) {
|
||||
cookie.value = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function preferred(list: string[], cookie: CookieRef<string | undefined>, 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)
|
||||
}
|
||||
}
|
||||
49
packages/layers/content/composables/useContentPrefix.ts
Normal file
49
packages/layers/content/composables/useContentPrefix.ts
Normal file
@ -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}
|
||||
}
|
||||
16
packages/layers/content/middleware/content-routing.global.ts
Normal file
16
packages/layers/content/middleware/content-routing.global.ts
Normal file
@ -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))
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user