gt-sanity

中文文档
4.0.31发布于 2026/11/04MITTypeScriptES Module构建来源已验证

General Translation integration with Sanity

原始使用文档
文档目录10 节
  1. Installation
  2. Quick Start
  3. Field-Level Localization
  4. Bringing your own plugin instance
  5. Excluding Fields from Translation
  6. Preserving Edits to Translations
  7. Translating From Other Locales
  8. Automatic Actions
  9. Browsing Translations by Locale
  10. Debug Info

General Translation

Documentation · Report Bug

gt-sanity

General Translation plugin for Sanity Studio v3.

Installation

npm install gt-sanity

Quick Start

import { defineConfig } from 'sanity';
import { gtPlugin } from 'gt-sanity';

export default defineConfig({
  plugins: [
    gtPlugin({
      sourceLocale: 'en',
      locales: ['es', 'fr'],
      // Initialize translated document slugs with a unique locale suffix,
      // e.g. "hello-world" -> "hello-world-es".
      dedupeFields: [{ fields: [{ property: '$.slug' }] }],
    }),
  ],
});

See the full documentation for guides and API reference.

Field-Level Localization

Field-level localization is powered by sanity-plugin-internationalized-array — the reference Sanity plugin. gtPlugin configures it for you from your locales; gt-sanity does not ship its own field-level UI, so Studio behavior always matches the native plugin.

gtPlugin({
  sourceLocale: 'en',
  locales: ['es', 'fr'],
  translateDocuments: ['post'],
  // Documents matched above are localized in place with
  // internationalized arrays instead of per-locale documents.
  translationLevel: 'internationalizedArray',
  fieldLevelLocalization: {
    enabled: true,
    fieldTypes: ['string', 'text'],
  },
});

Then use the generated types in your schemas (type: 'internationalizedArrayString', etc.).

Bringing your own plugin instance

Already registering sanity-plugin-internationalized-array yourself? Keep your setup — GT translation only reads and writes the stored { _key, _type, language, value } data, regardless of who registered the schema types. Leave fieldLevelLocalization disabled so the types are only registered once, and just opt the documents into field-level translation:

plugins: [
  internationalizedArray({
    languages: [
      { id: 'en', title: 'English' },
      { id: 'es', title: 'Spanish' },
    ],
    fieldTypes: ['string'],
  }),
  gtPlugin({
    sourceLocale: 'en',
    locales: ['es'],
    translateDocuments: ['post'],
    translationLevel: 'internationalizedArray',
    // No fieldLevelLocalization — your plugin instance owns the types.
  }),
],

To verify: open a document of a matched type, run Translate from the document actions menu (or the Translations tool), and confirm each localized field gains items for the target locales while the Studio UI (per-language add buttons, language labels) stays exactly as your plugin configures it.

Excluding Fields from Translation

Mark fields in your schema instead of maintaining a list in the plugin config. gt-sanity honors its own options.gt.exclude plus the exclusion options of the standard Sanity localization plugins:

defineField({
  name: 'internalNotes',
  type: 'string',
  options: {
    gt: { exclude: true }, // excluded from GT translation
    // Also honored:
    // documentInternationalization: { exclude: true }, // @sanity/document-internationalization
    // aiAssist: { exclude: true }, // @sanity/assist
  },
});

Exclusion applies at any depth, including fields of nested object types, and can also be set on a custom type definition's options to exclude every occurrence of that type (matching the native plugins' "field or type" semantics). The legacy localize: false field property is still supported. For id-based or cross-document rules (e.g. slug deduplication), the plugin-level ignoreFields / skipFields / dedupeFields options remain available.

Preserving Edits to Translations

Translated content often gets touched up in the Studio after it comes back from General Translation, and a later translation run would normally regenerate it. The Save local edits toggle in the Translations tool changes that: with it on, the translations currently in Sanity are uploaded to General Translation before a translation run, so content whose source text has not changed is reused from the Sanity version instead of being regenerated.

This is off by default; turning it on shows an explanation of the trade-off first, and the choice is remembered for that project and dataset. Turning it on means local content overwrites whatever General Translation holds for that source version — including a completed translation that has not been imported into Sanity yet. Import pending translations before enabling it if that matters to you.

Set the initial state of the toggle from plugin config:

gtPlugin({
  // ...
  preserveExistingTranslations: true,
});

To upload the translations already in Sanity without starting a translation run — useful when adopting the plugin on a project that was translated elsewhere — use Save Local Edits. It uploads the source files it needs, but does not enqueue any translation.

To regenerate translations and deliberately discard existing ones for a single run, use Retranslate from scratch in the Translate All dialog.

Translating From Other Locales

By default only documents in sourceLocale can be translated. To translate a document written in another locale — say, a page that only exists in French — from the Translate dialog on that document, turn on allowNonDefaultSourceLocales:

gtPlugin({
  // ...
  allowNonDefaultSourceLocales: true, // off by default
});

