# General Translation Integrations: 翻译 Shopify 商品
URL: https://generaltranslation.com/zh/docs/integrations/shopify/guides/translating-products.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 如何使用 General Translation 翻译 Shopify 商品内容，并将译文存储在 Shopify 中，或随店面一起打包。

商品标题、描述和选项来自 Shopify，而不是你的代码，因此 `gt-react` 和 `gt-next` 不会翻译它们。你只需翻译一次，再从以下两处之一提供译文即可。

## 如何选择 [#choosing]

* **将译文存储在 Shopify 中**：适用于商家频繁编辑目录的情况。由脚本通过 Admin API 注册译文，店面通过 `@inContext(language:)` 读取译文；编辑目录后无需重新构建店面。
* **将译文与店面打包在一起**：适用于没有 Shopify Admin 访问权限，或希望目录译文随代码一起审校和部署的情况。CLI 会在翻译文案的同一次运行中，一并翻译导出的 JSON 文件。示例店面采用的就是这种方式。

*备注：仪表板中不提供托管的 Shopify 连接。无论采用哪种方式，都需要你使用自己的 Shopify 和 General Translation 凭据，自行运行导出。*

## a) 在 Shopify 中存储译文 [#store-in-shopify]

Shopify 会针对每个区域设置，为资源的每个字段分别存储一份译文。脚本先通过 Admin API 导出源文本，再使用 [`translateMany`](/docs/platform/core/reference/gt-class-methods/translation/translate-many) 进行翻译，最后将结果写回 Shopify。

### 开始之前

你需要：

* 已在 Shopify 后台的 **Settings &gt; Languages** 中添加并发布目标语言。
* 一个 Shopify Admin API 访问令牌，该令牌须来自店铺中已安装的应用，并具有 `read_translations`、`write_translations` 和 `read_locales` 权限范围。Storefront API 令牌无法写入译文。
* 已安装 `generaltranslation`，并拥有一个具备 **Runtime translation** 权限 (`project:translations:generate`) 的项目 API 密钥。 (参见核心库[快速入门](/docs/platform/core/quickstart)。) 

请将 Admin 令牌和 API 密钥保存在脚本的环境变量中，切勿将其添加到店面的环境变量或浏览器应用包中。

### 导出源内容

针对需要翻译的每种资源类型，查询 `translatableResources`。每个字段都会返回其 `key`、源 `value`，以及用于唯一标识该源文本的 `digest`。

```graphql title="Admin API"
query CatalogExport($type: TranslatableResourceType!, $after: String) {
  translatableResources(first: 50, after: $after, resourceType: $type) {
    nodes {
      resourceId
      translatableContent { key value digest locale }
      translations(locale: "fr") { key value outdated }
    }
    pageInfo { hasNextPage endCursor }
  }
}
```

使用 `PRODUCT`、`PRODUCT_OPTION` 和 `PRODUCT_OPTION_VALUE` 等资源类型，并通过 `endCursor` 对结果分页。请保存好这些摘要，后续注册译文时需要用到。

### 使用 General Translation 进行翻译

