# General Translation Integrations: Localización de Shopify Hydrogen con React Router
URL: https://generaltranslation.com/es/docs/integrations/shopify/guides/localizing-react-router.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Cómo añadir gt-react a una tienda online de Hydrogen con React Router y mantener el contexto de idioma de Shopify sincronizado con él.

En una tienda online con React Router, `gt-react` traduce los textos de tu código y Hydrogen se encarga de comunicarse con Shopify. Lo específico de Shopify consiste en asignar al contexto de la solicitud de Hydrogen la misma configuración regional que la de la página.

*Nota: Esta guía corresponde a `@shopify/hydrogen` 2026.10 con React Router 8, o con React Router 7 y `future.v8_middleware: true` en `react-router.config.ts`.*

<Callout type="info">
  **Cambios en Hydrogen 2026.10:** `createHydrogenContext` y la plantilla exclusiva de React Router se han sustituido por `createShopifyRequestContext`, que es independiente del framework. Recibe el mismo idioma y país de `i18n`, además de un `pathPrefix` opcional. La skill `migrate-to-hydrogen-2026-10`, incluida en el paquete de Hydrogen, explica cómo realizar la actualización.
</Callout>

## Antes de empezar [#before-start]

* Configura Hydrogen en tu aplicación de React Router con `npx @shopify/hydrogen setup`. Tu middleware raíz crea el contexto de la solicitud de Shopify y el cliente de Storefront, y ejecuta `handleShopifyRoutes`.
* Completa el [Inicio rápido de React Router](/docs/react/react-router-quickstart). Tu cargador raíz devuelve una configuración regional y una instantánea de traducciones, y el `Layout` raíz renderiza [`GTProvider`](/docs/react/reference/components/gt-provider) y `<html lang>`. En esta guía, el [`<LocaleSelector>`](/docs/react/reference/components/locale-selector) del Inicio rápido se sustituye por un selector de idioma que cambia la URL.

## Resolver la configuración regional a partir de la URL [#request-locale]

Lee la configuración regional de la ruta de la solicitud para que cada configuración regional tenga su propia URL. Usa un único módulo tanto para `gt-react` como para Hydrogen:

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

// "/fr/products/x" -> "fr"; las rutas sin prefijo usan la configuración regional predeterminada.
export function getRequestLocale(request: Request): string {
  const [, segment] = new URL(request.url).pathname.split('/');
  return gtConfig.locales.includes(segment) ? segment : gtConfig.defaultLocale;
}

// "/products/x" -> "/fr/products/x"; la configuración regional predeterminada mantiene la ruta sin prefijo.
export function localizePath(pathname: string, locale: string): string {
  if (locale === gtConfig.defaultLocale) return pathname;
  return pathname === '/' ? `/${locale}` : `/${locale}${pathname}`;
}

// "/fr/products/x" -> "/products/x"; las rutas sin prefijo se devuelven tal cual.
export function stripLocale(pathname: string): string {
  const [, segment, ...rest] = pathname.split('/');
  return gtConfig.locales.includes(segment) ? `/${rest.join('/')}` : pathname;
}
```

En el cargador raíz, llama a `getRequestLocale(request)` en lugar de `parseLocale(request)` y pasa el resultado a [`getTranslationsSnapshot`](/docs/react/reference/functions/get-translations-snapshot).

*Nota: `parseLocale` lee primero la cookie de configuración regional y, después, `Accept-Language`. Si se usa por sí sola, sirve todos los idiomas desde una misma URL, por lo que los motores de búsqueda solo indexan un idioma.*

Para servir rutas con prefijo, anida tus páginas dentro de una ruta de layout opcional `:locale?`. Deja la ruta comodín de Hydrogen al final para que las rutas sin coincidencia sigan llegando a sus redirecciones:

```ts title="app/routes.ts"
import {type RouteConfig, index, route} from '@react-router/dev/routes';

export default [
  // Las páginas se renderizan sin prefijo o con el prefijo de una configuración regional de destino, como /fr.
  route(':locale?', 'routes/locale.tsx', [
    index('routes/home.tsx'),
    route('products/:handle', 'routes/product.tsx'),
  ]),
  route('*', 'routes/catchall.tsx'),
] satisfies RouteConfig;
```

En la ruta del layout, responde con un 404 cuando el segmento no corresponda a una configuración regional de destino configurada, para que `/en/...` y los prefijos desconocidos no rendericen una copia duplicada de la página:

```tsx title="app/routes/locale.tsx"
import {Outlet} from 'react-router';
import gtConfig from '../../gt.config.json';

// Solo las configuraciones regionales de destino son prefijos válidos; la configuración regional predeterminada no lleva prefijo.
export async function loader({params}: {params: {locale?: string}}) {
  if (params.locale && !gtConfig.locales.includes(params.locale)) {
    throw new Response(null, {status: 404});
  }
  return null;
}

