I'm trying to fetch translation JSONs from an API for my Next app that uses the /app directory. It all works fine on the server-side, since I can await the API call and pass the data globally as a "hook".
I've managed to do something similar on the client components using a Context, but there is a delay between the loading of the page and loading the translations. There's also no simple way for me to cache the data, like it's cached on the server side.

有没有办法在服务器端(可能在layout.tsx或其他地方)获取这些数据,并使其可供客户端组件全局访问?类似于它以前使用getInitialProps/getServerSideProps的方式.

以下是我的客户环境:

'use client';

import React, { createContext, useCallback, useEffect, useState } from 'react';
import { getActiveBrand } from '../actions/actions';
import { defaultLocale } from './translationProvider';

interface TranslationContextProps {
  translateProvider: (
    locale?: string
  ) => (path: string, templates?: { [key: string]: string }) => string;
}

const TranslationContext = createContext<TranslationContextProps>(
  {} as TranslationContextProps
); // ts hack :/

interface AuthProviderProps {
  children: React.ReactNode | React.ReactNode[];
}

const TranslationProvider = ({ children }: AuthProviderProps) => {
  const [translations, setTranslations] = useState(
    null as { [key: string]: any } | null
  );

  const translateProvider = useCallback(
    (locale?: string) => {
      const lang = locale || defaultLocale;
      const translation = translations ? translations[lang] : {};

      const translate = (
        path: string,
        templates?: { [key: string]: string }
      ) => {
        if (translation !== null) {
          const keys = path.split('.');

          let value: string;
          try {
            value = keys.reduce((a, c) => a[c], translation);
            if (templates && typeof value === 'string')
              Object.keys(templates).forEach(key => {
                value = value.replace(`{${key}}`, `${templates[key]}`);
              });
          } catch (e: any) {
            return path;
          }
          return value;
        }
        return path;
      };
      return translate;
    },
    [translations]
  );

  const getTranslations = async () => {
    const brand = await getActiveBrand();
    const languages = {} as { [key: string]: any };
    brand.data.languages.forEach(lang => {
      languages[lang.language] = JSON.parse(JSON.parse(lang.languageData));
    });
    setTranslations(languages);
  };

  useEffect(() => {
    getTranslations();
  }, []);

  return (
    <TranslationContext.Provider
      value={{
        translateProvider,
      }}
    >
      {children}
    </TranslationContext.Provider>
  );
};

export { TranslationProvider, TranslationContext };

我的服务器端"钩子":

import { getActiveBrand } from '../actions/actions';

export const availableLanguages = ['en', 'de', 'cs', 'sk', 'sl', 'hu'];
export const defaultLocale = 'de';

export async function translationProvider(lang: string) {
  const brand = await getActiveBrand();
  const languageRemote = brand.data.languages.find(
    l => l.language === lang
  )?.languageData;

  const translation = languageRemote
    ? JSON.parse(JSON.parse(languageRemote))
    : null;

  const t = (path: string, templates?: { [key: string]: string }): string => {
    if (translation !== null) {
      const keys = path.split('.');
      let value: string;
      try {
        value = keys.reduce((a, c) => a[c], translation);
        if (templates && typeof value === 'string')
          Object.keys(templates).forEach(key => {
            value = value.replace(`{${key}}`, `${templates[key]}`);
          });
      } catch (e: any) {
        return path;
      }
      return value;
    }
    return path;
  };

  return t;
}

推荐答案

解决了,非常感谢@nordic70

从本质上讲,我创建了一个上下文来传递翻译内容:

'use client';

import { createContext } from 'react';

interface TranslationContextProps {
  translation: any; // probably should be a type, not sure
}

const TranslationContext = createContext<TranslationContextProps>({} as TranslationContextProps); // ts hack :/

// VERY IMPORTANT!
// If you export just the provider as
// export const TranslationProvider = TranslationContext.Provider;
// it will not work!
// you also cannot use it just as <TranslationContext.Provider>...</TranslationContext.Provider>
// it HAS to be a component like this one:
export const TranslationProvider = ({
  children,
  translation,
}: {
  translation: any;
  children: React.ReactNode | React.ReactNode[];
}) => <TranslationContext.Provider value={{ translation: translation }}>{children}</TranslationContext.Provider>;

export default TranslationContext;

在你的layout.tsx:

// this is some pseudo code, you get the idea
const Layout = async ({children, params}) => {
  const translationJson = await callYourApi();
  //OR I did this for easy access in all server components:
  const { t, translation } = await translationProvider(params.locale);
  return (
      ...
      <TranslationProvider translation={translation}>
          {children}
      </TranslationProvider>
      ...
  );
}

下面是translationProvider的函数:

export async function translationProvider(lang: string) {
  // this is the fetch call to the API
  const translation = await getLanguageJson(lang);

  const t = (path: string, templates?: { [key: string]: string }): string => {
    if (Boolean(translation)) {
      const keys = path.split('.');

      let value: string;
      try {
        value = keys.reduce((a, c) => a[c], translation);
        if (templates && typeof value === 'string')
          Object.keys(templates).forEach((key) => {
            value = value.replace(`{${key}}`, `${templates[key]}`);
          });
      } catch (e: any) {
        return path;
      }
      return value;
    }
    return path;
  };

  return { t, translation };
}

现在,对于客户端组件,我执行了一个类似的函数,其中不是调用API,而是从上下文中获取活动翻译:

export function useTranslationProvider() {
  const { translation } = useContext(TranslationContext);
  
  const t = (path: string, templates?: { [key: string]: string }): string => {
    if (Boolean(translation)) {
      const keys = path.split('.');

      let value: string;
      try {
        value = keys.reduce((a, c) => a[c], translation);
        if (templates && typeof value === 'string')
          Object.keys(templates).forEach((key) => {
            value = value.replace(`{${key}}`, `${templates[key]}`);
          });
      } catch (e: any) {
        return path;
      }
      return value;
    }
    return path;
  };

  return { t };
}

然后,您可以访问从客户端组件中的API获得的翻译!

 const { t } = useTranslationProvider();

再次感谢@nordic70,他的回答为我节省了无数个小时.希望这能帮助更多有同样问题的人.

Reactjs相关问答推荐

LocalStore未存储正确的数据

有正确的方法来设置我的金牛座应用程序的图标吗?

react 表复选框不对任何操作做出react

单击空白区域时,Reaction Multiple下拉组件不关闭

以下代码是否存在安全问题?

如何用Reaction做交互式的MathML?

使用REACT-RUTER-DOM链接仅更新一个搜索参数

有没有比;KeyableShell;组成部分

错误:无法获取 RSC 负载.返回浏览器导航

REACT: UseState 没有更新变量(ant design 模态形式)

React Native 背景动画反转

运行 npm run build 出现问题

即使配置了 webpack.config.js,html-loader 也不起作用

使用 Vite 的 React 中的路由无法正常工作(构建中)

Material UI:使用 mui 手风琴时出现props 类型失败:isValidElement 不是函数

刷新页面时状态改变问题

React Router v6 - 访问嵌套路由和处理手写 url 的正确方法

使用 React 中的功能组件更改另一个组件的状态

使用 React、Redux 和 Firebase 的无限循环

无法有条件地更新useEffect中的setState