With it on, such a document's targets include sourceLocale.

Each translation group (a translation.metadata document) has exactly one original, and every other document in it is a translation that cannot be translated from:

  • A document in no group is an original.
  • In an existing group, the sourceLocale document is the original, as before.
  • When gt-sanity first translates a document outside sourceLocale, it records that document's language on the group as gtSourceLanguage, and that document becomes the original.

A group with no original — no marker and no sourceLocale document, or a marked original that was deleted — cannot be translated.

Translate All only covers originals in sourceLocale. It leaves out originals in other locales and their translations, and reports how many of those originals (already marked by an earlier translation) it skipped. Save Local Edits only applies to groups whose original is in sourceLocale, and field-level localization always translates from sourceLocale.

Turning allowNonDefaultSourceLocales off again once groups have been marked is not supported. gt-sanity then ignores the marker, as it did before the option existed, so a sourceLocale translation of an original in another locale is treated as an original and can be translated from again.

Automatic Actions

The Translate dialog on a document can refresh status, import a translation as soon as it lands, repoint references, and publish the result. Each is a switch in that dialog, and each has a plugin-level default:

gtPlugin({
  // ...
  autoRefresh: true, // poll for completed translations
  autoImport: true, // import a translation as soon as it completes
  autoPatchReferences: false, // repoint references after import
  autoPublish: false, // publish translated documents after import
});

The defaults turn on what only reads or writes drafts, and leave off anything that can reach readers:

  • autoRefresh only polls for status, and auto-import depends on it.
  • autoImport writes drafts, which are cheap to discard.
  • autoPatchReferences usually writes a draft too, but when a translated document is already published and has no draft, the reference rewrite is applied to a draft seeded from it rather than to the published document. Still opt-in, because it edits documents you may consider finished.
  • autoPublish publishes, and turning the switch back off unpublishes nothing.

These are starting points, not locks. Whatever the user sets in the Studio is remembered in localStorage for that project and dataset, and is preferred over the config on their next visit. The Save local edits toggle (preserveExistingTranslations) is remembered the same way; enabling it still shows its explanation first.

Translated documents are always created as drafts. With autoPublish off they stay that way until someone publishes them — so if a translation seems missing after a run, check the Drafts perspective before assuming it wasn't created.

Browsing Translations by Locale

By default a translated document sits in the same list as its source, so a type's list grows by one entry per locale. gtStructureItems groups them instead: each translatable type expands into a pane per locale.

import { structureTool } from 'sanity/structure';
import { gtStructureItems } from 'gt-sanity';

structureTool({
  structure: (S, context) =>
    S.list()
      .title('Content')
      .items([
        ...gtStructureItems(S, context),
        S.divider(),
        ...S.documentTypeListItems(),
      ]),
});

This is opt-in: the structure tool's layout belongs to structureTool() in your Studio config, so a plugin cannot set it for you. Use gtStructure() for the whole structure when you have no other list items:

import { gtStructure } from 'gt-sanity';

structureTool({ structure: gtStructure() });

The source pane includes documents whose language field is unset, since content that predates the plugin has no language. Types localized in place with internationalized arrays are skipped — their translations live inside the source document, so there is nothing to group. Pass types to override which types are grouped, and sourceTitle / localeTitle to change the pane titles.

Debug Info

The Translations tool footer shows the installed plugin version and a Debug info button. It opens the plugin's effective configuration — resolved source and target locales, translationLevel, which documents are matched, field rules, the active preferences, and whether the secrets document was found — with a button to copy it for a support request.

The API key is never included, only whether one is set.

版本与发布记录

gt-sanity 共发布 123 个版本,首次发布于 2025/09/18;过去 12 个月发布了 111 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
4.0.31latest2026/11/04今天
4.0.30-samsara.0samsara2026/10/295 天前
4.0.302026/10/296 天前
4.0.292026/10/2410 天前
4.0.282026/10/2411 天前
4.0.28-samsara.02026/10/2312 天前

其他发布通道:odysseus → 2.0.18-odysseus.8,iris → 3.1.7-iris.0,samsara → 4.0.30-samsara.0。

关于 gt-sanity 的常见问题

gt-sanity 是什么?

与Sanity的一般翻译集成

如何安装 gt-sanity?

在项目目录运行 npm install gt-sanity,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=19.2.0)、sanity(^6.9.2)、@sanity/ui(^4.0.1)、@sanity/icons(^5.2.1)、@sanity/schema(^6.9.2)、@sanity/mutator(^6.9.2)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

gt-sanity 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

gt-sanity 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

gt-sanity 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

gt-sanity 可以商用吗?

gt-sanity 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

gt-sanity 还在维护吗?

最新版本 4.0.31 发布于 2026/11/04(今天);至今共发布 123 个版本,其中过去 12 个月发布了 111 个;首次发布于 2025/09/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。