是否可以将 _app.jsx 组件中的布局与 next-i18next 一起使用?

UKR*_*man 5 next.js react-hooks next-i18next

为了创建一个网站,我使用 nextjs,在创建页面时,我将带有页眉和页脚的总体布局放入一个单独的临时组件中,并将页面组件包装在文件中_app.jsx

function App({ Component, ...rest }) {

  const { store, props } = wrapper.useWrappedStore(rest)

  return (
    <Provider store={store}>
      <Layout>
        <Component {...props.pageProps} />
      </Layout>
    </Provider>
  )
}

Run Code Online (Sandbox Code Playgroud)

一切正常,直到本地化成为问题,使用next-18next库进行翻译并添加后serverSideTranslations,每个页面上开始出现两个错误:

 react-i18next:: You will need to pass in an i18next instance by using initReactI18next
frontend-node_1      | TypeError: Cannot read properties of undefined (reading 'label')
frontend-node_1      |     at DropdownSwitcher (webpack-internal:///./src/components/header/translation/DropdownSwitcher.jsx:45:36)
frontend-node_1      |     at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5658:16)
frontend-node_1      |     at renderIndeterminateComponent (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5731:15)
frontend-node_1      |     at renderElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5946:7)
frontend-node_1      |     at renderMemo (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5868:3)
frontend-node_1      |     at renderElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6011:11)
frontend-node_1      |     at renderNodeDestructiveImpl (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6104:11)
frontend-node_1      |     at renderNodeDestructive (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6076:14)
frontend-node_1      |     at renderNode (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6259:12)
frontend-node_1      |     at renderHostElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5642:3)
Run Code Online (Sandbox Code Playgroud)

出现“label”错误是因为服务器上的 i18n 对象为空:

const DropdownSwitcher = () => {
  const { i18n } = useTranslation()

  const currentLanguage = useMemo(() => {             // language as undefined 
    return LANGUAGES.find((item) => item.language === i18n.language)
  }, [i18n.language])
  ....
Run Code Online (Sandbox Code Playgroud)

但客户端一切正常,没有错误。可能是什么原因以及如何修复它,因为文件中的应用程序本身_app.jsx被包装在appWithTranslationfrom next-i18next.

因此,出现了两个问题,如何修复react-i18next:: You will need to pass in an i18next instance by using initReactI18next以及为什么服务器上没有 i18n 对象?

我将布局移动到页面本身的级别,将其从 _app.js 中删除,但由于某种原因,然后某些内容useEffect()在标头中重复,尽管标头组件没有以任何方式更改并将布局带到该级别修复_app.jsx

如果没有足够的信息或者您需要一个直观的示例,我将尝试创建一个小程序来用开源来演示这一点。请写在评论中。

UKR*_*man 1

我解决了我的问题,但我忘了在这里提供答案,但我注意到有人也有这个问题,所以我会尽力帮助遇到这篇文章的人,尽管它仅与nextjs版本12相关,因为外观版本 14 的结构已经改进了很多,我认为应该不会再有像我这样的问题了。

1. 渲染布局

在官方文档中,有一整节内容描述了如何正确划分布局,使其根据 SPA 类型工作。

页面/index.jsx

// pages/index.jsx

import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'

export default function Page() {
  return (
    /** Your content */
  )
}

Page.getLayout = function getLayout(page) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}
Run Code Online (Sandbox Code Playgroud)

页面/_app.js

// pages/_app.js

export default function MyApp({ Component, pageProps }) {
  // Use the layout defined at the page level, if available
  const getLayout = Component.getLayout || ((page) => page)

  return getLayout(<Component {...pageProps} />)
}
Run Code Online (Sandbox Code Playgroud)

这种组件方法方法比使用它的方向要好得多,_app.jsx因为您可以扩展或替换它们,而不是制作一个粗糙的整体,例如我如何使用它:

// pages/ingex.jsx

function HomePage() {
  return (
    <HomeLayout>
      <Main />
    </HomeLayout>
  )
}

HomePage.getLayout = (page) => <MainLayout>{page}</MainLayout>
Run Code Online (Sandbox Code Playgroud)
// pages/about-us.jsx

const AboutUsPage = () => {
  return (
    <>
      <HomeLayout>
        <AboutUs />
      </HomeLayout>
    </>
  )
}

