Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<img src={hfIcon.src}>`
- **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

Expand Down
Binary file modified src/assets/images/aaif-skillhub.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 16 additions & 1 deletion src/components/blog/Grid.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,20 @@ const { posts } = Astro.props;
---

<div class="grid gap-6 row-gap-5 md:grid-cols-2 lg:grid-cols-4 -mb-6">
{posts.map((post) => <Item post={post} />)}
{
posts.map((post) =>
post.translation ? (
<div>
<div class="i18n-zh">
<Item post={post} />
</div>
<div class="i18n-en">
<Item post={post.translation} />
</div>
</div>
) : (
<Item post={post} />
)
)
}
</div>
13 changes: 12 additions & 1 deletion src/components/blog/List.astro
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ const { posts } = Astro.props;
{
posts.map((post) => (
<li class="mb-12 md:mb-20">
<Item post={post} />
{post.translation ? (
<>
<div class="i18n-zh">
<Item post={post} />
</div>
<div class="i18n-en">
<Item post={post.translation} />
</div>
</>
) : (
<Item post={post} />
)}
</li>
))
}
Expand Down
10 changes: 9 additions & 1 deletion src/components/blog/SinglePost.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
}
: {};
---

<section class="py-8 sm:py-16 lg:py-20 mx-auto">
<section class="py-8 sm:py-16 lg:py-20 mx-auto" {...translationAttrs}>
<article>
<header
class="relative overflow-hidden intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
Expand Down
6 changes: 6 additions & 0 deletions src/components/common/BasicScripts.astro
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ import { translations } from '~/utils/translations';
localStorage.lang = lang;
applyLanguage(lang);
document.dispatchEvent(new CustomEvent('languagechange', { detail: { lang } }));

// If the current page has a variant in the selected language, navigate to it
const translated = document.querySelector('[data-translation-url]');
if (translated && translated.getAttribute('data-post-lang') !== lang) {
window.location.assign(translated.getAttribute('data-translation-url'));
}
});

attachEvent('[data-aw-social-share]', 'click', function (_, elem) {
Expand Down
3 changes: 3 additions & 0 deletions src/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ const postCollection = defineCollection({
tags: z.array(z.string()).optional(),
author: z.string().optional(),

lang: z.enum(['zh', 'en']).optional(),
translationId: z.string().optional(),

metadata: metadataDefinition(),
}),
});
Expand Down
42 changes: 42 additions & 0 deletions src/data/post/skillhub-joins-aaif-en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
publishDate: 2026-07-14T00:00:00Z
title: 'Astron SkillHub Joins AAIF as an Associate Member'
excerpt: 'Astron SkillHub has officially joined the Agentic AI Foundation (AAIF) as an Associate Member, working alongside MCP, goose, AGENTS.md and other projects to advance the open-source agentic AI ecosystem.'
category: 'news'
image: '~/assets/images/aaif-skillhub.png'
tags: ['skillhub', 'aaif', 'linux-foundation', 'announcement']
author: 'iFLYTEK Open Source Team'
lang: 'en'
translationId: 'skillhub-joins-aaif'
---

# Astron SkillHub Joins AAIF as an Associate Member

Astron SkillHub has recently joined the Agentic AI Foundation (AAIF) as an Associate Member.

![Astron SkillHub listed among AAIF Associate Members](~/assets/images/aaif-skillhub.png)

The full member list is available on the AAIF website: [aaif.io/members](https://aaif.io/members/)

## About AAIF

The Agentic AI Foundation (AAIF) is a neutral, open foundation announced by the Linux Foundation in December 2025, dedicated to advancing open-source innovation in agentic AI through transparency, collaboration, and standardization. The foundation is anchored by three projects — the Model Context Protocol (MCP) contributed by Anthropic, goose contributed by Block, and AGENTS.md contributed by OpenAI — with platinum founding members including AWS, Anthropic, Block, Bloomberg, Cloudflare, Google, Microsoft, and OpenAI.

## What This Means

Joining AAIF recognizes Astron SkillHub's technical strength and ecosystem impact in the field of agent skill management. Going forward, Astron SkillHub will collaborate more closely with AAIF project communities such as MCP to jointly advance the open-source agentic AI ecosystem.

## About Astron SkillHub

[Astron SkillHub](https://github.com/iflytek/skillhub) is an enterprise-grade open-source agent skill registry that helps teams publish, discover, and manage reusable skill packages within their organizations. It supports self-hosted deployment, semantic versioning, team namespaces, RBAC-based governance, and audit logging, and is open-sourced under the Apache 2.0 license.

SkillHub is also a core project of the iFLYTEK [Astron Landscape](https://opensource.iflytek.com/landscape) — the world's first enterprise-grade open-source agent project landscape. Presented in the style of the CNCF Landscape, it maps the full iFLYTEK Astron open-source ecosystem, including the agent orchestration platform astron-agent, the RPA automation suite astron-rpa, the official skill library iFly-Skills, and the skill registry SkillHub, covering the complete agentic workflow stack from logic orchestration and tool invocation to skill governance.

## Links

- AAIF member list: [aaif.io/members](https://aaif.io/members/)
- SkillHub on GitHub: [github.com/iflytek/skillhub](https://github.com/iflytek/skillhub)
- SkillHub documentation: [iflytek.github.io/skillhub](https://iflytek.github.io/skillhub/)
- Astron Landscape: [opensource.iflytek.com/landscape](https://opensource.iflytek.com/landscape)

We welcome you to follow the project and contribute to the community!
2 changes: 2 additions & 0 deletions src/data/post/skillhub-joins-aaif.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ category: 'news'
image: '~/assets/images/aaif-skillhub.png'
tags: ['skillhub', 'aaif', 'linux-foundation', 'announcement']
author: 'iFLYTEK Open Source Team'
lang: 'zh'
translationId: 'skillhub-joins-aaif-en'
---

# Astron SkillHub 正式加入 AAIF 成为 Associate Member
Expand Down
7 changes: 7 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export interface Post {
tags?: Taxonomy[];
author?: string;

/** Content language of the post ('zh' when omitted in frontmatter). */
lang?: string;
/** ID of the counterpart post in the other language. */
translationId?: string;
/** Counterpart post in the other language, resolved at load time. */
translation?: Post;

metadata?: MetaData;

draft?: boolean;
Expand Down
33 changes: 29 additions & 4 deletions src/utils/blog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ const getNormalizedPost = async (post: CollectionEntry<'post'>): Promise<Post> =
tags: rawTags = [],
category: rawCategory,
author,
lang = 'zh',
translationId,
draft = false,
metadata = {},
} = data;
Expand Down Expand Up @@ -89,6 +91,9 @@ const getNormalizedPost = async (post: CollectionEntry<'post'>): Promise<Post> =
tags: tags,
author: author,

lang: lang,
translationId: translationId,

draft: draft,

metadata,
Expand All @@ -110,9 +115,17 @@ const load = async function (): Promise<Array<Post>> {
.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<Post>;
let _posts: Array<Post>;

/** */
Expand All @@ -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<Array<Post>> => {
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<Array<Post>> => {
if (!_posts) {
_posts = await load();
_posts = (await fetchAllPosts()).filter((post) => post.lang === 'zh');
}

return _posts;
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down
Loading