UNPKG

next-i18next

Version:

The easiest way to translate your NextJs apps.

225 lines (189 loc) 7.34 kB
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } import React from 'react'; import { withRouter } from 'next/router'; import hoistNonReactStatics from 'hoist-non-react-statics'; import { I18nextProvider, withSSR } from 'react-i18next'; import { nextI18NextMiddleware } from '../middlewares'; import { lngFromReq, lngPathCorrector, lngsToLoad } from '../utils'; import { NextStaticProvider } from '../components'; import { isServer } from '../utils'; export const appWithTranslation = function (WrappedComponent) { const WrappedComponentWithSSR = withSSR()(WrappedComponent); const nextI18Next = this; const { config, consoleMessage, i18n, initPromise } = nextI18Next; const clientLoadNamespaces = (lng, namespaces) => Promise.all(namespaces.filter(ns => !i18n.hasResourceBundle(lng, ns)).map(ns => i18n.reloadResources(lng, ns))); class AppWithTranslation extends React.Component { constructor(props) { super(props); if (!isServer()) { const changeLanguageCallback = (prevLng, newLng) => { const { router } = props; const { pathname, asPath, query } = router; const routeInfo = { pathname, query }; if (i18n.initializedLanguageOnce && typeof newLng === 'string' && prevLng !== newLng) { const { as, href } = lngPathCorrector(config, { as: asPath, href: routeInfo }, newLng); router.replace(href, as, { shallow: config.shallowRender }); } }; const changeLanguage = i18n.changeLanguage.bind(i18n); i18n.changeLanguage = async (newLng, callback = () => null) => { const prevLng = i18n.language; if (typeof newLng === 'string' && i18n.initializedLanguageOnce === true) { const usedNamespaces = Object.entries(i18n.reportNamespaces.usedNamespaces).filter(x => x[1] === true).map(x => x[0]); await clientLoadNamespaces(newLng, usedNamespaces); } return changeLanguage(newLng, () => { changeLanguageCallback(prevLng, newLng); callback(); }); }; } } static async getInitialProps(ctx) { await initPromise; /* Initiate vars to return */ const { req, res } = ctx.ctx; let initialI18nStore = {}; let initialLanguage = null; let i18nServerInstance = null; /* Preprocess the req via next-i18next and i18next middlewares */ if (req && res) { const middlewares = nextI18NextMiddleware(nextI18Next); for (const middleware of middlewares) { await new Promise(resolve => middleware(req, res, resolve)); } } /* Call getInitialProps on our wrapped _app */ let wrappedComponentProps = { pageProps: {} }; if (WrappedComponent.getInitialProps) { wrappedComponentProps = await WrappedComponent.getInitialProps(ctx); } if (typeof wrappedComponentProps.pageProps === 'undefined') { consoleMessage('error', 'If you have a getInitialProps method in your custom _app.js file, you must explicitly return pageProps. For more information, see: https://github.com/zeit/next.js#custom-app'); } /* Step 1: Determine initial language */ if (req && req.i18n) { initialLanguage = lngFromReq(req); /* Perform a lang change in case we're not on the right lang */ await req.i18n.changeLanguage(initialLanguage); } else if (Array.isArray(i18n.languages) && i18n.languages.length > 0) { initialLanguage = i18n.language; } /* Step 2: Determine namespace dependencies */ let namespacesRequired = config.ns; if (Array.isArray(wrappedComponentProps.pageProps.namespacesRequired)) { ({ namespacesRequired } = wrappedComponentProps.pageProps); } else { consoleMessage('warn', `You have not declared a namespacesRequired array on your page-level component: ${ctx.Component.displayName || ctx.Component.name || 'Component'}. This will cause all namespaces to be sent down to the client, possibly negatively impacting the performance of your app. For more info, see: https://github.com/isaachinman/next-i18next#4-declaring-namespace-dependencies`); } /* We must always send down the defaultNS, otherwise the client will trigger a request for it and issue the "Did not expect server HTML to contain a <h1> in <div>" error */ if (typeof config.defaultNS === 'string' && !namespacesRequired.includes(config.defaultNS)) { namespacesRequired.push(config.defaultNS); } /* Step 3: Perform data fetching, depending on environment */ if (req && req.i18n) { /* Detect the languages to load based upon the fallbackLng configuration */ const { fallbackLng } = config; const languagesToLoad = lngsToLoad(initialLanguage, fallbackLng, config.otherLanguages); /* Initialise the store with the languagesToLoad and necessary namespaces needed to render this specific tree */ languagesToLoad.forEach(lng => { initialI18nStore[lng] = {}; namespacesRequired.forEach(ns => { initialI18nStore[lng][ns] = (req.i18n.services.resourceStore.data[lng] || {})[ns] || {}; }); }); } else if (Array.isArray(i18n.languages) && i18n.languages.length > 0) { /* Load newly-required translations if changing route clientside */ await clientLoadNamespaces(i18n.languages[0], namespacesRequired); initialI18nStore = i18n.store.data; } /* Step 4: Overwrite i18n.toJSON method to be able to serialize the instance */ if (req && req.i18n) { req.i18n.toJSON = () => null; i18nServerInstance = req.i18n; } /* `pageProps` will get serialized automatically by NextJs */ return { initialI18nStore, initialLanguage, i18nServerInstance, ...wrappedComponentProps }; } render() { const { initialLanguage, initialI18nStore, i18nServerInstance } = this.props; return /*#__PURE__*/React.createElement(I18nextProvider, { i18n: i18nServerInstance || i18n }, /*#__PURE__*/React.createElement(NextStaticProvider, null, /*#__PURE__*/React.createElement(WrappedComponentWithSSR, _extends({ initialLanguage: initialLanguage, initialI18nStore: initialI18nStore }, this.props)))); } } return hoistNonReactStatics(withRouter(AppWithTranslation), WrappedComponent, { getInitialProps: true }); };