Commit b3c86a9

HPCesia <me@hpcesia.com>
2026-09-18 12:58:13
i18n: only link locales built for the current page
Assisted-by: crush:deepseek-v4-flash
1 parent c3e9a27
Changed files (4)
src
components
layouts
pages
[locale]
src/components/widgets/LocaleSwitcher.astro
@@ -6,9 +6,13 @@ import {
   type Locale,
 } from "intlayer";
 
-import { SITE_CONFIG } from "@/config.ts";
 import { getPathWithoutLocale, getRelativeLocaleUrl } from "@/utils/url.ts";
 
+interface Props {
+  availableLocales: Locale[];
+}
+
+const { availableLocales } = Astro.props;
 const locale = Astro.currentLocale;
 const globalI18n = getIntlayer("global", locale);
 const currentPath = Astro.url.pathname;
@@ -22,7 +26,7 @@ const getLocaleUrl = (target: Locale) =>
   onchange="location.href = this.value"
 >
   {
-    SITE_CONFIG.locales.map((target) => (
+    availableLocales.map((target) => (
       <option
         value={getLocaleUrl(target)}
         selected={target === locale}
src/components/Header.astro
@@ -1,11 +1,16 @@
 ---
-import { getIntlayer } from "intlayer";
+import { getIntlayer, type Locale } from "intlayer";
 
 import { SITE_CONFIG } from "@/config.ts";
 import { getRelativeLocaleUrl, url } from "@/utils/url.ts";
 
 import LocaleSwitcher from "@/components/widgets/LocaleSwitcher.astro";
 
+interface Props {
+  availableLocales: Locale[];
+}
+
+const { availableLocales } = Astro.props;
 const locale = Astro.currentLocale!;
 const globalI18n = getIntlayer("global", locale);
 ---
@@ -18,7 +23,7 @@ const globalI18n = getIntlayer("global", locale);
       <li><a href={getRelativeLocaleUrl("/about", locale)}>{globalI18n.about}</a></li>
       <li><a href={url("/feed.atom")}>{globalI18n.feed}</a></li>
     </ul>
-    <LocaleSwitcher />
+    <LocaleSwitcher availableLocales={availableLocales} />
   </nav>
 </header>
 
src/layouts/BaseLayout.astro
@@ -15,6 +15,7 @@ interface Props {
   title?: string;
   author?: string;
   description?: string;
+  availableLocales?: Locale[];
   ogType?: "website" | "article";
 }
 
@@ -25,9 +26,20 @@ const {
   title = SITE_CONFIG.title,
   author = SITE_CONFIG.author,
   description = globalI18n.description,
+  availableLocales = SITE_CONFIG.locales,
   ogType = "website",
 } = Astro.props;
 
+const pageLocales = SITE_CONFIG.locales.filter((candidate) =>
+  availableLocales.includes(candidate)
+);
+
+if (!pageLocales.includes(DEFAULT_LOCALE)) {
+  throw new Error(
+    `Page ${Astro.url.pathname} must be built for the default locale "${DEFAULT_LOCALE}".`,
+  );
+}
+
 const getAbsoluteLocaleUrl = (
   path: string,
   locale: Locale,
@@ -56,7 +68,7 @@ const getAbsoluteLocaleUrl = (
     <meta name="author" content={author} />
 
     {
-      SITE_CONFIG.locales.map((target) => (
+      pageLocales.map((target) => (
         <link rel="alternate" hreflang={target}
           href={getAbsoluteLocaleUrl(getPathWithoutLocale(Astro.url.pathname), target)}
         >
@@ -78,7 +90,7 @@ const getAbsoluteLocaleUrl = (
     <Font cssVariable="--font-stix-two-math" />
   </head>
   <body>
-    <Header />
+    <Header availableLocales={pageLocales} />
     <main>
       <slot />
     </main>
src/pages/[locale]/posts/[slug].astro
@@ -16,6 +16,10 @@ export const getStaticPaths = (async () => {
   const postsLocaleMap = getPostsLocaleMap(posts);
 
   return Object.entries(postsLocaleMap).flatMap(([slug, localeMap]) => {
+    const availableLocales = SITE_CONFIG.locales.filter((locale) =>
+      localeMap[locale] !== undefined
+    );
+
     return SITE_CONFIG.locales.map((locale) => {
       const entry = localeMap[locale];
       if (entry === undefined) {
@@ -23,14 +27,14 @@ export const getStaticPaths = (async () => {
       } else {
         return {
           params: { locale, slug },
-          props: { entry },
+          props: { entry, availableLocales },
         };
       }
     }).filter((i) => i !== undefined);
   });
 }) satisfies GetStaticPaths;
 
-const { entry } = Astro.props;
+const { entry, availableLocales } = Astro.props;
 
 const data = entry.data;
 const { Content } = await render(entry);
@@ -39,6 +43,7 @@ const { Content } = await render(entry);
   title={data.title}
   author={data.author}
   description={data.desc}
+  availableLocales={availableLocales}
   ogType="article"
 >
   <article>