From 614e928bf3bcc51bc5132cdd1b547b65626794c3 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 1 Apr 2026 23:02:31 +0200 Subject: [PATCH 1/9] Add Cloudflare AI landing translation layer --- astro.config.mjs | 35 +- bun.lock | 7 + package.json | 1 + src/components/Footer.astro | 40 +-- src/components/SEO.astro | 34 +- src/env.d.ts | 18 +- src/generated/pageVersions.ts | 643 ++++++++++++++++++++++++++++++++++ src/layouts/Layout.astro | 5 +- src/lib/alternateVersions.ts | 28 +- src/lib/landingTranslation.ts | 412 ++++++++++++++++++++++ src/middleware.ts | 200 ++++++++++- src/services/landingLocale.ts | 197 +++++++++++ wrangler.jsonc | 3 + 13 files changed, 1548 insertions(+), 75 deletions(-) create mode 100644 src/generated/pageVersions.ts create mode 100644 src/lib/landingTranslation.ts create mode 100644 src/services/landingLocale.ts diff --git a/astro.config.mjs b/astro.config.mjs index bc5b58c46..f6d0f29c7 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -7,13 +7,14 @@ import tailwindcss from '@tailwindcss/vite' import { filterSitemapByDefaultLocale, i18n } from 'astro-i18n-aut/integration' import { defineConfig } from 'astro/config' import { glob } from 'glob' -import { readFileSync, statSync } from 'node:fs' +import { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs' import os from 'node:os' import { fileURLToPath } from 'node:url' import starlightImageZoom from 'starlight-image-zoom' import starlightLlmsTxt from 'starlight-llms-txt' import { viteStaticCopy } from 'vite-plugin-static-copy' import config from './configs.json' +import { getAlternateLocaleEntries, normalizePathname, splitLocaleFromPathname } from './src/services/landingLocale' import { defaultLocale, localeNames, locales } from './src/services/locale' // Prefer the scheduler-aware CPU count and keep a clear override for CI/local tuning. @@ -23,6 +24,7 @@ const CPU_COUNT = Number.isFinite(BUILD_CONCURRENCY) && BUILD_CONCURRENCY > 0 ? const SRC_DIR = `${fileURLToPath(new URL('./src/', import.meta.url)) .replace(/\\/g, '/') .replace(/\/$/, '')}/` +const GENERATED_PAGE_VERSIONS_FILE = `${SRC_DIR}/generated/pageVersions.ts` // Build a map of page paths to their lastmod dates for sitemap function getPageLastModDates() { @@ -62,9 +64,31 @@ function getPageLastModDates() { } const pageLastModDates = getPageLastModDates() +writeGeneratedPageVersionModule(pageLastModDates) const docsExpludes = locales.map((locale) => `${locale}/**`) +function writeGeneratedPageVersionModule(lastModMap) { + mkdirSync(`${SRC_DIR}/generated`, { recursive: true }) + + const entries = [...lastModMap.entries()] + .sort(([leftPath], [rightPath]) => leftPath.localeCompare(rightPath)) + .map(([path, lastMod]) => ` ${JSON.stringify(path)}: ${JSON.stringify(lastMod.toISOString())},`) + .join('\n') + + writeFileSync( + GENERATED_PAGE_VERSIONS_FILE, + [ + `export const siteBuildVersion = ${JSON.stringify(new Date().toISOString())}`, + '', + 'export const pageVersionMap = {', + entries, + '} as const', + '', + ].join('\n'), + ) +} + export default defineConfig({ trailingSlash: 'always', site: `https://${config.base_domain.prod}`, @@ -123,11 +147,16 @@ export default defineConfig({ lastmod: new Date(), serialize(item) { // Check if this URL matches a page with a known lastmod date - const urlPath = new URL(item.url).pathname - const lastmod = pageLastModDates.get(urlPath) + const urlPath = normalizePathname(new URL(item.url).pathname) + const { pathname: basePath } = splitLocaleFromPathname(urlPath) + const lastmod = pageLastModDates.get(basePath) ?? pageLastModDates.get(urlPath) if (lastmod) { item.lastmod = lastmod.toISOString() } + item.links = getAlternateLocaleEntries(urlPath).map((locale) => ({ + lang: locale.hreflang, + url: new URL(locale.path, `https://${config.base_domain.prod}`).toString(), + })) return item }, }), diff --git a/bun.lock b/bun.lock index 3c69cd9ff..7d3367890 100644 --- a/bun.lock +++ b/bun.lock @@ -20,6 +20,7 @@ "astro-i18n-aut": "^0.7.3", "github-slugger": "^2.0.0", "glob": "^13.0.6", + "linkedom": "^0.18.12", "mermaid": "^11.13.0", "openai": "^6.27.0", "schema-dts": "^1.1.5", @@ -908,6 +909,8 @@ "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="], + "cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="], + "cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="], "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], @@ -1344,6 +1347,8 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "linkedom": ["linkedom@0.18.12", "", { "dependencies": { "css-select": "^5.1.0", "cssom": "^0.5.0", "html-escaper": "^3.0.3", "htmlparser2": "^10.0.0", "uhyphen": "^0.2.0" }, "peerDependencies": { "canvas": ">= 2" }, "optionalPeers": ["canvas"] }, "sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q=="], + "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], "lite-youtube-embed": ["lite-youtube-embed@0.3.4", "", {}, "sha512-aXgxpwK7AIW58GEbRzA8EYaY4LWvF3FKak6B9OtSJmuNyLhX2ouD4cMTxz/yR5HFInhknaYd2jLWOTRTvT8oAw=="], @@ -1866,6 +1871,8 @@ "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + "uhyphen": ["uhyphen@0.2.0", "", {}, "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA=="], + "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="], "ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="], diff --git a/package.json b/package.json index 31a66083e..65e576acc 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "astro-i18n-aut": "^0.7.3", "github-slugger": "^2.0.0", "glob": "^13.0.6", + "linkedom": "^0.18.12", "mermaid": "^11.13.0", "openai": "^6.27.0", "schema-dts": "^1.1.5", diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 0647a0e38..3b9729b17 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -1,24 +1,13 @@ --- import * as m from '@/paraglide/messages' -import { locales } from '@/services/locale' import { getRelativeLocaleUrl } from 'astro:i18n' +import { getAlternateLocaleEntries, getLocaleEntry } from '@/services/landingLocale' const year = new Date().getFullYear() - -// Map locale codes to flag emojis -const localeFlags: Record = { - de: '🇩🇪', - en: '🇺🇸', - es: '🇪🇸', - fr: '🇫🇷', - id: '🇮🇩', - it: '🇮🇹', - ja: '🇯🇵', - ko: '🇰🇷', - zh: '🇨🇳', -} - -const currentFlag = localeFlags[Astro.locals.locale] || '🇺🇸' +const displayLocale = Astro.locals.displayLocale || Astro.locals.locale || 'en' +const currentLocaleEntry = getLocaleEntry(displayLocale) +const localeOptions = getAlternateLocaleEntries(Astro.locals.requestedPathname || Astro.url.pathname) +const currentFlag = currentLocaleEntry.flag interface NavigationItem { name: string | (() => string) @@ -316,24 +305,29 @@ const navigation: Record = {
-
+