# General Translation Integrations: Sanity 插件 配置 URL: https://generaltranslation.com/zh/docs/integrations/sanity/reference/plugin-configuration.mdx --- title: Sanity 插件 配置 description: 为 Sanity Studio 配置 General Translation 的 gt-sanity 插件。gtPlugin 的 API 参考。 --- 在你的 Sanity config 中使用 `gtPlugin` 函数注册 General Translation。传入一个选项对象。 ```ts title="sanity.config.ts" import { gtPlugin } from 'gt-sanity'; gtPlugin({ sourceLocale: 'en', locales: ['es', 'fr'], translateDocuments: [{ type: 'article' }], }); ``` ## 选项 [#options] | 选项 | 说明 | 类型 | 可选 | 默认值 | | ----------------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------- | -- | ------------------------------------- | | [`sourceLocale`](#source-locale) | 源语言代码,例如 `en`。 | `string` | 是 | `defaultLocale`,然后使用库默认值 | | [`defaultLocale`](#default-locale) | `sourceLocale` 的别名,便于展开 `gt.config.json`。 | `string` | 是 | — | | [`locales`](#locales) | 目标区域设置代码。会移除源区域设置和重复条目。 | `string[]` | 否 | — | | [`customMapping`](#custom-mapping) | 自定义区域设置代码映射和属性覆盖。 | [`CustomMapping`](/docs/platform/core/reference/types/custom-mapping) | 是 | — | | [`apiKey`](#api-key) | General Translation API 密钥。 | `string` | 是 | Secrets 文档 | | [`projectId`](#project-id) | General Translation 项目 ID。 | `string` | 是 | Secrets 文档 | | [`secretsNamespace`](#secrets-namespace) | 私有凭据文档的 `_id`。 | `string` | 是 | `generaltranslation.secrets` | | [`languageField`](#language-field) | 用于存储区域设置的文档字段。 | `string` | 是 | `language` | | [`translateDocuments`](#translate-documents) | 过滤可翻译的文档。 | `TranslateDocumentFilter[] \| string[]` | 是 | `[]` | | [`singletons`](#singletons) | 视为单例的文档 ID。 | `string[]` | 是 | `[]` | | [`singletonMapping`](#singleton-mapping) | 将源 ID 和区域设置映射到翻译后的单例 ID。 | `(sourceDocumentId: string, locale: string) => string` | 是 | `` `${sourceDocumentId}-${locale}` `` | | [`showDocumentInternationalization`](#show-doc-i18n) | 自动添加 `@sanity/document-internationalization`。 | `boolean` | 是 | `true` | | [`internationalizedArray`](#internationalized-array) | 为字段级本地化配置 `sanity-plugin-internationalized-array`。 | `GTFieldLevelLocalizationConfig` | 是 | — | | [`fieldLevelLocalization`](#field-level-localization) | `internationalizedArray` 的别名。 | `GTFieldLevelLocalizationConfig` | 是 | — | | [`translationLevel`](#translation-level) | 选择文档级、字段级或混合翻译。 | `'document' \| 'internationalizedArray' \| 'mixed'` | 是 | `'document'` | | [`fieldLevelDocuments`](#field-level-documents) | 在混合模式下使用字段级本地化的文档类型。 | `TranslateDocumentFilter[] \| string[]` | 是 | `[]` | | [`preserveExistingTranslations`](#preserve-existing-translations) | **保存本地编辑**开关的初始状态。 | `boolean` | 是 | `false` | | [`ignoreFields`](#ignore-fields) | 从源内容复制但不翻译的字段。 | `FieldMatcher[]` | 是 | `[]` | | [`dedupeFields`](#dedupe-fields) | 从源内容复制,并在每个区域设置中保持唯一的字段。 | `FieldMatcher[]` | 是 | `[]` | | [`skipFields`](#skip-fields) | 从翻译后的文档中移除的字段。 | `FieldMatcher[]` | 是 | `[]` | | [`additionalStopTypes`](#additional-stop-types) | 额外保留且不翻译的 schema 类型。 | `string[]` | 是 | `[]` | | [`additionalSerializers`](#additional-serializers) | 用于 marks 和 block 类型的自定义 HTML 序列化器。 | `Partial` | 是 | `{}` | | [`additionalDeserializers`](#additional-deserializers) | 自定义 HTML 反序列化器。 | `CustomDeserializers` | 是 | `{}` | | [`additionalBlockDeserializers`](#additional-block-deserializers) | 自定义 Portable Text block 反序列化规则。 | `unknown[]` | 是 | `[]` | ## 区域设置选项 [#locale-options] ### `sourceLocale` [#source-locale] **类型** `string` · **可选** · **默认值** `defaultLocale`,否则使用库默认值 源语言代码,例如 `en`。插件按以下顺序解析源区域设置:先是 `sourceLocale`,然后是 `defaultLocale`,最后是 `generaltranslation` 库的默认值。 ### `defaultLocale` [#default-locale] **Type** `string` · **可选** `sourceLocale` 的别名,因此你可以直接将 `gt.config.json` 展开传入 插件。如果两者都已设置,则以 `sourceLocale` 为准。 ```ts import gtConfig from './gt.config.json'; gtPlugin({ ...gtConfig }); ``` ### `locales` [#locales] **Type** `string[]` · **Required** 要翻译到的目标区域设置代码,例如 `['es', 'fr', 'ja']`。插件会在配置翻译或 Sanity 的本地化插件之前,移除重复条目以及任何与解析后的源区域设置相同的条目。 | 版本 | 变更 | | ------- | --------------------------------------------- | | `3.1.1` | 在配置翻译和 Sanity 本地化插件之前,移除重复的目标区域设置以及解析后的源区域设置。 | ### `customMapping` [#custom-mapping] **类型** [`CustomMapping`](/docs/platform/core/reference/types/custom-mapping) · **可选** 用于将区域设置代码自定义映射到名称,或覆盖区域设置属性。会传递给 `generaltranslation` 库。参见 [CustomMapping](/docs/platform/core/reference/types/custom-mapping)。 ## 凭据 [#credentials] 默认情况下,该 插件 会从 Sanity 的私有文档中读取凭据。请参阅[存储凭据](/docs/integrations/sanity/guides/configuring-sanity#store-credentials)。 ### `apiKey` [#api-key] **类型** `string` · **可选** · **默认值** 从 secrets 文档读取 你的 General Translation API 密钥。如果已设置,启动时会将其传递给库。如果存在 secrets 文档,则其 `secret` 字段会在运行时优先生效。建议优先使用 secrets 文档,以免将密钥纳入版本控制。 ### `projectId` [#project-id] **类型** `string` · **可选** · **默认值** 从 secrets 文档读取 你的 General Translation 项目 ID。如果存在 secrets 文档,则其中的 `project` 字段会在运行时优先生效。 ### `secretsNamespace` [#secrets-namespace] **类型** `string` · **可选** · **默认值** `generaltranslation.secrets` 用于存放凭证的私有 Sanity 文档的 `_id`。该 插件 会通过 `_id` 获取此文档,并将其 `secret` 字段作为 API 密钥,`project` 字段作为 project ID 读取。给 `_id` 加上前导 `.` 后,该文档即使位于公共数据集中也会保持私有。 ```js title="populateSecrets.js" import { getCliClient } from 'sanity/cli'; const client = getCliClient({ apiVersion: '2026-04-06' }); client.createOrReplace({ _id: 'generaltranslation.secrets', _type: 'generaltranslation.secrets', secret: process.env.GT_API_KEY, project: process.env.GT_PROJECT_ID, }); ``` ## 文档相关选项 [#document-options] ### `languageField` [#language-field] **类型** `string` · **可选** · **默认值** `language` 用于存储每个文档级翻译区域设置的字段。请为使用文档级本地化进行翻译的文档类型添加一个同名字段,并在查询时使用它来获取特定区域设置的文档。字段级本地化不使用此字段。 ### `translateDocuments` [#translate-documents] **类型** `TranslateDocumentFilter[] | string[]` · **可选** · **默认值** `[]` 用于筛选哪些文档可以翻译。接受筛选对象或简写类型字符串。每个字符串条目 `'article'` 都会标准化为 `{ type: 'article' }`,不包含 `documentId` 或 `type` 的条目会被丢弃。 ```ts gtPlugin({ sourceLocale: 'en', locales: ['es'], translateDocuments: [ { type: 'article' }, { documentId: 'homepage' }, 'page', // { type: 'page' } 的简写 ], }); ``` `TranslateDocumentFilter` 的结构如下: ```ts type TranslateDocumentFilter = { documentId?: string; // 通过 _id 匹配特定文档 type?: string; // 匹配指定 schema 类型的所有文档 }; ``` `type` 条目还会决定 [`showDocumentInternationalization`](#show-doc-i18n) 启用哪些 schema 类型。 ### `singletons` [#singletons] **类型** `string[]` · **可选** · **默认值** `[]` 被视为单例文档的文档 ID,例如站点设置或导航。它们的翻译后文档 ID 由 [`singletonMapping`](#singleton-mapping) 推导。 ### `singletonMapping` [#singleton-mapping] **类型** `(sourceDocumentId: string, locale: string) => string` · **可选** · **默认值** `` `${sourceDocumentId}-${locale}` `` 将单例的源文档 ID 和区域设置映射为翻译后单例的文档 ID。默认映射规则是确定的,因此 `siteSettings` 在西班牙语中会变为 `siteSettings-es`。 ```ts gtPlugin({ sourceLocale: 'en', locales: ['es'], singletons: ['siteSettings'], singletonMapping: (sourceDocumentId, locale) => `${sourceDocumentId}_${locale}`, }); ``` ### `showDocumentInternationalization` [#show-doc-i18n] **类型** `boolean` · **可选** · **默认值** `true` 当为 `true` 时,插件 会添加 `@sanity/document-internationalization` 插件,并提供语言徽章、翻译菜单以及按语言划分的文档模板。它会将 [`translateDocuments`](#translate-documents) 中的 `type` entries 作为 schema 类型,并将源区域设置后接标准化后的目标区域设置作为受支持的语言,因此仅在 `translateDocuments` 包含文档类型时才会生效。使用 国际化数组 原地本地化的文档类型会被自动排除。将其设为 `false` 即可自行管理国际化。 如果你已经在 Studio 中注册了 `@sanity/document-internationalization`,请保留现有 setup,并将此选项设为 `false`,这样该插件 (以及它的 `translation.metadata` 文档类型) 就只会注册一次——注册两次会导致重复的 schema 类型错误。翻译功能在你的实例中可照常工作:无论这些内容是由哪次注册添加的,该插件都会通过 [`languageField`](#language-field) 和 `translation.metadata` 文档读取和写入文档。请在两处配置中使用相同的语言 ID 和 language field。 ## 字段级本地化 [#field-level] 字段级本地化会将每种区域设置的值以国际化数组的形式存储在同一个文档中。它基于 [`sanity-plugin-internationalized-array`](https://github.com/sanity-io/sanity-plugin-internationalized-array) 实现:`gtPlugin` 会配置该原生插件,而该插件会注册 `internationalizedArray*` schema 类型以及 Studio 编辑 UI。存储的数据采用 `{ _key, _type, language, value }` 这种项结构,因此现有的国际化数组内容无需迁移——而且无论这些类型是由 `gtPlugin` 注册,还是由你自己的 `internationalizedArray()` 注册,翻译的工作方式都相同。 **v3 中的变更:** 字段级本地化现由 `sanity-plugin-internationalized-array` 提供,而不是由 GT 生成的类型和组件提供。`createInternationalizedArrayTypes`、`FieldLevelUIComponents` 以及 `typePrefix`、`includeCompatibilityTypes` 和 `components` 选项已被移除;如果传入已移除的选项,系统会记录警告并将其忽略。 ### `internationalizedArray` [#internationalized-array] **类型** `GTFieldLevelLocalizationConfig` · **可选** 用于配置字段级本地化的 `sanity-plugin-internationalized-array`。区域设置标识始终来自 `sourceLocale` 和 `locales`;如果你自行注册原生插件,请不要设置此选项,以确保 schema 类型只注册一次。 ```ts type GTFieldLevelLocalizationConfig = { enabled?: boolean; // 默认值:false fieldTypes?: FieldLevelFieldType[]; // 默认值:['string', 'text'] languageTitles?: Record; getLanguageTitle?: (locale: string) => string; defaultLanguages?: string[]; // 默认值:[sourceLocale] // 透传至 sanity-plugin-internationalized-array apiVersion?: string; buttonLocations?: ('field' | 'unstable__fieldAction' | 'document')[]; buttonAddAll?: boolean; languageDisplay?: 'titleOnly' | 'codeOnly' | 'titleAndCode'; }; type FieldLevelFieldType = | string | { name: string; type: string; title?: string; of?: unknown[]; fields?: unknown[]; options?: Record; }; ``` `fieldTypes` 条目可接受 Sanity 类型名称 (`'string'`、`'text'`) 、`'block'` 简写 (一个 Portable Text 数组) ,或用于定义自定义包装字段的对象条目;名为 `seo` 的对象条目会注册 `internationalizedArraySeo`。当两者都已设置时,`getLanguageTitle` 会覆盖 `languageTitles`。`defaultLanguages` 用于控制在空的本地化字段中预填充哪些区域设置,默认值为源区域设置。`apiVersion`、`buttonLocations`、`buttonAddAll` 和 `languageDisplay` 会原样传递给原生插件。 ### `fieldLevelLocalization` [#field-level-localization] **类型** `GTFieldLevelLocalizationConfig` · **可选** 是 [`internationalizedArray`](#internationalized-array) 的说明性别名。 ### `translationLevel` [#translation-level] **类型** `'document' | 'internationalizedArray' | 'mixed'` · **可选** · **默认值** `'document'` 控制匹配文档的翻译方式: * `'document'` 为每个区域设置创建一份文档 * `'internationalizedArray'` 对已配置字段进行原地本地化 * `'mixed'` 对 [`fieldLevelDocuments`](#field-level-documents) 采用字段级本地化,对其他所有内容采用文档级本地化 ### `fieldLevelDocuments` [#field-level-documents] **类型** `TranslateDocumentFilter[] | string[]` · **可选** · **默认** `[]` 指定在 `translationLevel` 为 `'mixed'` 时使用国际化数组的文档类型。每一项都是一个类型过滤器,例如 `{ type: 'siteSettings' }`,或简写字符串 `'siteSettings'`。这里不支持文档 ID 过滤器。 ```ts gtPlugin({ sourceLocale: 'en', locales: ['es', 'fr'], translateDocuments: [{ type: 'post' }, { type: 'siteSettings' }], internationalizedArray: { enabled: true, fieldTypes: ['string', 'text', 'block'], languageTitles: { es: 'Español', fr: 'Français' }, }, translationLevel: 'mixed', fieldLevelDocuments: [{ type: 'siteSettings' }], }); ``` 然后在 schema 中使用生成的类型: ```ts defineField({ name: 'title', type: 'internationalizedArrayString', }); ``` ## 翻译工作流选项 [#workflow-options] ### `preserveExistingTranslations` [#preserve-existing-translations] **类型** `boolean` · **可选** · **默认值** `false` 设置**保存本地编辑**开关的初始状态。启用后,会在执行翻译任务前将 Sanity 中当前的翻译发送到 General Translation。这样,源文本未变更的内容将保留现有措辞,不会被再次翻译。 ```ts title="sanity.config.ts" gtPlugin({ sourceLocale: 'en', locales: ['es', 'fr'], translateDocuments: [{ type: 'post' }], preserveExistingTranslations: true, }); ``` 此选项会在 Studio 加载时设置该开关的初始状态。Editor 可通过 **Translations** 工具或文档标签页在当前会话中将其打开或关闭,且该选择在本次会话的剩余时间内优先适用。 启用该开关后,Sanity 中的内容会替换 General Translation 为该文档版本保存的内容,包括已完成但尚未导入的翻译。请先导入所有待处理的翻译,再启用此开关。 请参阅[保留对翻译的编辑](/docs/integrations/sanity/guides/translating-content#preserve-edits)。 ## 字段匹配器 [#field-matchers] `ignoreFields`、`dedupeFields` 和 `skipFields` 均接受一个由 `FieldMatcher` 对象组成的数组。匹配器通过 JSONPath `property` 表达式匹配字段,并且还可通过 `documentId` 将范围可选地限制到单个源文档。若要在某个字段出现的所有位置都排除它,建议在 schema 中通过 [schema 排除选项](#schema-exclusion) 对其进行标记。 ```ts type FieldMatcher = { documentId?: string | null; // 通过 _id 限定到特定源文档 fields?: { property: string; // JSONPath 表达式,例如 $.slug type?: string; // 可选的 schema 类型提示,例如 slug }[]; }; ``` ### `ignoreFields` [#ignore-fields] **类型** `FieldMatcher[]` · **可选** · **默认值** `[]` 这些字段会直接从源文档复制到翻译后的文档,不会发送到翻译 API。适用于在不同区域设置中应保持完全一致的值,例如类别或标签。 ```ts gtPlugin({ sourceLocale: 'en', locales: ['es'], ignoreFields: [ // 对所有文档原样复制 category 字段 { fields: [{ property: '$.category' }] }, // 仅对一个文档原样复制 tags 字段 { documentId: 'homepage', fields: [{ property: '$.tags' }] }, ], }); ``` ### `dedupeFields` [#dedupe-fields] **类型** `FieldMatcher[]` · **可选** · **默认值** `[]` 首次创建翻译后的文档时,会从源值复制这些字段,并通过附加区域设置使其保持唯一。常用于 slug。 ```ts gtPlugin({ sourceLocale: 'en', locales: ['es', 'fr'], // "about" 变为 "about-es" 和 "about-fr" dedupeFields: [{ fields: [{ property: '$.slug', type: 'slug' }] }], }); ``` 对于 slug 字段,插件 会更新 slug 对象的 `current` 值。如果之后有人修改了翻译后的 slug,后续导入会保留这个修改后的值。 ### `skipFields` [#skip-fields] **类型** `FieldMatcher[]` · **可选** · **默认值** `[]` 会从翻译后的文档中完全移除的字段。 ```ts gtPlugin({ sourceLocale: 'en', locales: ['es'], skipFields: [ { fields: [{ property: '$.slug', type: 'slug' }] }, { documentId: 'homepage', fields: [{ property: '$.debugInfo' }] }, ], }); ``` ## schema 排除选项 [#schema-exclusion] 字段和类型可以直接在 schema 中排除,无需在 plugin-config 中添加条目。在序列化期间,plugin 会检查每个 schema 定义的 `options` 中是否包含这些命名空间,并将匹配项从发送去翻译的内容中移除: | 选项 | 源插件 | | ---------------------------------------------- | --------------------------------------- | | `options.gt.exclude` | `gt-sanity` | | `options.documentInternationalization.exclude` | `@sanity/document-internationalization` | | `options.aiAssist.exclude` | `@sanity/assist` | 每个选项都接受 `boolean`;只有显式设为 `true` 时才会排除。排除规则适用于任意嵌套深度。被排除的内容绝不会发送去翻译,因此翻译后的文档会保留源值不变。 ```ts defineField({ name: 'internalNotes', type: 'string', options: { gt: { exclude: true } }, }); ``` 在自定义类型定义的 `options` 中设置排除选项后,该类型的所有出现位置都会被排除,这与原生插件的“字段或类型”语义一致。旧版的 `localize: false` 字段属性也仍然会继续生效。 `gt-sanity` 会扩展 Sanity 的 schema 选项类型 (即 `GTSchemaFieldOptions` 接口) ,因此 `options.gt` 在任何字段定义中都能通过类型检查。 *注意:schema 排除是根据字段名称和类型来标记字段的;如果规则需要按 ID 匹配特定文档,或按区域设置转换值,请使用 [`ignoreFields`、`skipFields` 和 `dedupeFields`](#field-matchers)。* ## 序列化 [#serialization] 该插件会先将文档序列化为 HTML 以便翻译,再将结果反序列化回 Sanity 字段。大多数项目都不需要这些选项。 ### `additionalStopTypes` [#additional-stop-types] **类型** `string[]` · **可选** · **默认值** `[]` 添加到[默认 停止类型](#stop-types)中的额外 schema 类型,这些类型会被保留而不进行翻译。 ```ts gtPlugin({ sourceLocale: 'en', locales: ['es'], additionalStopTypes: ['codeBlock', 'mux.video', 'mux.videoAsset'], }); ``` ### `additionalSerializers` [#additional-serializers] **类型** `Partial` · **可选** · **默认值** `{}` 与默认序列化器合并使用的自定义序列化器,遵循 `@portabletext/to-html` 的组件结构 (如 `types`、`marks`、`block`、`list`、`listItem` 等) 。最常见的用法是序列化自定义 `marks`。 对于自定义 `marks`,请用 `attachGTData` 包裹输出,以确保该标记的数据在翻译后仍能保留。 ```ts title="sanity.config.ts" import { attachGTData, gtPlugin } from 'gt-sanity'; gtPlugin({ sourceLocale: 'en', locales: ['es'], additionalSerializers: { marks: { link: ({ value, children }) => attachGTData(`${children}`, value, 'markDef'), inlineMath: ({ value, children }) => attachGTData(`${children}`, value, 'markDef'), }, }, }); ``` `attachGTData(html, data, 'markDef')` 会将 `data` 进行 base64 编码后写入 `html` 第一个元素的 `data-gt-internal` 属性中,并返回更新后的 HTML。导入时,plugin 会读取该属性以重新构建 mark 定义。 ```ts function attachGTData( html: string, data: Record, type: 'markDef' ): string; ``` ### `additionalDeserializers` [#additional-deserializers] **类型** `CustomDeserializers` · **可选** · **默认值** `{}` 按类型区分的自定义反序列化器,用于将已翻译的 HTML 元素转换回 Sanity 对象。 ```ts type CustomDeserializers = { types?: Record< string, (element: HTMLElement) => Record | unknown[] >; } & Record; ``` ### `additionalBlockDeserializers` [#additional-block-deserializers] **类型** `unknown[]` · **可选** · **默认值** `[]` 额外的 Portable Text 块反序列化规则,会追加到插件的内置规则后面。每条规则都是一个带有 `deserialize(node, next)` 方法的对象,其结构与 `@portabletext/block-tools` 的规则格式一致。 ## 默认停止类型 [#stop-types] 这些 schema 类型会被保留,且绝不会发送去翻译。你可以使用 [`additionalStopTypes`](#additional-stop-types) 添加更多类型。 ```ts const defaultStopTypes = [ 'reference', 'date', 'datetime', 'file', 'geopoint', 'image', 'number', 'crop', 'hotspot', 'boolean', 'url', 'color', 'code', ]; ``` slug 字段默认*不会*被跳过,因此除非你使用 [`dedupeFields`](#dedupe-fields) 或 [`skipFields`](#skip-fields),否则 slug 的 `current` 字符串会被翻译。 ## 导出的辅助工具 [#helpers] `gt-sanity` 还导出了一些用于高级序列化和自定义文档节点的基础组件。大多数项目都不需要它们。 * `TranslationsTab` — `structureTool` 的文档标签页组件。参见[配置 Sanity](/docs/integrations/sanity/guides/configuring-sanity#translations-tab)。 * `attachGTData` / `detachGTData` — 用于附加和读取自定义序列化器使用的编码标记数据。 * `BaseDocumentSerializer`, `BaseDocumentDeserializer`, `BaseDocumentMerger` — 默认的序列化、反序列化和合并实现。 * `defaultStopTypes`, `customSerializers` — 默认的 停止类型 和序列化器集合。 * `documentInternationalization` 及其类型 (`DocumentInternationalizationConfig`, `Language`, `Metadata`, `TranslationReference`) — 从 `@sanity/document-internationalization` 重新导出。 * `internationalizedArray`、`internationalizedArrayLanguageFilter` 和 `isInternationalizedArrayItemType`,以及 `InternationalizedArrayPluginConfig` 和 `InternationalizedArrayLanguage` 类型 — 从 `sanity-plugin-internationalized-array` 重新导出。 * `GTSchemaFieldOptions` — `options.gt` 对应的 schema 选项接口。参见 [Schema 排除选项](#schema-exclusion)。 ## 完整示例 [#complete-example] ```ts title="sanity.config.ts" import { defineConfig } from 'sanity'; import { attachGTData, gtPlugin } from 'gt-sanity'; export default defineConfig({ plugins: [ gtPlugin({ // 必填 sourceLocale: 'en', locales: ['es', 'fr', 'de', 'ja'], // 文档与字段 languageField: 'language', translateDocuments: [{ type: 'article' }, { type: 'page' }], singletons: ['siteSettings', 'navigation'], singletonMapping: (id, locale) => `${id}_${locale}`, // 字段行为 ignoreFields: [{ fields: [{ property: '$.category' }] }], dedupeFields: [{ fields: [{ property: '$.slug', type: 'slug' }] }], skipFields: [{ fields: [{ property: '$.internalNotes' }] }], // 序列化 — 仅在默认配置无法处理你的 schema 时使用 additionalSerializers: { marks: { link: ({ value, children }) => attachGTData(`${children}`, value, 'markDef'), }, }, }), ], }); ```