export default function LocaleLayout() {
  return <Outlet />;
}
```

Si usas rutas basadas en archivos, nombra el layout `($locale).tsx` y anida los archivos de página dentro de él.

Los manejadores de solicitud propios de Hydrogen, como `/api/cart`, `/api/predictive-search`, `/checkout` y las rutas de Customer Account, responden en el middleware raíz antes de que se renderice cualquier página, por lo que no llevan prefijo.

### Mantén los manejadores de solicitud de Hydrogen en el idioma de la página [#handler-locale]

Las solicitudes a `/api/cart` y `/api/predictive-search` no llevan prefijo de configuración regional, así que `getRequestLocale` les asigna la configuración regional predeterminada. Como resultado, en una página en francés el carrito y los resultados de búsqueda mostrarían el texto del catálogo en inglés.

Guarda la configuración regional de la página en una cookie y léela solo en esas solicitudes a `/api` sin prefijo. Para las páginas, la URL sigue siendo el único origen de la configuración regional. Reemplaza `getRequestLocale` en `app/lib/i18n.ts`:

```ts title="app/lib/i18n.ts"
export const LOCALE_COOKIE_NAME = 'shop_locale';

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

export function readCookie(request: Request, name: string): string | undefined {
  const cookie = request.headers.get('Cookie')?.split(/;\s*/).find((part) => part.startsWith(`${name}=`));
  return cookie?.slice(name.length + 1);
}

/** Las páginas usan su prefijo de URL; los manejadores de solicitud `/api` de Hydrogen usan la configuración regional de la página que los llamó. */
export function getRequestLocale(request: Request): string {
  const {pathname} = new URL(request.url);
  const [, segment] = pathname.split('/');
  if (gtConfig.locales.includes(segment)) return segment;
  if (!pathname.startsWith('/api/')) return gtConfig.defaultLocale;
  const remembered = readCookie(request, LOCALE_COOKIE_NAME);
  // Solo se admiten configuraciones regionales configuradas; cualquier otro valor recurre a la predeterminada.
  return remembered && allLocales.includes(remembered) ? remembered : gtConfig.defaultLocale;
}

/** Cargas de página y navegaciones en el cliente, pero no llamadas a `/api` ni precargas especulativas. */
export function isPageNavigation(request: Request): boolean {
  const purpose = `${request.headers.get('Purpose') ?? ''} ${request.headers.get('Sec-Purpose') ?? ''}`;
  return (
    request.method === 'GET' &&
    !new URL(request.url).pathname.startsWith('/api/') &&
    !purpose.includes('prefetch')
  );
}
```

El middleware raíz de la siguiente sección establece la cookie cuando la configuración regional de una navegación entre páginas no coincide con la guardada. Las precargas no la establecen, de modo que un enlace precargado a otro idioma no puede cambiar el idioma del carrito antes de que el comprador haga clic en él.

## Pasa el idioma a Shopify [#storefront-language]

Asigna cada configuración regional a su `LanguageCode` de Shopify y pásalo a `createShopifyRequestContext` en tu middleware raíz. A partir de ahí, el cliente de Storefront rellena `$language` y `$country` en cada consulta que declare `@inContext(country: $country, language: $language)`.

```ts title="app/root.tsx"
import {
  createShopifyRequestContext,
  createStorefrontClient,
  handleShopifyRedirects,
  handleShopifyRoutes,
} from '@shopify/hydrogen';
import gtConfig from '../gt.config.json';
import {getRequestLocale, isPageNavigation, LOCALE_COOKIE_NAME, readCookie} from '~/lib/i18n';
import type {Route} from './+types/root';

// Una entrada por cada configuración regional de gt.config.json.
const SHOPIFY_LANGUAGES = {en: 'EN', fr: 'FR', ja: 'JA'} as const;