将纯文本字段 (如商品标题、商品类型、SEO 标题和描述，以及选项名称和选项值) 发送到 [`translateMany`](/docs/platform/core/reference/gt-class-methods/translation/translate-many)。添加 [`metadata.context`](/docs/platform/core/reference/types/entry-metadata#context)，确保简短标签也能准确翻译。

```ts title="scripts/translate-catalog.ts"
import { GT } from 'generaltranslation';

const gt = new GT({
  apiKey: process.env.GT_API_KEY,
  projectId: process.env.GT_PROJECT_ID,
  sourceLocale: 'en',
});

const results = await gt.translateMany(
  fields.map((field) => ({
    source: field.value,
    metadata: {
      context: `Shopify ${field.resourceType} ${field.key}. Preserve product brand names, measurements, and model numbers.`,
    },
  })),
  { sourceLocale: 'en', targetLocale: 'fr' }
);
```

结果按输入顺序返回，每条结果单独报告成功或失败。对于失败的条目，应重新尝试翻译，而不是直接注册。

富文本字段 (例如产品的 `body_html`) 包含 HTML。通过 [`metadata.fileFormat`](/docs/platform/core/reference/types/entry-metadata#file-format) 进行的格式感知翻译仅支持 Markdown 和 MDX 文档，因此如果没有保留 HTML 标记的处理步骤，请勿将原始 HTML 直接传给 [`translateMany`](/docs/platform/core/reference/gt-class-methods/translation/translate-many)。

### 在 Shopify 中注册译文

使用 Admin API 的 [`translationsRegister`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/translationsRegister) mutation 写入每条译文。将源内容摘要作为 `translatableContentDigest` 传入，这样一来，如果某条译文对应的源内容在导出后发生了变更，Shopify 会拒绝该译文。

```graphql title="Admin API"
mutation CatalogRegister($resourceId: ID!, $translations: [TranslationInput!]!) {
  translationsRegister(resourceId: $resourceId, translations: $translations) {
    translations { key locale value outdated }
    userErrors { field code }
  }
}
```

```json title="Variables"
{
  "resourceId": "gid://shopify/Product/123",
  "translations": [
    {
      "locale": "fr",
      "key": "title",
      "value": "Sweat à capuche en coton",
      "translatableContentDigest": "<digest from export>"
    }
  ]
}
```

`locale` 请使用 Shopify 的区域设置代码，它可能与你在 General Translation 中使用的区域设置不同。 (参见[区域设置代码](/docs/integrations/shopify/reference/locale-handling#locale-codes)。) 

<Callout type="warn">
  `translationsRegister` 会覆盖该键在该区域设置下的现有译文，包括商家在后台手动输入的译文，以及其他应用写入的译文。写入前，请先将当前译文与你的脚本上次注册的值进行比对，并跳过不是由你写入的字段。
</Callout>

### 在店面中读取译文

在目录查询中添加 `@inContext(language: $language)`，并传入该区域设置对应的 Shopify `LanguageCode`。若存在译文，Shopify 会返回翻译后的值，否则返回源值。Hydrogen 的 Storefront 客户端会根据其请求上下文自动填充 `$language`。 (参见[使用 React Router 本地化 Shopify Hydrogen](/docs/integrations/shopify/guides/localizing-react-router#storefront-language)。) 

Shopify 和店面缓存刷新后，翻译后的目录内容即会显示，无需重新部署。

### 保持译文最新

商家编辑商品后，该商品的字段摘要 (digest) 会随之变化，Shopify 会将现有译文标记为 `outdated`。此时请重新运行导出，仅翻译摘要有变化或尚无译文的字段，然后注册翻译结果。

## b) 将译文与店面一起打包 [#bundle]

*注意：以下示例来自 Next.js 示例店面，其区域设置的初始化设置沿用了 [Next.js 指南](/docs/integrations/shopify/guides/localizing-nextjs)中的做法。*

通过 Storefront API 导出目录中的文本，并直接以源文本作为键。将短标签 (标题、选项名称和值、替代文本) 与富文本 (描述、页面正文) 分别写入不同的文件，这样只有短标签会发送到浏览器。富文本的清理方式须与店面渲染时保持一致，以确保键能够匹配：

```ts title="scripts/export-catalog.ts"
const labels = new Set<string>();
const content = new Set<string>();
for (const product of catalog.products.nodes) {
  labels.add(product.title).add(product.productType);
  content.add(product.description).add(sanitizeShopifyHtml(product.descriptionHtml));
  for (const image of product.images.nodes) if (image.altText) labels.add(image.altText);
  for (const option of product.options) {
    labels.add(option.name);
    for (const value of option.optionValues) labels.add(value.name);
  }
}

async function writeCatalog(file: string, texts: Set<string>): Promise<number> {
  const entries = [...texts].filter((text) => text.trim()).sort((a, b) => a.localeCompare(b, "en"));
  await writeFile(
    new URL(`../catalog/en/${file}`, import.meta.url),
    `${JSON.stringify(Object.fromEntries(entries.map((text) => [text, text])), null, 2)}\n`,
  );
  return entries.length;
}
```

在 `gt.config.json` 的 `files.json` 下添加导出的文件，与现有的 `files.gt` 条目并列。这样，[`npx gt translate`](/docs/cli/reference/commands/translate) 就会在翻译文案的同一次运行中一并写入 `catalog/<locale>/labels.json` 和 `catalog/<locale>/content.json`：

```json title="gt.config.json"
{
  "files": {
    "json": { "include": ["catalog/[locale]/labels.json", "catalog/[locale]/content.json"] }
  }
}
```

以实时的源文本为键查找译文，找不到时回退到源文本本身。这样，上次导出后在 Shopify 中编辑过的内容都会显示最新文本：

```ts title="lib/catalog/index.ts"
/** 以商家文本当前的英文源文本作为键查找翻译；若无对应翻译，则回退为源文本。 */
export function translateCatalogText(catalog: Catalog, text: string): string;
export function translateCatalogText(catalog: Catalog, text: string | undefined): string | undefined;
export function translateCatalogText(catalog: Catalog, text: string | undefined) {
  return (text && catalog[text]) || text;
}
```

```ts title="lib/catalog/server.ts"
/** 简短标签（标题、选项名称和值、替代文本），供购物车等客户端组件使用。 */
export function getCatalogLabels(locale: string): Catalog {
  return catalogs[locale]?.labels ?? {};
}

/** 返回一个翻译函数，按当前请求的区域设置翻译 Shopify 商家文本。 */
export async function getCatalogText() {
  const catalog = catalogs[await getLocale()];
  const merged: Catalog = catalog ? { ...catalog.content, ...catalog.labels } : {};
  function ct(text: string): string;
  function ct(text: string | undefined): string | undefined;
  function ct(text: string | undefined) {
    return translateCatalogText(merged, text);
  }
  return ct;
}
```

* 通过 [`<Var>`](/docs/react/reference/components/var) 将已翻译的文本传入 [`<T>`](/docs/react/reference/components/t)，不要将其作为源文案传入。
* 对于购物车和预测搜索等客户端组件，请在布局中将 `getCatalogLabels(locale)` 传给上下文提供者。不要将富文本或其他区域设置的消息目录发送到浏览器。
* 凡是用于选择变体、构建 URL 或传递给 Shopify 的选项值，都应保留源语言，只翻译向用户显示的标签。
* 如需引导商品名称的翻译 (例如保留品牌术语或翻译服装类词汇) ，请在仪表板中为项目的上下文分组添加词汇表术语和自定义提示词。

## Next steps

- /docs/integrations/shopify/guides/localizing-react-router
- /docs/integrations/shopify/guides/localizing-nextjs
- /docs/integrations/shopify/guides/selling-in-markets

## Sitemap

See the full [sitemap](https://generaltranslation.com/sitemap.md) for all pages.
