Skip to content
Closed
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
61 changes: 60 additions & 1 deletion src/config/redirect.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { languagesArray } from '../i18n/locales';

// Redirect .html and non-.html blog post URLs to the new format. This will ensure that any existing links to blog posts will continue to work and redirect users to the correct location on the new site.
const blog = {
'/2024/07/16/welcome-post.html': '/en/blog/2024-07-16-welcome-post',
Expand Down Expand Up @@ -64,6 +66,63 @@ const pages = {
'/en/changelog/4x': 'https://github.com/expressjs/express/releases',
};

const redirects = { ...blog, ...api_v2, ...pages };
const api = {
'/5x/api.html': '/en/5x/api',
'/4x/api.html': '/en/4x/api',
'/3x/api.html': '/en/3x/api',
'/5x/api': '/en/5x/api',
'/4x/api': '/en/4x/api',
'/3x/api': '/en/3x/api',
};

const verDocsArr = [
'/advanced/developing-template-engines',
'/guide/overriding-express-api',
'/guide/behind-proxies',
'/guide/debugging',
'/guide/error-handling',
'/guide/using-template-engines',
'/guide/using-middleware',
'/guide/writing-middleware',
'/guide/routing',
'/starter/faq',
'/starter/examples',
'/starter/static-files',
'/starter/basic-routing',
'/starter/installing',
'/starter/generator',
'/starter/hello-world',
];

// const verDocs = {
// '/en/advanced/developing-template-engines': '/en/5x/advanced/developing-template-engines',
// '/en/guide/overriding-express-api': '/en/5x/guide/overriding-express-api',
// '/en/guide/behind-proxies': '/en/5x/guide/behind-proxies',
// '/en/guide/debugging': '/en/5x/guide/debugging',
// '/en/guide/error-handling': '/en/5x/guide/error-handling',
// '/en/guide/using-template-engines': '/en/5x/guide/using-template-engines',
// '/en/guide/using-middleware': '/en/5x/guide/using-middleware',
// '/en/guide/writing-middleware': '/en/5x/guide/writing-middleware',
// '/en/guide/routing': '/en/5x/guide/routing',
// '/en/starter/faq': '/en/5x/starter/faq',
// '/en/starter/examples': '/en/5x/starter/examples',
// '/en/starter/static-files': '/en/5x/starter/static-files',
// '/en/starter/basic-routing': '/en/5x/starter/basic-routing',
// '/en/starter/installing': '/en/5x/starter/installing',
// '/en/starter/generator': '/en/5x/starter/generator',
// '/en/starter/hello-world': '/en/5x/starter/hello-world',
// };

const generateVerDocs = () => {
return Object.fromEntries(
languagesArray.flatMap((lang) =>
verDocsArr.map((route) => [`/${lang.code}${route}`, `/${lang.code}/5x${route}`])
)
);
};

const verDocs = generateVerDocs();

const redirects = { ...blog, ...api_v2, ...pages, ...api, ...verDocs };

export default redirects;
6 changes: 3 additions & 3 deletions src/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ const blogCollection = defineCollection({

const npmCollection = defineCollection({
loader: async () => {
const res = await fetch('https://registry.npmjs.org/express/latest');
const { version } = await res.json();
return [{ id: 'express', version }];
// const res = await fetch('https://registry.npmjs.org/express/latest');
// const { version } = await res.json();
return [{ id: 'express', version: '5x' }];
},
schema: z.object({ version: z.string() }),
});
Expand Down
2 changes: 1 addition & 1 deletion src/content/api/3x/api.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Express 3.x - Referencia de API
title: Express 3.x
description: Access the API reference for Express.js version 3.x, noting that this version is end-of-life and no longer maintained - includes details on modules and methods.
---

Expand Down
173 changes: 29 additions & 144 deletions src/pages/[lang]/[...slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,182 +10,67 @@ import { resourcesMenu } from '@/config/menu/resources';
import type { LanguageCode } from '@i18n/locales';

export async function getStaticPaths() {
const DEFAULT_VERSION = '5x';
const VERSION_PREFIXES = ['5x', '4x', '3x'];
const DEFAULT_LANG = 'en';

const pages = await getCollection('docs');
const langArr = Object.keys(languages);
const verDocs = await getCollection('docs');
const api = await getCollection('api');
const contentPages = await getCollection('pages');
const nonVerDocs = await getCollection('pages');

type AnyPage = (typeof pages)[0] | (typeof api)[0] | (typeof contentPages)[0];
type AnyPage = (typeof verDocs)[0] | (typeof api)[0] | (typeof nonVerDocs)[0];

const paths: Array<{
params: { lang: string; slug: string };
props: { page: AnyPage; version?: string };
}> = [];

// Helper to create a map of existing content for fallback logic
const existingContent = new Map<string, AnyPage>();
pages.forEach((page) => {
verDocs.forEach((page) => {
const [lang, ...slugParts] = page.id.split('/');
const fullSlug = slugParts.join('/');
existingContent.set(`${lang}/${fullSlug}`, page);
});

api.forEach((page) => {
existingContent.set(`en/${page.id}`, page);
});

pages.forEach((page) => {
const [lang, ...slugParts] = page.id.split('/');
const fullSlug = slugParts.join('/');

const isVersioned = VERSION_PREFIXES.some((v) => fullSlug.startsWith(`${v}/`));

// TODO: wait for crowdin setup, after this write logic to use english page
// if translated page is not available for versioned docs
paths.push({
params: { lang, slug: fullSlug },
props: { page, version: isVersioned ? slugParts[0] : DEFAULT_VERSION },
props: { page, version: slugParts[0] },
});
});

contentPages.forEach((page) => {
nonVerDocs.forEach((page) => {
const [lang, ...slugParts] = page.id.split('/');
const fullSlug = slugParts.join('/');

// TODO: wait for crowdin setup, after this write logic to use english page
// if translated page is not available for non versioned docs
paths.push({
params: { lang, slug: fullSlug },
props: { page },
});
});

for (const lang of Object.keys(languages)) {
api.forEach((page) => {
const slugParts = page.id.split('/');
const version = slugParts[0];
const slug = page.id;

paths.push({
params: { lang, slug },
props: { page, version },
});
});
}

const defaultVersionApiPages = api.filter((page) => page.id.startsWith(`${DEFAULT_VERSION}/`));

for (const lang of Object.keys(languages)) {
defaultVersionApiPages.forEach((page) => {
const [, ...restSlugParts] = page.id.split('/');
const nonVersionedSlug = restSlugParts.join('/');

const exists = paths.some(
(p) => p.params.lang === lang && p.params.slug === nonVersionedSlug
);

if (!exists && nonVersionedSlug) {
api.forEach((page) => {
const slugParts = page.id.split('/');
const version = slugParts[0];
const slug = page.id;

if (slugParts.length === 2) {
// only add translations pages for '/en/5x/api', '/en/4x/api', '/en/3x/api'
// We are not translating apis this is for lagacy urls support
//! Can we use redirects for 27 pages?
langArr.forEach((lang) => {
paths.push({
params: { lang, slug: nonVersionedSlug },
props: { page, version: DEFAULT_VERSION },
params: { lang, slug },
props: { page, version },
});
}
});
}

const defaultVersionPages = pages.filter((page) => {
const [, ...slugParts] = page.id.split('/');
return slugParts[0] === DEFAULT_VERSION;
});

defaultVersionPages.forEach((page) => {
const [lang, , ...restSlugParts] = page.id.split('/');
const nonVersionedSlug = restSlugParts.join('/');

const exists = paths.some((p) => p.params.lang === lang && p.params.slug === nonVersionedSlug);

if (!exists && nonVersionedSlug) {
});
} else {
// add properties/methods pages only in english
// TODO: disable translations btn on properties/methods pages
paths.push({
params: { lang, slug: nonVersionedSlug },
props: { page, version: DEFAULT_VERSION },
params: { lang: 'en', slug },
props: { page, version },
});
}
});

// Fallback for non-versioned default-version slugs: use English content for missing languages
const englishDefaultVersionPages = defaultVersionPages.filter((p) =>
p.id.startsWith(`${DEFAULT_LANG}/`)
);

for (const lang of Object.keys(languages)) {
if (lang === DEFAULT_LANG) continue;

englishDefaultVersionPages.forEach((page) => {
const [, , ...restSlugParts] = page.id.split('/');
const nonVersionedSlug = restSlugParts.join('/');

const exists = paths.some(
(p) => p.params.lang === lang && p.params.slug === nonVersionedSlug
);

if (!exists && nonVersionedSlug) {
paths.push({
params: { lang, slug: nonVersionedSlug },
props: { page, version: DEFAULT_VERSION },
});
}
});
}

// Fallback: Create paths for non-English languages using English pages content
for (const lang of Object.keys(languages)) {
if (lang === DEFAULT_LANG) continue;

const englishContentPages = contentPages.filter((p) => p.id.startsWith(`${DEFAULT_LANG}/`));

englishContentPages.forEach((page) => {
const [, ...slugParts] = page.id.split('/');
const fullSlug = slugParts.join('/');

const langSpecificExists = paths.some(
(p) => p.params.lang === lang && p.params.slug === fullSlug
);

if (!langSpecificExists) {
paths.push({
params: { lang, slug: fullSlug },
props: { page },
});
}
});
}

// Fallback: Create paths for non-English languages using English docs content
for (const lang of Object.keys(languages)) {
if (lang === DEFAULT_LANG) continue; // Skip English, already added

const englishPages = pages.filter((p) => p.id.startsWith(`${DEFAULT_LANG}/`));

englishPages.forEach((page) => {
const [, ...slugParts] = page.id.split('/');
const fullSlug = slugParts.join('/');

const langSpecificExists = paths.some(
(p) => p.params.lang === lang && p.params.slug === fullSlug
);

// If no language-specific version exists, add fallback to English
if (!langSpecificExists) {
paths.push({
params: { lang, slug: fullSlug },
props: {
page,
version: slugParts[0] === DEFAULT_VERSION ? DEFAULT_VERSION : slugParts[0],
},
});
}
});
}

return paths;
}

Expand Down
2 changes: 1 addition & 1 deletion src/pages/[lang]/blog/[slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {

export async function getStaticPaths() {
const posts = await getCollection('blog');

//! Should we remove translations for blog posts?
return Object.keys(languages).flatMap((lang) =>
posts.map((post) => {
const filename = post.id.split('/').pop()!;
Expand Down
Loading