export const middleware: Route.MiddlewareFunction[] = [
  async ({context, request}, next) => {
    const locale = getRequestLocale(request) as keyof typeof SHOPIFY_LANGUAGES;
    const requestContext = createShopifyRequestContext({
      request,
      // El idioma depende de la página; el país se mantiene en el mercado en el que vendes.
      // pathPrefix mantiene las redirecciones de variantes y rutas estándar de Hydrogen en las URL de la configuración regional.
      i18n: {
        language: SHOPIFY_LANGUAGES[locale],
        country: 'US',
        pathPrefix: locale === gtConfig.defaultLocale ? '' : `/${locale}`,
      },
    });
    const storefrontClient = createStorefrontClient({type: 'public', requestContext, config});

    const shopifyRoute = handleShopifyRoutes({request, requestContext, storefrontClient, routeTemplates, handlers});
    if (shopifyRoute) return shopifyRoute;

    context.set(storefrontClientContext, storefrontClient);

    const response = await next();
    if (response.status === 404) {
      const redirect = await handleShopifyRedirects({request, routeTemplates, storefrontClient});
      if (redirect) return redirect;
    }
    // Guarda la configuración regional de la página para los manejadores de solicitud `/api` de Hydrogen. Establece la cookie antes de
    // applyResponseHeaders, que convierte en privada y no almacenable en caché cualquier respuesta que establezca una cookie.
    if (isPageNavigation(request) && readCookie(request, LOCALE_COOKIE_NAME) !== locale) {
      response.headers.append('Set-Cookie', `${LOCALE_COOKIE_NAME}=${locale}; Path=/; SameSite=Lax`);
    }
    storefrontClient.requestContext.applyResponseHeaders(response.headers);
    return response;
  },
];
```

`config`, `routeTemplates`, `handlers` y `storefrontClientContext` provienen de la configuración inicial de Hydrogen que ya tienes; solo `i18n` y la cookie de configuración regional son nuevos. Las respuestas de página que establecen la cookie no se almacenan en caché, así que esto solo ocurre en la primera página que visita un comprador y cuando cambia de idioma.

Esta guía mantiene el mercado de EE. UU. para todos los idiomas, de modo que al cambiar a francés cambian los textos, pero no el país del comprador ni la moneda del carrito. Si trabajas con varios mercados, permite que los compradores elijan su país por separado, o asocia cada configuración regional a un mercado y transfiere el carrito cuando cambie el idioma. (Consulta [Vender en varios mercados](/docs/integrations/shopify/guides/selling-in-markets)).

## Dar formato a los precios [#prices]

Da formato al importe de Shopify con el código de divisa ISO 4217 que devuelve Shopify. [`<Currency>`](/docs/react/reference/components/currency) da formato según la configuración regional activa, pero nunca convierte el importe.

```tsx title="app/components/LocalizedMoney.tsx"
import {Currency} from 'gt-react';

/** Da formato al precio de Shopify según la configuración regional activa, sin convertir la moneda. */
export function LocalizedMoney({data}: {data: {amount?: string; currencyCode?: string}}) {
  // Las respuestas optimistas del carrito pueden omitir temporalmente los campos de coste.
  if (data.amount == null || !data.currencyCode) return null;
  return <Currency currency={data.currencyCode}>{data.amount}</Currency>;
}
```

## Enlazar dentro de la configuración regional [#links]

Mantén sin prefijo las rutas de tu código y añade el prefijo de la configuración regional actual al renderizar un enlace. Obtén la configuración regional activa con [`useLocale`](/docs/react/reference/hooks/use-locale) y pasa `localizePath(to, locale)` al `Link` de React Router.

Para cambiar de idioma, cambia el prefijo de la URL actual. Así, el cargador raíz lee la nueva configuración regional y el carrito se conserva tal cual. No uses [`<LocaleSelector>`](/docs/react/reference/components/locale-selector) en este caso: guarda la configuración regional en una cookie y recarga la misma URL, que sigue resolviéndose a la configuración regional anterior.

```tsx title="app/components/LanguageSwitcher.tsx"
import {useLocale, useLocales} from 'gt-react';
import {useLocation, useNavigate} from 'react-router';
import {localizePath, stripLocale} from '~/lib/i18n';

/** Cambia el idioma modificando el prefijo de la URL; el cargador raíz lee la nueva configuración regional desde la URL. */
export function LanguageSwitcher() {
  const locale = useLocale();
  const locales = useLocales();
  const {pathname, search} = useLocation();
  const navigate = useNavigate();
  return (
    <select
      aria-label="Language"
      value={locale}
      onChange={(event) => navigate(localizePath(stripLocale(pathname), event.target.value) + search)}
    >
      {locales.map((option) => (
        // Muestra cada idioma con su nombre en ese mismo idioma, como "français" o "日本語".
        <option key={option} value={option} lang={option}>
          {new Intl.DisplayNames([option], {type: 'language'}).of(option)}
        </option>
      ))}
    </select>
  );
}
```

Para enlazar a las rutas de Customer Account, como `/account/login`, usa un `<a>` o un `<form>` simple, no `Link`. Sus manejadores de solicitud hacen una redirección a Shopify, y la navegación client-side no puede seguirla.

## Generar traducciones [#generate]

Envuelve el texto en [`<T>`](/docs/react/reference/components/t), usa [`useGT`](/docs/react/reference/hooks/use-gt) para cadenas como los valores de `aria-label` y ejecuta [`npx gt translate`](/docs/cli/reference/commands/translate) antes de `react-router build`. Las traducciones que tu cargador incluye en el paquete están disponibles desde el primer renderizado en el servidor, así que las respuestas en caché ya las incluyen.

Para que el texto siga envuelto y traducido a medida que cambia la tienda online, conecta el repositorio a [Locadex](/docs/platform/locadex/quickstart). Locadex abre pull requests con las nuevas traducciones para que las revises.

## Next steps

- /docs/integrations/shopify/guides/localizing-nextjs
- /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.
