# General Translation Integrations: Localizar una tienda de Shopify con Next.js
URL: https://generaltranslation.com/es/docs/integrations/shopify/guides/localizing-nextjs.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Cómo localizar una tienda de Vercel Shop con gt-next, rutas con prefijo de configuración regional y Cache Components.

[Vercel Shop](https://github.com/vercel/shop) es una plantilla de Next.js App Router basada en Hydrogen, el SDK de tiendas de Shopify independiente del framework. Con `gt-next`, la configuración regional predeterminada se sirve sin prefijo, las demás configuraciones regionales se sirven bajo un prefijo como `/fr/...` y cada página se puede seguir prerrenderizando una vez por configuración regional. Todos los idiomas venden en un mismo mercado de Shopify; si quieres que cada idioma tenga su propio mercado y moneda, consulta después la guía [Vender en varios mercados](/docs/integrations/shopify/guides/selling-in-markets).

*Nota: Esta guía se basa en la plantilla con Next.js 16, `cacheComponents: true`, `@shopify/hydrogen` 2026.10 y `gt-next` 11.4, las versiones que usa la [tienda de ejemplo](https://gt-shopify-vercel.vercel.app), disponible en inglés, francés, japonés, coreano e indonesio.*

## Antes de empezar [#before-start]

Instala `gt-next` y la CLI `gt` como dependencia de desarrollo, tal como se indica en el [Inicio rápido de Next.js](/docs/react/nextjs-quickstart). En esta guía, el middleware del Inicio rápido se sustituye por el `proxy.ts` que ya incluye la plantilla.

## Configura gt-next [#configure]

### 1. Añade el plugin de configuración

Añade [`withGTConfig`](/docs/react/nextjs/config) a la lista de plugins que la plantilla pasa a `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. Añade las funciones de solicitud

Con Cache Components, la configuración regional debe obtenerse del parámetro raíz `[locale]`, no de cabeceras ni de cookies. Crea los archivos [`getLocale`](/docs/react/nextjs/reference/functions/get-locale) y [`getRegion`](/docs/react/nextjs/reference/functions/get-region) en la raíz del proyecto; `withGTConfig` los detecta automáticamente. (Consulta [Uso de Cache Components](/docs/react/nextjs/cache-components#request-values)).

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

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

// El parámetro raíz `[locale]` forma parte de la clave de caché estática, así que leerlo mantiene las páginas prerrenderizables.
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";

// La región es el país del mercado de Shopify en el que vende la configuración regional de la página.
export default async function getRegion(): Promise<string> {
  return getCommerceLocale((await locale()) ?? defaultLocale).country;
}
```

`getCommerceLocale` asocia una configuración regional con su país e idioma de Shopify. (Ver [Consultar Shopify en cada idioma](#shopify-language)).

### 3. Generar y cargar traducciones localmente

Cache Components requiere una función [`loadTranslations`](/docs/react/reference/functions/load-translations) personalizada, ya que la carga remota no es segura durante el prerrenderizado. Usa la CLI para escribir las traducciones en tu código fuente e impórtalas desde el paquete. (Consulta [Almacenar traducciones localmente](/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` añade `lib/`. De forma predeterminada, la CLI solo analiza `src/`, `app/`, `pages/` y `components/`, mientras que la plantilla guarda los metadatos y las etiquetas en `lib/`.

```ts title="loadTranslations.ts"
// `pnpm translate` genera estos archivos antes de la compilación; al incluirlos en el bundle, las páginas se pueden prerrenderizar.
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 ?? {};
}
```

Ejecuta [`npx gt translate`](/docs/cli/reference/commands/translate) después de modificar los textos y haz commit de los archivos generados, o ejecútalo antes de `next build`. Si los archivos están en el repositorio, la tienda puede compilarse sin una clave de API de General Translation.

## Enrutar solicitudes según la configuración regional [#routing]

### Mover las rutas bajo `[locale]`

1. Mueve todas las rutas de la tienda a `app/[locale]/` y elimina `app/layout.tsx`, de modo que `[locale]` sea un parámetro raíz. Deja `api/`, `md/`, `agent/`, `sitemap*`, `robots.ts`, `manifest.ts`, `llms.txt`, `global-error.tsx` y los iconos estáticos directamente en `app/`.

2. Cambia los argumentos de tipo de `PageProps<"/...">` y `LayoutProps<"/...">` a `"/[locale]/..."`.

3. En `app/[locale]/layout.tsx`, devuelve todas las configuraciones regionales desde `generateStaticParams`, rechaza las configuraciones regionales no compatibles, establece `<html lang>` y envuelve el body en [`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>{/* enlace de salto, proveedor del carrito, navegación, main, pie de página */}{children}</GTProvider>
         </body>
       </html>
     );
   }
   ```

4. Añade una ruta comodín que llame a `notFound()`, para que las rutas sin coincidencia rendericen `app/[locale]/not-found.tsx` dentro del layout localizado en lugar de la página 404 predeterminada de 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">
  **Advertencia:** En `gt-next` 11.4, [`GTProvider`](/docs/react/reference/components/gt-provider) lee `usePathname()` en el cliente. Por ello, con Cache Components, el prerrenderizado de una ruta dinámica sin parámetros estáticos falla. En las rutas que no tengan ninguno, como la ruta comodín y `account/orders/[id]`, devuelve un marcador de posición desde `generateStaticParams`.
</Callout>

### Enrutar solicitudes en el proxy

Lee la lista de configuraciones regionales una sola vez desde `gt.config.json` y separa el prefijo de cada ruta:

```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);
}

/** Divide `/fr/products/x` en `fr` y `/products/x`; las rutas sin prefijo pertenecen a la configuración regional predeterminada. */
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 };
}