AboutUsPage.getLayout = (page) => (
  <MainLayout withNav>
    <LayoutContext.Consumer>
      {({ device }) => device.isMobile && <NavigationMobile />}
    </LayoutContext.Consumer>
    {page}
  </MainLayout>
)
Run Code Online (Sandbox Code Playgroud)

通过这种方法,React 仍然像一个 spa 和一个类似的页面一样工作about-us,其中也将有NavigationMobile,将简单地比较它。

2. next-i18next 错误

关键在于 next-i18next 库首先配置不正确(更准确地说,需要纠正)。为了正确配置所有内容,我必须执行以下操作:

- 将包含翻译文件的文件夹移至公共文件夹。这是必要的,以便库配置(我们将在下面进行一些配置)可以看到翻译文件并与它们交互

- 配置 next-i18next.config.js 以与客户端配合使用。这是一个带有一些注释的示例设置。还有文档的链接以及我在设置时找到的一些其他资源。

下一个-i18next.config.js

const path = require('path')

const LANGUAGES = ['en', 'pl', 'uk']
const DEFAULT_LANGUAGE = 'en'

// if it is the server, then the full path, if the client, then the relative path.
const localePath =
  typeof window === 'undefined' ? path.resolve('public', 'translation') : '/public/translation'

module.exports = {
  i18n: {
    defaultLocale: DEFAULT_LANGUAGE,
    locales: LANGUAGES,
    fallbackLng: LANGUAGES,
    nsSeparator: '::',
    keySeparator: '::',
    // How to use libraries for i18next like LanguageDetector
    use: [require('i18next-intervalplural-postprocessor')],
    serializeConfig: false,
  },
  localePath: localePath,
}

Run Code Online (Sandbox Code Playgroud)

- 在 _app.jsx 文件中配置 next-i18next。这里一切都如文档中所述。

import { appWithTranslation } from 'next-i18next'
import nextI18NextConfig from '../../next-i18next.config'


function App({ Component, ...rest }) {
  const { store, props } = wrapper.useWrappedStore(rest)
  const getLayout = Component.getLayout || ((page) => page)

 //WARNING!!! You don't have to have your own i18next initialization like i18next.use(LanguageDetector).use(intervalPlural).init({ detection: options }) this is all done by the next-i18next library

  return (
    <Provider store={store}>
      <AppHOC>{getLayout(<Component {...props.pageProps} />)}</AppHOC>
    </Provider>
  )
}

export default appWithTranslation(App, nextI18NextConfig)
Run Code Online (Sandbox Code Playgroud)

- 调用serverSideTranslations函数时需要传递配置。为了让您的生活更轻松,最好将此函数的实现转移到另一个文件中,这是我的做法的示例:

// utils/serverSideTranslations.js

import { serverSideTranslations as baseServerSideTranslations } from 'next-i18next/serverSideTranslations'
import { dt } from '../../constants/defaultTranslate'
import { DEFAULT_LANGUAGE } from '../../constants/languages'
import nextI18NextConfig from '../../../next-i18next.config.js'

const serverSideTranslations = async (locale, domains = []) => {
  return await baseServerSideTranslations(locale, [...dt, ...domains], nextI18NextConfig, [
    DEFAULT_LANGUAGE,
  ])
}

export default serverSideTranslations
Run Code Online (Sandbox Code Playgroud)

- 最后,在页面上使用此功能。

import MainLayout from '../components/layouts/MainLayout'
import serverSideTranslations from '../utils/serverSideTranslations'
import HomeLayout from '../components/home/HomeLayout'
import Main from '../components/home/main/Main'

function HomePage() {
  return (
    <HomeLayout>
      <Main />
    </HomeLayout>
  )
}

HomePage.getLayout = (page) => <MainLayout>{page}</MainLayout>

export const getServerSideProps = async ({ locale }) => {
  // Wrapping in Promis.all is not necessary, I use it simply so that if there are any other asynchronous operations, then not to use them through await and not to block each other's work
  const [translations] = await Promise.all([
    serverSideTranslations(locale, ['home']),
  ])

  return {
    props: {
      ...translations,
    },
  }
}

export default HomePage
Run Code Online (Sandbox Code Playgroud)

希望对大家有帮助,有什么意见可以在评论里留言