import React from 'react'; import {withPrefix, WrapPageElementBrowserArgs} from 'gatsby'; // @ts-ignore import browserLang from 'browser-lang'; import { I18NextContext, LANGUAGE_KEY, PageContext, PluginOptions, LocaleNode, Resource, ResourceKey } from '../types'; import i18next, {i18n as I18n} from 'i18next'; import {I18nextProvider} from 'react-i18next'; import {I18nextContext} from '../i18nextContext'; import outdent from 'outdent'; const withI18next = (i18n: I18n, context: I18NextContext) => (children: any) => { return ( <I18nextProvider i18n={i18n}> <I18nextContext.Provider value={context}>{children}</I18nextContext.Provider> </I18nextProvider> ); }; const removePathPrefix = (pathname: string) => { const pathPrefix = withPrefix('/'); if (pathname.startsWith(pathPrefix)) { return pathname.replace(pathPrefix, '/'); } return pathname; }; export const wrapPageElement = ( {element, props}: WrapPageElementBrowserArgs<any, PageContext>, { i18nextOptions = {}, redirect = true, generateDefaultLanguagePage = false, siteUrl, localeJsonNodeName = 'locales', fallbackLanguage }: PluginOptions ) => { if (!props) return; const {data, pageContext, location} = props; const {routed, language, languages, originalPath, defaultLanguage, path} = pageContext.i18n; const isRedirect = redirect && !routed; if (isRedirect) { const {search} = location; // Skip build, Browsers only if (typeof window !== 'undefined') { let detected = window.localStorage.getItem(LANGUAGE_KEY) || browserLang({ languages, fallback: fallbackLanguage || language }); if (!languages.includes(detected)) { detected = language; } window.localStorage.setItem(LANGUAGE_KEY, detected); if (detected !== defaultLanguage) { const queryParams = search || ''; const newUrl = withPrefix( `/${detected}${removePathPrefix(location.pathname)}${queryParams}${location.hash}` ); // @ts-ignore window.___replace(newUrl); return null; } } } const localeNodes: Array<{node: LocaleNode}> = data?.[localeJsonNodeName]?.edges || []; if (languages.length > 1 && localeNodes.length === 0 && process.env.NODE_ENV === 'development') { console.error( outdent` No translations were found in "${localeJsonNodeName}" key for "${originalPath}". You need to add a graphql query to every page like this: export const query = graphql\` query($language: String!) { ${localeJsonNodeName}: allLocale(language: {eq: $language}}) { edges { node { ns data language } } } } \`; ` ); } const namespaces = localeNodes.map(({node}) => node.ns); // We want to set default namespace to a page namespace if it exists // and use other namespaces as fallback // this way you dont need to specify namespaces in pages let defaultNS = i18nextOptions.defaultNS || 'translation'; defaultNS = namespaces.find((ns) => ns !== defaultNS) || defaultNS; const fallbackNS = namespaces.filter((ns) => ns !== defaultNS); const resources: Resource = localeNodes.reduce<Resource>((res: Resource, {node}) => { const parsedData: ResourceKey = JSON.parse(node.data); if (!(node.language in res)) res[node.language] = {}; res[node.language][node.ns] = parsedData; return res; }, {}); const i18n = i18next.createInstance(); i18n.init({ ...i18nextOptions, resources, lng: language, fallbackLng: defaultLanguage, defaultNS, fallbackNS, react: { useSuspense: false } }); if (i18n.language !== language) { i18n.changeLanguage(language); } const context = { routed, language, languages, originalPath, defaultLanguage, generateDefaultLanguagePage, siteUrl, path }; return withI18next(i18n, context)(element); };