Skip to content

Commit 9934b8a

Browse files
committed
Feature real i18n support for Next App Router
1 parent ea1e504 commit 9934b8a

File tree

7 files changed

+50
-44
lines changed

7 files changed

+50
-44
lines changed

src/next-appdir/DsfrHead.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { getScriptToRunAsap } from "../useIsDark/scriptToRunAsap";
88
import { fontUrlByFileBasename } from "./zz_internal/fontUrlByFileBasename";
99
import { getDefaultColorSchemeServerSide } from "./zz_internal/defaultColorScheme";
1010
import { setLink, type RegisteredLinkProps } from "../link";
11-
import { setUseLang } from "../i18n";
1211
//NOTE: As of now there is no way to enforce ordering in Next Appdir
1312
//See: https://github.com/vercel/next.js/issues/16630
1413
// @import url(...) doesn't work. Using Sass and @use is our last resort.
@@ -21,14 +20,12 @@ export type DsfrHeadProps = {
2120
preloadFonts?: (keyof typeof fontUrlByFileBasename)[];
2221
/** Default: <a /> */
2322
Link?: (props: RegisteredLinkProps & { children: ReactNode }) => ReturnType<React.FC>;
24-
/** Default: ()=> "fr" */
25-
useLang?: () => string;
2623
};
2724

2825
const isProduction = process.env.NODE_ENV !== "development";
2926

3027
export function DsfrHead(props: DsfrHeadProps) {
31-
const { preloadFonts = [], Link, useLang } = props;
28+
const { preloadFonts = [], Link } = props;
3229

3330
const defaultColorScheme = getDefaultColorSchemeServerSide();
3431

@@ -38,12 +35,6 @@ export function DsfrHead(props: DsfrHeadProps) {
3835
}
3936
},[Link]);
4037

