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.
Is there a way to fetch this data on the server side (maybe in layout.tsx or elsewhere) and have it accessible globally for the client components? Similarly to how it worked with getInitialProps / getServerSideProps before.
Here’s my clientside context:
'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 };
And my server side "hook":
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;
}
2
Answers
Solved, with enormous thanks to @nordic70!
Essentially, I created a context to pass down the translation content:
In your
layout.tsx
:Here's the
translationProvider
function:Now, for the client components, I made a similar function, where instead of calling the API, you take the active translation from the context:
And then you have access to the translations you got from the API in client components!
Big thanks to @nordic70 again, his answer saved me countless hours. Hope this will help more people having the same issues.
I would create a context provider client component called within a server component that gets the data. Than pass the data from the server component as prop into the context provider.
Some pseudo code.