feat(website): bilingual i18n (English default, Danish at /da) + SEO
Add @nuxtjs/i18n: English is the default locale (no prefix), Danish lives under /da (prefix_except_default). Both server-rendered and indexed with hreflang alternates + per-locale canonical (useLocaleHead in app.vue). First-visit browser language is auto-detected and remembered in the i18n_redirected cookie (redirectOn root). - Keep the hand-authored COPY object; useLang/useCopy now read the i18n locale; useLocalizeHref/useLangToggle added. Every internal link is localized so navigation stays in-locale. - Clear segmented EN|DA language switcher (active segment filled) replacing the ambiguous "en · da" pill. - SEO: useSeoMeta defaults are locale-aware; og:image switches per locale (English / Danish share card); favicon links; robots.txt + sitemap.xml; env-aware siteUrl/baseUrl (localhost in dev, dezky.eu in prod). - Update the cookie policy (dezky-lang -> i18n_redirected). - Gate the Traefik wss:443 HMR behind DEZKY_TRAEFIK so localhost dev/DevTools connect over plain ws (fixes the DevTools disconnect loop).
This commit is contained in:
@@ -6,10 +6,15 @@
|
||||
// the Docker app stack. Locally it runs behind Traefik at dezky.local /
|
||||
// www.dezky.local with the same mkcert TLS as the rest of the platform.
|
||||
|
||||
const siteUrl = process.env.NUXT_PUBLIC_SITE_URL
|
||||
|| (process.env.NODE_ENV === 'production' ? 'https://dezky.eu' : 'http://localhost:3000')
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2026-01-01',
|
||||
devtools: { enabled: true },
|
||||
|
||||
modules: ['@nuxtjs/i18n'],
|
||||
|
||||
css: ['~/assets/styles/tokens.css', '~/assets/styles/base.css'],
|
||||
|
||||
// Auto-import from the shared packages/ui workspace in addition to the
|
||||
@@ -25,10 +30,12 @@ export default defineNuxtConfig({
|
||||
|
||||
app: {
|
||||
head: {
|
||||
// Marketing site is light by default (the design's primary theme). The
|
||||
// page sets <html lang> reactively based on the da/en toggle.
|
||||
htmlAttrs: { lang: 'da' },
|
||||
// <html lang>/dir + hreflang alternates are managed by @nuxtjs/i18n
|
||||
// (useLocaleHead in app.vue). Static favicons + theme-color live here.
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
|
||||
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32.png' },
|
||||
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },
|
||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
||||
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' },
|
||||
{
|
||||
@@ -36,6 +43,41 @@ export default defineNuxtConfig({
|
||||
href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap',
|
||||
},
|
||||
],
|
||||
// theme-color + favicons are static; the OG/Twitter/canonical SEO is set
|
||||
// in app.vue via useSeoMeta so og:url/og:image are absolute against the
|
||||
// env-aware siteUrl (runtimeConfig below) and og:url is per-route.
|
||||
meta: [
|
||||
{ name: 'theme-color', content: '#FAFAF7' },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// Public site URL used to build absolute canonical / OG / sitemap URLs.
|
||||
// Defaults to localhost in dev (so local share-image previews work) and
|
||||
// the production domain otherwise; override with NUXT_PUBLIC_SITE_URL.
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
siteUrl,
|
||||
},
|
||||
},
|
||||
|
||||
// Bilingual: English is the default (no prefix); Danish lives under /da.
|
||||
// Both are server-rendered and indexed with hreflang alternates. First-time
|
||||
// visitors are auto-detected (cookie-remembered) on the root path only.
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
strategy: 'prefix_except_default',
|
||||
baseUrl: siteUrl,
|
||||
locales: [
|
||||
{ code: 'en', language: 'en', name: 'English' },
|
||||
{ code: 'da', language: 'da-DK', name: 'Dansk' },
|
||||
],
|
||||
detectBrowserLanguage: {
|
||||
useCookie: true,
|
||||
cookieKey: 'i18n_redirected',
|
||||
redirectOn: 'root',
|
||||
fallbackLocale: 'en',
|
||||
alwaysRedirect: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -44,10 +86,12 @@ export default defineNuxtConfig({
|
||||
// Vite 7 added a strict host check; allow the Traefik-fronted hostnames
|
||||
// this site is served on in dev.
|
||||
allowedHosts: ['dezky.local', 'www.dezky.local'],
|
||||
hmr: {
|
||||
protocol: 'wss',
|
||||
clientPort: 443,
|
||||
},
|
||||
// HMR/DevTools websocket. Default (localhost:3000 dev) lets Vite infer
|
||||
// ws on the page host/port. When served behind Traefik TLS at
|
||||
// dezky.local, set DEZKY_TRAEFIK=1 so the client connects via wss:443.
|
||||
hmr: process.env.DEZKY_TRAEFIK === '1'
|
||||
? { protocol: 'wss', clientPort: 443 }
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user