41-
useMemo(() => {
42-
if (useLang !== undefined) {
43-
setUseLang({ useLang });
44-
}
45-
},[useLang]);
46-
4738
return (
4839
<>
4940
{isProduction &&

src/next-appdir/DsfrProvider.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,39 @@
11
"use client";
22

3-
import React, { useEffect } from "react";
3+
import React, { useMemo, useEffect } from "react";
44
import type { ReactNode } from "react";
55
import { isBrowser } from "../tools/isBrowser";
66
import { SsrIsDarkProvider } from "../useIsDark/server";
7-
import { dsfrEffect } from "./start";
7+
import { dsfrEffect } from "./zz_internal/start";
88
import { GdprStoreProvider } from "../gdpr/GdprStore";
99
import { getDefaultColorSchemeClientSide } from "./zz_internal/defaultColorScheme";
10+
import { setUseLang } from "../i18n";
1011

1112
export type DsfrProviderProps = {
1213
children: ReactNode;
14+
lang?: string;
1315
};
1416

1517
export function DsfrProvider(props: DsfrProviderProps) {
16-
const { children } = props;
18+
const { children, lang } = props;
1719

1820
useEffect(() => {
1921
dsfrEffect();
2022
}, []);
2123

24+
useMemo(()=> {
25+
if (lang === undefined) {
26+
return;
27+
}
28+
29+
setUseLang({ "useLang": () => lang });
30+
31+
}, [lang]);
32+
2233
if (isBrowser) {
23-
return <GdprStoreProvider>{children}</GdprStoreProvider>;
34+
return (
35+
<GdprStoreProvider>{children}</GdprStoreProvider>
36+
);
2437
}
2538

2639
const defaultColorScheme = getDefaultColorSchemeClientSide();
Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
11
import { data_fr_scheme, data_fr_theme } from "../useIsDark/constants";
22
import type { ColorScheme } from "../useIsDark";
33
import { type DefaultColorScheme, setDefaultColorSchemeServerSide } from "./zz_internal/defaultColorScheme";
4+
import { setUseLang } from "../i18n";
45

56
const suppressHydrationWarning = true;
67

7-
//export function getColorSchemeHtmlAttributes(params: { defaultColorScheme: "system"; }): { suppressHydrationWarning: true; };
8-
//export function getColorSchemeHtmlAttributes(params: { defaultColorScheme: ColorScheme; }): { suppressHydrationWarning: true; } & Record<typeof data_fr_scheme | typeof data_fr_theme, ColorScheme>;
9-
export function getColorSchemeHtmlAttributes(params: {
8+
export function getHtmlAttributes(params: {
109
defaultColorScheme: DefaultColorScheme;
11-
}): { suppressHydrationWarning: true } & (
10+
lang?: string;
11+
}): { suppressHydrationWarning: true; lang?: string; } & (
1212
| Record<typeof data_fr_scheme | typeof data_fr_theme, ColorScheme>
1313
| {}
1414
) {
15-
const { defaultColorScheme } = params;
15+
16+
const { defaultColorScheme, lang } = params;
1617

1718
setDefaultColorSchemeServerSide({ defaultColorScheme });
1819

20+
if (lang !== undefined) {
21+
setUseLang({ "useLang": () => lang });
22+
}
23+
1924
if (defaultColorScheme === "system") {
20-
return { suppressHydrationWarning };
25+
return {
26+
lang,
27+
suppressHydrationWarning
28+
};
2129
}
2230

2331
return {
32+
lang,
2433
suppressHydrationWarning,
2534
[data_fr_scheme]: defaultColorScheme,
2635
[data_fr_theme]: defaultColorScheme
2736
};
2837
}
38+

src/next-appdir/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export type { RegisterLink, RegisteredLinkProps } from "../link";
1+
export type { RegisterLink } from "../link";
22
export type { DefaultColorScheme } from "./zz_internal/defaultColorScheme";
3-
export { startReactDsfr } from "./start";
3+
export { startReactDsfr } from "./zz_internal/start";

src/next-appdir/start.ts renamed to src/next-appdir/zz_internal/start.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import type { ReactNode } from "react";
2-
import { start } from "../start";
3-
import type { RegisterLink, RegisteredLinkProps } from "../link";
4-
import { setLink } from "../link";
5-
import { setUseLang } from "../i18n";
6-
import { type DefaultColorScheme, setDefaultColorSchemeClientSide } from "./zz_internal/defaultColorScheme";
7-
import { isBrowser } from "../tools/isBrowser";
8-
9-
export type { RegisterLink, RegisteredLinkProps };
2+
import { start } from "../../start";
3+
import type { RegisteredLinkProps } from "../../link";
4+
import { setLink } from "../../link";
5+
import { type DefaultColorScheme, setDefaultColorSchemeClientSide } from "./defaultColorScheme";
6+
import { isBrowser } from "../../tools/isBrowser";
107

118
let isAfterFirstEffect = false;
129
const actions: (() => void)[] = [];
@@ -17,21 +14,15 @@ export function startReactDsfr(params: {
1714
verbose?: boolean;
1815
/** Default: <a /> */
1916
Link?: (props: RegisteredLinkProps & { children: ReactNode }) => ReturnType<React.FC>;
20-
/** Default: ()=> "fr" */
21-
useLang?: () => string;
2217
}) {
23-
const { defaultColorScheme, verbose = false, Link, useLang } = params;
18+
const { defaultColorScheme, verbose = false, Link } = params;
2419

2520
setDefaultColorSchemeClientSide({ defaultColorScheme });
2621

2722
if (Link !== undefined) {
2823
setLink({ Link });
2924
}
3025

31-
if (useLang !== undefined) {
32-
setUseLang({ useLang });
33-
}
34-
3526
if (isBrowser) {
3627
start({
3728
defaultColorScheme,

test/integration/next-appdir/app/StartDsfr.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { startReactDsfr } from "@codegouvfr/react-dsfr/next-appdir";
44
import { defaultColorScheme } from "./defaultColorScheme";
55
import Link from "next/link";
66

7-
87
declare module "@codegouvfr/react-dsfr/next-appdir" {
98
interface RegisterLink {
109
Link: typeof Link;
@@ -13,9 +12,7 @@ declare module "@codegouvfr/react-dsfr/next-appdir" {
1312

1413
startReactDsfr({
1514
defaultColorScheme,
16-
Link,
17-
// Uncomment to test in english
18-
// useLang: () => "en",
15+
Link
1916
});
2017

2118
export default function StartDsfr(){

test/integration/next-appdir/app/layout.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { NextAppDirEmotionCacheProvider } from "tss-react/next";
22
import { DsfrHead } from "@codegouvfr/react-dsfr/next-appdir/DsfrHead";
33
import { DsfrProvider } from "@codegouvfr/react-dsfr/next-appdir/DsfrProvider";
44
import { ConsentBanner } from "@codegouvfr/react-dsfr/ConsentBanner";
5-
import { getColorSchemeHtmlAttributes } from "@codegouvfr/react-dsfr/next-appdir/getColorSchemeHtmlAttributes";
5+
import { getHtmlAttributes } from "@codegouvfr/react-dsfr/next-appdir/getHtmlAttributes";
66
import StartDsfr from "./StartDsfr";
77
import { defaultColorScheme } from "./defaultColorScheme";
88
import MuiDsfrThemeProvider from "@codegouvfr/react-dsfr/mui";
@@ -21,9 +21,13 @@ declare module "@codegouvfr/react-dsfr/gdpr" {
2121

2222
export default function RootLayout({ children }: { children: JSX.Element; }) {
2323

24+
//NOTE: If we had i18n setup we would get lang from the props.
25+
//See https://github.com/vercel/next.js/blob/canary/examples/app-dir-i18n-routing/app/%5Blang%5D/layout.tsx
26+
const lang = "en";
27+
2428
return (
2529
<html
26-
{...getColorSchemeHtmlAttributes({ defaultColorScheme })}
30+
{...getHtmlAttributes({ defaultColorScheme, lang })}
2731
//NOTE: Scrollbar always visible to avoid layout shift when modal are opened
2832
style={{
2933
"overflow": "-moz-scrollbars-vertical",
@@ -56,7 +60,7 @@ export default function RootLayout({ children }: { children: JSX.Element; }) {
5660
"flexDirection": "column"
5761
}}
5862
>
59-
<DsfrProvider>
63+
<DsfrProvider lang={lang}>
6064
<ConsentBanner gdprLinkProps={{ href: "/mui" }} siteName='Next Test App' services={[
6165
{
6266
name: "matomo",

0 commit comments

Comments
 (0)