How to access browser language during SSR?

In an SPA you would normally access navigator.language but during SSR the navigator is undefined. How can I access the browser language/locale during SSR?
13 Replies
ekafoo
ekafoo3y ago
I have a helper that im using in my projects.
export const getLocale = (req: IncomingMessage & { cookies?: { [key: string]: string } }): string => {
let language = '';
switch (true) {
case !!req.cookies?.[langCookie]:
language = req.cookies?.[langCookie];
break;
case !!req:
language = req.headers?.['accept-language']?.split(',')?.[0];
break;
default:
break;
}
const splittedLanguage = language?.split('-')?.[0];
return i18nConfig.locales.includes(splittedLanguage) ? splittedLanguage : i18nConfig.defaultLocale;
};
export const getLocale = (req: IncomingMessage & { cookies?: { [key: string]: string } }): string => {
let language = '';
switch (true) {
case !!req.cookies?.[langCookie]:
language = req.cookies?.[langCookie];
break;
case !!req:
language = req.headers?.['accept-language']?.split(',')?.[0];
break;
default:
break;
}
const splittedLanguage = language?.split('-')?.[0];
return i18nConfig.locales.includes(splittedLanguage) ? splittedLanguage : i18nConfig.defaultLocale;
};
First i check if the user has the langugae cookie settet, indicating that the user has changed the language. Second take the first langage in the accept-language. Last i splitt the lang, for example if it is en-US i only need the en and check it against available languages that my app supports. If it is a language that i dont support i fallback to the apps default locale
Michael P. Jung
Michael P. JungOP3y ago
Where does the IncomingMessage come from? Ah... got it (I think)
Michael P. Jung
Michael P. JungOP3y ago
SolidStart Beta Docuentation
SolidStart Beta Documentation
Early release documentation and resources for SolidStart Beta
ekafoo
ekafoo3y ago
Using it in multiple nextjs applications, havent tried in in solid but should work without problems. Using it like this in my application:
export const getServerSideProps = async ({ req, resolvedUrl }) => {
return {
props: {
...(await loadNamespaces({
locale: getLocale(req),
pathname: resolvedUrl,
pages: {
[resolvedUrl]: ['common', 'button'],
},
})),
},
};
};`
export const getServerSideProps = async ({ req, resolvedUrl }) => {
return {
props: {
...(await loadNamespaces({
locale: getLocale(req),
pathname: resolvedUrl,
pages: {
[resolvedUrl]: ['common', 'button'],
},
})),
},
};
};`
Michael P. Jung
Michael P. JungOP3y ago
In solid it's different. Though I'm still having troubles. When using useServerContext() I just get an empty object.
ekafoo
ekafoo3y ago
Hmm okej, will try on my solid-project!
Michael P. Jung
Michael P. JungOP3y ago
It's probably because I'm trying to use useServerContext outside a component. Jepp. That's the reason. The server context is only available as soon as the root component is rendered. Just got it working.
ekafoo
ekafoo3y ago
Yes got it working to! Mine looks like this:
const langCookie = 'locale';
const defaultLocale = 'en';
const locales = [defaultLocale, 'es'];

export const getLocale = (req: Request & { cookies?: { [key: string]: string } }): string => {
let language = '';
switch (true) {
case !!req.cookies?.[langCookie]:
language = req.cookies?.[langCookie] as string;
break;
case !!req:
language = req.headers?.get('accept-language')?.split(',')?.[0] as string;
break;
default:
break;
}
const splittedLanguage = language?.split('-')?.[0];
return locales.includes(splittedLanguage) ? splittedLanguage : defaultLocale;
};
const langCookie = 'locale';
const defaultLocale = 'en';
const locales = [defaultLocale, 'es'];

export const getLocale = (req: Request & { cookies?: { [key: string]: string } }): string => {
let language = '';
switch (true) {
case !!req.cookies?.[langCookie]:
language = req.cookies?.[langCookie] as string;
break;
case !!req:
language = req.headers?.get('accept-language')?.split(',')?.[0] as string;
break;
default:
break;
}
const splittedLanguage = language?.split('-')?.[0];
return locales.includes(splittedLanguage) ? splittedLanguage : defaultLocale;
};
export default function AuthLayout() {
const data = useRouteData<typeof routeData>();
const event = useServerContext();

const language = () => isServer && getLocale(event.request);

return (
<Suspense fallback={<div>Loading</div>}>
<div>{language()}</div>
<Layout token={data()?.token as Authorization} user={data()?.user as UserResponse}>
<Outlet />
</Layout>
</Suspense>
);
}
export default function AuthLayout() {
const data = useRouteData<typeof routeData>();
const event = useServerContext();

const language = () => isServer && getLocale(event.request);

return (
<Suspense fallback={<div>Loading</div>}>
<div>{language()}</div>
<Layout token={data()?.token as Authorization} user={data()?.user as UserResponse}>
<Outlet />
</Layout>
</Suspense>
);
}
And if i change browser language i get different languages in the div
Michael P. Jung
Michael P. JungOP3y ago
That's how my code looks. I'll implement language switching at a higher level. This is just my helper method for now:
/**
* Autodetect language from browser returning a valid and supported
* `<LanguageCode>`. If the code is not supporte the `fallbackLanguage`
* is used instead.
*/

function browserLanguage(fallbackLanguage: Language): Language {
const event = useServerContext()
const language = isServer
? event.request.headers.get('accept-language')?.split('-')[0]
: navigator.language
return language !== undefined && language in resources
? (language as Language)
: fallbackLanguage
}
/**
* Autodetect language from browser returning a valid and supported
* `<LanguageCode>`. If the code is not supporte the `fallbackLanguage`
* is used instead.
*/

function browserLanguage(fallbackLanguage: Language): Language {
const event = useServerContext()
const language = isServer
? event.request.headers.get('accept-language')?.split('-')[0]
: navigator.language
return language !== undefined && language in resources
? (language as Language)
: fallbackLanguage
}
ekafoo
ekafoo3y ago
Do you get the correct langage on the html-tag?
Michael P. Jung
Michael P. JungOP3y ago
Yes.
ekafoo
ekafoo3y ago
Nice, did you run browserLanguage in root.tsx?
Michael P. Jung
Michael P. JungOP3y ago
Initially I just had it as part of an imported module. But that caused useServerContext to return an empty object. After putting it into the root render function it did return the proper values.

Did you find this page helpful?