Skip to content

Commit dde9adf

Browse files
committed
Make browser language detection work with SSR
1 parent 8be19c6 commit dde9adf

File tree

3 files changed

+44
-45
lines changed

3 files changed

+44
-45
lines changed

src/lib/i18n.tsx

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import React, { createContext, useContext, useMemo } from "react";
22
import type { ReactNode } from "react";
3-
import { isBrowser } from "./tools/isBrowser";
3+
import { createStatefulObservable, useRerenderOnChange } from "./tools/StatefulObservable";
44

5-
let langIfNoProvider: undefined | string;
5+
const langContext = createContext<string | undefined>(undefined);
66

7-
export function setLangToUseIfProviderNotUsed(lang: string) {
8-
langIfNoProvider = lang;
9-
}
7+
const $browserLanguageDifferentFromFrAfterFirstEffect = createStatefulObservable<
8+
string | undefined
9+
>(() => undefined);
1010

11-
const langContext = createContext<string | undefined>(undefined);
11+
export function useLang(): string {
12+
useRerenderOnChange($browserLanguageDifferentFromFrAfterFirstEffect);
1213

13-
export function useLang(): string | undefined {
14-
const lang = useContext(langContext);
14+
let lang = useContext(langContext);
1515

1616
if (lang === undefined) {
17-
return langIfNoProvider ?? (!isBrowser ? undefined : navigator.language);
17+
lang = $browserLanguageDifferentFromFrAfterFirstEffect.current ?? "fr";
1818
}
1919

2020
return lang;
@@ -106,10 +106,6 @@ export function createComponentI18nApi<
106106
const lang = useLang();
107107

108108
const bestMatchLang = useMemo(() => {
109-
if (lang === undefined) {
110-
return "fr";
111-
}
112-
113109
const bestApproxLang = getLanguageBestApprox({
114110
"languages": Object.keys(messagesByLang),
115111
"languageLike": lang
@@ -142,3 +138,17 @@ export function createComponentI18nApi<
142138
[`add${componentName}Translations`]: addTranslations
143139
} as any;
144140
}
141+
142+
export function startI18nLogic(params: { registerEffectAction: (action: () => void) => void }) {
143+
const { registerEffectAction } = params;
144+
145+
registerEffectAction(() => {
146+
const lang = navigator.language;
147+
148+
if (lang === "fr") {
149+
return;
150+
}
151+
152+
$browserLanguageDifferentFromFrAfterFirstEffect.current = lang;
153+
});
154+
}

src/lib/start.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { isBrowser } from "./tools/isBrowser";
22
import { assert } from "tsafe/assert";
33
import { symToStr } from "tsafe/symToStr";
4-
import { setLangToUseIfProviderNotUsed } from "./i18n";
4+
import { startI18nLogic } from "./i18n";
55
import type { ColorScheme } from "./darkMode";
66
import { startClientSideIsDarkLogic } from "./darkMode";
77

88
export type Params = {
99
defaultColorScheme: ColorScheme | "system";
10-
/** If not specified it will fall back to browser preference */
11-
langIfNoProvider?: string;
1210
/** Default: false */
1311
verbose?: boolean;
1412
};
@@ -21,7 +19,7 @@ export type NextParams = {
2119
let isStarted = false;
2220

2321
async function startReactDsfrWithOptionalNextParams(params: Params, nextParams?: NextParams) {
24-
const { defaultColorScheme, verbose = false, langIfNoProvider } = params;
22+
const { defaultColorScheme, verbose = false } = params;
2523

2624
assert(
2725
isBrowser,
@@ -37,30 +35,25 @@ async function startReactDsfrWithOptionalNextParams(params: Params, nextParams?:
3735

3836
isStarted = true;
3937

40-
if (langIfNoProvider !== undefined) {
41-
setLangToUseIfProviderNotUsed(langIfNoProvider);
42-
}
38+
const registerEffectAction: (action: () => void) => void =
39+
nextParams === undefined ? action => action() : nextParams.registerEffectAction;
4340

4441
startClientSideIsDarkLogic({
4542
"colorSchemeExplicitlyProvidedAsParameter": defaultColorScheme,
4643
"doPersistDarkModePreferenceWithCookie":
4744
nextParams === undefined ? false : nextParams.doPersistDarkModePreferenceWithCookie,
48-
"registerEffectAction":
49-
nextParams === undefined ? action => action() : nextParams.registerEffectAction
45+
registerEffectAction
5046
});
5147

48+
startI18nLogic({ registerEffectAction });
49+
5250
(window as any).dsfr = { verbose, "mode": "manual" };
5351

5452
await import("../dsfr/dsfr.module" as any);
5553

5654
const { dsfr } = window as unknown as { dsfr: { start: () => void } };
5755

58-
if (nextParams === undefined) {
59-
dsfr.start();
60-
return;
61-
} else {
62-
nextParams.registerEffectAction(() => dsfr.start());
63-
}
56+
registerEffectAction(() => dsfr.start());
6457
}
6558

6659
export function startReactDsfr(params: Params) {

src/next.tsx

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import faviconWebmanifestUrl from "./dsfr/favicon/manifest.webmanifest";
3131
import type { ColorScheme } from "./lib/darkMode";
3232
import DefaultDocument from "next/document";
3333
import { getAssetUrl } from "./lib/tools/getAssetUrl";
34-
import { setLangToUseIfProviderNotUsed } from "./lib/i18n";
3534
import { getColors } from "./lib/colors";
3635
import "./dsfr/dsfr.css";
3736
import "./dsfr/utility/icons/icons.css";
@@ -115,23 +114,17 @@ export function createNextDsfrIntegrationApi(params: Params): NextDsfrIntegratio
115114
let isAfterFirstEffect = false;
116115
const actions: (() => void)[] = [];
117116

118-
{
119-
startDsfrReactParams.langIfNoProvider ??= "fr";
120-
121-
if (isBrowser) {
122-
startReactDsfrNext(startDsfrReactParams, {
123-
doPersistDarkModePreferenceWithCookie,
124-
"registerEffectAction": action => {
125-
if (isAfterFirstEffect) {
126-
action();
127-
} else {
128-
actions.push(action);
129-
}
117+
if (isBrowser) {
118+
startReactDsfrNext(startDsfrReactParams, {
119+
doPersistDarkModePreferenceWithCookie,
120+
"registerEffectAction": action => {
121+
if (isAfterFirstEffect) {
122+
action();
123+
} else {
124+
actions.push(action);
130125
}
131-
});
132-
} else {
133-
setLangToUseIfProviderNotUsed(startDsfrReactParams.langIfNoProvider);
134-
}
126+
}
127+
});
135128
}
136129

137130
const isDarkPropKey = "dsfrIsDark";
@@ -148,6 +141,9 @@ export function createNextDsfrIntegrationApi(params: Params): NextDsfrIntegratio
148141
}
149142

150143
useEffect(() => {
144+
if (isAfterFirstEffect) {
145+
return;
146+
}
151147
isAfterFirstEffect = true;
152148
actions.forEach(action => action());
153149
}, []);

0 commit comments

Comments
 (0)