# General Translation Integrations: Локализация витрины Shopify на Next.js
URL: https://generaltranslation.com/ru/docs/integrations/shopify/guides/localizing-nextjs.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Как локализовать витрину Vercel Shop с помощью gt-next, маршрутов с префиксом локали и Cache Components.

[Vercel Shop](https://github.com/vercel/shop) — это шаблон для Next.js App Router на основе Hydrogen, не зависящего от фреймворка SDK Shopify для создания витрин. С `gt-next` локаль по умолчанию отдаётся без префикса, остальные локали размещаются под префиксом, например `/fr/...`, а каждую страницу по-прежнему можно предварительно отрендерить — по одному разу для каждой локали. Все языки работают в рамках одного рынка Shopify; чтобы назначить каждому языку собственный рынок и валюту, затем выполните шаги из руководства [Продажи на нескольких рынках](/docs/integrations/shopify/guides/selling-in-markets).

*Примечание: это руководство написано для шаблона с Next.js 16, `cacheComponents: true`, `@shopify/hydrogen` 2026.10 и `gt-next` 11.4 — именно эти версии используются в [примере витрины](https://gt-shopify-vercel.vercel.app), которая доступна на английском, французском, японском, корейском и индонезийском языках.*

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

Установите `gt-next`, а также CLI `gt` в качестве зависимости для разработки, как описано в руководстве [Быстрый старт с Next.js](/docs/react/nextjs-quickstart). В этом руководстве вместо промежуточного ПО из «Быстрого старта» используется уже имеющийся в шаблоне файл `proxy.ts`.

## Настройка gt-next [#configure]

### 1. Добавьте плагин конфигурации

Добавьте [`withGTConfig`](/docs/react/nextjs/config) в список плагинов, который шаблон передаёт в `withShopConfig`:

```ts title="next.config.ts"
import { withGTConfig } from "gt-next/config";

export default withShopConfig(nextConfig, [
  (config: NextConfig) => withGTConfig(config), // [!code ++]
  shopConfig.botid.isEnabled && withBotId,
  shopConfig.agent.isEnabled && withEve,
]);
```

### 2. Добавьте функции запроса

При использовании Cache Components локаль должна браться из корневого параметра `[locale]`, а не из заголовков или cookie-файлов. Создайте файлы [`getLocale`](/docs/react/nextjs/reference/functions/get-locale) и [`getRegion`](/docs/react/nextjs/reference/functions/get-region) в корневом каталоге проекта — `withGTConfig` обнаружит их автоматически. (См. [Использование Cache Components](/docs/react/nextjs/cache-components#request-values)).

```ts title="getLocale.ts"
import { locale } from "next/root-params";

import { defaultLocale } from "@/lib/i18n";

// Корневой параметр `[locale]` входит в статический ключ кэша, поэтому его чтение не мешает предварительному рендерингу страниц.
export default async function getLocale(): Promise<string> {
  return (await locale()) ?? defaultLocale;
}
```

```ts title="getRegion.ts"
import { locale } from "next/root-params";

import { defaultLocale, getCommerceLocale } from "@/lib/i18n";

// Регион — это страна рынка Shopify, на котором продаёт локаль страницы.
export default async function getRegion(): Promise<string> {
  return getCommerceLocale((await locale()) ?? defaultLocale).country;
}
```

`getCommerceLocale` сопоставляет локаль с соответствующими страной и языком в Shopify (см. [Запросы к Shopify на каждом языке](#shopify-language)).

### 3. Генерация и локальная загрузка переводов

Для Cache Components требуется пользовательская функция [`loadTranslations`](/docs/react/reference/functions/load-translations), поскольку удалённая загрузка небезопасна во время пререндеринга. Запишите переводы в исходный код с помощью CLI и импортируйте их из бандла. (См. [Локальное хранение переводов](/docs/react/guides/storing-translations).)

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["fr", "ja", "ko", "id"],
  "src": ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}", "lib/**/*.{ts,tsx}"],
  "files": {
    "gt": { "output": "public/_gt/[locale].json" }
  }
}
```

* `src` добавляет `lib/`. По умолчанию CLI сканирует только `src/`, `app/`, `pages/` и `components/`, а в шаблоне метаданные и метки хранятся в `lib/`.

```ts title="loadTranslations.ts"
// `pnpm translate` создаёт эти файлы перед сборкой; благодаря включению их в бандл страницы можно пререндерить.
const translations: Record<string, () => Promise<{ default: object }>> = {
  fr: () => import("./public/_gt/fr.json"),
  ja: () => import("./public/_gt/ja.json"),
  ko: () => import("./public/_gt/ko.json"),
  id: () => import("./public/_gt/id.json"),
};

export default async function loadTranslations(locale: string): Promise<object> {
  const loaded = await translations[locale]?.();
  return loaded?.default ?? {};
}
```

Запускайте [`npx gt translate`](/docs/cli/reference/commands/translate) после изменения текстов и коммитьте сгенерированные файлы либо выполняйте эту команду перед `next build`. Если файлы закоммичены, витрина собирается без ключа API General Translation.

## Маршрутизация запросов по локали [#routing]

### Перенесите маршруты в `[locale]`

1. Перенесите все маршруты витрины в `app/[locale]/` и удалите `app/layout.tsx`, чтобы `[locale]` стал корневым параметром. Оставьте `api/`, `md/`, `agent/`, `sitemap*`, `robots.ts`, `manifest.ts`, `llms.txt`, `global-error.tsx` и статические иконки непосредственно в `app/`.

2. Замените аргументы типов `PageProps<"/...">` и `LayoutProps<"/...">` на `"/[locale]/..."`.

3. В `app/[locale]/layout.tsx` верните все локали из `generateStaticParams`, отклоните неподдерживаемые локали, задайте `<html lang>` и оберните body в [`GTProvider`](/docs/react/reference/components/gt-provider):

   ```tsx title="app/[locale]/layout.tsx"
   import { GTProvider } from "gt-next";
   import { getLocale } from "gt-next/server";
   import { notFound } from "next/navigation";

   import { isSupportedLocale, locales } from "@/lib/i18n";

   export const ensureStatic = "prefetch";

   export function generateStaticParams() {
     return locales.map((locale) => ({ locale }));
   }

   export default async function RootLayout({ children }: LayoutProps<"/[locale]">) {
     const locale = await getLocale();
     if (!isSupportedLocale(locale)) notFound();
     return (
       <html lang={locale}>
         <body>
           <GTProvider>{/* ссылка «перейти к содержимому», провайдер корзины, навигация, main, подвал */}{children}</GTProvider>
         </body>
       </html>
     );
   }
   ```

4. Добавьте catch-all-маршрут, вызывающий `notFound()`, чтобы для несовпавших путей рендерилась страница `app/[locale]/not-found.tsx` внутри локализованной компоновки, а не стандартная страница 404 Next.js:

   ```tsx title="app/[locale]/[...rest]/page.tsx"
   import { notFound } from "next/navigation";

   import { PLACEHOLDER_HANDLE } from "@/lib/static-params";

   export function generateStaticParams() {
     return [{ rest: [PLACEHOLDER_HANDLE] }];
   }

   export default function CatchAllPage(): never {
     notFound();
   }
   ```

<Callout type="warn">
  **Предупреждение:** В `gt-next` 11.4 [`GTProvider`](/docs/react/reference/components/gt-provider) читает `usePathname()` на клиенте. Из-за этого при включённых Cache Components динамический маршрут без статических параметров не проходит пререндеринг. Для маршрутов без статических параметров, например catch-all-маршрута и `account/orders/[id]`, возвращайте плейсхолдер из `generateStaticParams`.
</Callout>

### Маршрутизация запросов в прокси

Один раз прочитайте список локалей из `gt.config.json` и отделяйте префикс от каждого пути:

```ts title="lib/i18n/index.ts"
import gtConfig from "@/gt.config.json";

export const defaultLocale = gtConfig.defaultLocale;
export const locales = [gtConfig.defaultLocale, ...gtConfig.locales];

export function isSupportedLocale(value: string | undefined): value is string {
  return value !== undefined && locales.includes(value);
}

/** Разделяет `/fr/products/x` на `fr` и `/products/x`; пути без префикса относятся к локали по умолчанию. */
export function splitLocalePath(pathname: string): { locale: string; pathname: string } {
  const [, segment, ...rest] = pathname.split("/");
  if (segment !== defaultLocale && isSupportedLocale(segment)) {
    return { locale: segment, pathname: `/${rest.join("/")}` };
  }
  return { locale: defaultLocale, pathname };
}

/** Локаль по умолчанию остаётся без префикса, чтобы у каждой страницы был один канонический URL. */
export function localizePath(pathname: string, locale: string): string {
  if (locale === defaultLocale) return pathname;
  return pathname === "/" ? `/${locale}` : `/${locale}${pathname}`;
}
```

В `proxy.ts` оставьте диспетчеризацию `handleShopifyRoutes` из шаблона как есть, чтобы Shopify первым обрабатывал запросы корзины, предиктивного поиска и учётной записи клиента. Затем перенаправляйте (rewrite) все остальные запросы страниц на соответствующий маршрут `/[locale]`. API и другие пути, не относящиеся к страницам, не трогайте:

```ts title="proxy.ts"
// Диспетчеризация из шаблона без изменений: если включены аккаунты покупателей, ветка
// с поддержкой сессий добавляет обработчики аккаунтов и менеджер сессий.
const shopifyRoute = customerSession
  ? handleShopifyRoutes({
      ...routeOptions,
      handlers: customerHandlers(customerSession, isAuthPath),
      sessionManager: createCustomerSessionManager(request),
    })
  : handleShopifyRoutes({ ...routeOptions, handlers: [cartHandlers, predictiveSearchHandlers] });
if (shopifyRoute) return shopifyRoute;

const { locale, pathname: unprefixedPathname } = splitLocalePath(pathname);
const refinedPath = getRefinedCollectionPath(unprefixedPathname, request.nextUrl.search);
const localizedPath = isUnlocalizedPath(pathname)
  ? null
  : `/${locale}${refinedPath ?? (unprefixedPathname === "/" ? "" : unprefixedPathname)}`;
// Страницы находятся в app/[locale]; локаль по умолчанию отдаётся без префикса, а внутри выполняется rewrite.
const rewritePath =
  getMarkdownRewritePath(request) ?? (localizedPath === pathname ? null : localizedPath);
```

```ts title="proxy.ts"
const UNLOCALIZED_PATH = /^\/(?:api|md|agent|eve|_eve_internal|\.well-known|__shopify)(?:\/|$)/;

function isUnlocalizedPath(pathname: string): boolean {
  return UNLOCALIZED_PATH.test(pathname);
}

// Страницы магазина — в отличие от маршрутов API и эндпоинтов Shopify с точкой в имени, таких как `/cart.js`.
function isPagePath(pathname: string): boolean {
  return !isUnlocalizedPath(pathname) && !/\.[a-z]+$/i.test(pathname);
}
```

Поскольку `splitLocalePath` никогда не считает локаль по умолчанию префиксом, `/en/products/x` переписывается в `/en/en/products/x` и возвращает 404. Таким образом, у каждой страницы только один канонический URL.

### Локализуйте ссылки

Передавайте строковые href через [`useLocalizeHref`](/docs/react/nextjs/reference/hooks/use-localize-href) внутри компонента `Link` шаблона, чтобы внутренние ссылки сохраняли текущую локаль. То же самое сделайте для `router.push`, `router.replace` и навигации при поиске.

```tsx title="components/ui/link.tsx"
import { useLocalizeHref } from "gt-next/link";

// Навигация сохраняет активную локаль; для локали по умолчанию префикс не добавляется.
const localizeHref = useLocalizeHref();
const localizedHref = typeof href === "string" ? localizeHref(href) : href;
```

Для переключателя языка замените префикс в текущем URL, а название каждого языка указывайте на нём самом:

```tsx title="components/nav/language-switcher.tsx"
"use client";

import { T, useLocale, useLocales } from "gt-next";
import { useId } from "react";

import { localizePath, splitLocalePath } from "@/lib/i18n";

/** Выводит название каждого языка на нём самом («Français», «日本語»), чтобы покупатели сразу узнавали свой. */
function getEndonym(locale: string): string {
  const name = new Intl.DisplayNames([locale], { type: "language" }).of(locale) ?? locale;
  return name.charAt(0).toLocaleUpperCase(locale) + name.slice(1);
}

export function LanguageSwitcher() {
  const id = useId();
  const locale = useLocale();
  const locales = useLocales();
  return (
    <>
      <label htmlFor={id} className="sr-only">
        <T>Language</T>
      </label>
      <select
        id={id}
        value={locale}
        onChange={(event) => {
          // Смена локали меняет корневой макет, поэтому переходим с полной перезагрузкой страницы.
          const { pathname } = splitLocalePath(window.location.pathname);
          window.location.assign(
            `${localizePath(pathname, event.target.value)}${window.location.search}`,
          );
        }}
      >
        {locales.map((option) => (
          <option key={option} lang={option} value={option}>
            {getEndonym(option)}
          </option>
        ))}
      </select>
    </>
  );
}
```

### Добавьте SEO-метаданные

Укажите для каждой страницы канонический URL в её собственной локали и альтернативную версию `hreflang` для каждой локали. Функции `buildAlternates` и `buildOpenGraph` из шаблона в `lib/seo/index.ts` синхронны и не могут получить локаль, поэтому перенесите их в `lib/seo/server.ts` как асинхронные функции:

1. В `lib/seo/index.ts` экспортируйте `buildCanonicalPath` и удалите `buildAlternates` и `buildOpenGraph`.
2. Добавьте асинхронные версии в `lib/seo/server.ts`:

   ```ts title="lib/seo/server.ts"
   import { getLocale } from "gt-next/server";
   import type { Metadata } from "next";

   import { localizePath, locales } from "@/lib/i18n";
   import { buildCanonicalPath } from "@/lib/seo";
   import type { SearchParamsInput } from "@/lib/seo/types";

   /** Канонический URL для локали запроса, а также альтернативные версии hreflang для каждой локали. */
   export async function buildAlternates({
     pathname,
     searchParams,
   }: {
     pathname: string;
     searchParams?: SearchParamsInput;
   }): Promise<Metadata["alternates"]> {
     const canonicalPath = buildCanonicalPath(pathname, searchParams);
     const locale = await getLocale();
     return {
       canonical: localizePath(canonicalPath, locale),
       languages: {
         ...Object.fromEntries(locales.map((code) => [code, localizePath(canonicalPath, code)])),
         "x-default": canonicalPath,
       },
     };
   }
   ```

   Точно так же сделайте асинхронной `buildOpenGraph` и локализуйте в ней URL и локали. Open Graph ожидает значения в формате `language_TERRITORY`, например `fr_FR`, поэтому преобразуйте локаль именно здесь, а для `hreflang` и `<html lang>` оставьте обычный тег:

   ```ts title="lib/seo/server.ts"
   /** Локали Open Graph имеют формат language_TERRITORY, например fr_FR; для "fr" подставляется наиболее вероятная территория. */
   function toOpenGraphLocale(locale: string): string {
     const { language, region } = new Intl.Locale(locale).maximize();
     return `${language}_${region}`;
   }

   // В buildOpenGraph, рядом с существующими полями:
   const locale = await getLocale();
   return {
     url: localizePath(url, locale),
     locale: toOpenGraphLocale(locale),
     alternateLocale: locales.filter((code) => code !== locale).map(toOpenGraphLocale),
   };
   ```

   `maximize()` выбирает наиболее вероятную территорию, например `fr_FR` для `fr` и `zh_TW` для `zh-Hant`. Если для какой-либо локали наиболее вероятная территория не совпадает с той, на которую вы ориентируетесь, замените преобразование явным сопоставлением.
3. В `generateMetadata` каждой страницы импортируйте обе вспомогательные функции из `@/lib/seo/server` и вызывайте их с `await`:

   ```tsx title="app/[locale]/page.tsx"
   import { buildAlternates, buildOpenGraph } from "@/lib/seo/server"; // [!code highlight]

   export async function generateMetadata(): Promise<Metadata> {
     const gt = await getGT();
     const title = gt("Home");
     const description = gt("Explore featured products, curated collections, and seasonal campaigns.");
     return {
       title: `${title} | ${shopConfig.site.name}`,
       description,
       alternates: await buildAlternates({ pathname: "/" }), // [!code highlight]
       openGraph: await buildOpenGraph({ title, description, url: "/", type: "website" }), // [!code highlight]
     };
   }
   ```

   После удаления старых вспомогательных функций страница, которая всё ещё импортирует их из `@/lib/seo`, не пройдёт проверку типов — так вы найдёте все пропущенные места вызова.

Также перечислите в карте сайта URL для каждой локали и добавьте к каждому из них записи `<xhtml:link rel="alternate" hreflang="...">` для всех локалей.

## Запросы к Shopify на каждом языке [#shopify-language]

Сопоставьте каждую локаль со страной и языком Shopify, которые передаются в вызовы Storefront API в шаблоне. Все локали относятся к единственному рынку магазина, поэтому меняется только язык:

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

// Единый рынок для всех языков. Валюта (Currency) всегда берётся из ответа Shopify.
const STORE_COUNTRY: CommerceLocale["country"] = "US";

// Язык Storefront API для каждой локали из gt.config.json.
const SHOPIFY_LANGUAGES: Record<string, CommerceLocale["language"]> = {
  en: "EN",
  fr: "FR",
  ja: "JA",
  ko: "KO",
  id: "ID",
};

export function getCommerceLocale(locale: string): CommerceLocale {
  const language = SHOPIFY_LANGUAGES[locale];
  if (!language) throw new Error(`No Shopify language for locale: ${locale}`);
  return { country: STORE_COUNTRY, language };
}

/** Прокси запоминает локаль страницы в этом cookie-файле, чтобы вызовы обработчиков Hydrogen (`/api/cart`) использовали тот же язык. */
export const LOCALE_COOKIE_NAME = "shop_locale";
/** В этом заголовке запроса прокси передаёт разрешённую локаль серверным действиям и начальным данным корзины. */
export const LOCALE_HEADER_NAME = "x-shop-locale";
```

Каждый язык должен быть допустимым значением `LanguageCode` из Storefront API (см. [Коды локалей](/docs/integrations/shopify/reference/locale-handling#locale-codes)). Если вы [храните переводы каталога в Shopify](/docs/integrations/shopify/guides/translating-products#store-in-shopify), также опубликуйте каждый язык в магазине: для неопубликованного языка Shopify возвращает исходный текст. Для переводов, включённых в бандл витрины, публиковать языки не нужно.

### Передавайте язык в запросы к каталогу

Геттеры каталога в шаблоне уже принимают опцию `locale` типа `CommerceLocale` и передают её в `@inContext`. Передавайте значение локали со всех страниц и из всех компонентов. Геттеры с `"use cache"` получают его как аргумент, поэтому для каждого языка создаётся отдельная запись в кэше.

```ts title="lib/i18n/server.ts"
import { getLocale } from "gt-next/server";
import { headers } from "next/headers";

import type { CommerceLocale } from "@/lib/config/types";
import { defaultLocale, getCommerceLocale, isSupportedLocale, LOCALE_HEADER_NAME } from "@/lib/i18n";

/** Страна и язык Shopify для `[locale]` страницы; передавайте их в каждый запрос к каталогу. */
export async function getRequestCommerceLocale(): Promise<CommerceLocale> {
  return getCommerceLocale(await getLocale());
}

/** То же значение для динамического кода без параметров маршрута (серверные действия, начальное заполнение корзины); берётся из заголовка, переданного прокси. */
export async function getForwardedCommerceLocale(): Promise<CommerceLocale> {
  const locale = (await headers()).get(LOCALE_HEADER_NAME) ?? undefined;
  return getCommerceLocale(isSupportedLocale(locale) ? locale : defaultLocale);
}
```

```tsx title="app/[locale]/products/[handle]/page.tsx"
const product = await getProduct({
  handle,
  locale: await getRequestCommerceLocale(),
});
```

Серверные действия, например быстрая покупка и **Load more**, не могут считывать параметры маршрута. Вместо этого получайте значение через `getForwardedCommerceLocale()`.

### Запускайте обработчики Hydrogen на том же языке

Обработчики корзины и предиктивного поиска в Hydrogen берут язык и страну из `i18n` в контексте запроса. У запросов вроде `/api/cart` нет префикса локали, поэтому прокси запоминает локаль страницы в cookie-файле и передаёт её серверным компонентам и серверным действиям в заголовке:

```ts title="proxy.ts"
import {
  defaultLocale,
  getCommerceLocale,
  isSupportedLocale,
  LOCALE_COOKIE_NAME,
  LOCALE_HEADER_NAME,
  splitLocalePath,
} from "@/lib/i18n";

const rememberedLocale = request.cookies.get(LOCALE_COOKIE_NAME)?.value;
const requestLocale = resolveRequestLocale(request);
// Спекулятивная предзагрузка не должна менять корзину покупателя или запомненный язык.
const isPageNavigation =
  request.method === "GET" &&
  isPagePath(pathname) &&
  !request.headers.has("next-router-prefetch") &&
  request.headers.get("purpose") !== "prefetch" &&
  !request.headers.get("sec-purpose")?.includes("prefetch");

// Обработчики корзины и поиска Hydrogen работают на языке покупателя.
const requestContext = createCustomerRequestContext(
  request,
  getCommerceLocale(requestLocale),
  requestLocale,
);

// После того как handleShopifyRoutes отклонил запрос:
const headers = forwardCustomerRefreshAttempt(
  request.nextUrl,
  requestContext.getForwardedRequestHeaders(),
);
headers.set(LOCALE_HEADER_NAME, requestLocale);

const response = rewritePath
  ? NextResponse.rewrite(new URL(`${rewritePath}${request.nextUrl.search}`, request.url), {
      request: { headers },
    })
  : NextResponse.next({ request: { headers } });

// В ответе:
if (isPageNavigation && rememberedLocale !== locale) {
  response.cookies.set(LOCALE_COOKIE_NAME, locale, { path: "/", sameSite: "lax" });
}

/** Для страниц — локаль из URL; для эндпоинтов Shopify без префикса, например `/api/cart`, — запомненная локаль страницы. */
function resolveRequestLocale(request: NextRequest): string {
  const { pathname } = request.nextUrl;
  const { locale } = splitLocalePath(pathname);
  if (locale !== defaultLocale || isPagePath(pathname)) return locale;
  const remembered = request.cookies.get(LOCALE_COOKIE_NAME)?.value;
  return isSupportedLocale(remembered) ? remembered : defaultLocale;
}
```

Сохраните импорт `forwardCustomerRefreshAttempt` из `@/lib/auth/request`, который уже есть в шаблоне. Вызывайте его до выбора между `NextResponse.rewrite` и `NextResponse.next`, чтобы страницы аккаунта после rewrite тоже получали заголовок попытки обновления и не пытались снова и снова повторить неудавшееся обновление токена.

Принимайте в `createCustomerRequestContext` локаль Shopify и локаль страницы, а URL-префикс локали передавайте в `i18n.pathPrefix`: `/fr` для французских URL и пустую строку для локали по умолчанию. Без этого Hydrogen не распознаёт `/fr/products/hoodie?variant=123` как URL товара и пропускает перенаправление на вариант, из-за чего страница может показать вариант по умолчанию вместо указанного в ссылке:

```ts title="lib/auth/request.ts"
import { defaultLocale } from "@/lib/i18n";

export function createCustomerRequestContext(
  request: Request,
  i18n: CommerceLocale = shopConfig.localization,
  locale: string = defaultLocale,
): ShopifyRequestContext {
  return createShopifyRequestContext({
    i18n: {
      country: i18n.country,
      language: i18n.language,
      // Hydrogen распознаёт URL товаров с этим префиксом и выполняет для них перенаправления.
      pathPrefix: locale === defaultLocale ? "" : `/${locale}`,
    },
    request,
  });
}
```

Благодаря значениям по умолчанию остальные места шаблона, где вызывается эта функция, продолжают работать. Передайте `getCommerceLocale` и в серверные начальные данные корзины (`lib/cart/server.ts`), вместо того чтобы читать `shopConfig.localization`.

## Перевод текста [#copy]

Размещайте текст прямо в компонентах, как это сделано в шаблоне:

* Оборачивайте JSX в [`<T>`](/docs/react/reference/components/t). Динамические значения оборачивайте в [`<Var>`](/docs/react/reference/components/var), [`<Num>`](/docs/react/reference/components/num), [`<Currency>`](/docs/react/reference/components/currency) или [`<DateTime>`](/docs/react/reference/components/datetime), а количества — в [`<Plural>`](/docs/react/reference/components/plural). [`<T>`](/docs/react/reference/components/t) работает как в серверных, так и в клиентских компонентах.
* Строки — например, значения `aria-label`, метаданные и пропсы — переводите с помощью [`getGT`](/docs/react/nextjs/reference/functions/get-gt) в серверных компонентах и [`useGT`](/docs/react/reference/hooks/use-gt) в клиентских.
* Метки, определённые вне компонента (например, варианты сортировки), помечайте с помощью [`msg`](/docs/react/reference/functions/msg). Разрешайте их в месте рендеринга с помощью [`getMessages`](/docs/react/nextjs/reference/functions/get-messages) или [`useMessages`](/docs/react/reference/hooks/use-messages).

```tsx title="app/[locale]/page.tsx"
import { T } from "gt-next";
import { getGT } from "gt-next/server";

export async function generateMetadata(): Promise<Metadata> {
  const gt = await getGT();
  return { title: gt("Home") };
}

export default async function HomePage() {
  const gt = await getGT();
  return (
    <>
      <h1>
        <T>A storefront localized with General Translation</T>
      </h1>
      <ProductsGrid title={gt("Products")} />
    </>
  );
}
```

```tsx title="app/[locale]/search/page.tsx"
<T>
  <Plural
    n={total}
    one={<><Num>{total}</Num> result</>}
    other={<><Num>{total}</Num> results</>}
  />
</T>
```

<Callout type="warn">
  **Предупреждение:** CLI находит строки `gt()`, отслеживая переменную, которой присвоен результат [`getGT()`](/docs/react/nextjs/reference/functions/get-gt) или [`useGT()`](/docs/react/reference/hooks/use-gt). Выполняйте присваивание в отдельной строке, например `const gt = await getGT()`. Строки, полученные из деструктурированного результата `Promise.all`, не извлекаются и отображаются на языке-источнике.
</Callout>

Форматируйте цены Shopify с учётом локали и кода валюты, который возвращает Shopify. Функция шаблона `formatPrice(money, locale)` использует `formatMoney` из Hydrogen, поэтому сумма 36.95 EUR на французском отображается как `36,95 €`.

## Перевод содержимого каталога [#catalog]

Названия, описания и опции товаров поступают из Shopify. Их переводы можно либо хранить в Shopify и получать через директиву `@inContext(language:)` (её уже передаёт `getCommerceLocale`), либо включать в бандл витрины с помощью CLI, как это сделано в примере витрины. (См. [Перевод товаров Shopify](/docs/integrations/shopify/guides/translating-products)).

## Next steps

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

## Sitemap

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