From 2d3c1994d2037eb895def867ae573175be4aa564 Mon Sep 17 00:00:00 2001 From: TheMeinerLP Date: Sun, 11 Oct 2026 00:17:58 +0200 Subject: [PATCH] fix(seo): fall back to the localised default description A page without its own description fell back to the static English site.description before the i18n seo.default_description key, so a German page could get English meta and og text. The WebSite schema description and og defaults also read site.description, so they stayed English on every locale. usePageSeo now prefers the localised key and only then site.description. A nuxtSiteConfig.description key per locale makes nuxt-site-config resolve the site description per locale. Claude-Session: https://claude.ai/code/session_019zws4ceJu2jctk3XT48fMX --- i18n/locales/de.json | 3 + i18n/locales/en.json | 3 + layers/content-core/composables/usePageSeo.ts | 4 +- nuxt.config.ts | 1 + tests/seo/default-description.spec.ts | 70 +++++++++++++++++++ 5 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 tests/seo/default-description.spec.ts diff --git a/i18n/locales/de.json b/i18n/locales/de.json index 8a94afee..7f6bec76 100644 --- a/i18n/locales/de.json +++ b/i18n/locales/de.json @@ -206,6 +206,9 @@ "linkedin": "LinkedIn", "github": "GitHub" }, + "nuxtSiteConfig": { + "description": "OneLiteFeather ist ein deutschsprachiges Minecraft-Netzwerk seit 2019 – mit Community-Bauten, Events und Open-Source-Software für Paper und Minestom." + }, "seo": { "default_description": "OneLiteFeather ist ein deutschsprachiges Minecraft-Netzwerk seit 2019 – mit Community-Bauten, Events und Open-Source-Software für Paper und Minestom.", "default_keywords": "OneLiteFeather, Minecraft, Minecraft Server, Minecraft Netzwerk, Plugins, Open Source, Paper, Velocity, Java, Bedrock, Entwicklungstools, Community" diff --git a/i18n/locales/en.json b/i18n/locales/en.json index db9dbb50..9176b72b 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -206,6 +206,9 @@ "linkedin": "LinkedIn", "github": "GitHub" }, + "nuxtSiteConfig": { + "description": "OneLiteFeather is a German-speaking Minecraft network founded in 2019, with community builds, events and open-source software for Paper and Minestom." + }, "seo": { "default_description": "OneLiteFeather is a German-speaking Minecraft network founded in 2019, with community builds, events and open-source software for Paper and Minestom.", "default_keywords": "OneLiteFeather, Minecraft, Minecraft server, Minecraft network, plugins, open source, Paper, Velocity, Java, Bedrock, development tools, community" diff --git a/layers/content-core/composables/usePageSeo.ts b/layers/content-core/composables/usePageSeo.ts index bf78ee74..52f7230c 100644 --- a/layers/content-core/composables/usePageSeo.ts +++ b/layers/content-core/composables/usePageSeo.ts @@ -68,9 +68,7 @@ export function usePageSeo(opts: PageSeoOptions = {}) { const pageTitle = computed(() => opts.title || site.name) const pageDescription = computed(() => { - if (opts.description) return opts.description - if (site.description) return site.description - return t('seo.default_description') + return opts.description || t('seo.default_description') || site.description }) // Resolve og:locale from the current locale code diff --git a/nuxt.config.ts b/nuxt.config.ts index ef8ca33b..f2afb7c7 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -93,6 +93,7 @@ export default defineNuxtConfig({ ] }) }, + // Localised description: i18n key `nuxtSiteConfig.description` (WebSite, og defaults). site: { url: 'http://localhost:3000', name: 'OneLiteFeather Network', diff --git a/tests/seo/default-description.spec.ts b/tests/seo/default-description.spec.ts new file mode 100644 index 00000000..ccca87eb --- /dev/null +++ b/tests/seo/default-description.spec.ts @@ -0,0 +1,70 @@ +// @vitest-environment nuxt +import { readFileSync } from 'node:fs' +import { mockNuxtImport, mountSuspended } from '@nuxt/test-utils/runtime' +import { flushPromises } from '@vue/test-utils' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { defineComponent, h, unref } from 'vue' +import { usePageSeo } from '../../layers/content-core/composables/usePageSeo' +import { repoRoot } from '../helpers/sources' + +interface LocaleFile { + seo: { default_description: string } + nuxtSiteConfig?: { description?: string } +} + +const messages = (code: string): LocaleFile => JSON.parse(readFileSync(`${repoRoot}/i18n/locales/${code}.json`, 'utf8')) + +const DE_DEFAULT = messages('de').seo.default_description + +// The static English site description that nuxt.config.ts declares. Tests +// override it to prove the page default does not come from it. +let siteDescriptionOverride: string | undefined +let seoMeta: Record = {} + +mockNuxtImport('useSiteConfig', (original) => () => { + const site = original() + if (siteDescriptionOverride === undefined) return site + return new Proxy(site, { + get: (target, key) => (key === 'description' ? siteDescriptionOverride : Reflect.get(target, key)) + }) +}) + +mockNuxtImport('useSeoMeta', (original) => (input: Record) => { + seoMeta = input + return original(input as Parameters[0]) +}) + +const probe = defineComponent({ + setup() { + usePageSeo({ title: 'Imprint' }) + return () => h('div') + } +}) + +const renderDescription = async (route: string): Promise => { + await mountSuspended(probe, { route }) + await flushPromises() + return String(unref(seoMeta.description)) +} + +beforeEach(() => { + siteDescriptionOverride = undefined + seoMeta = {} + // Nuxt OG image's compiler macro; the test environment has no transform for it. + vi.stubGlobal('defineOgImage', () => undefined) +}) + +describe('default page description', () => { + it('is the German default for a German page without its own description', async () => { + expect(await renderDescription('/de/imprint')).toBe(DE_DEFAULT) + }) + + it('prefers the localised default over the static site description', async () => { + siteDescriptionOverride = 'Static English site description' + expect(await renderDescription('/de/imprint')).toBe(DE_DEFAULT) + }) + + it('gives WebSite the German description on German pages', () => { + expect(messages('de').nuxtSiteConfig?.description).toBe(DE_DEFAULT) + }) +})