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) + }) +})