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,