# General Translation Integrations: Продажи на нескольких рынках
URL: https://generaltranslation.com/ru/docs/integrations/shopify/guides/selling-in-markets.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Как связать каждый язык с отдельным рынком Shopify и переносить корзину, когда покупатель меняет язык.

Shopify возвращает цены, валюту и наличие товаров для рынка, который определяется значением `country` в `@inContext`. В руководствах по локализации все языки используют один и тот же рынок, поэтому при смене языка меняется только текст. В этом руководстве каждый язык связывается с собственным рынком, как в [примере витрины](https://gt-shopify-vercel.vercel.app), — так на каждом языке отображаются ещё и цены и валюта соответствующего рынка.

*Примечание: код дополняет модули `proxy.ts` и `lib/i18n` из руководства [Локализация витрины Shopify на Next.js](/docs/integrations/shopify/guides/localizing-nextjs).*

## Перед началом работы [#before-start]

* Выполните шаги из руководства [Локализация витрины Shopify на Next.js](/docs/integrations/shopify/guides/localizing-nextjs).
* Добавьте рынок Shopify для каждой страны, которую вы связываете с языком. Если вы храните переводы каталога в Shopify, опубликуйте каждый язык на соответствующем рынке.

Связывать языки с рынками необязательно. Чтобы покупатели могли выбирать страну независимо от языка, вместо этого добавьте отдельный селектор страны и при её смене обновляйте данные покупателя в корзине (buyer identity). (См. [Язык и рынок](/docs/integrations/shopify/reference/locale-handling#language-market).)

## Сопоставьте каждую локаль с рынком [#market-map]

Замените функцию `getCommerceLocale`, рассчитанную на один рынок, таблицей соответствия, которая сопоставляет каждой локали страну и язык Shopify:

```ts title="lib/i18n/index.ts"
import type { CommerceLocale } from "@/lib/config/types";

// Каждому языку соответствует один рынок Shopify, поэтому Shopify возвращает цены и валюту этого рынка.
// Сама валюта всегда берётся из ответа Shopify, а не из этой таблицы.
const markets: Record<string, CommerceLocale> = {
  en: { country: "US", language: "EN" },
  fr: { country: "FR", language: "FR" },
  ja: { country: "JP", language: "JA" },
  ko: { country: "KR", language: "KO" },
  id: { country: "ID", language: "ID" },
};

export function getCommerceLocale(locale: string): CommerceLocale {
  const market = markets[locale];
  if (!market) throw new Error(`No Shopify market for locale: ${locale}`);
  return market;
}
```

Каждая страна в сопоставлении должна относиться к одному из рынков Shopify этого магазина, а каждый язык должен быть значением `LanguageCode` из Storefront API (см. [Коды локалей](/docs/integrations/shopify/reference/locale-handling#locale-codes)).

Для чтения каталога, обработчиков Hydrogen и [`getRegion`](/docs/react/nextjs/reference/functions/get-region) больше ничего менять не нужно: они уже используют `getCommerceLocale`, поэтому теперь каждая локаль обращается к своему рынку, а геттеры с `"use cache"` хранят отдельную запись кэша для каждого рынка. При создании новых корзин передавайте страну рынка в качестве `countryCode` покупателя.

## Перенос корзины между рынками [#move-cart]

Корзина сохраняет валюту страны своего покупателя. В обработчиках корзины Hydrogen нет действия для смены страны покупателя, поэтому, если переход на другую страницу может сменить рынок, прокси считывает фактическую страну корзины и обновляет её до рендеринга страницы:

```ts title="lib/cart/request.ts"
const CART_COUNTRY_QUERY = gql(`#graphql
  query cartCountry($cartId: ID!) {
    cart(id: $cartId) {
      buyerIdentity { countryCode }
    }
  }
`);

const CART_COUNTRY_UPDATE_MUTATION = gql(`#graphql
  mutation cartCountryUpdate($cartId: ID!, $countryCode: CountryCode!) {
    cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: { countryCode: $countryCode }) {
      cart { buyerIdentity { countryCode } }
      userErrors {
        message
      }
    }
  }
`);

export async function moveCartToMarket(
  request: Request,
  requestContext: ShopifyRequestContext,
): Promise<void> {
  const cartId = getCartId({ cookie: request.headers.get("cookie") ?? undefined });
  if (!cartId) return;
  const client = createRequestStorefrontClient(requestContext);
  const countryCode = requestContext.i18n.country;
  // Читаем корзину в обход кеша: другая вкладка или сессия могла перенести её в другой рынок.
  const current = await client.graphql(CART_COUNTRY_QUERY, { variables: { cartId } });
  if (current.errors) throw new Error("Unable to read the cart's country");
  const cart = current.data?.cart;
  // Корзину с истёкшим сроком действия обрабатывает уже имеющийся в шаблоне механизм создания корзины.
  if (!cart || cart.buyerIdentity.countryCode === countryCode) return;

  const result = await client.graphql(CART_COUNTRY_UPDATE_MUTATION, {
    variables: { cartId, countryCode },
  });
  if (result.errors) throw new Error("Unable to update the cart's country");
  const updated = result.data.cartBuyerIdentityUpdate;
  if (
    !updated ||
    updated.userErrors.length > 0 ||
    updated.cart?.buyerIdentity.countryCode !== countryCode
  ) {
    throw new Error("The cart's country was not updated");
  }
}
```

В `proxy.ts` переносите корзину только в том случае, если рынок может смениться, а cookie-файл локали устанавливайте лишь после успешного переноса. Этот код заменяет обновление cookie-файла из руководства по Next.js:

```ts title="proxy.ts"
// Смена языка или просмотр страницы без cookie-файла локали может перенести корзину на другой рынок.
const marketMayChange = isPageNavigation && rememberedLocale !== locale;

// После того как handleShopifyRoutes отклонит запрос, но до ответа страницы и установки cookie-файла локали.
if (marketMayChange) {
  try {
    await moveCartToMarket(request, requestContext);
  } catch {
    // Не рендерим цены нового рынка рядом с корзиной другого рынка.
    const failure = new NextResponse("Unable to switch the cart's market. Please reload to retry.", {
      status: 503,
      headers: { "Cache-Control": "no-store" },
    });
    // Обновление могло дойти до Shopify, хотя здесь и завершилось сбоем, поэтому сбрасываем
    // сохранённую локаль; при следующем переходе на страницу корзина будет прочитана заново.
    failure.cookies.delete(LOCALE_COOKIE_NAME);
    return failure;
  }
}

// При формировании ответа. Выполняется только после успешного moveCartToMarket.
if (marketMayChange) {
  response.cookies.set(LOCALE_COOKIE_NAME, locale, { path: "/", sameSite: "lax" });
}
```

Как работает проверка:

* **Стоимость.** При навигации в пределах одного языка проверка не выполняется. Переключение языка или посещение без cookie-файла добавляет одно некэшируемое чтение корзины, а мутацию — только если страны различаются. Не кэшируйте запрос страны корзины.
* **Сбои.** При сетевых сбоях, ошибках GraphQL и `userErrors` мутации возвращается 503, чтобы не выводить цены нового рынка рядом с корзиной старого рынка. Поскольку проверка запускается только при смене рынка, сбой корзины Shopify блокирует переключение языка, но не просмотр сайта.
* **Восстановление.** Cookie-файл локали устанавливается только после успешного переноса. Неудачный перенос (в том числе если запрос дошёл до Shopify, но ответ был потерян) удаляет cookie-файл, поэтому при следующем переходе на любую страницу на любом языке корзина снова считывается. Cookie-файл остаётся лишь подсказкой, а не гарантией того, к какой стране относится корзина: если страна была изменена в обход этого прокси, она исправится при следующем переключении языка или посещении без cookie-файла.

После успешного обновления страны Shopify пересчитывает цены позиций корзины. [Переключатель языка](/docs/integrations/shopify/guides/localizing-nextjs#routing) выполняет переход с полной загрузкой страницы, поэтому прокси видит каждое переключение.

## Next steps

- /docs/integrations/shopify/guides/localizing-nextjs
- /docs/integrations/shopify/guides/translating-products
- /docs/integrations/shopify/guides/localizing-react-router

## Sitemap

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