/** La configuración regional predeterminada se mantiene sin prefijo para que cada página conserve una única URL canónica. */
export function localizePath(pathname: string, locale: string): string {
  if (locale === defaultLocale) return pathname;
  return pathname === "/" ? `/${locale}` : `/${locale}${pathname}`;
}
```

En `proxy.ts`, conserva tal cual el despacho `handleShopifyRoutes` de la plantilla para que Shopify atienda primero las solicitudes del carrito, de la búsqueda predictiva y de la cuenta de cliente. Luego, reescribe todas las demás solicitudes de página a su ruta `/[locale]`. No toques las rutas de API ni las demás rutas que no correspondan a páginas:

```ts title="proxy.ts"
// El despacho de la plantilla, sin cambios: con las cuentas de cliente habilitadas, la rama
// que tiene en cuenta la sesión añade los manejadores de solicitud de cuenta y un administrador de sesiones.
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)}`;
// Las páginas están en app/[locale]; la configuración regional predeterminada se sirve sin prefijo y se reescribe internamente.
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);
}

// Páginas de la tienda, a diferencia de las rutas de API y de los endpoints de Shopify con extensión, como `/cart.js`.
function isPagePath(pathname: string): boolean {
  return !isUnlocalizedPath(pathname) && !/\.[a-z]+$/i.test(pathname);
}
```

Como `splitLocalePath` nunca trata la configuración regional predeterminada como prefijo, `/en/products/x` se reescribe como `/en/en/products/x` y devuelve un 404. Cada página tiene una única URL canónica.

### Localizar enlaces

Pasa los `href` de tipo cadena por [`useLocalizeHref`](/docs/react/nextjs/reference/hooks/use-localize-href) dentro del componente `Link` de la plantilla para que los enlaces internos conserven la configuración regional actual. Haz lo mismo con `router.push`, `router.replace` y la navegación de búsqueda.

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

// Mantiene la navegación en la configuración regional activa; la configuración regional predeterminada va sin prefijo.
const localizeHref = useLocalizeHref();
const localizedHref = typeof href === "string" ? localizeHref(href) : href;
```

Para el selector de idioma, cambia el prefijo de la URL actual y muestra el nombre de cada idioma en su propio idioma:

```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";

