- {frontmatter.pubDate.toDateString()}
- {frontmatter.description}
- Written by: {frontmatter.author}
-
- ...
- ```
-
- 最后,教程博客项目包括一个 RSS 源。这个函数还必须使用 `getCollection` 和 `data` 对象,并转换为异步函数:
-
- ```js title="src/pages/rss.xml.js" {4-5, 10-15}
- import rss from "@astrojs/rss";
- import { getCollection } from "astro:content";
-
- export async function GET() {
- const posts = await getCollection('posts');
- return rss({
- title: 'Astro Learner | Blog',
- description: 'My journey learning Astro',
- site: 'https://my-blog-site.netlify.app',
- items: posts.map((post) => ({
- title: post.data.title,
- pubDate: post.data.pubDate,
- description: post.data.description,
- link: `/posts/${post.slug}/`,
- })),
- customData: `en-us`,
- });
- }
- ```
-
-有关使用内容集合的博客教程的完整示例,请参见教程 repo 的[内容集合分支](https://github.com/withastro/blog-tutorial-demo/tree/content-collections)。
+如果你已经有了一个现成的 Astro 项目,比如一个博客,它使用 Markdown 或 MDX 文件在 `src/pages/` 内部的子文件夹中,可以考虑将相关内容或数据文件迁移到内容集合中。
+
+请参考我们的[手把手教程](/zh-cn/tutorials/add-content-collections/)中的示例,了解如何将位于 `src/pages/posts/` 的基本博客示例转换为 `src/content/posts` 目录下的示例。该教程使用了[构建博客教程的完整项目](https://github.com/withastro/blog-tutorial-demo)的代码库。
## 用 Remark 修改 Frontmatter
@@ -755,5 +564,4 @@ const { remarkPluginFrontmatter } = await blogPost.render();
-remark 和 rehype 管道只在渲染内容时运行,这就解释了为什么只有在对内容条目调用 `render()` 之后才可以使用 `remarkPluginFrontmatter`。相比之下,`getCollection()` 和 `getEntry()` 不能直接返回这些值,因为它们不会渲染内容。
-
+remark 和 rehype 管道只在渲染内容时运行,这就解释了为什么只有在对内容条目调用 `render()` 之后才可以使用 `remarkPluginFrontmatter`。相比之下,`getCollection()` 和 `getEntry()` 不能直接返回这些值,因为它们不会渲染内容。
\ No newline at end of file
diff --git a/src/content/docs/zh-cn/tutorial/6-islands/3.mdx b/src/content/docs/zh-cn/tutorial/6-islands/3.mdx
index 522e402aa6f72..0e28d085c1b58 100644
--- a/src/content/docs/zh-cn/tutorial/6-islands/3.mdx
+++ b/src/content/docs/zh-cn/tutorial/6-islands/3.mdx
@@ -54,7 +54,7 @@ const textCase = "uppercase";
## Next Steps
-继续阅读我们的[指南](/zh-cn/guides/content-collections/#从基于文件的路由迁移),将该项目迁移到内容集合
+继续阅读我们有关于[如何为该项目添加视图过渡动画](/zh-cn/tutorials/add-view-transitions/)以及[如何使用内容集合来管理你的博客文章](/zh-cn/tutorials/add-content-collections/)的扩展教程。
[开始一个新的 Astro 项目](/zh-cn/getting-started/)
diff --git a/src/content/docs/zh-cn/tutorials/add-content-collections.mdx b/src/content/docs/zh-cn/tutorials/add-content-collections.mdx
new file mode 100644
index 0000000000000..897e234f706f9
--- /dev/null
+++ b/src/content/docs/zh-cn/tutorials/add-content-collections.mdx
@@ -0,0 +1,385 @@
+---
+title: 教程 - 在项目中使用内容集合
+description: >-
+ 将构建博客教程的代码从基于文件路由的形式转换为内容集合的形式。
+i18nReady: true
+---
+import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro';
+import Box from '~/components/tutorial/Box.astro';
+import MultipleChoice from '~/components/tutorial/MultipleChoice.astro';
+import PreCheck from '~/components/tutorial/PreCheck.astro';
+import Option from '~/components/tutorial/Option.astro';
+
+**内容集合**是一种强大的方式来管理类似内容的组合,例如博客文章。集合有助于组织你的文档,验证你的 YAML frontmatter,以及为你所有内容提供自动的 TypeScript 类型安全(即使你自己不编写任何 TypeScript 代码)。
+
+
+ - 将你的博客文章文件夹移动到 `src/content/` 中
+ - 创建一个模式用于定义你的博客文章 frontmatter
+ - 使用 `getCollection()` 获取博客文章内容和元数据
+
+
+## 前期准备
+
+你将需要 **一个已有的 Astro 项目并在 `src/pages/` 文件夹中包含 Markdown 或 MDX 文件**。
+
+本教程使用[构建博客教程的完整项目](https://github.com/withastro/blog-tutorial-demo)来演示如何将博客转换为内容集合。你可以在本地 fork 并使用该代码库,或者通过[在 StackBlitz 上编辑博客教程代码](https://stackblitz.com/github/withastro/blog-tutorial-demo/tree/complete?file=src%2Fpages%2Findex.astro)在浏览器中完成教程。
+
+你也可以使用自己的 Astro 项目按照这些步骤进行操作,但你需要根据你的代码库调整指令。
+
+我们建议你先使用我们的示例项目完成这个简短的教程。然后,你可以利用所学知识在自己的项目中创建内容集合。
+
+## 构建博客教程代码
+
+在[构建博客入门教程](/zh-cn/tutorial/0-introduction/)中,你了解了 Astro [内置的基于文件的路由](/zh-cn/core-concepts/routing/#静态路由):`src/pages/` 文件夹中的任何 `.astro`、`.md` 或 `.mdx` 文件都会自动成为你站点上的页面。
+
+为了创建位于 `https://example.com/posts/post-1/` 的第一篇博客文章,你已经创建了一个 `/posts/` 文件夹,并添加了文件 `post-1.md`。每当你想在站点上添加一篇新的博客文章时,你只需在该文件夹中添加一个新的 Markdown 文件。
+
+## 页面与集合
+
+即使使用内容集合,你仍会使用 `src/pages/` 文件夹来管理独立页面,例如“关于我”页面。但是,将博客文章移动到特殊的 `src/content/` 文件夹中将允许你使用更强大和高效的 API 来生成博客文章索引和展示单独的博客文章。
+
+与此同时,你将在代码编辑器中获得更好的提示和自动补全,因为你将拥有一个 **[模式(schema)](/zh-cn/guides/content-collections/#定义集合模式)**,用于为每篇文章定义一个通用的结构,Astro 也将帮助你强制匹配该结构。在模式中,你可以指定什么时候 frontmatter 属性是需要的,例如描述或作者信息,以及每个属性的数据类型应该是什么,例如字符串或数组。这将更早地发现许多错误,并提供了详细的错误信息,帮助你快速定位问题。
+
+在我们的指南中阅读更多关于[Astro 内容集合(content collections)](/zh-cn/guides/content-collections/)的内容,或者按照以下说明开始将基本博客从 `src/pages/posts/` 转换到 `src/content/posts/`。
+
+
+
+### 测试一下
+
+1. 哪个类型的页面你可能会保留在 `src/pages/` 中?
+
+
+
+
+
+
+
+2. 哪一个**不是**将博客文章移动到内容集合的好处?
+
+
+
+
+
+
+
+3. 内容集合使用了 TypeScript……
+
+
+
+
+
+
+
+
+## 使用内容集合扩展博客教程
+
+下面的步骤将向你展示如何通过为博客文章创建内容集合来扩展构建博客教程的完整项目。
+
+### 升级依赖项
+
+1. 在终端中运行以下命令,将 Astro 升级到最新版本,并将所有集成升级为它们的最新版本:
+
+
+
+ ```shell
+ # 更新至 Astro v3.x
+ npm install astro@latest
+
+ # 示例:更新博客教程的 Preact 集成
+ npm install @astrojs/preact@latest
+ ```
+
+
+ ```shell
+ # 更新至 Astro v3.x
+ pnpm install astro@latest
+
+ # 示例:更新博客教程的 Preact 集成
+ pnpm install @astrojs/preact@latest
+ ```
+
+
+ ```shell
+ # 更新至 Astro v3.x
+ yarn add astro@latest
+
+ # 示例:更新博客教程的 Preact 集成
+ yarn add @astrojs/preact@latest
+ ```
+
+
+
+ :::tip
+ 如果你正在使用自己的项目,请确保更新已安装的任何依赖项。示例博客教程的代码库只使用了 Preact 集成。
+ :::
+
+2. 在博客教程中,使用的是不那么严格的 `base` TypeScript 设置。要使用内容集合,你必须通过以下方式之一为内容集合[设置 TypeScript](/zh-cn/guides/content-collections/#设置-typescript),即使用 `strict` 或 `strictest` 设置,或者在 `tsconfig.json` 中添加两个选项。
+
+ 为了在博客教程示例的其余部分中使用内容集合,将以下两个 TypeScript 配置选项添加到配置文件中:
+
+ ```json title="tsconfig.json" ins={5,6}
+ {
+ // 注意:如果你使用的是 "astro/tsconfigs/strict" 或 "astro/tsconfigs/strictest",则无需更改
+ "extends": "astro/tsconfigs/base",
+ "compilerOptions": {
+ "strictNullChecks": true,
+ "allowJs": true
+ }
+ }
+ ```
+
+### 为博客文章创建一个集合
+
+3. 创建一个新的名为 `src/content/posts/` 的**集合**(文件夹)。
+
+4. 将所有现有的博客文章(`.md` 文件)从 `src/pages/posts/` 移动到这个新的集合中。
+
+5. 创建一个 `src/content/config.ts` 文件,用于为你的 `postsCollection` [定义一个模式](/zh-cn/guides/content-collections/#定义集合模式)。对于现有的博客教程代码,向文件中添加如下内容以定义其博客文章中使用的所有 frontmatter 属性:
+
+ ```ts title="src/content/config.ts"
+ // 从 `astro:content` 导入辅助工具
+ import { z, defineCollection } from "astro:content";
+ // 为每一个集合定义一个 `type` 和 `schema`
+ const postsCollection = defineCollection({
+ type: 'content',
+ schema: z.object({
+ title: z.string(),
+ pubDate: z.date(),
+ description: z.string(),
+ author: z.string(),
+ image: z.object({
+ url: z.string(),
+ alt: z.string()
+ }),
+ tags: z.array(z.string())
+ })
+ });
+ // 导出一个单独的 `collections` 对象来注册你的集合
+ export const collections = {
+ posts: postsCollection,
+ };
+ ```
+
+### 从一个集合生成页面
+
+6. 生成一个名为 `src/pages/posts/[...slug].astro` 的页面文件。当 Markdown 和 MDX 文件位于一个集合内时,Astro 的基于文件的路由不再自动将它们转换为页面,因此你需要创建一个页面来负责生成每个单独的博客文章。
+
+7. 添加以下代码来[查询你的集合](/zh-cn/guides/content-collections/#查询集合),以便将每个博客文章的 slug 和页面内容提供给它将生成的每个页面:
+
+ ```astro title="src/pages/posts/[...slug].astro"
+ ---
+ import { getCollection } from 'astro:content';
+ import MarkdownPostLayout from '../../layouts/MarkdownPostLayout.astro';
+
+ export async function getStaticPaths() {
+ const blogEntries = await getCollection('posts');
+ return blogEntries.map(entry => ({
+ params: { slug: entry.slug }, props: { entry },
+ }));
+ }
+
+ const { entry } = Astro.props;
+ const { Content } = await entry.render();
+ ---
+ ```
+
+8. 在 Markdown 页面的布局中渲染你的 `` 组件,这样可以为所有的文章指定一个共同的布局。
+
+ ```astro title="src/pages/posts/[...slug].astro" ins={15-17}
+ ---
+ import { getCollection } from 'astro:content';
+ import MarkdownPostLayout from '../../layouts/MarkdownPostLayout.astro';
+
+ export async function getStaticPaths() {
+ const blogEntries = await getCollection('posts');
+ return blogEntries.map(entry => ({
+ params: { slug: entry.slug }, props: { entry },
+ }));
+ }
+
+ const { entry } = Astro.props;
+ const { Content } = await entry.render();
+ ---
+
+
+
+ ```
+
+9. 移除每个单独的博客文章 frontmatter 中的 `layout` 定义。当渲染时,你的内容会被包裹在一个布局中,因此不再需要这个属性。
+
+ ```md title="src/content/posts/post-1.md" del={2}
+ ---
+ layout: ../../layouts/MarkdownPostLayout.astro
+ title: 'My First Blog Post'
+ pubDate: 2022-07-01
+ ...
+ ---
+ ```
+
+### 用 `getCollection()` 代替 `Astro.glob()`
+
+10. 在任何你有博客文章列表的地方,比如教程中的博客页面 (`src/pages/blog.astro/`),你需要将 `Astro.glob()` 替换为 [`getCollection()`](/zh-cn/reference/api-reference/#getcollection) 来获取 Markdown 文件的内容和元数据。
+
+ ```astro title="src/pages/blog.astro" "post.data" "getCollection(\"posts\")" "/posts/${post.slug}/" del={7} ins={2,8}
+ ---
+ import { getCollection } from "astro:content";
+ import BaseLayout from "../layouts/BaseLayout.astro";
+ import BlogPost from "../components/BlogPost.astro";
+
+ const pageTitle = "My Astro Learning Blog";
+ const allPosts = await Astro.glob("../pages/posts/*.md");
+ const allPosts = await getCollection("posts");
+ ---
+ ```
+
+11. 你还需要更新对于每个 `post` 所返回数据的引用。现在你会在每个对象的 `data` 属性上找到你的 frontmatter 值。此外,当使用集合时,每个 `post` 对象将具有一个页面的 `slug`,而不是完整的 URL。
+
+ ```astro title="src/pages/blog.astro" "data" "/posts/$\{post.slug\}/" del={14} ins={15}
+ ---
+ import { getCollection } from "astro:content";
+ import BaseLayout from "../layouts/BaseLayout.astro";
+ import BlogPost from "../components/BlogPost.astro";
+
+ const pageTitle = "My Astro Learning Blog";
+ const allPosts = await getCollection("posts");
+ ---
+
+ This is where I will post about my journey learning Astro.
+
+ {
+ allPosts.map((post) => (
+ )}
+
+ ))
+ }
+
+
+ ```
+
+12. 教程中的博客项目还会使用 `src/pages/tags/[tag].astro` 动态为每个标签生成一个页面,并在 `src/pages/tags/index.astro` 中展示标签列表。
+
+ 将与上述相同的修改应用到这两个文件中:
+
+ - 使用 `getCollection("posts")` 代替 `Astro.glob()` 来获取所有博客文章的数据
+ - 使用 `data` 而不是 `frontmatter` 来访问所有 frontmatter 值
+ - 通过为博客文章的 `slug` 添加 `/posts/` 路径来创建页面 URL
+
+ 生成单独标签页面的页面现在如下所示:
+
+ ```astro title="src/pages/tags/[tag].astro" "post.data.tags" "getCollection(\"posts\")" "post.data.title" ins={2} "/posts/${post.slug}/"
+ ---
+ import { getCollection } from "astro:content";
+ import BaseLayout from "../../layouts/BaseLayout.astro";
+ import BlogPost from "../../components/BlogPost.astro";
+
+ export async function getStaticPaths() {
+ const allPosts = await getCollection("posts");
+ const uniqueTags = [...new Set(allPosts.map((post) => post.data.tags).flat())];
+
+ return uniqueTags.map((tag) => {
+ const filteredPosts = allPosts.filter((post) =>
+ post.data.tags.includes(tag)
+ );
+ return {
+ params: { tag },
+ props: { posts: filteredPosts },
+ };
+ });
+ }
+
+ const { tag } = Astro.params;
+ const { posts } = Astro.props;
+ ---
+
+
+ Posts tagged with {tag}
+
+ { posts.map((post) => ) }
+
+
+ ```
+
+
+ ### 试一试 - 在标签索引页面中更新查询语句
+
+ 导入并使用 `getCollection` 来获取在 `src/pages/tags/index.astro` 中的博客文章中使用的标签,按照[上述相同的步骤](#用-getcollection-代替-astroglob)进行操作。
+
+
+ 参考代码
+ ```astro title="src/pages/tags/index.astro" "post.data" "getCollection(\"posts\")" ins={2}
+ ---
+ import { getCollection } from "astro:content";
+ import BaseLayout from "../../layouts/BaseLayout.astro";
+ const allPosts = await getCollection("posts");
+ const tags = [...new Set(allPosts.map((post) => post.data.tags).flat())];
+ const pageTitle = "Tag Index";
+ ---
+ ...
+ ```
+
+
+
+### 更新 frontmatter 值以匹配你的模式
+
+13. 如果需要的话,更新项目中任何不匹配集合模式的 frontmatter 值,例如在布局中的值。
+
+ 在博客教程示例中,`pubDate` 是一个字符串。而根据定义了博客文章 frontmatter 类型的模式,`pubDate` 现在将是一个 `Date` 对象。
+
+ 要在博客文章布局中呈现日期,将其转换为字符串:
+
+ ```astro title="src/layouts/MarkdownPostLayout.astro" ins="toString()"
+ ...
+
+ {frontmatter.pubDate.toString().slice(0,10)}
+ {frontmatter.description}
+ Written by: {frontmatter.author}
+
+ ...
+ ```
+
+### 更新 RSS 函数
+
+14. 最后,博客教程项目包含了一个 RSS feed。这个函数也必须使用 `getCollection()` 来返回你的博客文章信息。然后,你将使用返回的 `data` 对象来生成 RSS 项。
+
+ ```js title="src/pages/rss.xml.js" del={2,11} ins={3,6,12-17}
+ import rss from '@astrojs/rss';
+ import { pagesGlobToRssItems } from '@astrojs/rss';
+ import { getCollection } from 'astro:content';
+
+ export async function GET(context) {
+ const posts = await getCollection("posts");
+ return rss({
+ title: 'Astro Learner | Blog',
+ description: 'My journey learning Astro',
+ site: context.site,
+ items: await pagesGlobToRssItems(import.meta.glob('./**/*.md')),
+ items: posts.map((post) => ({
+ title: post.data.title,
+ pubDate: post.data.pubDate,
+ description: post.data.description,
+ link: `/posts/${post.slug}/`,
+ })),
+ customData: `en-us`,
+ })
+ }
+ ```
+
+要查看使用内容集合的博客教程的完整示例,请参阅教程存储库的 [Content Collections 分支](https://github.com/withastro/blog-tutorial-demo/tree/content-collections)。
\ No newline at end of file