From d054b9b464cba309c075342c3eb900e4f280b70f Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:23:39 +0200 Subject: [PATCH 1/5] feat: tutorial custom page title --- .../docs/en/tutorial/0-introduction/index.mdx | 3 +++ src/content/docs/en/tutorial/1-setup/index.mdx | 3 +++ src/content/docs/en/tutorial/6-islands/4.mdx | 3 +++ src/content/i18n-schema.ts | 1 + src/content/i18n/en.yml | 1 + src/routeData.ts | 12 +++++++++++- 6 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/content/docs/en/tutorial/0-introduction/index.mdx b/src/content/docs/en/tutorial/0-introduction/index.mdx index 77e26cd9061c8..d1f984594bd21 100644 --- a/src/content/docs/en/tutorial/0-introduction/index.mdx +++ b/src/content/docs/en/tutorial/0-introduction/index.mdx @@ -8,6 +8,9 @@ description: >- Learn the basics of Astro with a project-based tutorial. All the background knowledge you need to get started! i18nReady: true +head: + - tag: title + content: Build a blog tutorial | Docs --- import Checklist from '~/components/Checklist.astro'; import Box from '~/components/tutorial/Box.astro'; diff --git a/src/content/docs/en/tutorial/1-setup/index.mdx b/src/content/docs/en/tutorial/1-setup/index.mdx index 075b28ff39582..e2d4491df246b 100644 --- a/src/content/docs/en/tutorial/1-setup/index.mdx +++ b/src/content/docs/en/tutorial/1-setup/index.mdx @@ -8,6 +8,9 @@ description: >- Prepare your development environment, and create and deploy your first Astro site i18nReady: true +head: + - tag: title + content: 'Build a blog tutorial: Unit 1 - Setup | Docs' --- import Checklist from '~/components/Checklist.astro'; import Box from '~/components/tutorial/Box.astro'; diff --git a/src/content/docs/en/tutorial/6-islands/4.mdx b/src/content/docs/en/tutorial/6-islands/4.mdx index 43b3be3f709c0..06ad6a55abe89 100644 --- a/src/content/docs/en/tutorial/6-islands/4.mdx +++ b/src/content/docs/en/tutorial/6-islands/4.mdx @@ -5,6 +5,9 @@ description: |- Tutorial: Build your first Astro blog — Convert your blog from file-based routing to content collections i18nReady: true +head: + - tag: title + content: 'Build a blog tutorial: Make a content collection | Docs' --- import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; import Box from '~/components/tutorial/Box.astro'; diff --git a/src/content/i18n-schema.ts b/src/content/i18n-schema.ts index da484d8d16d01..e116ed1da3732 100644 --- a/src/content/i18n-schema.ts +++ b/src/content/i18n-schema.ts @@ -55,6 +55,7 @@ export const AstroDocsI18nSchema = z // Tutorial Navigation 'tutorial.trackerLabel': z.string(), 'tutorial.unit': z.string(), + 'tutorial.title.prefix': z.string(), // Tutorial 'tutorial.getReady': z.string(), // Code snippet vocabulary diff --git a/src/content/i18n/en.yml b/src/content/i18n/en.yml index e08b61e1177f9..0a2ea867c6f03 100644 --- a/src/content/i18n/en.yml +++ b/src/content/i18n/en.yml @@ -51,6 +51,7 @@ progress.done: Complete # Tutorial Navigation tutorial.trackerLabel: Tutorial Tracker tutorial.unit: Unit +tutorial.title.prefix: 'Build a blog tutorial: {{title}}' # Tutorial tutorial.getReady: Get ready to… # Code snippet vocabulary diff --git a/src/routeData.ts b/src/routeData.ts index 96026f9d5224f..5036d5b8087a8 100644 --- a/src/routeData.ts +++ b/src/routeData.ts @@ -11,13 +11,23 @@ export const onRequest = defineRouteMiddleware((context) => { }); function updateHead(context: APIContext) { - const { head, isFallback, lang } = context.locals.starlightRoute; + const { head, entry, isFallback, lang, entryMeta } = context.locals.starlightRoute; const ogImageUrl = getOgImageUrl(context.url.pathname, !!isFallback); const imageSrc = ogImageUrl ?? '/default-og-image.png'; const canonicalImageSrc = new URL(imageSrc, context.site); const is404 = context.url.pathname.endsWith('/404/'); + const title = head.find((item) => item.tag === 'title'); + const frontmatterTitle = entry.data.head.find((item) => item.tag === 'title'); + if (title && entry.id.split('/')[1] === 'tutorial' && !frontmatterTitle) { + title.content = context.locals.t('tutorial.title.prefix', { + title: title.content, + // TODO(HiDeoo) comment + lng: entryMeta.lang, + }); + } + head.push({ tag: 'meta', attrs: { property: 'og:image', content: canonicalImageSrc.href } }); head.push({ tag: 'meta', attrs: { name: 'twitter:image', content: canonicalImageSrc.href } }); head.push({ tag: 'meta', attrs: { name: 'twitter:site', content: 'astrodotbuild' } }); From 504d384cfd88a77fb1e703b4e96937000bd952f9 Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:52:51 +0200 Subject: [PATCH 2/5] refactor: middleware + comments --- src/routeData.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/routeData.ts b/src/routeData.ts index 5036d5b8087a8..1b2ba368b676d 100644 --- a/src/routeData.ts +++ b/src/routeData.ts @@ -13,21 +13,24 @@ export const onRequest = defineRouteMiddleware((context) => { function updateHead(context: APIContext) { const { head, entry, isFallback, lang, entryMeta } = context.locals.starlightRoute; - const ogImageUrl = getOgImageUrl(context.url.pathname, !!isFallback); - const imageSrc = ogImageUrl ?? '/default-og-image.png'; - const canonicalImageSrc = new URL(imageSrc, context.site); - const is404 = context.url.pathname.endsWith('/404/'); - const title = head.find((item) => item.tag === 'title'); const frontmatterTitle = entry.data.head.find((item) => item.tag === 'title'); - if (title && entry.id.split('/')[1] === 'tutorial' && !frontmatterTitle) { + + // Update the title of tutorial entry which do not provide a custom title in their frontmatter. + if (isTutorialEntry(entry) && title && !frontmatterTitle) { title.content = context.locals.t('tutorial.title.prefix', { title: title.content, - // TODO(HiDeoo) comment + // Explicitly use the language based on the page content, which can be different from the + // page language for fallback pages. lng: entryMeta.lang, }); } + const ogImageUrl = getOgImageUrl(context.url.pathname, !!isFallback); + const imageSrc = ogImageUrl ?? '/default-og-image.png'; + const canonicalImageSrc = new URL(imageSrc, context.site); + const is404 = context.url.pathname.endsWith('/404/'); + head.push({ tag: 'meta', attrs: { property: 'og:image', content: canonicalImageSrc.href } }); head.push({ tag: 'meta', attrs: { name: 'twitter:image', content: canonicalImageSrc.href } }); head.push({ tag: 'meta', attrs: { name: 'twitter:site', content: 'astrodotbuild' } }); @@ -50,7 +53,7 @@ function updateHead(context: APIContext) { function updateTutorialPagination(starlightRoute: StarlightRouteData) { const { entry, locale, pagination } = starlightRoute; - if (entry.id.split('/')[1] !== 'tutorial') return; + if (!isTutorialEntry(entry)) return; const tutorialPages = getTutorialPages(pages, locale!); const i = tutorialPages.findIndex((p) => p.id === entry.id); @@ -81,3 +84,7 @@ function updateTutorialPagination(starlightRoute: StarlightRouteData) { }; } } + +function isTutorialEntry(entry: StarlightRouteData['entry']) { + return entry.id.split('/')[1] === 'tutorial'; +} From d831f510bb38b5c8cbcb095e59b87a7f1da6b949 Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:56:29 +0200 Subject: [PATCH 3/5] feat: tutorial check in page titles --- src/content/docs/en/tutorial/2-pages/index.mdx | 3 +++ src/content/docs/en/tutorial/3-components/index.mdx | 3 +++ src/content/docs/en/tutorial/4-layouts/index.mdx | 3 +++ src/content/docs/en/tutorial/5-astro-api/index.mdx | 3 +++ src/content/docs/en/tutorial/6-islands/index.mdx | 3 +++ 5 files changed, 15 insertions(+) diff --git a/src/content/docs/en/tutorial/2-pages/index.mdx b/src/content/docs/en/tutorial/2-pages/index.mdx index 58225f7b20c5b..047a954694906 100644 --- a/src/content/docs/en/tutorial/2-pages/index.mdx +++ b/src/content/docs/en/tutorial/2-pages/index.mdx @@ -6,6 +6,9 @@ description: |- Tutorial: Build your first Astro blog — Create, style, and link to pages posts on your site i18nReady: true +head: + - tag: title + content: 'Build a blog tutorial: Unit 2 - Pages | Docs' --- import Checklist from '~/components/Checklist.astro'; import Box from '~/components/tutorial/Box.astro'; diff --git a/src/content/docs/en/tutorial/3-components/index.mdx b/src/content/docs/en/tutorial/3-components/index.mdx index 3a220042d75d8..659faa9d697ea 100644 --- a/src/content/docs/en/tutorial/3-components/index.mdx +++ b/src/content/docs/en/tutorial/3-components/index.mdx @@ -6,6 +6,9 @@ description: |- Tutorial: Build your first Astro blog — Build Astro components to reuse code for common elements across your website i18nReady: true +head: + - tag: title + content: 'Build a blog tutorial: Unit 3 - Components | Docs' --- import Box from '~/components/tutorial/Box.astro'; import Checklist from '~/components/Checklist.astro'; diff --git a/src/content/docs/en/tutorial/4-layouts/index.mdx b/src/content/docs/en/tutorial/4-layouts/index.mdx index f9207df4bbe4e..1013af18fc0d3 100644 --- a/src/content/docs/en/tutorial/4-layouts/index.mdx +++ b/src/content/docs/en/tutorial/4-layouts/index.mdx @@ -8,6 +8,9 @@ description: >- Use Astro layouts to share common elements and styles across your pages and posts i18nReady: true +head: + - tag: title + content: 'Build a blog tutorial: Unit 4 - Layouts | Docs' --- import Box from '~/components/tutorial/Box.astro'; import Checklist from '~/components/Checklist.astro'; diff --git a/src/content/docs/en/tutorial/5-astro-api/index.mdx b/src/content/docs/en/tutorial/5-astro-api/index.mdx index de288e78a63b1..c139c4fd33635 100644 --- a/src/content/docs/en/tutorial/5-astro-api/index.mdx +++ b/src/content/docs/en/tutorial/5-astro-api/index.mdx @@ -8,6 +8,9 @@ description: >- Fetching and using data from project files to dynamically generate pages content and routes i18nReady: true +head: + - tag: title + content: 'Build a blog tutorial: Unit 5 - Astro API | Docs' --- import Box from '~/components/tutorial/Box.astro'; import Checklist from '~/components/Checklist.astro'; diff --git a/src/content/docs/en/tutorial/6-islands/index.mdx b/src/content/docs/en/tutorial/6-islands/index.mdx index 8c481dd09f5c9..7274291bff591 100644 --- a/src/content/docs/en/tutorial/6-islands/index.mdx +++ b/src/content/docs/en/tutorial/6-islands/index.mdx @@ -6,6 +6,9 @@ description: |- Tutorial: Build your first Astro blog — Use Astro islands to bring frontend framework components into your Astro site i18nReady: true +head: + - tag: title + content: 'Build a blog tutorial: Unit 6 - Astro Islands | Docs' --- import Box from '~/components/tutorial/Box.astro'; import Checklist from '~/components/Checklist.astro'; From e143ff81966bf32835d9c0bdf8e4ed8156a292a2 Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Thu, 10 Jul 2025 18:33:26 +0200 Subject: [PATCH 4/5] refactor: add prefix if translation is available --- src/content/i18n/fr.yml | 1 + src/routeData.ts | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/content/i18n/fr.yml b/src/content/i18n/fr.yml index 826c79e7acafe..4c5ae34aa587b 100644 --- a/src/content/i18n/fr.yml +++ b/src/content/i18n/fr.yml @@ -51,6 +51,7 @@ progress.done: Terminer # Tutorial Navigation tutorial.trackerLabel: Suivi du tutoriel tutorial.unit: Unité +tutorial.title.prefix: "Tutoriel de création d'un blog : {{title}}" # Tutorial tutorial.getReady: Préparez-vous à… # Code snippet vocabulary diff --git a/src/routeData.ts b/src/routeData.ts index 1b2ba368b676d..638a230712ab7 100644 --- a/src/routeData.ts +++ b/src/routeData.ts @@ -18,12 +18,22 @@ function updateHead(context: APIContext) { // Update the title of tutorial entry which do not provide a custom title in their frontmatter. if (isTutorialEntry(entry) && title && !frontmatterTitle) { - title.content = context.locals.t('tutorial.title.prefix', { - title: title.content, - // Explicitly use the language based on the page content, which can be different from the - // page language for fallback pages. - lng: entryMeta.lang, + // Check if a prefix translation exists for the page content language, without any possible + // fallback. + const isPrefixTranslated = context.locals.t.exists('tutorial.title.prefix', { + // `exists()` checks for the translation key using fallbacks by default. + lngs: [entryMeta.lang], }); + + if (isPrefixTranslated) { + // If a prefix translation exists, use it to format the title. + title.content = context.locals.t('tutorial.title.prefix', { + title: title.content, + // Explicitly use the language based on the page content, which can be different from the + // page language for fallback pages. + lng: entryMeta.lang, + }); + } } const ogImageUrl = getOgImageUrl(context.url.pathname, !!isFallback); From 18ba0035b80c4f5f840fc783435e0e82e8feb723 Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Thu, 10 Jul 2025 18:53:57 +0200 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20`lng`=20=E2=86=92=20`lngs`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routeData.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/routeData.ts b/src/routeData.ts index 638a230712ab7..1a312bec43f06 100644 --- a/src/routeData.ts +++ b/src/routeData.ts @@ -21,7 +21,6 @@ function updateHead(context: APIContext) { // Check if a prefix translation exists for the page content language, without any possible // fallback. const isPrefixTranslated = context.locals.t.exists('tutorial.title.prefix', { - // `exists()` checks for the translation key using fallbacks by default. lngs: [entryMeta.lang], }); @@ -31,7 +30,7 @@ function updateHead(context: APIContext) { title: title.content, // Explicitly use the language based on the page content, which can be different from the // page language for fallback pages. - lng: entryMeta.lang, + lngs: [entryMeta.lang], }); } }