/** Nombra cada idioma en su propia lengua ("Français", "日本語") para que los compradores reconozcan el suyo. */
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) => {
          // Al cambiar de configuración regional cambia el layout raíz, así que se navega con una carga completa de la página.
          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>
    </>
  );
}
```

### Añadir metadatos SEO

Asigna a cada página una URL canónica en su propia configuración regional y una versión alternativa `hreflang` para cada configuración regional. Las funciones `buildAlternates` y `buildOpenGraph` de la plantilla, en `lib/seo/index.ts`, son síncronas y no pueden leer la configuración regional, así que muévelas a `lib/seo/server.ts` como funciones asíncronas:

1. En `lib/seo/index.ts`, exporta `buildCanonicalPath` y elimina `buildAlternates` y `buildOpenGraph`.
2. Añade las versiones asíncronas a `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 canónica para la configuración regional de la solicitud, más versiones alternativas hreflang para cada configuración regional. */
   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,
       },
     };
   }
   ```

   Convierte `buildOpenGraph` en asíncrona del mismo modo y localiza su URL y sus configuraciones regionales. Open Graph espera valores con el formato `language_TERRITORY`, como `fr_FR`, así que convierte ahí la configuración regional y conserva la etiqueta simple para `hreflang` y `<html lang>`:

   ```ts title="lib/seo/server.ts"
   /** Las configuraciones regionales de Open Graph usan language_TERRITORY, como fr_FR; "fr" se convierte en su territorio más probable. */
   function toOpenGraphLocale(locale: string): string {
     const { language, region } = new Intl.Locale(locale).maximize();
     return `${language}_${region}`;
   }

   // En buildOpenGraph, junto a sus campos existentes:
   const locale = await getLocale();
   return {
     url: localizePath(url, locale),
     locale: toOpenGraphLocale(locale),
     alternateLocale: locales.filter((code) => code !== locale).map(toOpenGraphLocale),
   };
   ```

   `maximize()` elige el territorio más probable, como `fr_FR` para `fr` y `zh_TW` para `zh-Hant`. Si el territorio más probable de alguna configuración regional no coincide con el que atiendes, sustituye la conversión por un mapa explícito.
3. En la función `generateMetadata` de cada página, importa ambas funciones auxiliares desde `@/lib/seo/server` y llámalas con `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]
     };
   }
   ```

   Una vez eliminadas las funciones auxiliares antiguas, cualquier página que siga importando desde `@/lib/seo` fallará en la comprobación de tipos, lo que te permite localizar todas las llamadas que se te hayan pasado.

Incluye también en el mapa del sitio la URL de cada configuración regional, cada una con entradas `<xhtml:link rel="alternate" hreflang="...">` para todas las configuraciones regionales.

## Consultar Shopify en cada idioma [#shopify-language]

Asigna cada configuración regional al país y al idioma de Shopify que admiten las llamadas a la Storefront API de la plantilla. Todas las configuraciones regionales comparten el mismo y único mercado de la tienda, así que solo cambia el idioma:

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

// El mercado en el que se vende en todos los idiomas. La moneda siempre proviene de la respuesta de Shopify.
const STORE_COUNTRY: CommerceLocale["country"] = "US";

// El idioma de la Storefront API para cada configuración regional en 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 };
}

/** El proxy recuerda la configuración regional de la página en esta cookie, para que las llamadas al manejador de solicitud de Hydrogen (`/api/cart`) usen el mismo idioma. */
export const LOCALE_COOKIE_NAME = "shop_locale";
/** El proxy reenvía la configuración regional resuelta a las acciones de servidor y a la inicialización del carrito mediante esta cabecera de solicitud. */
export const LOCALE_HEADER_NAME = "x-shop-locale";
```

