diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1245e5..3e4d7a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
### Added
- **Hugging Face Icon Asset**: Download official Hugging Face SVG logo to `src/assets/images/hugging-face.svg`; replace inline placeholder SVGs in `ProjectCard.astro` and `[slug].astro` with `
`
+- **Blog Post Language Variants**: Posts can declare `lang` and `translationId` frontmatter to link zh/en versions — the site language toggle switches between variants on post pages (`data-translation-url` navigation in `BasicScripts.astro`), list/grid cards render both variants and swap via `.i18n-zh`/`.i18n-en` classes, and translated variants are excluded from pagination, RSS, related and adjacent posts (`fetchPosts` vs `fetchAllPosts` in `blog.ts`). First bilingual post: Astron SkillHub joining AAIF as Associate Member (news, zh + en)
### Fixed
diff --git a/src/assets/images/aaif-skillhub.png b/src/assets/images/aaif-skillhub.png
index 4866dbd..a0e82d8 100644
Binary files a/src/assets/images/aaif-skillhub.png and b/src/assets/images/aaif-skillhub.png differ
diff --git a/src/components/blog/Grid.astro b/src/components/blog/Grid.astro
index 1b62be4..ccdac2d 100644
--- a/src/components/blog/Grid.astro
+++ b/src/components/blog/Grid.astro
@@ -10,5 +10,20 @@ const { posts } = Astro.props;
---
- {posts.map((post) =>
)}
+ {
+ posts.map((post) =>
+ post.translation ? (
+
+ ) : (
+
+ )
+ )
+ }
diff --git a/src/components/blog/List.astro b/src/components/blog/List.astro
index 6a80ae3..842dcca 100644
--- a/src/components/blog/List.astro
+++ b/src/components/blog/List.astro
@@ -13,7 +13,18 @@ const { posts } = Astro.props;
{
posts.map((post) => (
-
+ {post.translation ? (
+ <>
+
+
+
+
+
+
+ >
+ ) : (
+
+ )}
))
}
diff --git a/src/components/blog/SinglePost.astro b/src/components/blog/SinglePost.astro
index 906c427..6c03985 100644
--- a/src/components/blog/SinglePost.astro
+++ b/src/components/blog/SinglePost.astro
@@ -17,9 +17,17 @@ export interface Props {
}
const { post, url } = Astro.props;
+
+// Expose the language variant to the global language toggle in BasicScripts
+const translationAttrs = post.translation
+ ? {
+ 'data-post-lang': post.lang,
+ 'data-translation-url': getPermalink(post.translation.permalink, 'post'),
+ }
+ : {};
---
-
+
): Promise =
tags: rawTags = [],
category: rawCategory,
author,
+ lang = 'zh',
+ translationId,
draft = false,
metadata = {},
} = data;
@@ -89,6 +91,9 @@ const getNormalizedPost = async (post: CollectionEntry<'post'>): Promise =
tags: tags,
author: author,
+ lang: lang,
+ translationId: translationId,
+
draft: draft,
metadata,
@@ -110,9 +115,17 @@ const load = async function (): Promise> {
.sort((a, b) => b.publishDate.valueOf() - a.publishDate.valueOf())
.filter((post) => !post.draft);
+ // Link language variants declared via `translationId` frontmatter
+ results.forEach((post) => {
+ if (post.translationId) {
+ post.translation = results.find((p) => p.id === post.translationId || p.slug === post.translationId);
+ }
+ });
+
return results;
};
+let _allPosts: Array;
let _posts: Array;
/** */
@@ -130,10 +143,19 @@ export const blogTagRobots = APP_BLOG.tag.robots;
export const blogPostsPerPage = APP_BLOG?.postsPerPage;
-/** */
+/** All posts, including non-default-language variants (used to build post pages). */
+export const fetchAllPosts = async (): Promise> => {
+ if (!_allPosts) {
+ _allPosts = await load();
+ }
+
+ return _allPosts;
+};
+
+/** Posts in the default language; translated variants are reachable via the language toggle. */
export const fetchPosts = async (): Promise> => {
if (!_posts) {
- _posts = await load();
+ _posts = (await fetchAllPosts()).filter((post) => post.lang === 'zh');
}
return _posts;
@@ -187,7 +209,7 @@ export const getStaticPathsBlogList = async ({ paginate }: { paginate: PaginateF
/** */
export const getStaticPathsBlogPost = async () => {
if (!isBlogEnabled || !isBlogPostRouteEnabled) return [];
- return (await fetchPosts()).flatMap((post) => ({
+ return (await fetchAllPosts()).flatMap((post) => ({
params: {
blog: post.permalink,
},
@@ -252,6 +274,7 @@ export async function getRelatedPosts(originalPost: Post, maxResults: number = 4
const postsWithScores = allPosts.reduce((acc: { post: Post; score: number }[], iteratedPost: Post) => {
if (iteratedPost.slug === originalPost.slug) return acc;
+ if (originalPost.translation && iteratedPost.slug === originalPost.translation.slug) return acc;
let score = 0;
if (iteratedPost.category && originalPost.category && iteratedPost.category.slug === originalPost.category.slug) {
@@ -285,7 +308,9 @@ export async function getRelatedPosts(originalPost: Post, maxResults: number = 4
/** */
export async function getAdjacentPosts(currentPost: Post): Promise<{ prev?: Post; next?: Post }> {
const posts = await fetchPosts();
- const idx = posts.findIndex((p) => p.slug === currentPost.slug);
+ // A translated variant is not in the default-language list; anchor on its counterpart
+ const anchor = currentPost.lang !== 'zh' && currentPost.translation ? currentPost.translation : currentPost;
+ const idx = posts.findIndex((p) => p.slug === anchor.slug);
if (idx === -1) return {};
return {
prev: idx < posts.length - 1 ? posts[idx + 1] : undefined,