# General Translation Integrations: 複数のマーケットでの販売
URL: https://generaltranslation.com/ja/docs/integrations/shopify/guides/selling-in-markets.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 言語ごとに専用の Shopify マーケットを割り当て、買い物客が言語を切り替えたときにカートを引き継ぐ方法。

Shopify は、`@inContext` の `country` で選択されたマーケットに応じて、価格、通貨、在庫状況を返します。ローカライゼーションのガイドではすべての言語で同じマーケットを使用するため、言語を切り替えても変わるのはテキストだけです。このガイドでは、[サンプルストアフロント](https://gt-shopify-vercel.vercel.app)と同じように言語ごとに専用のマーケットを割り当て、テキストだけでなく、そのマーケットの価格と通貨も表示されるようにします。

*注: このコードは、[Next.js の Shopify ストアフロントのローカライズ](/docs/integrations/shopify/guides/localizing-nextjs)で作成した `proxy.ts` モジュールと `lib/i18n` モジュールを拡張したものです。*

## 始める前に [#before-start]

* [Next.js の Shopify ストアフロントのローカライズ](/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";

// 各言語は1つの 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 マーケットに属している必要があります。また、すべての言語は Storefront API の `LanguageCode` でなければなりません ([ロケールコード](/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` では、マーケットが変わる可能性がある場合にのみカートの移動処理を実行し、その処理が成功した後にのみロケールクッキーを設定します。これは、Next.js ガイドにあるクッキーの更新処理の代わりとなるものです：

```ts title="proxy.ts"
// 言語の切り替え、またはロケールクッキーのないページ表示では、カートが別のマーケットに移る可能性があります。
const marketMayChange = isPageNavigation && rememberedLocale !== locale;

// handleShopifyRoutes がリクエストを処理しなかった後、ページレスポンスの返却やロケールクッキーの設定より前に実行します。
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" });
}
```

このチェックの動作は次のとおりです。

* **コスト。** 同じ言語内でのナビゲーションではチェックをスキップします。言語を切り替えた場合やクッキーなしでアクセスした場合は、キャッシュされないカートの読み取りが1回発生し、国が異なる場合にのみミューテーションも実行されます。カートの国を取得するクエリはキャッシュしないでください。
* **失敗時。** ネットワーク障害、GraphQL エラー、ミューテーションの `userErrors` が発生した場合は、旧マーケットのカートの横に新マーケットの価格をレンダリングするのではなく、503 を返します。チェックはマーケットが変わる場合にしか実行されないため、Shopify のカートで障害が起きても妨げられるのは言語の切り替えだけで、閲覧には影響しません。
* **復旧。** ロケールのクッキーは、移動が成功した後にのみ設定されます。移動に失敗した場合 (Shopify には到達したもののレスポンスが失われた場合も含む) はクッキーが削除されるため、どの言語であっても次のページナビゲーションで改めてカートが読み取られます。クッキーはあくまでヒントであり、カートの国を保証するものではありません。このプロキシを経由せずに国が変更された場合は、次に言語を切り替えたとき、またはクッキーなしでアクセスしたときに修正されます。

国の更新が成功すると、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.
