Refactor language switching logic for blog posts, insights and products

- Ensure URLs maintain correct structure without duplicating language prefixes
- Update script to dynamically reconstruct URLs when switching languages
- Improve logic for adding and removing language prefixes based on current path
This commit is contained in:
Emil Gulamov 2024-09-08 14:53:53 +04:00
parent dc0e780116
commit d1c619bb9e
14 changed files with 104 additions and 81 deletions

View file

@ -74,20 +74,18 @@ import Icon from "./icons/Icon.astro";
// Disable the selection of the same language
if (lang === url.pathname.split("/")[1]) return;
if (
url.pathname.includes("/post") ||
url.pathname.includes("/insight") ||
url.pathname.includes("/item")
) {
if (url.pathname.includes("en")) {
pathParts.unshift(lang);
pathParts.splice(2, 0, lang);
} else {
pathParts.unshift(lang);
pathParts.splice(2, 0, "en");
// Determine if the current URL already has a language prefix
const currentLang = languages.includes(pathParts[0] as TLanguage) ? pathParts[0] : "en";
// Remove current language prefix from pathParts
if (languages.includes(pathParts[0] as TLanguage)) {
pathParts.shift();
}
} else {
// Determine if we are switching to a different language
if (lang !== currentLang) {
if (lang !== "en") {
// Add the new language prefix for non-English
pathParts.unshift(lang);
}
}

View file

@ -19,7 +19,9 @@ interface Props {
which automatically prefetches the linked page to speed up navigation. -->
<a
class="group relative block rounded-xl outline-none ring-zinc-500 transition duration-500 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none"
href={blogLocale !== "" ? `/${blogLocale}/blog/${blogEntry.slug}/` : `/blog/${blogEntry.slug}/`}
href={blogLocale && blogLocale !== "en"
? `/${blogLocale}/blog/${blogEntry.slug.replace(/^fr\//, "")}/`
: `/blog/${blogEntry.slug.replace(/^en\//, "")}/`}
data-astro-prefetch
>
<!-- The container for the blog post's cover image. Uses astro:assets' Image for image source -->

View file

@ -36,7 +36,7 @@ interface Props {
>
<a
class="outline-none ring-zinc-500 transition duration-300 hover:text-orange-400 focus-visible:ring dark:text-neutral-300 dark:ring-zinc-200 dark:hover:text-neutral-50 dark:focus:outline-none"
href={recentBlogLocale !== "" ? `/${recentBlogLocale}/blog/${blogEntry.slug}/` : `/blog/${blogEntry.slug}/`}
href={recentBlogLocale && recentBlogLocale !== "en" ? `/${recentBlogLocale}/blog/${blogEntry.slug.replace(/^fr\//, '')}/` : `/blog/${blogEntry.slug.replace(/^en\//, '')}/`}
>
{blogEntry.data.description}
</a>
@ -57,7 +57,7 @@ interface Props {
<!-- Read More button which is a link to the blog post detailed page -->
<div class="mt-5">
<PrimaryCTA
url={recentBlogLocale !== "" ? `/${recentBlogLocale}/blog/${blogEntry.slug}/` : `/blog/${blogEntry.slug}/`}
url={recentBlogLocale && recentBlogLocale !== "en" ? `/${recentBlogLocale}/blog/${blogEntry.slug.replace(/^fr\//, '')}/` : `/blog/${blogEntry.slug.replace(/^en\//, '')}/`}
title="Read More"
data-astro-prefetch
/>

View file

@ -6,11 +6,13 @@ import type { CollectionEntry } from "astro:content";
const {
insightEntry,
insightLocale,
label = Astro.currentLocale === "fr" ? "Lire plus" : "Read more",
} = Astro.props;
interface Props {
insightEntry: CollectionEntry<"insights">;
insightLocale?: string;
label?: string;
}
---
@ -18,7 +20,9 @@ interface Props {
<!-- The anchor tag is the root container for the "Insight" card. It links to the insight detail page. -->
<a
class="group rounded-xl outline-none ring-zinc-500 transition duration-300 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none"
href={`/insights/${insightEntry.slug}/`}
href={insightLocale && insightLocale !== "en"
? `/${insightLocale}/insights/${insightEntry.slug.replace(/^fr\//, "")}/`
: `/insights/${insightEntry.slug.replace(/^en\//, "")}/`}
>
<!-- This is the container for the insight's cover image. -->
<div class="relative overflow-hidden rounded-xl pt-[50%] sm:pt-[70%]">

View file

@ -14,7 +14,9 @@ interface Props {
<a
class="group block rounded-xl outline-none ring-zinc-500 transition duration-300 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none"
href={recentBlogLocale !== "" ? `/${recentBlogLocale}/blog/${blogEntry.slug}/` : `/blog/${blogEntry.slug}/`}
href={recentBlogLocale && recentBlogLocale !== "en"
? `/${recentBlogLocale}/blog/${blogEntry.slug.replace(/^fr\//, "")}/`
: `/blog/${blogEntry.slug.replace(/^en\//, "")}/`}
data-astro-prefetch
>
<div>

View file

@ -18,7 +18,9 @@ const imageClass =
<!-- A clickable card that leads to the details of the product-->
<a
href={productLocale !== "" ? `/${productLocale}/products/${product.slug}/` : `/products/${product.slug}/`}
href={productLocale && productLocale !== "en"
? `/${productLocale}/products/${product.slug.replace(/^fr\//, "")}/`
: `/products/${product.slug.replace(/^en\//, "")}/`}
data-astro-prefetch
class="group relative flex h-48 items-end overflow-hidden rounded-xl shadow-lg outline-none ring-zinc-500 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none md:h-80"
>

View file

@ -18,7 +18,9 @@ const imageClass =
<!-- The anchor tag is the main container for the product card. When clicked, this leads to the details of the product. -->
<a
href={productLocale !== "" ? `/${productLocale}/products/${product.slug}/` : `/products/${product.slug}/`}
href={productLocale && productLocale !== "en"
? `/${productLocale}/products/${product.slug.replace(/^fr\//, "")}/`
: `/products/${product.slug.replace(/^en\//, "")}/`}
data-astro-prefetch
class="group relative flex h-48 items-end overflow-hidden rounded-lg shadow-xl outline-none ring-zinc-500 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none md:col-span-2 md:h-80"
>

View file

@ -12,25 +12,26 @@ import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import { SITE } from "@data/constants";
// getStaticPaths is used to pre-render all routes based on the blog posts
// Update getStaticPaths for English posts
export async function getStaticPaths() {
const blogPosts = await getCollection("blog", ({ id }) => {
return id.startsWith("en/");
});
return blogPosts.map((post) => ({
params: { slug: post.slug },
const blogPosts = await getCollection("blog", ({ id }) =>
id.startsWith("en/")
);
return blogPosts.map((post) => {
const slugWithoutLang = post.slug.replace(/^en\//, ""); // Remove the "en/" prefix
return {
params: { slug: slugWithoutLang },
props: { post },
}));
};
});
}
// Get the current post's data
const { post } = Astro.props;
// Get all blog posts
const blogPosts: CollectionEntry<"blog">[] = await getCollection(
"blog",
({ id }) => {
return id.startsWith("en/");
}
({ id }) => id.startsWith("en/")
);
// Filter out the current post to get related posts
@ -54,9 +55,7 @@ const pageTitle: string = `${post.data.title} | ${SITE.title}`;
<div class="flex items-center justify-between gap-x-2">
<div>
<!--Post metadata and author info-->
<span
class="font-bold text-neutral-700 dark:text-neutral-300"
>
<span class="font-bold text-neutral-700 dark:text-neutral-300">
{post.data.author}
</span>
<ul class="text-xs text-neutral-500">

View file

@ -12,41 +12,35 @@ import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import { SITE } from "@data/constants";
// getStaticPaths is used to pre-render all routes based on the blog posts
// Update getStaticPaths for French posts
export async function getStaticPaths() {
const blogPosts = await getCollection("blog", ({ id }) => {
return id.startsWith("fr/");
});
return blogPosts.map((post) => ({
params: { slug: post.slug },
const blogPosts = await getCollection("blog", ({ id }) =>
id.startsWith("fr/")
);
return blogPosts.map((post) => {
const slugWithoutLang = post.slug.replace(/^fr\//, ""); // Remove the "fr/" prefix
return {
params: { lang: "fr", slug: slugWithoutLang },
props: { post },
}));
};
});
}
// Get the current post's data
const { post } = Astro.props;
// Get all blog posts
const blogPosts: CollectionEntry<"blog">[] = await getCollection("blog",
({ id }) => {
return id.startsWith("fr/");
}
// Fetch related posts
const blogPosts: CollectionEntry<"blog">[] = await getCollection(
"blog",
({ id }) => id.startsWith("fr/")
);
// Filter out the current post to get related posts
// Note: This is a very basic way of choosing related posts, just for the purpose of the example.
// In a production site, you might want to implement a more robust algorithm, choosing related posts based on tags, categories, dates, authors, or keywords.
// See example: https://blog.codybrunner.com/2024/adding-related-articles-with-astro-content-collections/
const relatedPosts: CollectionEntry<"blog">[] = blogPosts.filter(
(blogEntry) => blogEntry.slug !== post.slug
);
const pageTitle: string = `${post.data.title} | ${SITE.title}`;
---
<MainLayout
title={pageTitle}
>
<MainLayout title={pageTitle}>
<section class="mx-auto max-w-3xl px-4 pb-12 pt-6 sm:px-6 lg:px-8 lg:pt-10">
<div class="max-w-2xl">
<div class="mb-6 flex items-center justify-between">
@ -159,7 +153,11 @@ const pageTitle: string = `${post.data.title} | ${SITE.title}`;
</div>
<div class="grid grid-cols-2 gap-6">
{relatedPosts.map((entry) => <CardRelated blogEntry={entry} recentBlogLocale="fr" /> )}
{
relatedPosts.map((entry) => (
<CardRelated blogEntry={entry} recentBlogLocale="fr" />
))
}
</div>
</section>
</MainLayout>

View file

@ -108,7 +108,7 @@ const pageTitle: string = `Blog | ${SITE.title}`;
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{
insightPosts.map((insightEntry) => (
<CardInsight insightEntry={insightEntry} />
<CardInsight insightEntry={insightEntry} insightLocale="fr" />
))
}
</div>

View file

@ -7,11 +7,14 @@ import { getCollection } from "astro:content";
// Use `getStaticPaths` to generate static routes for generated pages on build
export async function getStaticPaths() {
const insightPosts = await getCollection("insights");
return insightPosts.map((post) => ({
params: { slug: post.slug },
const insightPosts = await getCollection("insights", ({ id }) => id.startsWith("fr/"));
return insightPosts.map((post) => {
const slugWithoutLang = post.slug.replace(/^fr\//, ''); // Remove the "fr/" prefix
return {
params: { lang: 'fr', slug: slugWithoutLang },
props: { post },
}));
};
});
}
// Get the props for this page that define a specific insight post

View file

@ -14,14 +14,18 @@ declare global {
}
}
// This gets the static paths for all the unique products
export async function getStaticPaths() {
const productEntries = await getCollection("products", ({ id }) => {
return id.startsWith("fr/");
});
return productEntries.map((product) => ({
params: { slug: product.slug },
const productEntries = await getCollection("products", ({ id }) =>
id.startsWith("fr/")
);
return productEntries.map((product) => {
const slugWithoutLang = product.slug.replace(/^fr\//, ""); // Remove the "fr/" prefix
return {
params: { lang: "fr", slug: slugWithoutLang },
props: { product },
}));
};
});
}
const { product } = Astro.props;

View file

@ -7,11 +7,16 @@ import { getCollection } from "astro:content";
// Use `getStaticPaths` to generate static routes for generated pages on build
export async function getStaticPaths() {
const insightPosts = await getCollection("insights");
return insightPosts.map((post) => ({
params: { slug: post.slug },
const insightPosts = await getCollection("insights", ({ id }) =>
id.startsWith("en/")
);
return insightPosts.map((post) => {
const slugWithoutLang = post.slug.replace(/^en\//, ""); // Remove the "fr/" prefix
return {
params: { slug: slugWithoutLang },
props: { post },
}));
};
});
}
// Get the props for this page that define a specific insight post

View file

@ -15,14 +15,18 @@ declare global {
}
// This gets the static paths for all the unique products
export async function getStaticPaths() {
const productEntries = await getCollection("products", ({ id }) => {
return id.startsWith("en/");
});
return productEntries.map((product) => ({
params: { slug: product.slug },
const productEntries = await getCollection("products", ({ id }) =>
id.startsWith("en/")
);
return productEntries.map((product) => {
const slugWithoutLang = product.slug.replace(/^en\//, ""); // Remove the "en/" prefix
return {
params: { slug: slugWithoutLang },
props: { product },
}));
};
});
}
const { product } = Astro.props;