Как использовать React Suspense для части перевода (например, с компонентом Trans)

Вопрос или проблема

Смотрите https://codesandbox.io/p/devbox/i18n-trans-component-with-suspense-p4jt2t?workspaceId=3104452f-3c7c-4688-a065-c2cdc2657c24

Как правильно использовать компонент Trans (или альтернативу), чтобы достичь своей цели и часть переведенного предложения могла загружаться асинхронно?

Я думал, что это может сработать:

"use client";

import { Suspense } from "react";
import { Trans } from "react-i18next"; // https://react.i18next.com/latest/trans-component

const mockedFetch = (delay: number) => {
  return new Promise<string>((resolve) => {
    setTimeout(() => {
      const last4DigitsOfCard = "3423";
      resolve(last4DigitsOfCard);
    }, delay);
  });
};

function PersonalizedNumber() {
  // TODO: Удалите все моки. Замените реальными запросами к серверу.
  const delay = 2_000;

  // Мы здесь выбрасываем промис, чтобы Suspense мог поймать его и показать загрузчик:
  const sentencePromise = mockedFetch(delay);

  // `Suspense` автоматически обработает это как состояние загрузки:
  return sentencePromise;
}

function Loading() {
  return <div>Загрузка...</div>;
}

function Last4DigitsOfCard() {
  return (
    <Suspense fallback={<Loading />}>
      <PersonalizedNumber />
    </Suspense>
  );
}

export function YourPrepaidCardEndingInLastFour({
  locale,
}: {
  locale: string;
}) {
  return (
    <Trans
      locale={locale}
      i18nKey="yourPrepaidCardIsReady"
      components={{
        last4DigitsOfCard: <Last4DigitsOfCard />,
      }}
    />
  );
}

Ключ перевода:

"yourPrepaidCardIsReady": "Ваша предоплаченная карта, завершающаяся на <last4DigitsOfCard/>, активна и готова к использованию."

Но я получаю:

Unhandled Runtime Error
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

Это, похоже, работает.

https://codesandbox.io/p/devbox/i18n-trans-component-with-suspense-forked-l325zh?workspaceId=3104452f-3c7c-4688-a065-c2cdc2657c24

"use client";

import { useEffect, useState } from "react";
import { Trans } from "react-i18next"; // https://react.i18next.com/latest/trans-component

// TODO: Удалите все моки. Замените реальными запросами к серверу.
const delay = 2_000;
const sampleLast4DigitsOfCard = "3423";

const mockedFetch = (delayMs: number) => {
  return new Promise<string>((resolve) => {
    setTimeout(() => {
      resolve(sampleLast4DigitsOfCard);
    }, delayMs);
  });
};

function Loading() {
  return (
    <span style={{ border: "1px solid black", padding: "2px" }}>
      Загрузка...
    </span>
  );
}

function Last4DigitsOfCardSimple() {
  return sampleLast4DigitsOfCard;
}

function Last4DigitsOfCardAsync() {
  const [value, setValue] = useState<string | null>(null);

  useEffect(() => {
    async function main() {
      const lastFourDigits = await mockedFetch(delay);
      setValue(lastFourDigits);
    }

    main();
  }, []);

  return value ?? <Loading />;
}

export function YourPrepaidCardEndingInLastFour({
  locale,
}: {
  locale: string;
}) {
  return (
    <div>
      <div>
        Асинхронный:{" "}
        <Trans
          locale={locale}
          i18nKey="yourPrepaidCardIsReady"
          components={{
            last4DigitsOfCard: <Last4DigitsOfCardAsync />,
          }}
        />
      </div>
      <br />
      <br />
      <div>
        Простой (синхронный):{" "}
        <Trans
          locale={locale}
          i18nKey="yourPrepaidCardIsReady"
          components={{
            last4DigitsOfCard: <Last4DigitsOfCardSimple />,
          }}
        />
      </div>
    </div>
  );
}

Но он использует useEffect вместо Suspense.

Мне интересно узнать, как это сделать с помощью Suspense.

Ответ или решение

Использование React Suspense в сочетании с компонентом Trans из библиотеки react-i18next может быть немного сложным, особенно если вы хотите загружать часть перевода асинхронно. Ваша цель заключается в том, чтобы использовать контейнер Suspense для асинхронного получения данных, таких как последние четыре цифры кредитной карты, и отобразить их при загрузке.

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

Вот как можно реализовать вашу задачу с использованием React Suspense:

Шаг 1: Создайте отдельный компонент для загрузки

Вместо того, чтобы возвращать Promise напрямую в компоненте, создайте компонент, который будет использовать Suspense и имитировать асинхронную загрузку.

"использовать клиент";

import { Suspense } from "react";
import { Trans } from "react-i18next";

const mockedFetch = (delay) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      const last4DigitsOfCard = "3423";
      resolve(last4DigitsOfCard);
    }, delay);
  });
};

// Создаем обертку для использования с Suspense
function UseSuspenseForLast4Digits() {
  const last4DigitsPromise = mockedFetch(2000);

  if (last4DigitsPromise && typeof last4DigitsPromise.then === 'function') {
    throw last4DigitsPromise; // Бросаем промис, чтобы Suspense мог обработать его
  }

  return last4DigitsPromise; // Замените на фактическое получение данных
}

function Loading() {
  return <div>Loading...</div>;
}

function Last4DigitsOfCard() {
  return (
    <Suspense fallback={<Loading />}>
      <UseSuspenseForLast4Digits />
    </Suspense>
  );
}

export function YourPrepaidCardEndingInLastFour({ locale }) {
  return (
    <Trans
      locale={locale}
      i18nKey="yourPrepaidCardIsReady"
      components={{
        last4DigitsOfCard: <Last4DigitsOfCard />,
      }}
    />
  );
}

Шаг 2: Переопределите компонент Suspense

Теперь вы можете использовать данный подход в вашем компоненте Last4DigitsOfCard, чтобы обеспечить плавную загрузку данных с использованием Suspense.

Шаг 3: Убедитесь, что ваш перевод корректен

Сперва вам нужно настроить ваш файл переводов. Вот пример перевода в JSON:

{
  "yourPrepaidCardIsReady": "Ваша предоплаченная карта, заканчивающаяся на <last4DigitsOfCard />, активна и готова к использованию."
}

Заключение

Теперь компонент будет работать с использованием React Suspense, обеспечивая асинхронную загрузку данных без прямого использования useEffect. Это позволяет более аккуратно обрабатывать состояние загрузки и отображать компонент, который будет вызывать новый контент текущего времени. Таким образом, вы сможете добиться желаемого поведения выполнения кода.

Оцените материал
Добавить комментарий

Капча загружается...