# General Translation Integrations: Перевод товаров Shopify
URL: https://generaltranslation.com/ru/docs/integrations/shopify/guides/translating-products.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Как переводить контент товаров Shopify с помощью General Translation и хранить переводы в 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` и ключ API проекта с разрешением **Runtime translation** (`project:translations:generate`). (См. [Быстрый старт](/docs/platform/core/quickstart) по основной библиотеке.)

Храните токен Admin API и ключ 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 в [`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). Передавайте дайджест источника в параметре `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)` к запросам каталога и передайте `LanguageCode` Shopify, соответствующий локали. Shopify возвращает переведённое значение, если оно есть, а иначе — исходное значение. Клиент Storefront в Hydrogen берёт значение `$language` из своего контекста запроса. (См. [Локализация Shopify Hydrogen с React Router](/docs/integrations/shopify/guides/localizing-react-router#storefront-language)).

Переведённый контент каталога появится после обновления кешей Shopify и витрины — повторное развёртывание не требуется.

### Поддерживайте актуальность переводов

Когда продавец редактирует товар, дайджест его полей меняется, и 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;
}
```

Добавьте экспортированные файлы в раздел `files.json` в `gt.config.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;
}
```

* Передавайте переведённый текст в [`<T>`](/docs/react/reference/components/t) через [`<Var>`](/docs/react/reference/components/var), а не как исходный текст.
* Для клиентских компонентов, таких как корзина и предиктивный поиск, передавайте `getCatalogLabels(locale)` в провайдер контекста в компоновке. Не отправляйте в браузер форматированный текст и каталоги других локалей.
* Оставляйте значения вариантов на языке-источнике везде, где они используются для выбора варианта товара, формирования URL или передаются в Shopify. Переводите только отображаемую подпись.
* Чтобы управлять переводом названий товаров (например, оставлять бренды без перевода или переводить названия предметов одежды), добавьте термины Glossary и пользовательские промпты в контекстную группу проекта в панели управления.

## 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.
