Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,15 @@ export default defineConfig({
icon(),
react(),
sitemap({
// Only the canonical /<locale>/… URLs belong in the sitemap. The catch-all
// Only the canonical URLs belong in the sitemap. The catch-all
// `[...path].astro` also emits `noindex` redirect stubs: language-less ones
// (e.g. `/guide/x`, `/` → `/en/`) and per-locale ones for moved pages
// (e.g. `/guide/x`) and per-locale ones for moved pages
// (e.g. `/en/resources/middleware/csurf`). Exclude both.
// Keep this locale list in sync with `i18n.locales` below.
filter: (page) => {
const { pathname } = new URL(page);
// The homepage is served at `/` and is the canonical for `/en/`.
if (pathname === '/') return true;
if (!/^\/(de|en|es|fr|it|ja|ko|pt-br|zh-cn|zh-tw)(\/|$)/.test(pathname)) return false;
const unlocalized = pathname.replace(/^\/[a-z-]+/, '').replace(/\/$/, '');
return !movedPagePaths.has(unlocalized);
Expand Down
29 changes: 29 additions & 0 deletions src/components/HomePage.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
import { Hero, Features, AnnouncementBar } from '@components/patterns';
import { Card, Col } from '@components/primitives';
import Layout from '@layouts/Layout.astro';
import { getHomePath, getLangFromUrl, useTranslations } from '@i18n/utils';

const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
const canonicalPathname = getHomePath(lang);
---

<Layout canonicalPathname={canonicalPathname}>
<AnnouncementBar />
<Hero />
<Features title={t('features.title')}>
<Col xs={12} md={6} lg={6} class="features__card-col">
<Card title={t('features.webapplication.title')} body={t('features.webapplication.body')} />
</Col>
<Col xs={12} md={6} lg={6} class="features__card-col">
<Card title={t('features.api.title')} body={t('features.api.body')} />
</Col>
<Col xs={12} md={6} lg={6} class="features__card-col">
<Card title={t('features.performance.title')} body={t('features.performance.body')} />
</Col>
<Col xs={12} md={6} lg={6} class="features__card-col">
<Card title={t('features.middleware.title')} body={t('features.middleware.body')} />
</Col>
</Features>
</Layout>
5 changes: 3 additions & 2 deletions src/components/patterns/Breadcrumbs/Breadcrumbs.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { Body } from '@components/primitives';
import { Icon } from 'astro-icon/components';
import type { BreadcrumbItem } from '@utils/content';
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
import { getHomePath, getLangFromUrl, useTranslations } from '@/i18n/utils';
import './Breadcrumbs.css';

interface Props {
Expand All @@ -16,6 +16,7 @@ interface Props {
const { items } = Astro.props;
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
const homeHref = getHomePath(lang);
---

<nav aria-label={t('nav.breadcrumb')}>
Expand All @@ -24,7 +25,7 @@ const t = useTranslations(lang);
<Body
vMargin={false}
as="a"
href={`/${lang}`}
href={homeHref}
class="breadcrumb-link"
aria-label={t('nav.home')}
>
Expand Down
5 changes: 3 additions & 2 deletions src/components/patterns/Header/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import { Icon } from 'astro-icon/components';
import { Flex } from '@/components/primitives';
import { ThemeSwitcher, SearchBox, LanguageSelect } from '@components/patterns';
import { languagesArray } from '@/i18n/locales';
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
import { getHomePath, getLangFromUrl, useTranslations } from '@/i18n/utils';

const currentLang = getLangFromUrl(Astro.url);
const t = useTranslations(currentLang);
const homeHref = getHomePath(currentLang);
---

<header class="header">
Expand All @@ -30,7 +31,7 @@ const t = useTranslations(currentLang);
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
</button>
<a href={`/${currentLang}/`} class="logo-link" aria-label={t('nav.home')}>
<a href={homeHref} class="logo-link" aria-label={t('nav.home')}>
<Image
src="/images/logos/express-kawaii.webp"
alt="Express.js kawaii logo"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ const options = languages.map((lang) => ({
const currentPath = window.location.pathname;
const newPath = replaceLanguageInPath(currentPath, code);

window.location.href = newPath;
// The English homepage is canonically served at `/`, not `/en/`.
window.location.href = newPath === '/en/' ? '/' : newPath;
}) as EventListener);
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/patterns/Sidebar/SidebarMenu.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Body } from '@/components/primitives';
import { Image } from 'astro:assets';
import { Icon } from 'astro-icon/components';
import { VersionSwitcher } from '@/components/patterns';
import { useTranslations } from '@/i18n/utils';
import { getHomePath, useTranslations } from '@/i18n/utils';
import { DEFAULT_VERSION } from '@/config/versions';
import SidebarItemsList from './SidebarItemsList.astro';
import {
Expand Down Expand Up @@ -78,7 +78,7 @@ const commonItemsListProps = {
<nav class="sidebar-nav" aria-label={t('nav.mainNavigation')}>
<ul class="sidebar-nav-list">
<li class="sidebar-logo-item">
<a href={`/${lang}/`} class="logo-link sidebar-nav-item" aria-label={t('nav.home')}>
<a href={getHomePath(lang)} class="logo-link sidebar-nav-item" aria-label={t('nav.home')}>
<Image
src="/images/logos/express-kawaii.webp"
alt="Express.js kawaii logo"
Expand Down
7 changes: 7 additions & 0 deletions src/i18n/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export function useTranslations(lang: keyof typeof ui) {
return getNestedValue(ui[lang], key) ?? getNestedValue(ui[defaultLang], key) ?? key;
};
}
/**
* Homepage path for a language. English is served at the root.
*/
export function getHomePath(lang: string): string {
return lang === defaultLang ? '/' : `/${lang}/`;
}

/**
* Create a regex pattern to match language prefixes in URLs
*/
Expand Down
27 changes: 15 additions & 12 deletions src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,21 @@ const lang = getLangFromUrl(Astro.url);

{
!isBlogOrApi &&
['x-default', ...languageCodes].map((altLang) => (
<link
rel="alternate"
hreflang={altLang}
href={
new URL(
replaceLanguageInPath(canonicalPath, altLang === 'x-default' ? 'en' : altLang),
Astro.site || Astro.url.origin
)
}
/>
))
['x-default', ...languageCodes].map((altLang) => {
let altPath = replaceLanguageInPath(
canonicalPath,
altLang === 'x-default' ? 'en' : altLang
);
// The English homepage is canonically served at `/`, not `/en/`.
if (altPath === '/en/') altPath = '/';
return (
<link
rel="alternate"
hreflang={altLang}
href={new URL(altPath, Astro.site || Astro.url.origin)}
/>
);
})
}

<title>{pageTitle}</title>
Expand Down
2 changes: 2 additions & 0 deletions src/pages/[...path].astro
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ if (fallback) {
} else {
target = `/en/${slug}`;
}
// The English homepage is canonically served at `/`, not `/en/`.
if (target === '/en/') target = '/';
const canonical = new URL(target, Astro.site).href;
---

Expand Down
27 changes: 2 additions & 25 deletions src/pages/[lang]/index.astro
Original file line number Diff line number Diff line change
@@ -1,35 +1,12 @@
---
import { Hero, Features, AnnouncementBar } from '@components/patterns';
import { Card, Col } from '@components/primitives';
import Layout from '@layouts/Layout.astro';
import HomePage from '@components/HomePage.astro';
import { languageCodes } from '@i18n/locales';
import { getLangFromUrl, useTranslations } from '@i18n/utils';

export function getStaticPaths() {
return languageCodes.map((lang) => ({
params: { lang },
}));
}

const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
---

<Layout>
<AnnouncementBar />
<Hero />
<Features title={t('features.title')}>
<Col xs={12} md={6} lg={6} class="features__card-col">
<Card title={t('features.webapplication.title')} body={t('features.webapplication.body')} />
</Col>
<Col xs={12} md={6} lg={6} class="features__card-col">
<Card title={t('features.api.title')} body={t('features.api.body')} />
</Col>
<Col xs={12} md={6} lg={6} class="features__card-col">
<Card title={t('features.performance.title')} body={t('features.performance.body')} />
</Col>
<Col xs={12} md={6} lg={6} class="features__card-col">
<Card title={t('features.middleware.title')} body={t('features.middleware.body')} />
</Col>
</Features>
</Layout>
<HomePage />
6 changes: 5 additions & 1 deletion src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
<meta http-equiv="refresh" content="0;url=/en/" />
---
import HomePage from '@components/HomePage.astro';
---

<HomePage />
Loading