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:
parent
dc0e780116
commit
d1c619bb9e
14 changed files with 104 additions and 81 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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%]">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Reference in a new issue