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

View file

@ -19,7 +19,9 @@ interface Props {
which automatically prefetches the linked page to speed up navigation. --> which automatically prefetches the linked page to speed up navigation. -->
<a <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" 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 data-astro-prefetch
> >
<!-- The container for the blog post's cover image. Uses astro:assets' Image for image source --> <!-- 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 <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" 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} {blogEntry.data.description}
</a> </a>
@ -57,7 +57,7 @@ interface Props {
<!-- Read More button which is a link to the blog post detailed page --> <!-- Read More button which is a link to the blog post detailed page -->
<div class="mt-5"> <div class="mt-5">
<PrimaryCTA <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" title="Read More"
data-astro-prefetch data-astro-prefetch
/> />

View file

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

View file

@ -14,7 +14,9 @@ interface Props {
<a <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" 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 data-astro-prefetch
> >
<div> <div>

View file

@ -18,7 +18,9 @@ const imageClass =
<!-- A clickable card that leads to the details of the product--> <!-- A clickable card that leads to the details of the product-->
<a <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 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" 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. --> <!-- The anchor tag is the main container for the product card. When clicked, this leads to the details of the product. -->
<a <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 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" 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 type { CollectionEntry } from "astro:content";
import { SITE } from "@data/constants"; 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() { export async function getStaticPaths() {
const blogPosts = await getCollection("blog", ({ id }) => { const blogPosts = await getCollection("blog", ({ id }) =>
return id.startsWith("en/"); id.startsWith("en/")
}); );
return blogPosts.map((post) => ({ return blogPosts.map((post) => {
params: { slug: post.slug }, const slugWithoutLang = post.slug.replace(/^en\//, ""); // Remove the "en/" prefix
return {
params: { slug: slugWithoutLang },
props: { post }, props: { post },
})); };
});
} }
// Get the current post's data // Get the current post's data
const { post } = Astro.props; const { post } = Astro.props;
// Get all blog posts
const blogPosts: CollectionEntry<"blog">[] = await getCollection( const blogPosts: CollectionEntry<"blog">[] = await getCollection(
"blog", "blog",
({ id }) => { ({ id }) => id.startsWith("en/")
return id.startsWith("en/");
}
); );
// Filter out the current post to get related posts // 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 class="flex items-center justify-between gap-x-2">
<div> <div>
<!--Post metadata and author info--> <!--Post metadata and author info-->
<span <span class="font-bold text-neutral-700 dark:text-neutral-300">
class="font-bold text-neutral-700 dark:text-neutral-300"
>
{post.data.author} {post.data.author}
</span> </span>
<ul class="text-xs text-neutral-500"> <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 type { CollectionEntry } from "astro:content";
import { SITE } from "@data/constants"; 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() { export async function getStaticPaths() {
const blogPosts = await getCollection("blog", ({ id }) => { const blogPosts = await getCollection("blog", ({ id }) =>
return id.startsWith("fr/"); id.startsWith("fr/")
}); );
return blogPosts.map((post) => ({ return blogPosts.map((post) => {
params: { slug: post.slug }, const slugWithoutLang = post.slug.replace(/^fr\//, ""); // Remove the "fr/" prefix
return {
params: { lang: "fr", slug: slugWithoutLang },
props: { post }, props: { post },
})); };
});
} }
// Get the current post's data
const { post } = Astro.props; const { post } = Astro.props;
// Get all blog posts // Fetch related posts
const blogPosts: CollectionEntry<"blog">[] = await getCollection("blog", const blogPosts: CollectionEntry<"blog">[] = await getCollection(
({ id }) => { "blog",
return id.startsWith("fr/"); ({ 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( const relatedPosts: CollectionEntry<"blog">[] = blogPosts.filter(
(blogEntry) => blogEntry.slug !== post.slug (blogEntry) => blogEntry.slug !== post.slug
); );
const pageTitle: string = `${post.data.title} | ${SITE.title}`; const pageTitle: string = `${post.data.title} | ${SITE.title}`;
--- ---
<MainLayout <MainLayout title={pageTitle}>
title={pageTitle}
>
<section class="mx-auto max-w-3xl px-4 pb-12 pt-6 sm:px-6 lg:px-8 lg:pt-10"> <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="max-w-2xl">
<div class="mb-6 flex items-center justify-between"> <div class="mb-6 flex items-center justify-between">
@ -159,7 +153,11 @@ const pageTitle: string = `${post.data.title} | ${SITE.title}`;
</div> </div>
<div class="grid grid-cols-2 gap-6"> <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> </div>
</section> </section>
</MainLayout> </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"> <div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{ {
insightPosts.map((insightEntry) => ( insightPosts.map((insightEntry) => (
<CardInsight insightEntry={insightEntry} /> <CardInsight insightEntry={insightEntry} insightLocale="fr" />
)) ))
} }
</div> </div>

View file

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