diff --git a/README.md b/README.md
index 4807f17f8..5a871121f 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,36 @@
-# NeoForged Documentation
+# NeoForge 文档
-This repository is used to store documentation on NeoForge, the Minecraft modding API. It also contains documentation on NeoGradle, a Gradle plugin for developing NeoForge and mods using NeoForge.
+此存储库用于存储有关NeoForge的文档,NeoForge是Minecraft模组开发的API。它还包含有关NeoGradle的文档,NeoGradle是一个用于开发NeoForge和使用NeoForge的模组的Gradle插件。
-The documentation is built using [Docusaurus 3](https://docusaurus.io)
+该文档使用[Docusaurus 3](https://docusaurus.io)构建
-## Contributing
+## 贡献
-You can read the [contribution guidelines on the docs](https://docs.neoforged.net/contributing/).
+您可以在[文档贡献指南](https://docs.neoforged.net/contributing/)中阅读贡献指南
-If you wish to contribute to the documentation, fork and clone this repository.
+如果您希望为文档做出贡献,请 fork 并 clone 此存储库。
-The documentation uses Node.js 18. This can either be installed manually or using a version manager that supports `.node-version` or `.nvmrc`. For most version managers, the `install` and/or `use` command can be used to setup the correct Node.js version.
+本文档使用 Node.js 18。可以手动安装,也可以使用支持 `.node-version` 或 `.nvmrc` 的版本管理器安装。对于大多数版本管理器,可以使用 `install` 和/或 `use` 命令来设置正确的 Node.js 版本。
-For example:
+例如:
```bash
nvm install # or 'nvs use'
```
-You can run the following commands if you wish to preview the documentation website locally through the live development server. Most changes are reflected live without having to restart the server.
+如果您想通过实时开发服务器在本地预览文档网站,可以运行以下命令。大多数更改都会实时反馈,无需重新启动服务器。
```bash
npm install
npm run start
```
-### Building
+### 构建
-If you wish to build a static version of the documentation which can be deployed, you can run the following command:
+如果您希望构建可部署的静态版本的文档,您可以运行以下命令:
```bash
npm run build
```
-This command generates static content into the `build` directory and can be served using any static contents hosting service.
+此命令会生成静态内容到`build`目录,并可以使用任何静态内容托管服务来提供服务。
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 071dd3c6e..4a2a614c9 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -28,8 +28,8 @@ const config = {
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
- defaultLocale: "en",
- locales: ["en"],
+ defaultLocale: 'en',
+ locales: ['en','zh-Hans'],
},
presets: [
diff --git a/i18n/zh-Hans/code.json b/i18n/zh-Hans/code.json
new file mode 100644
index 000000000..e36f821a9
--- /dev/null
+++ b/i18n/zh-Hans/code.json
@@ -0,0 +1,424 @@
+{
+ "theme.docs.versions.unmaintainedVersionLabel": {
+ "message": "此为 {siteTitle} {versionLabel} 版的文档,现已不再积极维护。",
+ "description": "The label used to tell the user that he's browsing an unmaintained doc version"
+ },
+ "theme.docs.versions.LTSVersionLabel": {
+ "message": "This is documentation for {siteTitle} {versionLabel}, which is currently the LTS version.",
+ "description": "The label used to tell the user that they're browsing a LTS doc version"
+ },
+ "theme.docs.versions.latestVersionSuggestionLabel": {
+ "message": "最新的文档请参阅 {latestVersionLink} ({versionLabel})。",
+ "description": "The label used to tell the user to check the latest version"
+ },
+ "theme.docs.versions.latestVersionLinkLabel": {
+ "message": "最新版本",
+ "description": "The label used for the latest version suggestion link label"
+ },
+ "theme.ErrorPageContent.title": {
+ "message": "页面已崩溃。",
+ "description": "The title of the fallback page when the page crashed"
+ },
+ "theme.BackToTopButton.buttonAriaLabel": {
+ "message": "回到顶部",
+ "description": "The ARIA label for the back to top button"
+ },
+ "theme.blog.archive.title": {
+ "message": "历史博文",
+ "description": "The page & hero title of the blog archive page"
+ },
+ "theme.blog.archive.description": {
+ "message": "历史博文",
+ "description": "The page & hero description of the blog archive page"
+ },
+ "theme.blog.paginator.navAriaLabel": {
+ "message": "博文列表分页导航",
+ "description": "The ARIA label for the blog pagination"
+ },
+ "theme.blog.paginator.newerEntries": {
+ "message": "较新的博文",
+ "description": "The label used to navigate to the newer blog posts page (previous page)"
+ },
+ "theme.blog.paginator.olderEntries": {
+ "message": "较旧的博文",
+ "description": "The label used to navigate to the older blog posts page (next page)"
+ },
+ "theme.blog.post.paginator.navAriaLabel": {
+ "message": "博文分页导航",
+ "description": "The ARIA label for the blog posts pagination"
+ },
+ "theme.blog.post.paginator.newerPost": {
+ "message": "较新一篇",
+ "description": "The blog post button label to navigate to the newer/previous post"
+ },
+ "theme.blog.post.paginator.olderPost": {
+ "message": "较旧一篇",
+ "description": "The blog post button label to navigate to the older/next post"
+ },
+ "theme.blog.post.plurals": {
+ "message": "{count} 篇博文",
+ "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
+ },
+ "theme.blog.tagTitle": {
+ "message": "{nPosts} 含有标签「{tagName}」",
+ "description": "The title of the page for a blog tag"
+ },
+ "theme.tags.tagsPageLink": {
+ "message": "查看所有标签",
+ "description": "The label of the link targeting the tag list page"
+ },
+ "theme.colorToggle.ariaLabel": {
+ "message": "切换浅色/暗黑模式(当前为{mode})",
+ "description": "The ARIA label for the navbar color mode toggle"
+ },
+ "theme.colorToggle.ariaLabel.mode.dark": {
+ "message": "暗黑模式",
+ "description": "The name for the dark color mode"
+ },
+ "theme.colorToggle.ariaLabel.mode.light": {
+ "message": "浅色模式",
+ "description": "The name for the light color mode"
+ },
+ "theme.docs.breadcrumbs.navAriaLabel": {
+ "message": "页面路径",
+ "description": "The ARIA label for the breadcrumbs"
+ },
+ "theme.docs.DocCard.categoryDescription": {
+ "message": "{count} 个项目",
+ "description": "The default description for a category card in the generated index about how many items this category includes"
+ },
+ "theme.docs.paginator.navAriaLabel": {
+ "message": "文件选项卡",
+ "description": "The ARIA label for the docs pagination"
+ },
+ "theme.docs.paginator.previous": {
+ "message": "上一页",
+ "description": "The label used to navigate to the previous doc"
+ },
+ "theme.docs.paginator.next": {
+ "message": "下一页",
+ "description": "The label used to navigate to the next doc"
+ },
+ "theme.docs.tagDocListPageTitle.nDocsTagged": {
+ "message": "{count} 篇文档带有标签",
+ "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
+ },
+ "theme.docs.tagDocListPageTitle": {
+ "message": "{nDocsTagged}「{tagName}」",
+ "description": "The title of the page for a docs tag"
+ },
+ "theme.docs.versionBadge.label": {
+ "message": "版本:{versionLabel}"
+ },
+ "theme.docs.versions.unreleasedVersionLabel": {
+ "message": "此为 {siteTitle} {versionLabel} 版尚未发行的文档。",
+ "description": "The label used to tell the user that he's browsing an unreleased doc version"
+ },
+ "theme.common.editThisPage": {
+ "message": "编辑此页",
+ "description": "The link label to edit the current page"
+ },
+ "theme.common.headingLinkTitle": {
+ "message": "{heading}的直接链接",
+ "description": "Title for link to heading"
+ },
+ "theme.lastUpdated.atDate": {
+ "message": "于 {date} ",
+ "description": "The words used to describe on which date a page has been last updated"
+ },
+ "theme.lastUpdated.byUser": {
+ "message": "由 {user} ",
+ "description": "The words used to describe by who the page has been last updated"
+ },
+ "theme.lastUpdated.lastUpdatedAtBy": {
+ "message": "最后{byUser}{atDate}更新",
+ "description": "The sentence used to display when a page has been last updated, and by who"
+ },
+ "theme.navbar.mobileVersionsDropdown.label": {
+ "message": "选择版本",
+ "description": "The label for the navbar versions dropdown on mobile view"
+ },
+ "theme.NotFound.title": {
+ "message": "找不到页面",
+ "description": "The title of the 404 page"
+ },
+ "theme.tags.tagsListLabel": {
+ "message": "标签:",
+ "description": "The label alongside a tag list"
+ },
+ "theme.admonition.caution": {
+ "message": "警告",
+ "description": "The default label used for the Caution admonition (:::caution)"
+ },
+ "theme.admonition.danger": {
+ "message": "危险",
+ "description": "The default label used for the Danger admonition (:::danger)"
+ },
+ "theme.admonition.info": {
+ "message": "信息",
+ "description": "The default label used for the Info admonition (:::info)"
+ },
+ "theme.admonition.note": {
+ "message": "备注",
+ "description": "The default label used for the Note admonition (:::note)"
+ },
+ "theme.admonition.tip": {
+ "message": "提示",
+ "description": "The default label used for the Tip admonition (:::tip)"
+ },
+ "theme.admonition.warning": {
+ "message": "注意",
+ "description": "The default label used for the Warning admonition (:::warning)"
+ },
+ "theme.AnnouncementBar.closeButtonAriaLabel": {
+ "message": "关闭",
+ "description": "The ARIA label for close button of announcement bar"
+ },
+ "theme.blog.sidebar.navAriaLabel": {
+ "message": "最近博文导航",
+ "description": "The ARIA label for recent posts in the blog sidebar"
+ },
+ "theme.CodeBlock.copied": {
+ "message": "复制成功",
+ "description": "The copied button label on code blocks"
+ },
+ "theme.CodeBlock.copyButtonAriaLabel": {
+ "message": "复制代码到剪贴板",
+ "description": "The ARIA label for copy code blocks button"
+ },
+ "theme.CodeBlock.copy": {
+ "message": "复制",
+ "description": "The copy button label on code blocks"
+ },
+ "theme.CodeBlock.wordWrapToggle": {
+ "message": "切换自动换行",
+ "description": "The title attribute for toggle word wrapping button of code block lines"
+ },
+ "theme.DocSidebarItem.expandCategoryAriaLabel": {
+ "message": "展开侧边栏分类 '{label}'",
+ "description": "The ARIA label to expand the sidebar category"
+ },
+ "theme.DocSidebarItem.collapseCategoryAriaLabel": {
+ "message": "折叠侧边栏分类 '{label}'",
+ "description": "The ARIA label to collapse the sidebar category"
+ },
+ "theme.NavBar.navAriaLabel": {
+ "message": "主导航",
+ "description": "The ARIA label for the main navigation"
+ },
+ "theme.navbar.mobileLanguageDropdown.label": {
+ "message": "选择语言",
+ "description": "The label for the mobile language switcher dropdown"
+ },
+ "theme.NotFound.p1": {
+ "message": "我们找不到您要找的页面。",
+ "description": "The first paragraph of the 404 page"
+ },
+ "theme.NotFound.p2": {
+ "message": "请联系原始链接来源网站的所有者,并告知他们链接已损坏。",
+ "description": "The 2nd paragraph of the 404 page"
+ },
+ "theme.TOCCollapsible.toggleButtonLabel": {
+ "message": "本页总览",
+ "description": "The label used by the button on the collapsible TOC component"
+ },
+ "theme.blog.post.readMore": {
+ "message": "阅读更多",
+ "description": "The label used in blog post item excerpts to link to full blog posts"
+ },
+ "theme.blog.post.readMoreLabel": {
+ "message": "阅读 {title} 的全文",
+ "description": "The ARIA label for the link to full blog posts from excerpts"
+ },
+ "theme.blog.post.readingTime.plurals": {
+ "message": "阅读需 {readingTime} 分钟",
+ "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
+ },
+ "theme.docs.breadcrumbs.home": {
+ "message": "主页面",
+ "description": "The ARIA label for the home page in the breadcrumbs"
+ },
+ "theme.docs.sidebar.collapseButtonTitle": {
+ "message": "收起侧边栏",
+ "description": "The title attribute for collapse button of doc sidebar"
+ },
+ "theme.docs.sidebar.collapseButtonAriaLabel": {
+ "message": "收起侧边栏",
+ "description": "The title attribute for collapse button of doc sidebar"
+ },
+ "theme.docs.sidebar.navAriaLabel": {
+ "message": "文档侧边栏",
+ "description": "The ARIA label for the sidebar navigation"
+ },
+ "theme.docs.sidebar.closeSidebarButtonAriaLabel": {
+ "message": "关闭导航栏",
+ "description": "The ARIA label for close button of mobile sidebar"
+ },
+ "theme.docs.sidebar.toggleSidebarButtonAriaLabel": {
+ "message": "切换导航栏",
+ "description": "The ARIA label for hamburger menu button of mobile navigation"
+ },
+ "theme.docs.sidebar.expandButtonTitle": {
+ "message": "展开侧边栏",
+ "description": "The ARIA label and title attribute for expand button of doc sidebar"
+ },
+ "theme.docs.sidebar.expandButtonAriaLabel": {
+ "message": "展开侧边栏",
+ "description": "The ARIA label and title attribute for expand button of doc sidebar"
+ },
+ "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": {
+ "message": "← 回到主菜单",
+ "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)"
+ },
+ "theme.SearchBar.seeAll": {
+ "message": "查看全部 {count} 个结果"
+ },
+ "theme.SearchBar.label": {
+ "message": "搜索",
+ "description": "The ARIA label and placeholder for search button"
+ },
+ "theme.SearchModal.searchBox.resetButtonTitle": {
+ "message": "清除查询",
+ "description": "The label and ARIA label for search box reset button"
+ },
+ "theme.SearchModal.searchBox.cancelButtonText": {
+ "message": "取消",
+ "description": "The label and ARIA label for search box cancel button"
+ },
+ "theme.SearchModal.startScreen.recentSearchesTitle": {
+ "message": "最近搜索",
+ "description": "The title for recent searches"
+ },
+ "theme.SearchModal.startScreen.noRecentSearchesText": {
+ "message": "没有最近搜索",
+ "description": "The text when no recent searches"
+ },
+ "theme.SearchModal.startScreen.saveRecentSearchButtonTitle": {
+ "message": "保存这个搜索",
+ "description": "The label for save recent search button"
+ },
+ "theme.SearchModal.startScreen.removeRecentSearchButtonTitle": {
+ "message": "从历史记录中删除这个搜索",
+ "description": "The label for remove recent search button"
+ },
+ "theme.SearchModal.startScreen.favoriteSearchesTitle": {
+ "message": "收藏",
+ "description": "The title for favorite searches"
+ },
+ "theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": {
+ "message": "从收藏列表中删除这个搜索",
+ "description": "The label for remove favorite search button"
+ },
+ "theme.SearchModal.errorScreen.titleText": {
+ "message": "无法获取结果",
+ "description": "The title for error screen of search modal"
+ },
+ "theme.SearchModal.errorScreen.helpText": {
+ "message": "你可能需要检查网络连接。",
+ "description": "The help text for error screen of search modal"
+ },
+ "theme.SearchModal.footer.selectText": {
+ "message": "选中",
+ "description": "The explanatory text of the action for the enter key"
+ },
+ "theme.SearchModal.footer.selectKeyAriaLabel": {
+ "message": "Enter 键",
+ "description": "The ARIA label for the Enter key button that makes the selection"
+ },
+ "theme.SearchModal.footer.navigateText": {
+ "message": "导航",
+ "description": "The explanatory text of the action for the Arrow up and Arrow down key"
+ },
+ "theme.SearchModal.footer.navigateUpKeyAriaLabel": {
+ "message": "向上键",
+ "description": "The ARIA label for the Arrow up key button that makes the navigation"
+ },
+ "theme.SearchModal.footer.navigateDownKeyAriaLabel": {
+ "message": "向下键",
+ "description": "The ARIA label for the Arrow down key button that makes the navigation"
+ },
+ "theme.SearchModal.footer.closeText": {
+ "message": "关闭",
+ "description": "The explanatory text of the action for Escape key"
+ },
+ "theme.SearchModal.footer.closeKeyAriaLabel": {
+ "message": "Esc 键",
+ "description": "The ARIA label for the Escape key button that close the modal"
+ },
+ "theme.SearchModal.footer.searchByText": {
+ "message": "搜索提供",
+ "description": "The text explain that the search is making by Algolia"
+ },
+ "theme.SearchModal.noResultsScreen.noResultsText": {
+ "message": "没有结果:",
+ "description": "The text explains that there are no results for the following search"
+ },
+ "theme.SearchModal.noResultsScreen.suggestedQueryText": {
+ "message": "试试搜索",
+ "description": "The text for the suggested query when no results are found for the following search"
+ },
+ "theme.SearchModal.noResultsScreen.reportMissingResultsText": {
+ "message": "认为这个查询应该有结果?",
+ "description": "The text for the question where the user thinks there are missing results"
+ },
+ "theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": {
+ "message": "请告知我们。",
+ "description": "The text for the link to report missing results"
+ },
+ "theme.SearchModal.placeholder": {
+ "message": "搜索文档",
+ "description": "The placeholder of the input of the DocSearch pop-up modal"
+ },
+ "theme.SearchPage.documentsFound.plurals": {
+ "message": "找到 {count} 份文件",
+ "description": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
+ },
+ "theme.SearchPage.existingResultsTitle": {
+ "message": "「{query}」的搜索结果",
+ "description": "The search page title for non-empty query"
+ },
+ "theme.SearchPage.emptyResultsTitle": {
+ "message": "在文档中搜索",
+ "description": "The search page title for empty query"
+ },
+ "theme.SearchPage.inputPlaceholder": {
+ "message": "在此输入搜索字词",
+ "description": "The placeholder for search page input"
+ },
+ "theme.SearchPage.inputLabel": {
+ "message": "搜索",
+ "description": "The ARIA label for search page input"
+ },
+ "theme.SearchPage.algoliaLabel": {
+ "message": "通过 Algolia 搜索",
+ "description": "The ARIA label for Algolia mention"
+ },
+ "theme.SearchPage.noResultsText": {
+ "message": "未找到任何结果",
+ "description": "The paragraph for empty search result"
+ },
+ "theme.SearchPage.fetchingNewResults": {
+ "message": "正在获取新的搜索结果...",
+ "description": "The paragraph for fetching new search results"
+ },
+ "theme.ErrorPageContent.tryAgain": {
+ "message": "重试",
+ "description": "The label of the button to try again rendering when the React error boundary captures an error"
+ },
+ "theme.common.skipToMainContent": {
+ "message": "跳到主要内容",
+ "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation"
+ },
+ "theme.tags.tagsPageTitle": {
+ "message": "标签",
+ "description": "The title of the tag list page"
+ },
+ "theme.unlistedContent.title": {
+ "message": "未列出页",
+ "description": "The unlisted content banner title"
+ },
+ "theme.unlistedContent.message": {
+ "message": "此页面未列出。搜索引擎不会对其索引,只有拥有直接链接的用户才能访问。",
+ "description": "The unlisted content banner message"
+ }
+}
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-blog/options.json b/i18n/zh-Hans/docusaurus-plugin-content-blog/options.json
new file mode 100644
index 000000000..9239ff706
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-blog/options.json
@@ -0,0 +1,14 @@
+{
+ "title": {
+ "message": "Blog",
+ "description": "The title for the blog used in SEO"
+ },
+ "description": {
+ "message": "Blog",
+ "description": "The description for the blog used in SEO"
+ },
+ "sidebar.title": {
+ "message": "Recent posts",
+ "description": "The label for the left sidebar"
+ }
+}
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/current.json b/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/current.json
new file mode 100644
index 000000000..bede4b8bf
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/current.json
@@ -0,0 +1,14 @@
+{
+ "version.label": {
+ "message": "FG6",
+ "description": "The label for version current"
+ },
+ "sidebar.ngSidebar.category.ForgeGradle Configurations": {
+ "message": "ForgeGradle Configurations",
+ "description": "The label for category ForgeGradle Configurations in sidebar ngSidebar"
+ },
+ "sidebar.ngSidebar.category.Dependencies": {
+ "message": "Dependencies",
+ "description": "The label for category Dependencies in sidebar ngSidebar"
+ }
+}
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/version-5.x.json b/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/version-5.x.json
new file mode 100644
index 000000000..8a8d0d233
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/version-5.x.json
@@ -0,0 +1,18 @@
+{
+ "version.label": {
+ "message": "FG5",
+ "description": "The label for version 5.x"
+ },
+ "sidebar.ngSidebar.category.ForgeGradle Documentation": {
+ "message": "ForgeGradle Documentation",
+ "description": "The label for category ForgeGradle Documentation in sidebar ngSidebar"
+ },
+ "sidebar.ngSidebar.category.ForgeGradle Configurations": {
+ "message": "ForgeGradle Configurations",
+ "description": "The label for category ForgeGradle Configurations in sidebar ngSidebar"
+ },
+ "sidebar.ngSidebar.category.Dependencies": {
+ "message": "Dependencies",
+ "description": "The label for category Dependencies in sidebar ngSidebar"
+ }
+}
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json
new file mode 100644
index 000000000..b1aac2141
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json
@@ -0,0 +1,78 @@
+{
+ "version.label": {
+ "message": "1.20.x",
+ "description": "当前版本的标签"
+ },
+ "sidebar.mainSidebar.category.Getting Started with Neo": {
+ "message": "开始使用Neo",
+ "description": "侧边栏mainSidebar中“开始使用Neo”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Advanced Topics": {
+ "message": "高级主题",
+ "description": "侧边栏mainSidebar中“高级主题”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Block Entities": {
+ "message": "方块实体",
+ "description": "侧边栏mainSidebar中“方块实体”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Blocks": {
+ "message": "方块",
+ "description": "侧边栏mainSidebar中“方块”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Concepts": {
+ "message": "概念",
+ "description": "侧边栏mainSidebar中“概念”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Datagen": {
+ "message": "数据生成",
+ "description": "侧边栏mainSidebar中“数据生成”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Data Maps": {
+ "message": "数据映射",
+ "description": "侧边栏mainSidebar中“数据映射”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Data Storage": {
+ "message": "数据存储",
+ "description": "侧边栏mainSidebar中“数据存储”类别的标签"
+ },
+ "sidebar.mainSidebar.category.GUIs": {
+ "message": "图形用户界面",
+ "description": "侧边栏mainSidebar中“图形用户界面”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Items": {
+ "message": "物品",
+ "description": "侧边栏mainSidebar中“物品”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Legacy": {
+ "message": "遗留",
+ "description": "侧边栏mainSidebar中“遗留”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Miscellaneous": {
+ "message": "杂项",
+ "description": "侧边栏mainSidebar中“杂项”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Networking": {
+ "message": "网络",
+ "description": "侧边栏mainSidebar中“网络”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Resources": {
+ "message": "资源",
+ "description": "侧边栏mainSidebar中“资源”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Client": {
+ "message": "客户端",
+ "description": "侧边栏mainSidebar中“客户端”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Models": {
+ "message": "模型",
+ "description": "侧边栏mainSidebar中“模型”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Datapacks": {
+ "message": "数据包",
+ "description": "侧边栏mainSidebar中“数据包”类别的标签"
+ },
+ "sidebar.mainSidebar.category.Recipes": {
+ "message": "配方",
+ "description": "侧边栏mainSidebar中“配方”类别的标签"
+ }
+}
\ No newline at end of file
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/_category_.json
new file mode 100644
index 000000000..c392eef49
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/_category_.json
@@ -0,0 +1,3 @@
+{
+ "label": "Advanced Topics"
+}
\ No newline at end of file
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/accesstransformers.mdx b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/accesstransformers.mdx
new file mode 100644
index 000000000..829efaf09
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/accesstransformers.mdx
@@ -0,0 +1,167 @@
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# Access Transformers
+
+Access Transformers (简称 ATs) 允许开发者扩大类、方法和字段的可见性,以及修改其 `final` 属性。这些工具使得模组开发者能够访问和修改通常无法访问的类成员。
+
+[规范文档][specs]可以在 NeoForged 的 GitHub 上查看。
+
+## 添加 ATs
+
+将 Access Transformer 添加到您的 mod 项目与在 `build.gradle` 中添加一行代码一样简单:
+
+
+
+Access Transformers 需要在 `build.gradle` 和 `mods.toml` 中声明:
+
+```groovy
+// 在 build.gradle 中:
+// 此部分也是指定映射版本的地方
+minecraft {
+ accessTransformers {
+ file('src/main/resources/META-INF/accesstransformer.cfg')
+ }
+}
+```
+
+```toml
+# 在 mods.toml 中:
+[[accessTransformers]]
+file="META-INF/accesstransformer.cfg"
+```
+
+AT 文件可以位于上面几行指定的任何位置,但如果没有指定其他文件,NeoForge 将默认搜索 `META-INF/accesstransformer.cfg`。
+
+此外,可以指定多个 AT 文件并按顺序应用。 这对于具有多个软件包的大型模组非常有用。
+
+```groovy
+// In build.gradle:
+minecraft {
+ accessTransformers {
+ file('src/main/resources/accesstransformer_main.cfg')
+ file('src/additions/resources/accesstransformer_additions.cfg')
+ }
+}
+```
+
+```toml
+# In mods.toml
+[[accessTransformers]]
+file="accesstransformer_main.cfg"
+
+[[accessTransformers]]
+file="accesstransformer_additions.cfg"
+```
+
+添加或修改任何访问转换器后,必须刷新 Gradle 项目,以便使更改生效。
+
+
+
+在你的模组项目中添加一个 AT 也很简单,只需在 `build.gradle` 中添加一行代码:
+
+```groovy
+// 这一部分也是你指定映射版本的地方
+minecraft {
+ accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
+}
+```
+
+在开发环境中,AT 文件可以放在上述指定的任何位置。然而,在非开发环境中加载时,NeoForge 只会搜索你的 JAR 文件中 `META-INF/accesstransformer.cfg` 的确切路径。
+
+添加或修改访问转换器后,必须刷新 Gradle 项目,以便使更改生效。
+
+
+
+## 注释
+
+`#` 之后直到行尾的所有文本都将被视为注释并且不会被解析。
+
+## 访问控制修饰符
+
+访问控制修饰符指明目标将被转换到的新的成员的可见性。按可见性递减的顺序排列如下:
+
+* `public` - 对包内外的所有类可见
+* `protected` - 只对包内的类和子类可见
+* `default` - 只对包内的类可见
+* `private` - 只对类的内部可见
+
+特殊修饰符 `+f` 和 `-f` 可以分别添加到上述的修饰符之后,用以添加或移除 `final` 修饰符,当应用时可防止子类化、方法覆盖或字段修改。
+
+:::danger
+指令只修改直接引用的方法;任何覆盖的方法都不会被转换。建议确保转换的方法没有未转换覆盖的方法,否则这会限制其可见性,并可能导致 JVM 报错。
+
+可以安全转换的方法示例包括 `private` 方法、`final` 方法(或 `final` 类中的方法)以及 `static` 方法。
+:::
+
+## 目标和指令
+
+### 类
+
+定位类的格式如下:
+
+```
+
+```
+
+内部类通过组合外部类的完全限定名和内部类名,使用 `$` 作为分隔符来表示。
+
+### 字段
+
+定位字段的格式如下:
+
+```
+
+```
+
+### 方法
+
+定位方法需要特殊的语法来表示方法参数和返回类型:
+
+```
+ ()
+```
+
+#### 指定类型
+
+也称为"描述符":有关更多技术细节,请参见 [Java 虚拟机规范,SE 8,第 4.3.2 节和 4.3.3 节][jvmdescriptors]。
+
+* `B` - `byte`,有符号字节
+* `C` - `char`,Unicode 字符代码点,使用 UTF-16 编码
+* `D` - `double`,双精度浮点值
+* `F` - `float`,单精度浮点值
+* `I` - `integer`,32 位整数
+* `J` - `long`,64 位整数
+* `S` - `short`,有符号短整数
+* `Z` - `boolean`,`true` 或 `false` 值
+* `[` - 表示数组的一个维度
+ * 示例: `[[S` 表示 `short[][]`
+* `L;` - 表示引用类型
+ * 示例: `Ljava/lang/String;` 表示 `java.lang.String` 引
+
+用类型 _(注意使用斜线而非点)_
+* `(` - 表示方法描述符,参数应在此处提供,如果没有参数则不提供
+ * 示例: `(I)Z` 表示一个需要整型参数并返回布尔值的方法
+* `V` - 表示方法不返回值,只能在方法描述符的末尾使用
+ * 示例: `()V` 表示一个没有参数并且不返回任何结果的方法
+
+## 示例
+
+```
+# 将 Crypt 中的 ByteArrayToKeyFunction 接口公开
+public net.minecraft.util.Crypt$ByteArrayToKeyFunction
+
+# 将 MinecraftServer 中的 'random' 字段设为 protected 并移除 final 修饰
+protected-f net.minecraft.server.MinecraftServer random
+
+# 将 Util 中的 'makeExecutor' 方法公开,
+# 接受一个 String 并返回一个 ExecutorService
+public net.minecraft.Util makeExecutor(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService;
+
+# 将 UUIDUtil 中的 'leastMostToIntArray' 方法公开,
+# 接受两个 long 并返回一个 int 数组
+public net.minecraft.core.UUIDUtil leastMostToIntArray(JJ)[I
+```
+
+[specs]: https://github.com/NeoForged/AccessTransformers/blob/main/FMLAT.md
+[jvmdescriptors]: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/ber.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/ber.md
new file mode 100644
index 000000000..a83d801aa
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/ber.md
@@ -0,0 +1,28 @@
+BlockEntityRenderer
+==================
+
+`BlockEntityRenderer` 或 `BER` 用于以无法用静态烘焙模型(JSON,OBJ,B3D,其他)表示的方式渲染块。块实体渲染器要求块有一个 `BlockEntity`。
+
+创建 BER
+--------------
+
+要创建 BER,创建一个继承自 `BlockEntityRenderer` 的类。它需要一个泛型参数,指定块的 `BlockEntity` 类。泛型参数在 BER 的 `render` 方法中使用。
+
+对于给定的 `BlockEntityType`,只存在一个 BER。因此,应将特定于等级中的单个实例的值存储在传递给渲染器的块实体中,而不是在 BER 本身中。例如,每帧递增的整数,如果存储在 BER 中,将会在该类型的等级中的每一个块实体中每帧递增。
+
+### `render`
+
+每一帧都会调用这个方法来渲染块实体。
+
+#### 参数
+* `blockEntity`:这是正在渲染的块实体的实例。
+* `partialTick`:自上一完整 tick以来已经过去的以 tick 的分数表示的时间。
+* `poseStack`:这是一个堆栈,可以持有四维矩阵条目,这些条目可以偏移到块实体的当前位置。
+* `bufferSource`:一个渲染缓冲区,能够访问顶点消费者。
+* `combinedLight`:块实体上当前光值的整数。
+* `combinedOverlay`:一个设置为块实体当前覆盖层的整数,通常是 `OverlayTexture#NO_OVERLAY` 或 655,360。
+
+注册 BER
+-----------------
+
+要注册 BER,你必须订阅模组事件总线上的 `EntityRenderersEvent$RegisterRenderers` 事件,并调用 `#registerBlockEntityRenderer`。
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/index.md
new file mode 100644
index 000000000..84a2627e0
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/index.md
@@ -0,0 +1,125 @@
+## 注册方块实体
+
+方块实体的创建和移除是动态的,因此它们本身不是注册对象。要创建一个`BlockEntity`,你需要扩展`BlockEntity`类。相应地,另一个对象被注册以方便创建和引用动态对象的*类型*。对于`BlockEntity`,这些类型被称为`BlockEntityType`。
+
+`BlockEntityType`可以像其他注册对象一样被[注册][registration]。使用`BlockEntityType.Builder#of`来构建`BlockEntityType`,它接受两个参数:一个`BlockEntityType.BlockEntitySupplier`,它接受一个`BlockPos`和`BlockState`来创建新的`BlockEntity`实例,以及一个可变数量的`Block`,这些方块可以附加到此`BlockEntity`。
+
+```java
+// 对于某个DeferredRegister> REGISTER
+public static final RegistryObject> MY_BE = REGISTER.register("mybe", () -> BlockEntityType.Builder.of(MyBE::new, validBlocks).build(null));
+
+// 在MyBE中,一个BlockEntity子类
+public MyBE(BlockPos pos, BlockState state) {
+ super(MY_BE.get(), pos, state);
+}
+```
+
+## 创建方块实体
+
+要创建一个`BlockEntity`并将其附加到一个`Block`,你的`Block`子类必须实现`EntityBlock`接口。必须实现方法`EntityBlock#newBlockEntity(BlockPos, BlockState)`并返回你的`BlockEntity`的新实例。
+
+## 存储你的方块实体内的数据
+
+为了保存数据,覆盖以下两个方法:
+```java
+BlockEntity#saveAdditional(CompoundTag tag)
+
+BlockEntity#load(CompoundTag tag)
+```
+这些方法在包含`BlockEntity`的`LevelChunk`从标签加载/保存时调用。使用这些方法读写你的方块实体类中的字段。
+
+:::note
+每当你的数据发生变化时,你需要调用`BlockEntity#setChanged`;否则,在级别保存时可能会跳过包含你的`BlockEntity`的`LevelChunk`。
+:::
+
+:::danger
+调用`super`方法非常重要!
+
+标签名`id`、`x`、`y`、`z`、`ForgeData`和`ForgeCaps`由`super`方法保留。
+:::
+
+## `BlockEntities` 的定时器
+
+如果你需要一个定时的方块实体,例如跟踪熔炼过程中的进度,那么必须在`EntityBlock`内实现并覆盖另一个方法:`EntityBlock#getTicker(Level, BlockState, BlockEntityType)`。这可以实现不同的定时器,取决于用户所在的逻辑侧,或者只实现一个通用定时器。无论哪种情况,都必须返回一个`BlockEntityTicker`。由于这是一个功能接口,它可以仅采用表示定时器的方法:
+
+```java
+// 在某个Block子类内
+@Nullable
+@Override
+public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) {
+ return type == MyBlockEntityTypes.MYBE.get() ? MyBlockEntity::tick : null;
+}
+
+// 在MyBlockEntity内
+public static void tick(Level level, BlockPos pos, BlockState state, MyBlockEntity blockEntity) {
+ // 执行任务
+}
+```
+
+:::note
+这个方法每个tick都会被调用;因此,你应该避免在这里进行复杂的计算。如果可能,你应该每X个ticks进行更复杂的计算。(一秒钟内的ticks数量可能低于20,但不会更高)
+:::
+
+## 将数据同步到客户端
+
+有三种方法可以将数据同步到客户端:在LevelChunk加载时同步,在方块更新时同步,以及使用自定义网络消息同步。
+
+### 在LevelChunk加载时同步
+
+为此,你需要覆盖
+```java
+BlockEntity#getUpdateTag()
+
+IForgeBlockEntity#handleUpdateTag(CompoundTag tag)
+```
+第一个方法收集应该发送到客户端的数据,而第二个方法处理这些数据。如果你的`BlockEntity`不包含太多数据,你可能可以使用[存储你的方块实体内的数据][storing-data]部分中的方法。
+
+:::caution
+同步过多/无用的方块实体数据可能导致网络拥塞。你应该优化你的网络使用,只在客户端需要时发送客户端需要的信息。例如,通常没有必要在更新标签中发送方块实体的库存,因为这可以通过其[`AbstractContainerMenu`][menu]同步。
+:::
+
+### 在方块更新时同步
+
+这种方法有点复杂,但你只需覆盖两个或三个方法。这里是它的一个小示例实现:
+```java
+@Override
+public CompoundTag getUpdateTag() {
+ CompoundTag tag = new CompoundTag();
+ // 将你的数据写入标签
+ return tag;
+}
+
+@Override
+public Packet getUpdatePacket() {
+ // 从#getUpdateTag获取标签
+ return ClientboundBlockEntityDataPacket.create(this);
+}
+
+// 可以覆盖IForgeBlockEntity#onDataPacket。默认情况下,这将推迟到#load。
+```
+静态构造函数`ClientboundBlockEntityDataPacket#create`接受:
+
+* `BlockEntity`。
+* 一个可选的函数,从`BlockEntity`获取`CompoundTag`。默认情况下,这使用`BlockEntity#getUpdateTag`。
+
+现在,要发送数据包,服务器上必须给出更新通知。
+```java
+Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags)
+```
+`pos`应该是你的`BlockEntity`的位置。
+对于`oldState`和`newState`,你可以传递该位置当前的`BlockState`。
+`flags`是一个位掩码,应包含`2`,这将同步更改到客户端。有关更多信息以及其他标志,请参阅`Block`。标志`2`等同于`Block#UPDATE_CLIENTS`。
+
+### 使用自定义网络消息同步
+
+这种同步方式可能是最复杂的,但通常是最优化的,因为你可以确保只有你需要同步的数据实际上被同步。你应该首先查看[`Networking`][networking]部分,特别是[`SimpleImpl`][simple_impl],然后再尝试这种方式。一旦你创建了自定义网络消息,你可以使用`SimpleChannel#send(PacketDistributor$PacketTarget, MSG)`将其发送给加载了`BlockEntity`的所有用户。
+
+:::caution
+进行安全检查非常重要,当消息到达玩家时,`BlockEntity`可能已经被销毁/替换!你还应该检查块是否已加载(`Level#hasChunkAt(BlockPos)`)。
+:::
+
+[registration]: ../concepts/registries.md#methods-for-registering
+[storing-data]: #storing-data-within-your-blockentity
+[menu]: ../gui/menus.md
+[networking]: ../networking/index.md
+[simple_impl]: ../networking/simpleimpl.md
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/index.md
new file mode 100644
index 000000000..95f1376da
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/index.md
@@ -0,0 +1,235 @@
+## 统一方块的规则
+
+在开始之前,你需要明白在游戏中每个方块都只有一个。一个世界由成千上万个在不同位置引用该方块的实例组成。换句话说,同一个方块多次被显示。
+
+因此,一个方块只应该在[注册]期间实例化一次。一旦注册了方块,你可以根据需要使用已注册的引用。
+
+与大多数其他注册表不同,方块可以使用`DeferredRegister`的特殊版本,称为`DeferredRegister.Blocks`。 `DeferredRegister.Blocks`基本上就像`DeferredRegister`,但有一些细微的差别:
+
+- 它们是通过`DeferredRegister.createBlocks("yourmodid")`创建的,而不是通常的`DeferredRegister.create(...)`方法。
+- `#register`返回一个`DeferredBlock`,它扩展了`DeferredHolder`。 `T`是我们正在注册的方块类的类型。
+- 有一些帮助注册方块的方法。 更多详情请参见[下方]。
+
+现在,让我们注册我们的方块:
+
+```java
+//BLOCKS is a DeferredRegister.Blocks
+public static final DeferredBlock MY_BLOCK = BLOCKS.register("my_block", () -> new Block(...));
+```
+
+注册了方块后,所有对新的`my_block`的引用应使用此常量。例如,如果你想检查给定位置的方块是否是`my_block`,那么相应的代码看起来像这样:
+
+```java
+level.getBlockState(position) //返回在给定位置放置的方块状态
+ //highlight-next-line
+ .is(MyBlockRegistrationClass.MY_BLOCK.get());
+```
+
+这种方法也有一个方便的效果,即`block1 == block2`有效,并且可以代替Java的`equals`方法使用(当然,使用`equals`仍然有效,但是因为它还是通过引用进行比较,所以没有意义)。
+
+:::danger
+不要在注册外部调用`new Block()`!一旦你那样做了,会出现问题:
+
+- 方块必须在注册表解锁时创建。NeoForge为您解锁注册表,并稍后再冻结它们,所以注册是您创建方块的时机窗口。
+- 如果你在注册表再次冻结时尝试创建和/或注册方块,游戏将崩溃并报告一个“null”方块,这可能会非常混乱。
+- 如果你仍然设法拥有一个悬空的方块实例,游戏在同步和保存时将不能识别它,并将其替换为空气。
+:::
+
+## 创建方块
+
+如上所述,我们首先创建我们的`DeferredRegister.Blocks`:
+
+```java
+public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid");
+```
+
+### 基础方块
+
+对于不需要特殊功能的简单方块(如圆石,木板等),可以直接使用`Block`类。要做到这一点,在注册期间,用`BlockBehaviour.Properties`参数实例化`Block`。可以使用`BlockBehaviour.Properties#of`创建此`BlockBehaviour.Properties`参数,并可以通过调用其方法进行定制。这其中最重要的方法是:
+
+- `destroyTime`-决定破坏方块所需的时间。
+ - 石头的破坏时间为1.5,泥土为0.5,黑曜石为50,基岩为-1(不可破坏)。
+- `explosionResistance`-决定方块的抗爆性。
+ - 石头的抗爆性为6.0,泥土为0.5,黑曜石为1,200,基岩为3,600,000。
+- `sound`-设置方块在被击中,打破或放置时的声音。
+ - 默认值是`SoundType.STONE`。更多详细信息请参见[声音页面][sounds]。
+- `lightLevel`-设置方块的光线发射。接收一个带有`BlockState`参数的函数,返回0到15之间的值。
+ 例如,萤石使用`state -> 15`,火炬使用`state -> 14`。
+- `摩擦` - 设置方块的摩擦(滑滑的程度)。
+ - 默认值是0.6。冰使用0.98。
+
+例如,一个简单的实现可能看起来像这样:
+
+```java
+// BLOCKS is a DeferredRegister.Blocks
+public static final DeferredBlock MY_BETTER_BLOCK = BLOCKS.register(
+ "my_better_block",
+ () -> new Block(BlockBehaviour.Properties.of()
+ //highlight-start
+ .destroyTime(2.0f)
+ .explosionResistance(10.0f)
+ .sound(SoundType.GRAVEL)
+ .lightLevel(state -> 7)
+ //highlight-end
+ ));
+```
+
+有关更多文档,请参阅`BlockBehaviour.Properties`的源代码。有关更多示例,或查看Minecraft使用的值,请查看`Blocks`类。
+
+:::note
+重要的是要理解,世界中的一个方块并不同于库存中的东西。库存中看起来像方块的其实是`BlockItem`,它是一种特殊类型的[物品],在使用时会放置一个方块。这也就意味着,创造标签页或最大堆叠大小等内容都由相应的`BlockItem`处理。
+
+`BlockItem`必须与方块单独注册。这是因为方块不一定需要一个物品,例如,如果它不能被收集 (例如火)。
+### 更多功能
+
+直接使用`Block`只能创造非常基本的方块。如果你想添加功能,像是玩家交互或不同的碰撞箱,就需要一个扩展了`Block`的自定义类。`Block`类有许多可以被重写以实现不同功能的方法;更多信息请参见`Block`、`BlockBehaviour`和`IBlockExtension`类。另外,请查看下方的[使用方块][usingblocks]部分,了解一些方块最常见的用例。
+
+如果你想制作一个有不同变体的方块(想想一个有底部、顶部和双层变体的台阶),你应该使用[blockstates]。最后,如果你想要一个可以存储额外数据的方块(比如一个可以存储其库存的箱子),那么应该使用[block entity][blockentities]。这里的经验法则是,如果你有有限而且相当小的状态量(=最多几百个状态),使用blockstates;如果你有无限或近乎无限的状态量,使用方块实体。
+
+### `DeferredRegister.Blocks` 辅助器
+
+我们已经讨论了如何创建`DeferredRegister.Blocks`[上面],以及它返回`DeferredBlock`的内容。现在,让我们看看这个专门的`DeferredRegister`还有哪些辅助工具。我们先从`#registerBlock`开始:
+
+```java
+public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid");
+
+public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerBlock(
+ "example_block",
+ Block::new, // 将使用的属性传递到哪个工厂。
+ BlockBehaviour.Properties.of() // 要使用的属性。
+);
+```
+
+在内部,这将简单地通过应用属性参数到所提供的方块工厂(通常是构造函数)来调用`BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of()))`。
+
+如果你想使用`Block::new`,可以完全不使用工厂:
+
+```java
+public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerSimpleBlock(
+ "example_block",
+ BlockBehaviour.Properties.of() // 要使用的属性。
+);
+```
+
+这和之前的例子做的完全一样,只是稍微简洁了一些。当然,如果你想使用`Block`的子类而不是`Block`本身,你将不得不使用前面的方法。
+
+### 资源
+
+当你注册你的方块并将其放置在世界中时,你会发现它缺少如纹理等内容。这是因为[纹理]等内容是由Minecraft的资源系统处理的。要将纹理应用到方块上,你必须提供一个[模型]和一个与纹理和形状关联的[方块状态文件][bsfile]。阅读链接文章以获取更多信息。
+
+## 使用方块
+
+方块很少直接用来做事。实际上,可能在整个Minecraft中最常见的两个操作 - 获取位置上的方块,和设置位置上的方块 - 使用的是方块状态,而不是方块。一般的设计方法是让方块定义行为,但实际上通过方块状态来运行行为。因此,`BlockState`经常作为参数传递给`Block`的方法。有关如何使用方块状态的更多信息,以及如何从方块获取方块状态,请参见[使用方块状态][usingblockstates]。
+
+在几种情况下,`Block`的多个方法在不同的时间被使用。以下小节列出了最常见的与方块相关的流程。除非另有说明,否则所有方法都在逻辑两侧调用,并应在两侧返回相同的结果。
+
+### 放置方块
+
+方块放置逻辑是从`BlockItem#useOn`(或其某些子类的实现,例如用于睡莲的`PlaceOnWaterBlockItem`)调用的。有关游戏如何到达这一点的更多信息,请参见[交互流程][interactionpipeline]。实际上,这意味着一旦`BlockItem`被右键点击(例如圆石物品),这个行为就被调用。
+
+- 检查几个先决条件,例如你不是在旁观者模式下,所有要求的方块功能标志都已启用,或目标位置不在世界边界之外。如果至少有一个检查失败,流程结束。
+- 对当前位于被尝试放置方块的位置的方块调用`Block#canBeReplaced`。如果它返回`false`,流程结束。在这里返回`true`的显著案例是高草或雪层。
+- 调用`Block#getStateForPlacement`。这是根据上下文(包括位置,旋转和放置方块的侧面等信息)返回不同方块状态的地方。这对于例如可以以不同方向放置的方块很有用。
+- 用前一步获得的方块状态调用`Block#canSurvive`。如果返回`false`,流程结束。
+- 通过`Level#setBlock`调用将方块状态设置到游戏世界中。
+ - 在那个`Level#setBlock`调用中,调用`Block#onPlace`。
+- 调用`Block#setPlacedBy`。
+
+### 破坏方块
+
+破坏方块稍微复杂一些,因为它需要时间。这个过程可以大致分为三个阶段:“启动”,“挖掘”和“实际破坏”。
+
+- 当左键被点击时,进入“启动”阶段。
+- 现在,需要持续按住左键,进入“挖掘”阶段。**这个阶段的方法每个刻都会被调用。**
+- 如果“继续”阶段没有被中断(通过释放左键)并且方块被打破,那么进入“实际破坏”阶段。
+
+或者对于那些更喜欢伪代码的人:
+
+```java
+leftClick();
+initiatingStage();
+while (leftClickIsBeingHeld()) {
+ miningStage();
+ if (blockIsBroken()) {
+ actuallyBreakingStage();
+ break;
+ }
+}
+```
+
+以下小节进一步将这些阶段分解为实际的方法调用。
+
+#### “启动”阶段
+
+- 仅客户端:当左键和主手被触发时,会触发`InputEvent.InteractionKeyMappingTriggered`事件。如果事件被取消,流程结束。
+- 检查几个先决条件,例如你不是在旁观者模式下,主手中的`ItemStack`的所有必需功能标志都已启用,或被询问的方块不在世界边界之外。如果至少有一个检查失败,流程结束。
+- 触发`PlayerInteractEvent.LeftClickBlock`事件。如果事件被取消,流程结束。
+ - 注意当事件在客户端被取消时,不会向服务器发送数据包,因此服务器上不会运行任何逻辑。
+ - 然而,在服务器上取消此事件仍然会导致客户端代码运行,这可能会导致不同步!
+- 调用`Block#attack`。
+
+#### “挖掘”阶段
+
+- 触发`PlayerInteractEvent.LeftClickBlock`事件。如果事件被取消,流程移动到“结束”阶段。
+ - 注意当事件在客户端被取消时,不会向服务器发送数据包,因此服务器上不会运行任何逻辑。
+ - 然而,在服务器上取消此事件仍然会导致客户端代码运行,这可能会导致不同步!
+- 调用`Block#getDestroyProgress`并将其加到内部的破坏进度计数器上。
+ - `Block#getDestroyProgress`返回一个介于0和1之间的浮点值,表示破坏进度计数器每个刻应该增加多少。
+- 相应地更新进度覆盖(破裂纹理)。
+- 如果破坏进度大于1.0(即完成,即方块应该被破坏),则退出“挖掘”阶段并进入“实际破坏”阶段。
+
+#### “实际破坏”阶段
+
+- 调用`Item#onBlockStartBreak`。如果它返回`true`(决定方块不应被破坏),流程移动到“结束”阶段。
+- 仅服务器:调用`IBlockExtension#canHarvestBlock`。这决定了方块是否可以被收获,即是否可以带着掉落物被破坏。
+- 调用`Block#onDestroyedByPlayer`。如果它返回`false`,流程移动到“结束”阶段。在`Block#onDestroyedByPlayer`调用中:
+ - 调用`Block#playerWillDestroy`。
+ - 通过用`Blocks.AIR.defaultBlockState()`作为方块状态参数的`Level#setBlock`调用,从游戏世界中移除方块状态。
+ - 在那个`Level#setBlock`调用中,调用`Block#onRemove`。
+- 调用`Block#destroy`。
+- 仅服务器:如果之前对`IBlockExtension#canHarvestBlock`的调用返回了`true`,则调用`Block#playerDestroy`。
+- 仅服务器:调用`IBlockExtension#getExpDrop`。
+- 仅服务器:如果之前`IBlockExtension#getExpDrop`调用的结果大于0,就调用`Block#popExperience`。
+
+### 游戏刻
+
+游戏刻是一种机制,它在每1/20秒或50毫秒(“一个游戏刻”)更新(游戏刻)游戏的某些部分。方块提供了不同的游戏刻方法,这些方法以不同的方式被调用。
+
+#### 服务器游戏刻和游戏刻调度
+
+`Block#tick`在两种情况下被调用:通过默认的[随机刻][randomtick](见下文),或通过调度的游戏刻。可以通过`Level#scheduleTick(BlockPos, Block, int)`创建调度的游戏刻,其中`int`表示延迟。这在vanilla的多个地方被使用,例如,大型滴叶的倾斜机制就严重依赖于这个系统。其他显著的使用者包括各种红石组件。
+
+#### 客户端游戏刻
+
+`Block#animateTick`仅在客户端,每帧被调用。这是发生客户端仅行为的地方,例如火炬粒子的生成。
+
+#### 天气游戏刻
+
+天气游戏刻由`Block#handlePrecipitation`处理,并独立于常规游戏刻运行。它仅在服务器上被调用,仅当以某种形式下雨时,有1/16的机会被调用。这被用于例如收集雨水或雪水的炼药锅。
+
+#### 随机刻
+
+随机刻系统独立于常规游戏刻运行。随机刻必须通过调用`BlockBehaviour.Properties`的`BlockBehaviour.Properties#randomTicks()`方法来启用。这使得方块可以是随机刻机制的一部分。
+
+每个刻为一个区块中设定数量的方块执行随机刻。这个设定数量是通过`randomTickSpeed`游戏规则定义的。其默认值为3,每个刻,从区块中随机选择3个方块。如果这些方块启用了随机刻,则分别调用它们的`Block#randomTick`方法。
+
+`Block#randomTick`默认调用`Block#tick`,这是通常应该被覆盖的。仅当你特别希望随机刻和常规(调度)游戏刻有不同行为时,才应覆盖`Block#randomTick`。
+
+随机刻被Minecraft中的许多机制使用,例如植物生长、冰雪融化或铜氧化。
+
+[above]: #one-block-to-rule-them-all
+[below]: #deferredregisterblocks-helpers
+[blockentities]: ../blockentities/index.md
+[blockstates]: states.md
+[bsfile]: ../resources/client/models/index.md#blockstate-files
+[events]: ../concepts/events.md
+[interactionpipeline]: ../items/interactionpipeline.md
+[item]: ../items/index.md
+[model]: ../resources/client/models/index.md
+[randomtick]: #random-ticking
+[registration]: ../concepts/registries.md#methods-for-registering
+[resources]: ../resources/index.md#assets
+[sounds]: ../resources/client/sounds.md
+[textures]: ../resources/client/textures.md
+[usingblocks]: #using-blocks
+[usingblockstates]: states.md#using-blockstates
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/states.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/states.md
new file mode 100644
index 000000000..9551668f3
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/states.md
@@ -0,0 +1,151 @@
+Blockstates
+===========
+
+经常会遇到想要拥有不同状态的方块的情况。例如,小麦作物有八个生长阶段,为每个阶段制作一个单独的方块感觉并不合适。或者你有一个台阶或类台阶方块 - 一个底部状态、一个顶部状态,以及一个状态同时具有两者。
+
+这就是Blockstates发挥作用的地方。Blockstates是一种表示方块可以具有的不同状态的简便方法,如生长阶段或台阶放置类型。
+
+Blockstate 属性
+---------------------
+
+Blockstates使用一套属性系统。一个方块可以具有多种类型的多个属性。例如,一个末地传送门框架有两个属性:它是否有一个眼睛(`eye`,2个选项)和放置的方向(`facing`,4个选项)。所以总的来说,末地传送门框架有8(2 * 4)个不同的Blockstates:
+
+```
+minecraft:end_portal_frame[facing=north,eye=false]
+minecraft:end_portal_frame[facing=east,eye=false]
+minecraft:end_portal_frame[facing=south,eye=false]
+minecraft:end_portal_frame[facing=west,eye=false]
+minecraft:end_portal_frame[facing=north,eye=true]
+minecraft:end_portal_frame[facing=east,eye=true]
+minecraft:end_portal_frame[facing=south,eye=true]
+minecraft:end_portal_frame[facing=west,eye=true]
+```
+
+表示法 `blockid[property1=value1,property2=value,...]` 是表示文本形式Blockstate的标准方式,并且在游戏的某些地方使用,例如在命令中。
+
+如果您的方块没有定义任何Blockstate属性,它仍然有一个Blockstate - 那就是没有任何属性的那个,因为没有属性需要指定。这可以表示为 `minecraft:oak_planks[]` 或者简单的 `minecraft:oak_planks`。
+
+与方块一样,每个 `BlockState` 在内存中仅存在一个。这意味着可以并且应该使用 `==` 来比较 `BlockState`。`BlockState` 也是一个终极类,意味着它不能被扩展。**任何功能都在相应的[Block][block]类中!**
+
+何时使用Blockstates
+-----------------------
+
+### Blockstates vs. 独立方块
+
+一个好的经验法则是:**如果它有一个不同的名称,它应该是一个独立的方块**。一个例子是制作椅子方块:椅子的方向应该是一个属性,而不同类型的木头应该分成不同的方块。所以你会有一个椅子方块适用于每种木头类型,每个椅子方块有四个Blockstates(每个方向一个)。
+
+### Blockstates vs. [方块实体][blockentity]
+
+这里的经验法则是:**如果你有一个有限的状态量,使用blockstate,如果你有一个无限或几乎无限的状态量,使用方块实体。** 方块实体可以存储任意量的数据,但比blockstates慢。
+
+Blockstates和方块实体可以联合使用。例如,箱子使用Blockstate属性来表示诸如方向、是否被水淹没或成为双箱子等事物,同时通过方块实体存储库存、是否当前打开或与漏斗的互动等。
+
+没有一个明确的答案来回答“对于Blockstate来说,多少状态太多了?”的问题,但我们建议,如果您需要超过8-9比特的数据(即超过几百种状态),您应该使用方块实体代替。
+
+实现Blockstates
+------------------------
+
+要实现Blockstate属性,在您的方块类中创建或引用一个 `public static final Property>` 常量。虽然您可以自由制作自己的 `Property>` 实现,但游戏代码提供了几种便利实现,应该涵盖大多数用例:
+
+* `IntegerProperty`
+ * 实现 `Property`。定义一个持有整数值的属性。注意不支持负值。
+ * 通过调用 `IntegerProperty#create(String propertyName, int minimum, int maximum)` 创建。
+* `BooleanProperty`
+ * 实现 `Property`。定义一个持有 `true` 或 `false` 值的属性。
+ * 通过调用 `BooleanProperty#create(String propertyName)` 创建。
+* `EnumProperty>`
+ * 实现 `Property`。定义一个可以取枚举类值的属性。
+ * 通过调用 `EnumProperty#create(String propertyName, Class enumClass)` 创建。
+ * 还可以使用枚举值的子集(例如,16个`DyeColor`s中的4个),见 `EnumProperty#create` 的重载方法。
+* `DirectionProperty`
+ * `DirectionProperty`
+ * 扩展自 `EnumProperty`。定义了一个可以承载 `Direction`(方向)的属性。
+ * 通过调用 `DirectionProperty#create(String propertyName)` 来创建。
+ * 提供了几个便利的谓词方法。例如,要获取代表基本方向的属性,调用 `DirectionProperty.create("", Direction.Plane.HORIZONTAL)`;要获取X轴方向,调用 `DirectionProperty.create("", Direction.Axis.X)`。
+
+类 `BlockStateProperties` 包含了共享的原版属性,这些属性应尽可能使用或引用,而不是创建自己的属性。
+
+一旦你有了你的属性常量,在你的方块类中重写 `Block#createBlockStateDefinition(StateDefinition$Builder)`。在该方法中,调用 `StateDefinition.Builder#add(YOUR_PROPERTY);`。`StateDefinition.Builder#add` 有一个变长参数,所以如果你有多个属性,你可以一次性添加它们所有。
+
+每个方块还有一个默认状态。如果没有指定其他内容,缺省状态使用每个属性的默认值。你可以通过从构造函数中调用 `Block#registerDefaultState(BlockState)` 方法来更改默认状态。
+
+如果你希望改变放置方块时使用的 `BlockState`,请重写 `Block#getStateForPlacement(BlockPlaceContext)`。这可以用来设置方块的方向,比如基于玩家放置时的站立位置或方向。
+
+为进一步说明,这是 `EndPortalFrameBlock` 类相关部分的样子:
+
+```java
+public class EndPortalFrameBlock extends Block {
+ // 注意:直接使用 BlockStateProperties 中的值而不是在这里再次引用它们是可能的。
+ // 然而,为了简单和可读性考虑,推荐像这样添加常量。
+ public static final DirectionProperty FACING = BlockStateProperties.FACING;
+ public static final BooleanProperty EYE = BlockStateProperties.EYE;
+
+ public EndPortalFrameBlock(BlockBehaviour.Properties pProperties) {
+ super(pProperties);
+ // stateDefinition.any() 返回一个内部集合中的随机 BlockState,
+ // 我们不在意,因为我们 anyway 要自己设置所有值
+ registerDefaultState(stateDefinition.any()
+ .setValue(FACING, Direction.NORTH)
+ .setValue(EYE, false)
+ );
+ }
+
+ @Override
+ protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) {
+ // 这里是属性实际被添加到状态的地方
+ pBuilder.add(FACING, EYE);
+ }
+
+ @Override
+ @Nullable
+ public BlockState getStateForPlacement(BlockPlaceContext pContext) {
+ // 根据 BlockPlaceContext 确定放置此方块时将使用的状态的
+ // 代码
+ }
+}
+```
+
+使用 Blockstates
+----------------
+
+要从 `Block` 转换到 `BlockState`,调用 `Block#defaultBlockState()`。可以通过 `Block#registerDefaultState` 更改默认 blockstate,如上所述。
+
+你可以通过调用 `BlockState#getValue(Property>)` 来获取一个属性的值,传递你想获取值的属性。复用我们末地传送门框架的例子,这看起来像这样:
+
+```java
+// EndPortalFrameBlock.FACING 是一个 DirectionProperty,因此可以用来从 BlockState 中获取一个 Direction
+Direction direction = endPortalFrameBlockState.getValue(EndPortalFrameBlock.FACING);
+```
+
+如果你想获得一个具有不同值集的 `BlockState`,只需在现有的 block state 上调用 `BlockState#setValue(Property, T)` 并传入属性及其值。对于我们的杠杆来说,像这样:
+
+```java
+endPortalFrameBlockState = endPortalFrameBlockState.setValue(EndPortalFrameBlock.FACING, Direction.SOUTH);
+```
+
+:::note
+`BlockState` 是不可变的。这意味着当你调用 `#setValue(Property, T)` 时,你实际上不是在修改 blockstate。相反,内部进行了查找,你得到了你请求的 blockstate 对象,那是唯一存在的、具有这些确切属性值的对象。这也意味着,仅仅调用 `state#setValue` 而没有将其保存到一个变量中(例如回到 `state` 中)是无效的。
+:::
+
+要从场景中获取一个 `BlockState`,使用 `Level#getBlockState(BlockPos)`。
+
+### `Level#setBlock`
+
+要在场景中设置一个 `BlockState`,使用 `Level#setBlock(BlockPos, BlockState, int)`。
+
+`int` 参数需要额外的解释,因为它的含义不立即明显。它表示更新标志。
+
+为了正确设置更新标志,`Block` 中有一些以 `UPDATE_` 开头的 `int` 常量。这些常量可以被按位或在一起(例如 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS`)如果你希望组合它们。
+
+- `Block.UPDATE_NEIGHBORS` 向相邻方块发送更新。更具体地说,它调用了 `Block#neighborChanged`,它调用了许多方法,其中大部分以某种方式与红石相关。
+- `Block.UPDATE_CLIENTS` 将方块更新同步到客户端。
+- `Block.UPDATE_INVISIBLE` 显式不在客户端更新。这也覆盖了 `Block.UPDATE_CLIENTS`,导致更新不被同步。方块始终在服务器上更新。
+- `Block.UPDATE_IMMEDIATE` 强制在客户端的主线程上重新渲染。
+- `Block.UPDATE_KNOWN_SHAPE` 停止邻居更新递归。
+- `Block.UPDATE_SUPPRESS_DROPS` 禁止旧方块在该位置的掉落物。
+- `Block.UPDATE_MOVE_BY_PISTON` 仅被活塞代码用于表示方块被活塞移动。这主要是为了延迟光照引擎的更新。
+- `Block.UPDATE_ALL` 是 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS` 的别名。
+- `Block.UPDATE_ALL_IMMEDIATE` 是 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE` 的别名。
+- `Block.NONE` 是 `Block.UPDATE_INVISIBLE` 的别名。
+
+还有一个便利方法 `Level#setBlockAndUpdate(BlockPos pos, BlockState state)`,它在内部调用 `setBlock(pos, state, Block.UPDATE_ALL)`。
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/_category_.json
new file mode 100644
index 000000000..5e1f44f64
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/_category_.json
@@ -0,0 +1,3 @@
+{
+ "label": "Concepts"
+}
\ No newline at end of file
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/events.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/events.md
new file mode 100644
index 000000000..83debfc15
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/events.md
@@ -0,0 +1,137 @@
+# 事件系统
+
+NeoForge 的核心特性之一是其事件系统。在游戏中,各种事件根据游戏内的不同动作而触发。比如玩家右键点击、玩家或其他实体跳跃、方块渲染、游戏加载时等,都会触发相应的事件。模组开发者可以为这些事件编写处理函数,并在函数中实现他们期望的行为。
+
+这些事件会在相应的事件总线上触发。其中最重要的是 `NeoForge.EVENT_BUS`。此外,在游戏启动期间,系统会为每个加载的模组生成一个独立的模组总线,并传递给模组的构造函数。许多模组总线事件是并行触发的,这与总在同一个线程上运行的主总线事件不同,这种设计显著提高了启动速度。更多细节,请参考[下文][modbus]。
+
+## 注册事件处理函数
+
+注册事件处理函数有多种方式。所有这些方式的共同点是,每个事件处理函数都是一个只接收单一事件参数并且不返回结果(即返回类型为 `void`)的方法。
+
+### `IEventBus#addListener`
+
+最简单的注册方法是直接引用方法,如下所示:
+
+```java
+@Mod("yourmodid")
+public class YourMod {
+ public YourMod(IEventBus modBus) {
+ NeoForge.EVENT_BUS.addListener(YourMod::onLivingJump);
+ }
+
+ // 每次实体跳跃时为其恢复半颗心的生命值。
+ private static void onLivingJump(LivingJumpEvent event) {
+ Entity entity = event.getEntity();
+ // 仅在服务器端进行治疗
+ if (!entity.level().isClientSide()) {
+ entity.heal(1);
+ }
+ }
+}
+```
+
+### `@SubscribeEvent`
+
+另一种方式是使用注解来驱动事件处理,为处理函数添加 `@SubscribeEvent` 注解。然后将包含该处理函数的类的实例传递给事件总线,从而注册该实例中所有带有 `@SubscribeEvent` 注解的事件处理函数:
+
+```java
+public class EventHandler {
+ @SubscribeEvent
+ public void onLivingJump(LivingJumpEvent event) {
+ Entity entity = event.getEntity();
+ if (!entity.level().isClientSide()) {
+ entity.heal(1);
+ }
+ }
+}
+
+@Mod("yourmodid")
+public class YourMod {
+ public YourMod(IEventBus modBus) {
+ NeoForge.EVENT_BUS.addListener(new EventHandler());
+ }
+}
+```
+
+你还可以通过将所有事件处理函数设置为静态,并直接传递类本身,而不是类的实例来实现:
+
+```java
+public class EventHandler {
+ @SubscribeEvent
+ public static void onLivingJump(LivingJumpEvent event) {
+ Entity entity = event.getEntity();
+ if (!entity.level().isClientSide()) {
+ entity.heal(1);
+ }
+ }
+}
+
+@Mod("yourmodid")
+public class YourMod {
+ public YourMod(IEventBus modBus) {
+ NeoForge.EVENT_BUS.addListener(EventHandler.class);
+ }
+}
+```
+
+### `@Mod.EventBusSubscriber`
+
+我们可以进一步优化,将事件处理类标注为 `@Mod.EventBusSubscriber`。这个注解会被 NeoForge 自动识别,允许你从模组构造函数中移除所有与事件相关的代码。实际上,这等同于在模组构造结束时调用 `NeoForge.EVENT_BUS.register(EventHandler.class)`。这也意味着所有的处理函数必须设置为静态。
+
+虽然不是必须的,但强烈建议在注解中指定 `modid` 参数,以便在处理模组冲突时能够更容易进行调试。
+
+```java
+@Mod.EventBusSubscriber(modid = "yourmodid")
+public class EventHandler {
+ @SubscribeEvent
+ public static void onLivingJump(LivingJumpEvent event) {
+ Entity entity = event.getEntity();
+ if (!entity.level().isClientSide()) {
+ entity.heal(1);
+ }
+ }
+}
+```
+
+### 生命周期事件
+
+大多数模组总
+
+线事件被称为生命周期事件。生命周期事件在每个模组的生命周期中仅在启动时运行一次。很多这类事件是并行触发的,如果你想要在主线程上运行这些事件的代码,可以使用 `#enqueueWork(Runnable runnable)` 方法将它们加入队列。
+
+生命周期事件通常按以下顺序进行:
+
+- 调用模组构造函数。在这里或下一步注册你的事件处理函数。
+- 所有的 `@Mod.EventBusSubscriber` 被调用。
+- 触发 `FMLConstructModEvent` 事件。
+- 触发注册事件,包括 [`NewRegistryEvent`][newregistry]、[`DataPackRegistryEvent.NewRegistry`][newdatapackregistry] 以及每个注册表的 [`RegisterEvent`][registerevent]。
+- 触发 `FMLCommonSetupEvent` 事件。这是进行各种杂项设置的阶段。
+- 根据服务器类型触发侧边设置事件:如果在客户端,则为 `FMLClientSetupEvent`;如果在服务器,则为 `FMLDedicatedServerSetupEvent`。
+- 处理 `InterModComms`(详情见下文)。
+- 触发 `FMLLoadCompleteEvent` 事件。
+
+#### `InterModComms`
+
+`InterModComms` 是一个系统,允许模组开发者向其他模组发送消息以实现功能兼容。这个系统保存了模组的消息,所有方法都是线程安全的。主要通过两个事件推动:`InterModEnqueueEvent` 和 `InterModProcessEvent`。
+
+在 `InterModEnqueueEvent` 期间,你可以使用 `InterModComms#sendTo` 向其他模组发送消息。这些方法接受要发送消息到的模组的 ID、与消息数据相关的键(以区分不同的消息),以及持有消息数据的 `Supplier`。发送者可以选择性指定。
+
+接着,在 `InterModProcessEvent` 期间,你可以使用 `InterModComms#getMessages` 获取作为 `IMCMessage` 对象的所有接收到的消息的流。这些消息包含了数据的发送者、预期的接收者、数据键和实际数据的供应商。
+
+### 其他模组总线事件
+
+除了生命周期事件外,还有一些其他在模组总线上触发的杂项事件,主要是出于历史原因。这些事件通常不是并行运行的,与生命周期事件相反。例如:
+
+- `RegisterColorHandlersEvent`
+- `ModelEvent.BakingCompleted`
+- `TextureStitchEvent`
+
+:::warning
+计划在未来版本中将大多数这些事件转移到主事件总线上。
+:::
+
+[modbus]: #event-buses
+[newdatapackregistry]: registries.md#custom-datapack-registries
+[newregistry]: registries.md#custom-registries
+[registerevent]: registries.md#registerevent
+[side]: sides.md
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/registries.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/registries.md
new file mode 100644
index 000000000..c959c41e3
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/registries.md
@@ -0,0 +1,320 @@
+# 注册
+
+注册是将模组中的对象(如[物品][item]、[方块][block]、实体等)加入游戏并使其被游戏识别的过程。注册这些对象非常重要,因为如果不注册,游戏将无法识别这些对象,这将导致无法解释的行为和崩溃。
+
+简而言之,注册表是围绕映射注册名称(下面将说明)到注册对象的映射的封装,这些注册对象通常称为注册表项。注册名在同一注册表中必须唯一,但同一注册名可以出现在多个注册表中。最常见的例子是方块(在`BLOCKS`注册表中)具有与其同名的物品形式(在`ITEMS`注册表中)。
+
+每个注册对象都有一个唯一的名称,称为其注册名称。名称表示为[`ResourceLocation`][resloc]。例如,泥土方块的注册名称为`minecraft:dirt`,僵尸的注册名称为`minecraft:zombie`。当然,模组化对象不会使用`minecraft`命名空间;而是使用它们的模组ID。
+
+## 原版与模组化
+
+为了理解NeoForge的注册系统中所做的一些设计决策,我们首先看看Minecraft是如何处理这一问题的。我们将使用方块注册表作为例子,因为大多数其他注册表的工作方式相同。
+
+注册表通常注册[单例][singleton]。这意味着所有注册表项实际上只存在一次。例如,你在游戏中看到的所有石块实际上都是同一个石块,被多次显示。如果你需要石块,可以通过引用已注册的方块实例来获取它。
+
+Minecraft在`Blocks`类中注册所有方块。通过`register`方法,调用`Registry#register()`,其中第一个参数是方块注册表`BuiltInRegistries.BLOCK`。所有方块注册完成后,Minecraft会根据方块列表进行各种检查,例如验证所有方块是否已加载模型的自检。
+
+这一切之所以能够工作,是因为`Blocks`类在Minecraft中足够早地被类加载。模组并不会被Minecraft自动类加载,因此需要一些变通方法。
+
+## 注册方法
+
+NeoForge提供了两种注册对象的方式:`DeferredRegister`类和`RegisterEvent`。请注意,前者是后者的封装,并且为了防止错误,推荐使用。
+
+### `DeferredRegister`
+
+我们首先创建我们的`DeferredRegister`:
+
+```java
+public static final DeferredRegister BLOCKS = DeferredRegister.create(
+ // 我们想要使用的注册表。
+ // Minecraft的注册表可以在BuiltInRegistries中找到,NeoForge的注册表可以在NeoForgeRegistries中找到。
+ // 模组也可以添加它们自己的注册表,具体请参考各个模组的文档或源代码。
+ BuiltInRegistries.BLOCKS,
+ // 我们的模组ID。
+ ExampleMod.MOD_ID
+);
+```
+
+然后,我们可以将我们的注册表项添加为静态最终字段(有关在`new Block()`中添加什么参数,请参阅[关于方块的文章][block]):
+
+```java
+public static final DeferredHolder EXAMPLE_BLOCK = BLOCKS.register(
+ "example_block" // 我们的注册名称。
+ () -> new Block(...) // 我们想要注册的对象的供应商。
+);
+```
+
+`DeferredHolder`类持有我们的对象。类型参数`R`是我们正在注册到的注册表的类型(在这个例子中是`Block`)。类型参数`T`是我们供应商的类型。由于我们在这个例子中直接注册了一个`Block`,我们提供了`Block`作为第二个参数。如果我们
+
+要注册一个`Block`的子类的对象,例如`SlabBlock`,我们将在此提供`SlabBlock`。
+
+`DeferredHolder`是`Supplier`的子类。当我们需要时可以调用`DeferredHolder#get()`来获取我们注册的对象。`DeferredHolder`扩展`Supplier`的事实也允许我们使用`Supplier`作为我们字段的类型。这样,上面的代码块变为以下内容:
+
+```java
+public static final Supplier EXAMPLE_BLOCK = BLOCKS.register(
+ "example_block" // 我们的注册名称。
+ () -> new Block(...) // 我们想要注册的对象的供应商。
+);
+```
+
+请注意,有些地方明确要求使用`Holder`或`DeferredHolder`,而不仅仅接受任何`Supplier`。如果你需要其中的两者之一,最好将你的`Supplier`的类型更改回`Holder`或`DeferredHolder`。
+
+最后,由于整个系统是围绕注册事件的封装,我们需要告诉`DeferredRegister`根据需要将自己附加到注册事件上:
+
+```java
+// 这是我们的模组构造函数
+public ExampleMod(IModEventBus bus) {
+ // 高亮下一行
+ ExampleBlocksClass.BLOCKS.register(bus);
+ // 这里还有其他内容
+}
+```
+
+:::info
+有针对方块和物品的`DeferredRegister`的特化变体,它们提供辅助方法,分别称为[`DeferredRegister.Blocks`][defregblocks]和[`DeferredRegister.Items`][defregitems]。
+:::
+
+### `RegisterEvent`
+
+`RegisterEvent`是注册对象的第二种方式。这个[事件][event]在模组构造函数之后(因为这是`DeferredRegister`注册它们内部事件处理器的地方)和加载配置之前为每个注册表触发。`RegisterEvent`在模组事件总线上触发。
+
+```java
+@SubscribeEvent
+public void register(RegisterEvent event) {
+ event.register(
+ // 这是注册表的注册键。
+ // 从BuiltInRegistries获取vanilla注册表的,
+ // 或从NeoForgeRegistries.Keys获取NeoForge注册表的。
+ BuiltInRegistries.BLOCKS,
+ // 在这里注册你的对象。
+ registry -> {
+ registry.register(new ResourceLocation(MODID, "example_block_1"), new Block(...));
+ registry.register(new ResourceLocation(MODID, "example_block_2"), new Block(...));
+ registry.register(new ResourceLocation(MODID, "example_block_3"), new Block(...));
+ }
+ );
+}
+```
+
+## 查询注册表
+
+有时候,你可能会发现自己处于想要通过给定ID获取注册对象的情况,或者你想要获取某个注册对象的ID。由于注册表本质上是ID(`ResourceLocation`)到独立对象的映射,即可逆映射,这两种操作都是可行的:
+
+```java
+BuiltInRegistries.BLOCKS.get(new ResourceLocation("minecraft", "dirt")); // 返回泥土方块
+BuiltInRegistries.BLOCKS.getKey(Blocks.DIRT); // 返回资源位置"minecraft:dirt"
+
+// 假设ExampleBlocksClass.EXAMPLE_BLOCK.get()是具有ID"yourmodid:example_block"的Supplier
+BuiltInRegistries.BLOCKS.get(new ResourceLocation("yourmodid", "example_block")); // 返回示例方块
+BuiltInRegistries.BLOCKS.getKey(ExampleBlocksClass.EXAMPLE_BLOCK.get()); // 返回资源位置"yourmodid:example_block"
+```
+
+如果你只是想检查是否存在某个对象,这也是可能的,尽管只能用键:
+
+```java
+BuiltInRegistries.BLOCKS.containsKey(new ResourceLocation("minecraft", "dirt")); // true
+BuiltInRegistries.BLOCKS.containsKey(new ResourceLocation("create", "brass_ingot")); // 如果安装了Create则为true
+```
+
+正如最后一个示例所示,这适用于任何模组ID,因此是检查另一个模组中是否存在某个物品的完美方式。
+
+最后,我们还可以迭代注册表中的所有条目,无论是键还
+
+是条目(条目使用Java的`Map.Entry`类型):
+
+```java
+for (ResourceLocation id : BuiltInRegistries.BLOCKS.keySet()) {
+ // ...
+}
+for (Map.Entry entry : BuiltInRegistries.BLOCKS.entrySet()) {
+ // ...
+}
+```
+
+:::note
+查询操作始终使用vanilla `Registry`,而不是`DeferredRegister`。这是因为`DeferredRegister`只是注册工具。
+:::
+
+:::danger
+查询操作只有在注册完成后才安全使用。**不要在注册仍在进行时查询注册表!**
+:::
+
+## 自定义注册表
+
+自定义注册表允许你指定其他模组可能想要接入的附加系统。例如,如果你的模组要添加效果,你可以使效果成为一个注册表,从而允许其他模组添加效果到你的模组中,而无需你做任何其他事情。它还允许你自动执行一些操作,如同步条目。
+
+让我们从创建[注册表键][resourcekey]和注册表本身开始:
+
+```java
+// 我们在这里使用效果作为注册表的例子,不涉及效果实际是什么(因为这不重要)。
+// 当然,所有提到的效果都可以并且应该替换为你的注册表实际是什么。
+public static final ResourceKey> SPELL_REGISTRY_KEY = ResourceKey.createRegistryKey(new ResourceLocation("yourmodid", "spells"));
+public static final Registry SPELL_REGISTRY = new RegistryBuilder<>(SPELL_REGISTRY_KEY)
+ // If you want to enable integer id syncing, for networking.
+ // These should only be used in networking contexts, for example in packets or purely networking-related NBT data.
+ .sync(true)
+ // The default key. Similar to minecraft:air for blocks. This is optional.
+ .defaultKey(new ResourceLocation("yourmodid", "empty"))
+ // Effectively limits the max count. Generally discouraged, but may make sense in settings such as networking.
+ .maxId(256)
+ // Build the registry.
+ .create();
+```
+
+然后,通过将注册表注册到 `NewRegistryEvent` 中的根注册表来告诉游戏注册表存在:
+
+```java
+@SubscribeEvent
+static void registerRegistries(NewRegistryEvent event) {
+ event.register(SPELL_REGISTRY);
+}
+```
+
+现在,您可以像使用任何其他注册表一样,通过`DeferredRegister`和`RegisterEvent`注册新的注册表内容:
+```java
+public static final DeferredRegister SPELLS = DeferredRegister.create("yourmodid", SPELL_REGISTRY);
+public static final Supplier EXAMPLE_SPELL = SPELLS.register("example_spell", () -> new Spell(...));
+
+// Alternatively:
+@SubscribeEvent
+public static void register(RegisterEvent event) {
+ event.register(SPELL_REGISTRY, registry -> {
+ registry.register(new ResourceLocation("yourmodid", "example_spell"), () -> new Spell(...));
+ });
+}
+```
+
+# 数据包注册表
+
+数据包注册表(也称为动态注册表或世界生成注册表)是一种特殊的注册表,它在世界加载时从数据包 JSON 文件中加载数据,而不是在游戏启动时加载。默认的数据包注册表主要包括大多数世界生成注册表和其他一些注册表。
+
+数据包注册表允许通过 JSON 文件指定其内容。这意味着除了数据生成工具外,不需要任何代码(如果你不想自己编写 JSON 文件的话)。每个数据包注册表都有一个与之关联的编解码器(`Codec`),用于序列化,每个注册表的 ID 决定了其数据包路径:
+
+- Minecraft 的数据包注册表使用格式 `data/yourmodid/registrypath`(例如 `data/yourmodid/worldgen/biomes`,其中 `worldgen/biomes` 是注册表路径)。
+- 所有其他数据包注册表(NeoForge 或模组化的)使用格式 `data/yourmodid/registrynamespace/registrypath`(例如 `data/yourmodid/neoforge/loot_modifiers`,其中 `neoforge` 是注册表命名空间,`loot_modifiers` 是注册表路径)。
+
+可以从 `RegistryAccess` 获取数据包注册表。如果在服务器上,可以通过调用 `ServerLevel#registryAccess()` 来检索此 `RegistryAccess`;如果在客户端,可以通过调用 `Minecraft.getInstance().connection#registryAccess()` 来检索(后者仅在实际连接到世界时有效,否则连接将为 null)。然后可以像使用任何其他注册表一样使用这些调用的结果来获取特定元素或遍历内容。
+
+### 自定义数据包注册表
+
+自定义数据包注册表不需要构建 `Registry`。相反,它们只需要一个注册表键和至少一个编解码器(`Codec`)来序列化和反序列化其内容。根据之前的法术示例,将我们的法术注册表注册为数据包注册表的过程如下所示:
+
+```java
+public static final ResourceKey> SPELL_REGISTRY_KEY = ResourceKey.createRegistryKey(new ResourceLocation("yourmodid", "spells"));
+
+@SubscribeEvent
+public static void registerDatapackRegistries(DataPackRegistryEvent.NewRegistry event) {
+ event.dataPackRegistry(
+ // 注册表键。
+ SPELL_REGISTRY_KEY,
+ // 注册表内容的编解码器。
+ Spell.CODEC,
+ // 网络编解码器。通常与普通编解码器相同。
+ // 可能是普通编解码器的简化版本,省略了客户端不需要的数据。
+ // 可能为 null。如果为 null,则注册表条目根本不会同步到客户端。
+ // 可以省略,这在功能上与传递 null 相同(调用了一个带有两个参数的方法重载,该重载向普通的三参数方法传递 null)。
+ Spell.CODEC
+ );
+}
+```
+
+### 数据包注册表的数据生成
+
+由于手工编写所有 JSON 文件既繁琐又容易出错,NeoForge 提供了一个数据提供器来为你生成 JSON 文件。这适用于内置的和你自己的数据包注册表。
+
+首先,我们创建一个 `RegistrySetBuilder` 并向其添加条目(一个 `RegistrySetBuilder` 可以包含多个注册表的条目):
+
+```java
+new RegistrySetBuilder()
+ .add(Registries.CONFIGURED_FEATURE, bootstrap -> {
+ // 通过引导上下文注册配置特性。
+ })
+ .add(Registries.PLACED_FEATURE, bootstrap -> {
+ // 通过引导上下文注册放置特性。
+ });
+```
+
+`bootstrap` lambda 参数是我们实际用来注册对象的。要注册一个对象,我们这样调用 `#register`:
+
+```java
+// 我们对象的资源键。
+public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create(
+ Registries.CONFIGURED_FEATURE,
+ new ResourceLocation(MOD_ID, "example_configured_feature
+
+")
+);
+
+new RegistrySetBuilder()
+ .add(Registries.CONFIGURED_FEATURE, bootstrap -> {
+ bootstrap.register(
+ // 我们配置特性的资源键。
+ EXAMPLE_CONFIGURED_FEATURE,
+ // 实际的配置特性。
+ new ConfiguredFeature<>(Feature.ORE, new OreConfiguration(...))
+ );
+ })
+ .add(Registries.PLACED_FEATURE, bootstrap -> {
+ // ...
+ });
+```
+
+如果需要,`BootstrapContext`(在 1.20.4 及以下版本中名称误写为 `BootstapContext`)还可以用来从另一个注册表查找条目:
+
+```java
+public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create(
+ Registries.CONFIGURED_FEATURE,
+ new ResourceLocation(MOD_ID, "example_configured_feature")
+);
+public static final ResourceKey EXAMPLE_PLACED_FEATURE = ResourceKey.create(
+ Registries.PLACED_FEATURE,
+ new ResourceLocation(MOD_ID, "example_placed_feature")
+);
+
+new RegistrySetBuilder()
+ .add(Registries.CONFIGURED_FEATURE, bootstrap -> {
+ bootstrap.register(EXAMPLE_CONFIGURED_FEATURE, ...);
+ })
+ .add(Registries.PLACED_FEATURE, bootstrap -> {
+ HolderGetter> otherRegistry = bootstrap.lookup(Registries.CONFIGURED_FEATURE);
+ bootstrap.register(EXAMPLE_PLACED_FEATURE, new PlacedFeature(
+ otherRegistry.getOrThrow(EXAMPLE_CONFIGURED_FEATURE), // 获取配置特性
+ List.of() // 放置发生时无操作 - 替换为你的放置参数
+ ));
+ });
+```
+
+最后,我们在实际的数据提供器中使用我们的 `RegistrySetBuilder` 并将该数据提供器注册到事件中:
+
+```java
+@SubscribeEvent
+static void onGatherData(GatherDataEvent event) {
+ event.getGenerator().addProvider(
+ // 仅在生成服务器数据时运行数据包生成
+ event.includeServer(),
+ // 创建提供器
+ output -> new DatapackBuiltinEntriesProvider(
+ output,
+ event.getLookupProvider(),
+ // 我们的注册表集生成器来生成数据。
+ new RegistrySetBuilder().add(...),
+ // 我们正在生成的模组 ID 集合。通常只有你自己的模组 ID。
+ Set.of("yourmodid")
+ )
+ );
+}
+```
+
+[block]: ../blocks/index.md
+[blockentity]: ../blockentities/index.md
+[codec]: ../datastorage/codecs.md
+[datagen]: #data-generation-for-datapack-registries
+[datagenindex]: ../resources/index.md#data-generation
+[datapack]: ../resources/server/index.md
+[defregblocks]: ../blocks/index.md#deferredregisterblocks-helpers
+[defregitems]: ../items/index.md#deferredregisteritems
+[event]: ./events.md
+[item]: ../items/index.md
+[resloc]: ../misc/resourcelocation.md
+[resourcekey]: ../misc/resourcelocation.md#resourcekeys
+[singleton]: https://en.wikipedia.org/wiki/Singleton_pattern
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/sides.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/sides.md
new file mode 100644
index 000000000..e2154f25c
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/sides.md
@@ -0,0 +1,77 @@
+# 游戏两侧的区分
+
+和许多其他程序一样,Minecraft 遵循客户端-服务器的概念,其中客户端负责显示数据,而服务器负责更新数据。当我们使用这些术语时,我们对其含义有一个相当直观的理解...对吗?
+
+事实证明,并非如此。很多混淆源于 Minecraft 有两种不同的“侧”的概念,这取决于上下文:物理侧和逻辑侧。
+
+## 逻辑侧与物理侧
+
+### 物理侧
+
+当你打开 Minecraft 启动器,选择一个 Minecraft 安装并按下播放时,你启动了一个**物理客户端**。这里使用“物理”一词是在“这是一个客户端程序”的意义上。这尤其意味着客户端功能,如所有渲染相关的功能,都可以在这里使用。相比之下,**物理服务器**,也称为专用服务器,是在你启动一个 Minecraft 服务器 JAR 时打开的。虽然 Minecraft 服务器带有一个基本的 GUI,但它缺少所有仅限客户端的功能。最值得注意的是,各种客户端类在服务器 JAR 中缺失。在物理服务器上调用这些类将导致缺少类错误,即崩溃,因此我们需要对此进行防护。
+
+### 逻辑侧
+
+逻辑侧主要关注 Minecraft 的内部程序结构。**逻辑服务器**是游戏逻辑运行的地方。如时间和天气变化、实体更新、实体生成等都在服务器上运行。所有种类的数据,如库存内容,也都是服务器的责任。另一方面,**逻辑客户端**负责显示所有需要显示的内容。Minecraft 在一个名为 `net.minecraft.client` 的独立包中保留了所有客户端代码,并在一个名为渲染线程的独立线程中运行它,而其他所有内容都被视为公共代码(即客户端和服务器代码)。
+
+### 两者有何区别?
+
+物理侧和逻辑侧之间的区别最好通过两种情况来说明:
+
+- 玩家加入一个**多人游戏**世界。这相当直接:玩家的物理(和逻辑)客户端连接到别处的一个物理(和逻辑)服务器——玩家不关心在哪里;只要他们能连接,这就是所有客户端知道的,也是所有客户端需要知道的。
+- 玩家加入一个**单人游戏**世界。这里的情况变得有趣。玩家的物理客户端启动了一个逻辑服务器,然后现在作为逻辑客户端,连接到同一台机器上的那个逻辑服务器。如果你熟悉网络,你可以把它想象为连接到`localhost`(只是概念上的;没有实际的套接字或类似的东西涉及)。
+
+这两种情况也显示了主要问题:如果一个逻辑服务器可以使用你的代码,这并不保证物理服务器也能同样使用。这就是为什么你应该始终使用专用服务器进行测试,以检查意外行为。由于客户端和服务器分离不当导致的`NoClassDefFoundError`和`ClassNotFoundException`是模组制作中最常见的错误之一。另一个常见的错误是使用静态字段,并从两个逻辑侧访问它们;这特别棘手,因为通常没有迹象表明有什么问题。
+
+:::tip
+如果你需要将数据从一侧传输到另一侧,你必须[发送一个数据包
+
+][networking]。
+:::
+
+在 NeoForge 代码库中,物理侧由一个名为 `Dist` 的枚举表示,而逻辑侧由一个名为 `LogicalSide` 的枚举表示。
+
+:::info
+从历史上看,服务器 JAR 拥有客户端没有的类。在现代版本中,这种情况已不复存在;如果愿意,可以认为物理服务器是物理客户端的一个子集。
+:::
+
+## 执行侧特定操作
+
+### `Level#isClientSide()`
+
+这个布尔检查将是你最常用的检查侧的方式。在 `Level` 对象上查询此字段可以确定级别所属的**逻辑**侧:如果此字段为 `true`,则级别在逻辑客户端上运行。如果字段为 `false`,则级别在逻辑服务器上运行。据此,物理服务器将始终在此字段中包含 `false`,但我们不能假设 `false` 暗示物理服务器,因为此字段也可能在物理客户端内的逻辑服务器(即单人游戏世界)中为 `false`。
+
+只有在需要确定是否运行游戏逻辑和其他机制时才使用此检查。例如,如果你想在玩家每次点击你的方块时对玩家造成伤害,或者让你的机器将泥土处理成钻石,你应该确保 `#isClientSide` 为 `false` 后再进行。在逻辑客户端应用游戏逻辑可能导致最好的情况下出现数据不同步(幽灵实体、不同步的统计数据等),在最坏的情况下导致崩溃。
+
+:::tip
+这个检查应该作为你的默认选择。每当你有一个 `Level` 可用时,就使用这个检查。
+:::
+
+### `FMLEnvironment.dist`
+
+`FMLEnvironment.dist` 是 `Level#isClientSide()` 检查的**物理**对应项。如果此字段为 `Dist.CLIENT`,你就在物理客户端上。如果字段为 `Dist.SERVER`,你就在物理服务器上。
+
+检查物理环境在处理仅限客户端的类时非常重要。所有对客户端代码的调用都应始终包含在对 `Dist.CLIENT` 的检查中,并然后调用一个单独的类以防止意外的类加载:
+
+```java
+public class SomeCommonClass {
+ public void someCommonMethod() {
+ // 仅当你在物理客户端上时,SomeClientClass 才会被加载
+ if (FMLEnvironment.dist == Dist.CLIENT) {
+ SomeClientClass.someClientMethod();
+ }
+ }
+}
+
+public class SomeClientClass {
+ public static void someClientMethod() {
+ Minecraft.getInstance().whatever();
+ }
+}
+```
+
+:::tip
+通常期望模组在任一侧都能工作。这特别意味着,如果你正在开发一个仅限客户端的模组,你应该验证该模组实际上在物理客户端上运行,并且在不运行的情况下无操作。
+:::
+
+[networking]: ../networking/index.md
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/_category_.json
new file mode 100644
index 000000000..e3a3b9265
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/_category_.json
@@ -0,0 +1,3 @@
+{
+ "label": "Datagen"
+}
\ No newline at end of file
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/advancements.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/advancements.md
new file mode 100644
index 000000000..bca701e5c
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/advancements.md
@@ -0,0 +1,68 @@
+# 成就生成
+======================
+
+[成就] 可以通过构建一个新的 `AdvancementProvider` 并提供 `AdvancementSubProvider` 来为模组生成。成就可以手动创建和提供,或者为了方便,使用 `Advancement$Builder` 创建。提供者必须[添加][datagen]到 `DataGenerator` 中。
+
+:::note
+Forge 提供了一个扩展的 `AdvancementProvider`,名为 `ForgeAdvancementProvider`,它更适合生成成就。因此,本文档将使用 `ForgeAdvancementProvider` 以及子提供者接口 `ForgeAdvancementProvider$AdvancementGenerator`。
+:::
+
+```java
+// 在 MOD 事件总线上
+@SubscribeEvent
+public void gatherData(GatherDataEvent event) {
+ event.getGenerator().addProvider(
+ // 仅在生成服务器数据时运行生成器
+ event.includeServer(),
+ output -> new ForgeAdvancementProvider(
+ output,
+ event.getLookupProvider(),
+ event.getExistingFileHelper(),
+ // 生成成就的子提供者
+ List.of(subProvider1, subProvider2, /*...*/)
+ )
+ );
+}
+```
+
+`ForgeAdvancementProvider$AdvancementGenerator`
+-----------------------------------------------
+
+`ForgeAdvancementProvider$AdvancementGenerator` 负责生成成就,包含一个方法,该方法接收注册表查找、写入者 (`Consumer`) 和现有文件助手。
+
+```java
+// 在 ForgeAdvancementProvider$AdvancementGenerator 的某个子类中或作为 lambda 引用
+
+@Override
+public void generate(HolderLookup.Provider registries, Consumer writer, ExistingFileHelper existingFileHelper) {
+ // 在这里构建成就
+}
+```
+
+`Advancement$Builder`
+---------------------
+
+`Advancement$Builder` 是一个便利的实现,用于创建用于生成的 `Advancement`。它允许定义父成就、显示信息、完成成就时的奖励以及解锁成就的要求。只需指定要求即可创建一个 `Advancement`。
+
+虽然不是必需的,但有几个方法是重要的:
+
+方法 | 描述
+:---: | :---
+`parent` | 设置此成就直接链接到的成就。可以指定成就的名称或如果由模组制作者生成,则指定成就本身。
+`display` | 设置显示在聊天、弹窗和成就屏幕上的信息。
+`rewards` | 设置完成此成就时获得的奖励。
+`addCriterion` | 为成就添加条件。
+`requirements` | 指定条件是否必须全部为真,或者至少有一个为真。可以使用额外的重载来混合这些操作。
+
+一旦 `Advancement$Builder` 准备好建造,应调用 `#save` 方法,该方法需要写入者、成就的注册名和用于检查提供的父项是否存在的文件助手。
+
+```java
+// 在某个 ForgeAdvancementProvider$AdvancementGenerator#generate(registries, writer, existingFileHelper) 中
+Advancement example = Advancement.Builder.advancement()
+ .addCriterion("example_criterion", triggerInstance) // 如何解锁成就
+ .save(writer, name, existingFileHelper); // 将数据添加到构建器
+```
+
+[成就]: ../../resources/server/advancements.md
+[datagen]: ../index.md#data-providers
+[conditional]: ../../resources/server/conditional.md
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/glm.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/glm.md
new file mode 100644
index 000000000..e7aafcebe
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/glm.md
@@ -0,0 +1,30 @@
+# 全局战利品修改器生成
+===============================
+
+通过继承 `GlobalLootModifierProvider` 并实现 `#start` 方法,可以为模组生成[全局战利品修改器 (GLMs)][glm]。通过调用 `#add` 并指定修改器的名称和将被序列化的[修改器实例][instance],可以添加生成每个 GLM。实现后,必须将提供者[添加][datagen]到 `DataGenerator`。
+
+```java
+// 在 MOD 事件总线上
+@SubscribeEvent
+public void gatherData(GatherDataEvent event) {
+ event.getGenerator().addProvider(
+ // 通知生成器仅在生成服务器数据时运行
+ event.includeServer(),
+ output -> new MyGlobalLootModifierProvider(output, MOD_ID)
+ );
+}
+
+// 在某个 GlobalLootModifierProvider#start 中
+this.add("example_modifier", new ExampleModifier(
+ new LootItemCondition[] {
+ WeatherCheck.weather().setRaining(true).build() // 在下雨时执行
+ },
+ "val1",
+ 10,
+ Items.DIRT
+));
+```
+
+[glm]: ../../resources/server/glm.md
+[instance]: ../../resources/server/glm.md#igloballootmodifier
+[datagen]: ../index.md#data-providers
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/loottables.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/loottables.md
new file mode 100644
index 000000000..da652a3ed
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/loottables.md
@@ -0,0 +1,148 @@
+# 战利品表生成
+=====================
+
+通过构建一个新的 `LootTableProvider` 并提供 `LootTableProvider$SubProviderEntry`,可以为模组生成[战利品表][loottable]。提供者必须被[添加][datagen]到 `DataGenerator`。
+
+```java
+// 在 MOD 事件总线上
+@SubscribeEvent
+public void gatherData(GatherDataEvent event) {
+ event.getGenerator().addProvider(
+ // 告诉生成器仅在生成服务器数据时运行
+ event.includeServer(),
+ output -> new MyLootTableProvider(
+ output,
+ // 指定需要生成的表的注册名称,或者可以留空
+ Collections.emptySet(),
+ // 生成战利品的子提供者
+ List.of(subProvider1, subProvider2, /*...*/)
+ )
+ );
+}
+```
+
+`LootTableSubProvider`
+----------------------
+
+每个 `LootTableProvider$SubProviderEntry` 都接收一个 `LootTableSubProvider`,它为给定的 `LootContextParamSet` 生成战利品表。`LootTableSubProvider` 包含一个方法,该方法接收写入者(`BiConsumer`)来生成表。
+
+```java
+public class ExampleSubProvider implements LootTableSubProvider {
+
+ // 用于创建封装 Supplier 的工厂方法
+ public ExampleSubProvider() {}
+
+ // 用于生成战利品表的方法
+ @Override
+ public void generate(BiConsumer writer) {
+ // 在这里通过调用 writer#accept 生成战利品表
+ }
+}
+```
+
+然后可以将表添加到 `LootTableProvider#getTables` 中,适用于任何可用的 `LootContextParamSet`:
+
+```java
+// 传递到 LootTableProvider 构造函数的列表中
+new LootTableProvider.SubProviderEntry(
+ ExampleSubProvider::new,
+ // 为 'empty' 参数集生成战利品表
+ LootContextParamSets.EMPTY
+)
+```
+
+### `BlockLootSubProvider` 和 `EntityLootSubProvider` 子类
+
+对于 `LootContextParamSets#BLOCK` 和 `#ENTITY`,有特殊类型(分别是 `BlockLootSubProvider` 和 `EntityLootSubProvider`),它们提供额外的辅助方法来创建和验证是否存在战利品表。
+
+`BlockLootSubProvider` 的构造函数接受一个物品列表,用于确定如果一个方块被爆炸时是否可以生成战利品表,以及一个 `FeatureFlagSet`,用于确定是否启用了方块以便为其生成战利品表。
+
+```java
+// 在某个 BlockLootSubProvider 子类中
+public MyBlockLootSubProvider() {
+ super(Collections.emptySet(), FeatureFlags.REGISTRY.allFlags());
+}
+```
+
+`EntityLootSubProvider` 的构造函数接受一个 `FeatureFlagSet`,用于确定是否启用了实体类型以便为其生成战利品表。
+
+```java
+// 在某个 EntityLootSubProvider 子类中
+public MyEntityLootSubProvider() {
+ super(FeatureFlags.REGISTRY.allFlags());
+}
+```
+
+要使用它们,所有已注册的对象必须分别提供给 `BlockLootSubProvider#getKnownBlocks` 和 `EntityLootSubProvider#getKnownEntityTypes`。这些方法确保迭代中的所有对象都有一个战利品表。
+
+:::tip
+如果使用 `DeferredRegister` 来注册模组的对象,则 `#getKnown*` 方法可以通过 `DeferredRegister#getEntries` 提供条目:
+
+```java
+// 在某个 BlockLootSubProvider 子类中,用于某个 DeferredRegister BLOCK_REGISTRAR
+@Override
+protected Iterable getKnownBlocks() {
+ return BLOCK_REGISTRAR.getEntries() // 获取所有注册的条目
+ .stream() // 流式处理封装的对象
+ .flatMap(RegistryObject::stream) // 如果可用,则获取对象
+ ::iterator; // 创建迭代器
+}
+```
+:::
+
+通过实现 `#generate` 方法可以添加战利品表。
+
+```java
+// 在某个 BlockLootSubProvider 子类中
+@Override
+public void
+
+ generate() {
+ // 在这里添加战利品表
+}
+```
+
+战利品表生成器
+-------------------
+
+战利品表通过 `LootTableSubProvider` 接收为 `LootTable$Builder`。之后,指定的 `LootContextParamSet` 在 `LootTableProvider$SubProviderEntry` 中设置,然后通过 `#build` 构建。在构建之前,构建器可以指定条目、条件和修饰符,这些因素影响战利品表的功能。
+
+:::note
+战利品表的功能非常广泛,本文档无法全部涵盖。相反,每个组件将简要描述。每个组件的具体子类型可以使用 IDE 查找。它们的实现将留给读者作为练习。
+:::
+
+### LootTable
+
+战利品表是基本对象,可以使用 `LootTable#lootTable` 转换为所需的 `LootTable$Builder`。战利品表可以通过指定的方式构建,包括一系列池(通过 `#withPool` 应用)以及函数(通过 `#apply`),用于修改这些池的结果物品。
+
+### LootPool
+
+战利品池代表一个执行操作的组,并可以使用 `LootPool#lootPool` 生成一个 `LootPool$Builder`。每个战利品池可以指定条目(通过 `#add`),这些条目定义池中的操作,条件(通过 `#when`)定义是否应执行池中的操作,以及函数(通过 `#apply`)修改条目的结果物品。每个池可以执行尽可能多的次数(通过 `#setRolls`)。此外,还可以指定额外的执行次数(通过 `#setBonusRolls`),这受到执行者幸运值的影响。
+
+### LootPoolEntryContainer
+
+战利品条目定义了选中时要执行的操作,通常生成物品。每个条目都有一个相关的、[已注册][registered]的 `LootPoolEntryType`。它们还有自己的相关构建器,这些构建器是 `LootPoolEntryContainer$Builder` 的子类型。多个条目可以同时执行(通过 `#append`)或顺序执行,直到一个失败(通过 `#then`)。此外,条目可以在失败时默认为另一个条目(通过 `#otherwise`)。
+
+### LootItemCondition
+
+战利品条件定义了执行某些操作所需满足的要求。每个条件都有一个相关的、[已注册][registered]的 `LootItemConditionType`。它们还有自己的相关构建器,这些构建器是 `LootItemCondition$Builder` 的子类型。默认情况下,指定的所有战利品条件必须为真,才能执行操作。也可以指定战利品条件,使得只需一个条件为真即可(通过 `#or`)。此外,条件的结果输出可以被反转(通过 `#invert`)。
+
+### LootItemFunction
+
+战利品函数在将执行结果传递给输出之前修改结果。每个函数都有一个相关的、[已注册][registered]的 `LootItemFunctionType`。它们还有自己的相关构建器,这些构建器是 `LootItemFunction$Builder` 的子类型。
+
+#### NbtProvider
+
+NBT 提供者是由 `CopyNbtFunction` 定义的特殊类型函数。它们定义了从哪里拉取标签信息。每个提供者都有一个相关的、[已注册][registered]的 `LootNbtProviderType`。
+
+### NumberProvider
+
+数字提供者决定战利品池执行的次数。每个提供者都有一个相关的、[已注册][registered]的 `LootNumberProviderType`。
+
+#### ScoreboardNameProvider
+
+记分板提供者是由 `ScoreboardValue` 定义的一种特殊类型的数字提供者。它们定义了从哪个记分板拉取执行次数的名字。每个提供者都有一个关联的[已注册][registered]`LootScoreProviderType`。
+
+[loottable]: ../../resources/server/loottables.md
+[datagen]: ../index.md#data-providers
+[registered]: ../../concepts/registries.md
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/recipes.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/recipes.md
new file mode 100644
index 000000000..0abd31642
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/recipes.md
@@ -0,0 +1,197 @@
+配方生成
+=================
+
+可以通过继承 `RecipeProvider` 并实现 `#buildRecipes` 来为模组生成配方。一旦消费者接受了 `FinishedRecipe` 视图,就会为数据生成提供配方。 `FinishedRecipe` 可以手动创建和提供,或者为方便起见,使用 `RecipeBuilder` 创建。
+
+实现后,必须将提供者[添加][datagen]到 `DataGenerator`。
+
+```java
+// 在MOD事件总线上
+@SubscribeEvent
+public void gatherData(GatherDataEvent event) {
+ event.getGenerator().addProvider(
+ // 告诉生成器只在服务器数据生成时运行
+ event.includeServer(),
+ MyRecipeProvider::new
+ );
+}
+```
+
+`RecipeBuilder`
+---------------
+
+`RecipeBuilder` 是创建 `FinishedRecipe` 以便于生成的便利实现。它提供了解锁、分组、保存和获取配方结果的基本定义。分别通过 `#unlockedBy`、 `#group`、 `#save` 和 `#getResult` 实现。
+
+:::important
+在原生配方构建器中不支持配方的 [`ItemStack` 输出][stack]。必须以不同的方式构建 `FinishedRecipe`,以便现有的原生配方序列化器可以生成这些数据。
+:::
+
+:::warning
+正在生成的物品结果必须有一个有效的 `RecipeCategory` 指定,否则会抛出 `NullPointerException`。
+:::
+
+除了 [`SpecialRecipeBuilder`],所有的配方构建器都需要指定一个进步条件。所有的配方都会生成一个条件,如果玩家之前使用过这个配方,就会解锁这个配方。然而,必须指定一个额外的条件,允许玩家在没有任何先验知识的情况下获取到配方。如果指定的任何一个条件为真,那么玩家就会在配方书中获取到配方。
+
+:::tip
+配方条件通常使用 `InventoryChangeTrigger` 来在用户的库存中存在某些物品时解锁配方。
+:::
+
+### ShapedRecipeBuilder
+
+`ShapedRecipeBuilder` 用于生成有形状的配方。构建器可以通过 `#shaped` 初始化。可以在保存之前指定配方组、输入符号模式、成分的符号定义和配方解锁条件。
+
+```java
+// 在 RecipeProvider#buildRecipes(writer) 中
+ShapedRecipeBuilder builder = ShapedRecipeBuilder.shaped(RecipeCategory.MISC, result)
+ .pattern("a a") // 创建配方模式
+ .define('a', item) // 定义符号代表的物品
+ .unlockedBy("criteria", criteria) // 配方的解锁方式
+ .save(writer); // 将数据添加到构建器中
+```
+
+#### 额外的验证检查
+
+在构建之前,有形状的配方会进行一些额外的验证检查:
+
+* 必须定义一个模式,并且需要输入超过一个物品。
+* 所有模式行的宽度必须相同。
+* 一个符号不能被定义多次。
+* 空格字符 (`' '`) 是为了表示插槽中没有物品而保留的,因此不能被定义。
+* 模式必须使用用户定义的所有符号。
+
+### ShapelessRecipeBuilder
+
+`ShapelessRecipeBuilder` 用于生成无形状的配方。可以通过 `#shapeless` 初始化构建器。在保存之前,可以指定配方组、输入成分和配方解锁条件。
+
+```java
+// 在 RecipeProvider#buildRecipes(writer) 中
+ShapelessRecipeBuilder builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, result)
+ .requires(item) // 将物品添加到配方中
+ .unlockedBy("criteria", criteria) // 配方的解锁方式
+ .save(writer); // 将数据添加到构建器中
+```
+
+### SimpleCookingRecipeBuilder
+
+`SimpleCookingRecipeBuilder` 用于生成熔炼、爆炸、烟熏和营火烹饪配方。此外,使用 `SimpleCookingSerializer` 的自定义烹饪配方也可以使用这个构建器进行数据生成。可以分别通过 `#smelting`、 `#blasting`、 `#smoking`、 `#campfireCooking` 或 `#cooking` 初始化构建器。在保存之前,可以指定配方组和配方解锁条件。
+
+```java
+// 在 RecipeProvider#buildRecipes(writer) 中
+SimpleCookingRecipeBuilder builder = SimpleCookingRecipeBuilder.smelting(input, RecipeCategory.MISC, result, experience, cookingTime)
+ .unlockedBy("criteria", criteria) // 配方的解锁方式
+ .save(writer); // 将数据添加到构建器中
+```
+
+### SingleItemRecipeBuilder
+
+`SingleItemRecipeBuilder` 用于生成石切配方。此外,使用类似 `SingleItemRecipe$Serializer` 的序列化器的自定义单物品配方也可以使用这个构建器进行数据生成。可以通过 `#stonecutting` 或者构造函数分别初始化构建器。在保存之前,可以指定配方组和配方解锁条件。
+
+```java
+// 在 RecipeProvider#buildRecipes(writer) 中
+SingleItemRecipeBuilder builder = SingleItemRecipeBuilder.stonecutting(input, RecipeCategory.MISC, result)
+ .unlockedBy("criteria", criteria) // 配方的解锁方式
+ .save(writer); // 将数据添加到构建器中
+```
+
+非 `RecipeBuilder` 构建器
+----------------------------
+
+一些配方构建器由于缺少所有先前提到的配方使用的功能而不实现 `RecipeBuilder`。
+
+### SmithingTransformRecipeBuilder
+
+`SmithingTransformRecipeBuilder` 用于生成将物品转化的铁匠配方。此外,使用类似 `SmithingTransformRecipe$Serializer` 的序列化器的自定义配方也可以使用这个构建器进行数据生成。可以通过 `#smithing` 或者构造函数分别初始化构建器。在保存之前,可以指定配方解锁条件。
+
+```java
+// 在 RecipeProvider#buildRecipes(writer) 中
+SmithingTransformRecipeBuilder builder = SmithingTransformRecipeBuilder.smithing(template, base, addition, RecipeCategory.MISC, result)
+ .unlocks("criteria", criteria) // 配方的解锁方式
+ .save(writer, name); // 将数据添加到构建器中
+```
+
+### SmithingTrimRecipeBuilder
+
+`SmithingTrimRecipeBuilder` 用于生成用于装甲修剪的铁匠配方。此外,使用类似 `SmithingTrimRecipe$Serializer` 的序列化器的自定义升级配方也可以使用这个构建器进行数据生成。可以通过 `#smithingTrim` 或者构造函数分别初始化构建器。在保存之前,可以指定配方解锁条件。
+
+```java
+// 在 RecipeProvider#buildRecipes(writer) 中
+SmithingTrimRecipe builder = SmithingTrimRecipe.smithingTrim(template, base, addition, RecipeCategory.MISC)
+ .unlocks("criteria", criteria) // 配方的解锁方式
+ .save(writer, name); // 将数据添加到构建器中
+```
+
+### SpecialRecipeBuilder
+
+`SpecialRecipeBuilder` 用于为不能轻易约束到配方 JSON 格式(如染色护甲、焰火等)的动态配方生成空的 JSON。可以通过 `#special` 初始化构建器。
+
+```java
+// 在 RecipeProvider#buildRecipes(writer) 中
+SpecialRecipeBuilder.special(dynamicRecipeSerializer)
+ .save(writer, name); // 将数据添加到构建器中
+```
+条件性配方
+-------------------
+
+[条件性配方][conditional] 也可以通过 `ConditionalRecipe$Builder` 进行数据生成。可以使用 `#builder` 获取构建器。
+
+可以先调用 `#addCondition` 来指定每个配方的条件,然后在所有条件都指定后调用 `#addRecipe`。这个过程可以重复多次,由程序员自行决定。
+
+在所有配方都指定之后,可以在最后使用 `#generateAdvancement` 为每个配方添加进步(advancements)。或者,可以使用 `#setAdvancement` 设置条件性进步。
+
+```java
+// 在 RecipeProvider#buildRecipes(writer) 中
+ConditionalRecipe.builder()
+ // 添加配方的条件
+ .addCondition(...)
+ // 当条件为真时返回配方
+ .addRecipe(...)
+
+ // 添加下一个配方的条件
+ .addCondition(...)
+ // 当下一个条件为真时返回下一个配方
+ .addRecipe(...)
+
+ // 创建使用上述配方中的条件和解锁进步的条件性进步
+ .generateAdvancement()
+ .build(writer, name);
+```
+
+### IConditionBuilder
+
+为了简化向条件性配方添加条件,而不必手动构造每个条件实例,扩展的 `RecipeProvider` 可以实现 `IConditionBuilder`。接口添加了轻松构造条件实例的方法。
+
+```java
+// 在 ConditionalRecipe$Builder#addCondition 中
+(
+ // 如果 'examplemod:example_item'
+ // 或者 'examplemod:example_item2' 存在
+ // 并且
+ // 非FALSE
+
+ // 方法由 IConditionBuilder 定义
+ and(
+ or(
+ itemExists("examplemod", "example_item"),
+ itemExists("examplemod", "example_item2")
+ ),
+ not(
+ FALSE()
+ )
+ )
+)
+```
+
+自定义配方序列化器
+-------------------------
+
+通过创建一个可以构造 `FinishedRecipe` 的构建器,可以数据生成自定义配方序列化器。完成的配方将配方数据编码为JSON,并在存在时将其解锁进步也编码为JSON。此外,还需要指定配方的名称和序列化器,以知道写入位置以及加载时能解码对象的内容。一旦构造了 `FinishedRecipe`,只需将其传递给 `RecipeProvider#buildRecipes` 提供的 `Consumer`。
+
+:::tip
+`FinishedRecipe` 足够灵活,可以数据生成任何对象转换,不仅仅是物品。
+:::
+
+[datagen]: ../index.md#data-providers
+[ingredients]: ../../resources/server/recipes/ingredients.md#forge-types
+[stack]: ../../resources/server/recipes/index.md#recipe-itemstack-result
+[conditional]: ../../resources/server/conditional.md
+[special]: #specialrecipebuilder
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/tags.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/tags.md
new file mode 100644
index 000000000..d72c35a66
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/tags.md
@@ -0,0 +1,123 @@
+标签生成
+======
+
+通过继承`TagsProvider`并实现`#addTags`方法,可以为一个mod生成[标签]。实现后,必须将提供器[添加][datagen]到`DataGenerator`中。
+
+```java
+// 在MOD事件总线上
+@SubscribeEvent
+public void gatherData(GatherDataEvent event) {
+ event.getGenerator().addProvider(
+ // 通知生成器仅在生成服务器数据时运行
+ event.includeServer(),
+ // 继承自net.neoforged.neoforge.common.data.BlockTagsProvider
+ output -> new MyBlockTagsProvider(
+ output,
+ event.getLookupProvider(),
+ MOD_ID,
+ event.getExistingFileHelper()
+ )
+ );
+}
+```
+
+`TagsProvider`
+--------------
+
+标签提供器有两种用于生成标签的方法:通过`#tag`使用对象和其他标签创建一个标签,或者通过`#getOrCreateRawBuilder`使用其他对象类型的标签来生成标签数据。
+
+:::note
+通常,除非一个注册表包含来自不同注册表的对象的表示(例如,方块有项目表示以在库存中获得方块),否则提供器不会直接调用`#getOrCreateRawBuilder`。
+:::
+
+调用`#tag`时,会创建一个`TagAppender`,它作为链式消费者,用于向标签添加元素:
+
+方法 | 描述
+:---: | :---
+`add` | 通过其资源键将一个对象添加到标签中。
+`addOptional` | 通过其名称将一个对象添加到标签中。如果对象不存在,则加载时将跳过该对象。
+`addTag` | 通过其标签键将一个标签添加到标签中。内标签中的所有元素现在都是外标签的一部分。
+`addOptionalTag` | 通过其名称将一个标签添加到标签中。如果标签不存在,则加载时将跳过该标签。
+`replace` | 当为`true`时,此标签从其他数据包添加的所有先前加载的条目将被丢弃。如果在此之后加载了一个数据包,那么它仍然会将条目追加到标签中。
+`remove` | 通过其名称或键从标签中移除一个对象或标签。
+
+```java
+// 在某个TagProvider#addTags中
+this.tag(EXAMPLE_TAG)
+ .add(EXAMPLE_OBJECT) // 将一个对象添加到标签中
+ .addOptional(new ResourceLocation("othermod", "other_object")) // 向标签添加来自其他mod的对象
+
+this.tag(EXAMPLE_TAG_2)
+ .addTag(EXAMPLE_TAG) // 将一个标签添加到另一个标签
+ .remove(EXAMPLE_OBJECT) // 从此标签中移除一个对象
+```
+
+:::important
+如果你的mod的标签对其他mod的标签有软依赖(也就是说,其他mod可能在运行时不存在),那么你应该使用可选的方法来引用其他mod的标签。
+:::
+
+### 现有的供应商
+
+Minecraft中包含了一些可以被子类化的特定注册表的标签供应商。另外,一些供应商包含了额外的辅助方法,可以更容易地创建标签。
+
+注册对象类型 | 标签供应商
+:---: | :---
+`方块` | `BlockTagsProvider`\*
+`物品` | `ItemTagsProvider`
+`实体类型` | `EntityTypeTagsProvider`
+`流体` | `FluidTagsProvider`
+`游戏事件` | `GameEventTagsProvider`
+`生物群系` | `BiomeTagsProvider`
+`平面世界生成预设` | `FlatLevelGeneratorPresetTagsProvider`
+`世界预设` | `WorldPresetTagsProvider`
+`结构` | `StructureTagsProvider`
+`兴趣点类型` | `PoiTypeTagsProvider`
+`旗帜图案` | `BannerPatternTagsProvider`
+`猫的品种` | `CatVariantTagsProvider`
+`画的种类` | `PaintingVariantTagsProvider`
+`乐器` | `InstrumentTagsProvider`
+`伤害类型` | `DamageTypeTagsProvider`
+
+\* `BlockTagsProvider` 是Forge所添加的 `TagsProvider`。
+
+#### `ItemTagsProvider#copy`
+
+方块在物品栏中有物品形式的表示。因此,许多的方块标签也可以是物品标签。为了方便地生成与方块标签具有相同条目的物品标签,可以使用 `#copy` 方法,此方法接收要复制的方块标签和要复制到的物品标签。
+
+```java
+// 在 ItemTagsProvider#addTags 中
+this.copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG);
+```
+
+自定义标签提供者
+--------------------
+
+通过 `TagsProvider` 的子类,可以创建自定义的标签提供者,这需要传入注册键以生成标签。
+
+```java
+public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) {
+ super(output, Registries.RECIPE_TYPE, registries, MOD_ID, fileHelper);
+}
+```
+
+### 内在持有者标签提供者
+
+其中一种特殊的 `TagProvider` 是 `IntrinsicHolderTagsProvider`。当通过这个提供者使用 `#tag` 创建标签时,对象本身可以通过 `#add` 将自己添加到标签中。为此,在构造函数中提供了一个将对象转换为其 `ResourceKey` 的函数。
+
+```java
+// `IntrinsicHolderTagsProvider` 的子类型
+public AttributeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) {
+ super(
+ output,
+ ForgeRegistries.Keys.ATTRIBUTES,
+ registries,
+ attribute -> ForgeRegistries.ATTRIBUTES.getResourceKey(attribute).get(),
+ MOD_ID,
+ fileHelper
+ );
+}
+```
+
+[tags]: ../../resources/server/tags.md
+[datagen]: ../index.md#data-providers
+[custom]: ../../concepts/registries.md#custom-registries
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/_category_.json
new file mode 100644
index 000000000..43a13824e
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/_category_.json
@@ -0,0 +1,3 @@
+{
+ "label": "Data Maps"
+}
\ No newline at end of file
diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/index.md
new file mode 100644
index 000000000..d368ceb3f
--- /dev/null
+++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/index.md
@@ -0,0 +1,192 @@
+# 数据映射
+
+注册表数据映射包含可附加到注册表对象的数据驱动、可重载的对象。
+这个系统允许更容易地数据驱动游戏行为,因为它们提供了如同步或冲突解决等功能,从而带来更好、更可配置的用户体验。
+
+你可以将标签看作注册表对象 ➜ 布尔映射,而数据映射则是更灵活的注册表对象 ➜ 对象映射。
+
+数据映射可以附加到静态的内置注册表和动态的数据驱动的数据包注册表上。
+
+数据映射支持通过使用 `/reload` 命令或任何其他重新加载服务器资源的方法来重新加载。
+
+## 注册
+数据映射类型应该静态创建,然后注册到 `RegisterDataMapTypesEvent`(在 [mod事件总线](../concepts/events)上触发)。`DataMapType` 可以使用 `DataMapType$Builder` 通过 `DataMapType#builder` 创建。
+
+构建器提供了一个 `synced` 方法,可以用来标记数据映射为同步并将其发送给客户端。
+
+一个简单的 `DataMapType` 有两个泛型参数:`R`(数据映射所针对的注册表的类型)和 `T`(被附加的值)。因此,可以将附加到 `Item` 的 `SomeObject` 的数据映射表示为 `DataMapType- `。
+
+数据映射使用 [编解码器](../datastorage/codecs.md) 进行序列化和反序列化。
+
+以以下表示数据映射值的记录为例:
+```java
+public record DropHealing(
+ float amount, float chance
+) {
+ public static final Codec CODEC = RecordCodecBuilder.create(in -> in.group(
+ Codec.FLOAT.fieldOf("amount").forGetter(DropHealing::amount),
+ Codec.floatRange(0, 1).fieldOf("chance").forGetter(DropHealing::chance)
+ ).apply(in, DropHealing::new));
+}
+```
+
+:::warning
+值 (`T`) 应为 *不可变* 对象,否则如果对象附加到标签内的所有条目,则可能导致奇怪的行为(因为不会创建副本)。
+:::
+
+为了本例的目的,我们将使用此数据映射在玩家丢弃物品时治疗玩家。
+`DataMapType` 可以这样创建:
+```java
+public static final DataMapType
- DROP_HEALING = DataMapType.builder(
+ new ResourceLocation("mymod:drop_healing"), Registries.ITEM, DropHealing.CODEC
+).build();
+```
+然后使用 `RegisterDataMapTypesEvent#register` 注册到 `RegisterDataMapTypesEvent`。
+
+## 同步
+同步的数据映射将会将其值同步到客户端。可以使用 `DataMapType$Builder#synced(Codec networkCodec, boolean mandatory)` 标记数据映射为同步。
+然后将使用 `networkCodec` 同步数据映射的值。
+如果 `mandatory` 标志设置为 `true`,则不支持数据映射的客户端(包括 Vanilla 客户端)将无法连接到服务器,反之亦然。另一方面,非强制性的数据映射是可选的,因此它不会阻止任何客户端加入。
+
+:::tip
+单独的网络编解码器允许包大小更小,因为你可以选择发送哪些数据以及以什么格式发送。否则可以使用默认编解码器。
+:::
+
+## JSON结构和位置
+数据映射从位于 `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json` 的JSON文件加载,其中:
+- `mapNamespace` 是数据映射ID的命名空间
+- `mapPath` 是数据映射ID的路径
+- `registryNamespace` 是注册表ID的命名空间;如果命名空间是 `minecraft`,则此值将被省略
+- `registryPath` 是注册表ID的路径
+
+更多信息,请[查看专用页面](./structure.md)。
+
+## 使用
+由于数据映射可以用于任何注册表,因此可以通过 `Holder` 查询它们,而不是通过实际的注册表对象。
+你可以使用 `Holder#getData(DataMapType)` 查询数据映射值。如果该对象没有附加值,方法将返回 `null`。
+
+:::note
+只有引用持有者会在该方法中返回值。`直接` 持有者 **不会**。通常,你只会遇到引用持有者(它们由 `Registry#wrapAsHolder`、`Registry#getHolder` 或不同的 `builtInRegistryHolder` 方法返回)。
+:::
+
+为了继续上面的示例,我们可以如下实现我们预期的行为:
+```java
+public static void onItemDrop(final ItemTossEvent event) {
+ final ItemStack stack = event.getEntity().getItem();
+ // ItemStack 有一个 getItemHolder 方法,它将返回一个指向堆叠物的物品的 Holder
-
+ //高亮下一行
+ final DropHealing value = stack.getItemHolder().getData(DROP_HEALING);
+ // 由于 getData 如果物品没有附加 drop healing 值将返回 null,我们保护它不为 null
+ if (value != null) {
+ // 这里我们简单地使用值
+ if (event.getPlayer().level().getRandom().nextFloat() > value.chance()) {
+ event.getPlayer().heal(value.amount());
+ }
+ }
+}
+```
+
+## 高级数据映射
+高级数据映射是具有额外功能的数据映射。即,通过移除器合并值和选择性地移除它们的能力。对于值类似于集合(如 `Map` 或 `List`)的数据映射,强烈推荐实现某种形式的合并和移除器。
+
+`AdvancedDataMapType` 除了 `T` 和 `R` 之外还有一个泛型:`VR extends DataMapValueRemover`。这个额外的泛型允许你通过提高类型安全性来生成移除对象。
+
+### 创建
+你可以使用 `AdvancedDataMapType#builder` 创建 `AdvancedDataMapType`。与普通构建器不同,该方法返回的构建器将有两个额外的方法(`merger` 和 `remover`),并且它将返回一个 `AdvancedDataMapType`。注册方法保持不变。
+
+### 合并器
+高级数据映射可以通过 `AdvancedDataMapType#merger` 提供一个 `DataMapValueMerger`。这个合并器将用于处理尝试将值附加到同一对象的数据包之间的冲突。
+合并器将给出两个冲突的值及其来源(作为 `Either, ResourceKey>`,因为值可以附加到标签内的所有条目,而不仅仅是个别条目),并期望返回实际附加的值。
+通常,合并器应简单地合并值,并且除非必要(即如果合并不可能),否则不应执行“硬”覆盖。如果一个包想要绕过合并器,它可以通过指定对象级别的 `replace` 字段来实现。
+
+假设我们有一个将整数附加到物品的数据映射的情况:
+```java
+public class IntMerger implements DataMapValueMerger
- {
+ @Override
+ public Integer merge(Registry
- registry, Either, ResourceKey
- > first, Integer firstValue, Either, ResourceKey
- > second, Integer secondValue) {
+ //高亮下一行
+ return firstValue + secondValue;
+ }
+}
+```
+如果两个数据包附加到同一对象,则上述合并器将合并值。所以如果第一个包将值 `12` 附加到 `minecraft:carrot`,第二个包将值 `15` 附加到 `minecraft:carrot`,最终的值将是 `27`。然而,如果第二个包指定对象级别的 `replace` 字段,最终值将是 `15`,因为不会调用合并器。
+
+NeoForge 为合并列表、集合和映射提供了一些默认的合并器,位于 `DataMapValueMerger` 中。
+
+默认合并器(`DataMapValueMerger#defaultMerger`)具有你期望的普通数据包的典型行为,其中最新的值(来自最高的数据包)将覆盖之前的值。
+
+### 移除器
+高级数据映射可以通过 `AdvancedDataMapType#remover` 提供一个 `DataMapValueRemover`。移除器将允许选择性地移除数据映射值,有效地进行分解。
+虽然默认情况下一个数据包只能移除附加到注册表条目的整个对象,但有了移除器,它可以只从附加对象中移除特定的值(即,在映射的情况下,只移除具有给定键的项,或在列表的情况下,只移除具有特定属性的项)。
+
+传递给构建器的编解码器将解码移除器实例。然后这些移除器将给出当前附加的值及其来源,并期望创建一个新对象来替换旧值。
+或者,一个空的 `Optional` 将导致值被完全移除。
+
+一个从基于 `Map` 的数据映射中移除具有特定键的值的移除器示例:
+```java
+public record MapRemover(String key) implements DataMapValueRemover
- > {
+ public static final Codec CODEC = Codec.STRING.xmap(MapRemover::new, MapRemover::key);
+
+ @Override
+ public Optional