Cada idioma debe ser un `LanguageCode` válido de la Storefront API (consulta [Códigos de configuración regional](/docs/integrations/shopify/reference/locale-handling#locale-codes)). Si [almacenas las traducciones del catálogo en Shopify](/docs/integrations/shopify/guides/translating-products#store-in-shopify), publica también cada idioma en la tienda, ya que Shopify devuelve el texto de origen para los idiomas que no ha publicado. Las traducciones incluidas en el paquete de la tienda no requieren que los idiomas estén publicados.

### Pasa el idioma a las lecturas del catálogo

Los getters del catálogo de la plantilla ya aceptan una opción `locale` de tipo `CommerceLocale` y la pasan a `@inContext`. Pasa el valor de la configuración regional desde cada página y cada componente. Los getters que usan `"use cache"` lo reciben como argumento, de modo que cada idioma tiene su propia entrada en la caché.

```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";

/** País e idioma de Shopify para el `[locale]` de la página; pásalo en cada lectura del catálogo. */
export async function getRequestCommerceLocale(): Promise<CommerceLocale> {
  return getCommerceLocale(await getLocale());
}

/** El mismo valor, obtenido de la cabecera del proxy, para código dinámico sin parámetros de ruta (acciones de servidor, la inicialización del carrito). */
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(),
});
```

Las acciones de servidor, como la compra rápida y **Load more**, no pueden leer los parámetros de ruta. En su lugar, obtén el valor con `getForwardedCommerceLocale()`.

### Ejecuta los manejadores de solicitud de Hydrogen en el mismo idioma

Los manejadores de solicitud del carrito y de la búsqueda predictiva de Hydrogen obtienen el idioma y el país de `i18n` en el contexto de la solicitud. Las solicitudes como `/api/cart` no llevan prefijo de configuración regional, así que el proxy guarda la configuración regional de la página en una cookie y la reenvía en una cabecera a los Componentes de servidor y a las acciones de servidor:

```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);
// Las precargas especulativas no deben cambiar el carrito del comprador ni el idioma recordado.
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");

// Los manejadores de solicitud del carrito y de la búsqueda de Hydrogen se ejecutan en el idioma del comprador.
const requestContext = createCustomerRequestContext(
  request,
  getCommerceLocale(requestLocale),
  requestLocale,
);

// Una vez que handleShopifyRoutes rechaza la solicitud:
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 } });

// En la respuesta:
if (isPageNavigation && rememberedLocale !== locale) {
  response.cookies.set(LOCALE_COOKIE_NAME, locale, { path: "/", sameSite: "lax" });
}

/** La configuración regional de la URL para las páginas; la configuración regional de página recordada para endpoints de Shopify sin prefijo, como `/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;
}
```

Conserva la importación de `forwardCustomerRefreshAttempt` desde `@/lib/auth/request` que incluye la plantilla. Aplícala antes de elegir entre `NextResponse.rewrite` y `NextResponse.next`, para que las páginas de cuenta reescritas también reciban la cabecera de intento de actualización y no reintenten una y otra vez una actualización de token fallida.

Acepta la configuración regional de Shopify y la de la página en `createCustomerRequestContext`, y pasa el prefijo de URL de la configuración regional como `i18n.pathPrefix`: `/fr` para las URL en francés y una cadena vacía para la configuración regional predeterminada. Sin él, Hydrogen no reconoce `/fr/products/hoodie?variant=123` como URL de producto y omite la redirección de variante, por lo que la página puede mostrar la variante predeterminada en lugar de la enlazada:

```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 reconoce y redirige las URL de producto que usan este prefijo.
      pathPrefix: locale === defaultLocale ? "" : `/${locale}`,
    },
    request,
  });
}
```

Gracias a los valores predeterminados, las demás llamadas de la plantilla siguen funcionando. Pasa también `getCommerceLocale` a la inicialización del carrito en el servidor (`lib/cart/server.ts`) en lugar de leer `shopConfig.localization`.

## Traducir textos [#copy]

Mantén los textos inline en cada componente, como hace la plantilla:

* Envuelve el JSX en [`<T>`](/docs/react/reference/components/t). Envuelve los valores dinámicos en [`<Var>`](/docs/react/reference/components/var), [`<Num>`](/docs/react/reference/components/num), [`<Currency>`](/docs/react/reference/components/currency) o [`<DateTime>`](/docs/react/reference/components/datetime), y las cantidades en [`<Plural>`](/docs/react/reference/components/plural). [`<T>`](/docs/react/reference/components/t) funciona tanto en Componentes de servidor como en Componentes de cliente.
* Traduce las cadenas, como los valores de `aria-label`, los metadatos y las props, con [`getGT`](/docs/react/nextjs/reference/functions/get-gt) en Componentes de servidor y con [`useGT`](/docs/react/reference/hooks/use-gt) en Componentes de cliente.
* Marca con [`msg`](/docs/react/reference/functions/msg) las etiquetas definidas fuera de un componente, como las opciones de ordenación. Resuélvelas donde se renderizan con [`getMessages`](/docs/react/nextjs/reference/functions/get-messages) o [`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">
  **Advertencia:** La CLI detecta las cadenas de `gt()` rastreando la variable asignada a partir de [`getGT()`](/docs/react/nextjs/reference/functions/get-gt) o [`useGT()`](/docs/react/reference/hooks/use-gt). Asígnala en una línea independiente, como en `const gt = await getGT()`. Las cadenas que se leen a través del resultado desestructurado de un `Promise.all` no se extraen y se renderizan en el idioma de origen.
</Callout>

Da formato a los precios de Shopify con la configuración regional y el código de divisa ISO 4217 que devuelve Shopify. La función `formatPrice(money, locale)` de la plantilla usa `formatMoney` de Hydrogen, de modo que 36.95 EUR se renderiza como `36,95 €` en francés.

## Traducir el contenido del catálogo [#catalog]

Los títulos, las descripciones y las opciones de los productos provienen de Shopify. Puedes almacenar sus traducciones en Shopify y leerlas con `@inContext(language:)`, que `getCommerceLocale` ya se encarga de pasar, o bien incluirlas en el paquete de la tienda mediante la CLI, como hace la tienda de ejemplo. (Consulta [Traducir productos de 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.
