@braze/sanity-plugin-ai-翻译
一个添加 AI 动力的 Sanity Studio 插件 翻译 用于国际化内容的文件行动.
- 默认提供者 : 卫生代理动作( U)
client.agent.action.translate()(中文(简体) ) - 可选提供者 : OpenAI( 通过 Sanity 函数) 用于大型文档/ 长样式向导
- 输出 : 翻译创建为 内容版内版本文档 (分阶段出版)
- 额外 : 翻译引用文件、词汇表术语、被保护的短语、调试日志、字段排除和字段转换(便携式文本+标题)
编辑: 见独立用户指南 docs/user-guide.md。 。 。 .
目录
此插件会做什么
当您在 Sanity Studio 打开文档时( 对于配置的 schema 类型), 插件会添加一个 翻译 行动。 你可以这样:
- 选择一个或多个 目标语言
- 选择 :
- 是否从草案或已公布的版本翻译
- 发布翻译内容(新或已有)
- 是否也翻译引用的文档(带有深度控制)
- 使用 :
- 危险代理动作 (违约),或
- 开放AI (可选)
现存的译名总是被完整地再翻译;没有部分或只翻译模式.
翻译为: 版本文档 在一个 内容发布,所以你可以审查并公布在您的日程表.
先决条件
您的工作室必须支持国际化的文件 .
要求 :
- 卫生工作室 v 6. 12 或更新 与
@sanity/uiv4,反应19.2+,和节点.js 22.12+ (插件通过 Studio 的客户端引用 Sanity 函数,需要@sanity/client8.3+; 第6.12工作室是捆绑它的第一个发布) @sanity/document-internationalization配置sanity-plugin-internationalized-array安装和配置(用于词汇翻译)@sanity/client7.1.0+ (单位:千美元) (代理行动需要)
代理行动也需要:
- 您的策略必须部署在 Sanity , 这样 Agent Action 可以访问它(参见 部署计划a(中文(简体) )
- 重要的 API 版本要求 : 客户端必须使用
apiVersion: 'vX'(不是日期版本). 基于日期的版本可能导致Agent Actions失败.
安装
使用您的工作室软件包管理器( 例如使用) pnpm:
pnpm add @braze/sanity-plugin-ai-translation
若你还没有安装
pnpm add @sanity/document-internationalization sanity-plugin-internationalized-array
pnpm add @sanity/client@^7.1.0
配置( Studio)
1) 配置文档国际化
此插件旨在与 @sanity/document-internationalization。先配置它.
您通常会将您的支持语言定义为:
const SUPPORTED_LANGUAGES = [
{ id: "en-us", title: "English" },
{ id: "es", title: "Spanish" },
{ id: "fr", title: "French" },
];
然后为您要翻译的策略类型启用国际化:
documentInternationalization({
supportedLanguages: SUPPORTED_LANGUAGES,
schemaTypes: ["post", "page"],
languageField: "language", // your language field name (example)
});
(2) 添加 AI 翻译插件
添加 aiTranslationPlugin() 之后 documentInternationalization()数字 :
import { defineConfig } from "sanity";
import { documentInternationalization } from "@sanity/document-internationalization";
import { aiTranslationPlugin } from "@braze/sanity-plugin-ai-translation";
const SUPPORTED_LANGUAGES = [
{ id: "en", title: "English" },
{ id: "es", title: "Spanish" },
{ id: "fr", title: "French" },
];
export default defineConfig({
// ...your config...
plugins: [
documentInternationalization({
supportedLanguages: SUPPORTED_LANGUAGES,
schemaTypes: ["post", "page"],
languageField: "language",
}),
aiTranslationPlugin({
supportedLanguages: SUPPORTED_LANGUAGES,
translatableSchemaTypes: ["post", "page"],
languageFieldName: "language",
schemaId: "_.schemas.default",
maxConcurrentTranslationJobs: 5,
maxReferenceDepth: 3,
}),
],
});
页:1
schemaId通常为'_.schemas.default'大多数项目.languageFieldName必须匹配文档使用的字段(通常language) (中文(简体) ).glossaryDataset(可选)从同一项目的另一个数据集读取词汇术语. 见translationGlossaryTerm。 。 。 .
(3) 添加翻译历史元数据
此插件可以通过您附加到的元数据计划存储翻译历史 documentInternationalization。 。 。 .
添加 translationHistorySchema数字 :
import { translationHistorySchema } from "@braze/sanity-plugin-ai-translation";
documentInternationalization({
supportedLanguages: SUPPORTED_LANGUAGES,
schemaTypes: ["post", "page"],
languageField: "language",
metadataFields: [translationHistorySchema],
});
(4) 部署代理行动计划
特工行动需要进入你的部署计划.
从您的 Studio 项目目录 :
npx sanity deploy
如果代理动作因与计划相关的错误而失败, 请重新检查 :
- 计划部署
schemaId- 客户端
apiVersion设置为'vX'(不基于日期)
可选: OpenAI 模式
OpenAI模式是可选的. 如果启用, 用户可以选择 开放AI 作为翻译对话框中的翻译提供者.
工作室从不给OpenAI打电话或持有OpenAI API密钥. 它把组装的快件发送到一个已部署的 Sanity 功能 client.functions.invoke(..., { sync: true }),使用编辑室会话. 该函数使 OpenAI 调用 服务器条目 并返回已翻译的文本.
示例配置 :
aiTranslationPlugin({
supportedLanguages: SUPPORTED_LANGUAGES,
translatableSchemaTypes: ["post", "page"],
languageFieldName: "language",
schemaId: "_.schemas.default",
openai: {
enabled: true,
stackId: "ST-xxxxxxxx", // blueprint stack that contains the function (required)
functionName: "ai-translation",
model: "gpt-6.1-sol", // one of OPENAI_MODELS; set it explicitly in production
reasoningEffort: "medium", // 'low' | 'medium' | 'high'
defaultMode: "openai-only", // 'sanity-only' | 'openai-only'
},
});
所需资源
- 工作室的 Sanity 客户端必须是
@sanity/client8.1或较新,增加client.functions.invoke。如果是旧的,对话框显示 OpenAI 模式为不可用 . - 在堆栈中必须部署带有所配置名称的 pubsub 函数 。 写着
OPENAI_API_KEY从它自己的环境,允许列表模型,并返回requestOpenAICompletion。 。 。 . - 函数超时必须足够长的时间用于推理呼叫. 至少使用120秒; Sanity允许最多900秒. 插件等待引用的时间可达900秒.
警卫
- 不要在 Studio 配置或捆绑中放置 OpenAI API 密钥 。 它只属于函数环境中.
- 发送到功能上的有效载荷只包含信息、模型和推理努力。 它由客户端提供,因此功能必须验证它,并且不得将其中的任何字段当作身份检查.
为什么使用OpenAI模式?
- 较长文档和长样式指南的更大有效上下文窗口
- 确定序列化/去序化以保留结构
- 严格结构验证,防止数据腐败
费用
- OpenAI 调用会根据符号使用产生 API 费用 .
- 每个请求中的稳定部分(全球快取,地语快取,词汇表,被保护的词组,指令)在文档之前发送,因此OpenAI的缓存输入定价适用于每一个被翻译为同一种语言时.
模型
openai.model 必须是其中之一 OPENAI_MODELS (从联合国出口) 服务器条目) (中文(简体) ). 该列表也是唯一的支出守护,因为任何工作室用户都可以引用翻译功能:不允许亲级,O系列或3.5型.
| 型号 | 预定用途 |
|---|---|
gpt-6.1-sol | 默认. 每日营销与文献页面. |
gpt-6.1-terra | 批量运行或低评分页的预算选项. |
gpt-6-luna | 草稿和测试. |
gpt-6-astra | 法律版或旗舰版的专注费. |
gpt-5.1, (中文(简体) ), gpt-5, (中文(简体) ), gpt-5-mini | 上一代, 仍然支持已经使用它们的工作室 . |
DEFAULT_OPENAI_MODEL (单位:千美元)gpt-6.1-sol当 model 省略。 设定 model 因此,一个插件升级不能默默地改变工作室使用哪种模式和价格.
服务器条目
插件在 @braze/sanity-plugin-ai-translation/server 用于在浏览器外运行 OpenAI 调用(例如,在 Sanity 函数中)。 它不装入 React 或 Studio,它不是 Studio 捆绑的一部分,也不从环境中读取 API 密钥,所以被调用者传入.
import {
DEFAULT_OPENAI_MODEL,
isOpenAIModel,
OPENAI_MODELS,
requestOpenAICompletion,
} from "@braze/sanity-plugin-ai-translation/server";
// payload: { model, messages, reasoningEffort?, temperature? } assembled in Studio
if (!isOpenAIModel(payload.model)) {
throw new Error(`model must be one of ${OPENAI_MODELS.join(", ")}`);
}
const { content, tokensUsed } = await requestOpenAICompletion(payload, {
apiKey: process.env.OPENAI_API_KEY!,
});
第一个论据是客户端提供的请求,并在运行时被验证. 第二种持有服务器侧配置,如API密钥,从未从有效载荷读取.
- 超过20个信件的请求,或总共超过1,000,000个信件内容字符,在发送到OpenAI之前被拒绝.
- 仅
role财务报告和审定财务报表content每个消息都会被发送。 有效载荷中的任何其他场被投放. - 日志从不包含即时文本或文档文本,即使打开了插件日志。 它们只包括模型,消息角色,以及内容长度.
串行化,即时组装,结构验证,以及内容发布书写都留在工作室. 该条目不自行允许列表模型. 在打电话之前先在电话里说 isOpenAIModel (或 OPENAI_MODELS这是相同的列表 openai.model 插件选项接受,因此 Studio 和函数绝不对允许哪个模型有异议. GPT-5和GPT-6型号被送入了Resolution API;其他型号家庭回落到Chat Refulations.
运行翻译元数据
插件将每个翻译记录在 translation.metadata 一旦在内容发布中创建了该版本。 当该发布被删除或版本被丢弃时,条目仍然会指向被移除的文档. pruneTranslationMetadata 删除 translations 财务报告和审定财务报表 translationHistory 目标已不复存在的条目。 保留作为已公布文件、草稿或任何发行版本仍然存在的目标。 存档的放出对象也是这样:存档删除了放出版本,并解除存档恢复了它们,所以它们的条目必须活下来.
该插件已经浏览了将要翻译的每个文档的元数据 。 要删除文档立即删除, 请部署一个删除事件“ Sanity” 函数 . functions/ 在这个回购中是一个实例:
// sanity.blueprint.ts
defineDocumentFunction({
name: "prune-translation-metadata",
event: {
on: ["delete"],
filter: "_type in ['course', 'lesson']", // your translatableSchemaTypes
includeAllVersions: true,
projection: "{_id}",
},
});
// functions/prune-translation-metadata/index.ts
import { pruneTranslationMetadata } from "@braze/sanity-plugin-ai-translation/server";
import { createClient } from "@sanity/client";
import { documentEventHandler } from "@sanity/functions";
export const handler = documentEventHandler(async ({ context, event }) => {
const client = createClient({ ...context.clientOptions, apiVersion: "vX", useCdn: false });
await pruneTranslationMetadata({ client, documentIds: [event.data._id] });
});
过 dryRun: true 以获取不写任何东西而删除的条目.
要清理已有的数据集, 请运行 functions/scripts/fix-translation-metadata.mjs。它会拖动条目,然后将同一源的重复元数据文档合并为最古老的。 如果两个重复在不同的活文件上指向同一种语言,脚本会报告该组,而不改变. 除非你通过,否则就完了 --apply。首先构建插件(pnpm build),然后在未进行翻译的情况下运行 :
SANITY_PROJECT_ID=... SANITY_DATASET=... SANITY_WRITE_TOKEN=... pnpm --filter ./functions fix-metadata
SANITY_PROJECT_ID=... SANITY_DATASET=... SANITY_WRITE_TOKEN=... pnpm --filter ./functions fix-metadata -- --apply
SANITY_WRITE_TOKEN 在数据集中需要编辑器角色或更高.
用户指南(供编辑使用)
翻译文档
- 在 Studio 中打开其类型包含的文档
translatableSchemaTypes。 。 。 . - 打开文档动作菜单并单击 翻译。 。 。 .
- 选择一种或多种目标语言.
- 配置翻译选项(见下文).
- 点击 翻译 然后进行飞行前检查 翻译 再次开始.
翻译时,您会看到每种目标语言的工作状态(如果启用的话,还会看到任何引用的文件)。 每个工作都带有文档的图案预览标题的标签,同样的标签工作室列表使用. 视图 将完成的翻译用被绑住的放出窗格用新窗格打开,所以标题显示在放出决心中的真实标题和引用. 将发行版将整个工作室切换到它,而不只是新的面板:在切换回草稿之前,编辑到任何文档都会进入该发行版.
选择内容发布
翻译总是在 内容发布。 。 。 .
在对话框中,您通常可以:
- 创建新发布,或
- 将翻译添加到已有的活跃发行中
需要时使用释放 :
- 分阶段审查
- 一次协调出版多种译名
翻译选项
对话框提供 :
- 从草稿翻译 / 从公布的草稿翻译
- 源文件的哪个版本(及其参考文献)被翻译.
- 内容发布
- 创建新发行, 或者选择一个已有的活跃发行来添加翻译内容 .
- 翻译参考文献
- 还翻译参考文件并更新本地化版本点的参考文献.
- 最大参考深度
- 跨度参考图表(深度)
0意思是没有转弯).
- 跨度参考图表(深度)
- 提供者 (仅在启用 OpenAI 模式时)
- Sanity Agent Actions or OpenAI. (原始内容存档于2018-09-25). Sanity Agent Actions.
现有译名总能被完整地再译. 没有覆盖的切换, 也没有任何 diff 唯一的模式: 发行时持有完整的新译名, 您在出版前审查它 .
飞行前检查
点击 翻译 首先进行飞行前检查 翻译只开始于通过时 :
- 验证:每个要翻译的文档都必须通过它的计划验证. 有错误的文档被列出,链接打开并修正它们,然后 重新扫描。 。 。 .
- 未公布的参考资料更改: 列出带有草稿的引用文档,以便确认草稿或已公布的来源是你想要的.
- 现有译文汇编草稿:如果即将被替换的译名有未公布的草稿,对话框会警告草稿会过时. 从显示的链接中发布或丢弃每个草稿,或单击 继续吧 继续,不管怎样.
- 释放冲突: 当添加到已经包含同一文档和语言版本的现有版本中时,对话框会显示相互冲突的翻译,并要求您选择:
- 取消 回去选择另一个释放,
- 全部替换 删除发行中的现有版本并创建新的版本,或
- 跳过冲突 只翻译非冲突文档/语言对.
翻译引用文档
如果您启用引用翻译 :
- 该插件会穿行引用( 最多到所配置的深度) .
- 它翻译符合条件的引用文件(类型包括:
translatableSchemaTypes) (中文(简体) ). - 它更新了翻译文件中的参考文献,以指向本地化版本.
这对内容如:
- 参考作者员额
- 参考共享章节
- 参考相关内容的文件
创建什么
对于每个选定的目标语言,插件创建一个 已版本文档 在选定的内容发布中.
当引用被翻译时,翻译版本中的引用被重写到被翻译的目标上,即工作室本身链接未发表文件的方式: _weak: true 与 _strengthenOnPublish: { type }. 它们在发布时成为了强烈的参考,因此发布时没有验证错误而发布,而Studio可以使发布外的字段出场.
那样你就可以:
- 审查和编辑翻译版本
- 准备好就发布
内容配置(设置和词汇表)
插件引入了两种文档类型用于配置 :
translationSettings
以 _id == "translation-settings" 来配置 :
- 全球提示 (适用于所有翻译)
- 语言提示 (按目标语言说明)
- 受保护的短语 (从未翻译这些名词;例如:品牌名)
示例形状 :
{
_type: 'translationSettings',
_id: 'translation-settings',
globalPrompt: 'Maintain a professional tone. Preserve formatting and links.',
localePrompts: [
{_key: 'es', value: 'Use formal Spanish (usted).'},
{_key: 'fr', value: 'Use formal French.'},
],
protectedPhrases: ['Braze', 'iOS', 'Android', 'SDK']
}
注释:
localePrompts用途_key用于本地地址( 必须与您的supportedLanguages[].id) (中文(简体) ).- 被保护的短语可以被过滤到仅出现在源文档中的短语(以保持提示较小).
translationGlossaryTerm
创建术语表文档,强制对关键术语进行一致翻译.
这个计划用的是 internationalizedArrayString 您可以提供每个地方一个值.
概念实例:
{
_type: 'translationGlossaryTerm',
translations: [
{_key: 'en', value: 'dashboard'},
{_key: 'es', value: 'panel de control'},
{_key: 'fr', value: 'tableau de bord'},
]
}
词汇名词用于构建针对地语的翻译指令.
默认情况下,术语表从正在翻译的文件的数据集中读取. 共用一个数据集的词汇表 glossaryDataset 到同一工程中的另一个数据集:
aiTranslationPlugin({
// ...
glossaryDataset: "production",
});
- 术语与编辑室现有的会话一起读取,因此编辑需要读取该数据集的访问权限.
- 只有词汇表术语来自
glossaryDataset。 。 。 .translationSettings(global proone, locale proones, 被保护的短语)仍然从文档的数据集读取. - 这两套数据集的词汇表都没有写入任何内容.
高级配置
翻译中不包括字段
使用 excludeFields 防止某些字段被翻译(slugs, enums, 旗帜等).
支持 :
- 字段名称在任何地方匹配(例如).
'status'(中文(简体) ) - 精确路径( 例如 ).
'seo.title'(中文(简体) ) - 数组路径/通配符(例如).
'body[*].imageType'(中文(简体) )
示例
aiTranslationPlugin({
// ...
excludeFields: ["slug", "status", "seo.canonicalUrl", "body[*].variant"],
});
重要行为:
- 插件将 没有 允许
languageFieldName将被排除在外。 如果包含的话,会被去除以防止翻译出错. - Sanity系统字段
_id, (中文(简体) ),_type, (中文(简体) ),_key, (中文(简体) ),_rev, (中文(简体) ),_createdAt, (中文(简体) ),_updatedAt,以及任何其他_- 前置字段)从不需要排除. 两个提供者都没有翻译它们: Agent Action 从 id 的数据集读取文档, OpenAI 序列器跳过_-前置钥匙 以它们为目标的模式(例如:_id或者说body[*]._key)被以警告而忽略,因为剥去它们会去掉id和管道所依赖的密钥.
外地转换
一些字段(如可移植文本或标题格式)在翻译前从转换中受益.
在此序列中运行的变换 :
- 过滤排除字段
- 应用变换( 生成可变表示)
- 翻译( 聖物代理或 OpenAI)
- 恢复变换( 回到原始结构)
- 恢复排除字段(语言除外)
- 在发行时创建版本文档
内置变迁包括:
richTextTransform便携式文本vttCaptionTransformvTT 标题wistiaCaptionTransform用于 Wistia 标题( 如果您的计划使用)
示例设置 :
import {
aiTranslationPlugin,
richTextTransform,
vttCaptionTransform,
wistiaCaptionTransform,
} from "@braze/sanity-plugin-ai-translation";
aiTranslationPlugin({
// ...
fieldTransforms: [
{
...vttCaptionTransform,
matcher: { schemaType: "video", fieldName: "captions" },
},
{
...wistiaCaptionTransform,
matcher: { schemaType: "videoDoc", fieldName: "wistiaCaptions" },
},
richTextTransform,
],
});
秩序事项: 第一场比赛获胜.
货币和深度
maxConcurrentTranslationJobs控制同时运行的翻译数量(默认行为是保守的).maxReferenceDepth控制引用的深度(设置为0以禁用 traversal .
调试出错( D)
启用调试日志
此插件使用一个集中调试日志 。 输出默认关闭 。 要打开它, 请在主机工作室中设置此环境变量, 然后重新启动 dev 服务器或重建( 值在构建时内含) :
# Standalone Sanity Studio
SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING=true
# Studio embedded in Next.js
NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING=true
要关闭, 删除变量或设置到其它值 .
共同问题
代理动作立即失败
- 确认
@sanity/client实值 7.1.0+ (单位:千美元) - 确认您的客户端 API 版本
apiVersion: 'vX'(不基于日期) - 确认计划已部署:
npx sanity deploy - 确认
schemaId匹配您部署的策略( 通常'_.schemas.default'(中文(简体) )
翻译完成但内容没有改变
- 确保文档类型包含在内
translatableSchemaTypes - 请检查排除字段或变换是否阻碍更新
- 如果使用 OpenAI 模式, 结构验证可以拒绝修改非文本字段的翻译
引用翻译缓慢
- 减少
maxReferenceDepth - 减少
maxConcurrentTranslationJobs - 确保您不会不必要地翻译大参考图
开发( 本回购)
此寄存器是一个 pnpm 单列波 :
package/——插件包demo-nextjs/用嵌入式工作室演示 Next.js 应用程序
常见命令 :
pnpm dev
pnpm build
cd package
pnpm test
pnpm typecheck
pnpm lint
CI (中文(简体) ).github/workflows/ci.yml运行 biome ci,插件类型检查、测试套件和插件基于每个拉出请求和推出 main。 。 。 .
检查一下 服务器条目 对抗真实的 OpenAI API, 运行 pnpm check:openai-server 从 repo 根或从 package/它写着: OPENAI_API_KEY 来自环境或 .env 文档。 见 package/scripts/README.md 详细情况.
许可证
麻省理工学院