I am using nextjs v12.2, @mui/material v5.9, next-i18next v11.3
I am trying to implement dynamic Rtl/Ltr support based on changing language
I used this example for integration of Next.JS with Material UI v5 and this document to change the direction of MUI ponents
The problem is "each time when I switch between language generated class for mui ponent Like below (screenshot) for all mui ponent, in this example created a repeated class for mui input ponent"
My current implementation is as follows:
_document
import * as React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import createEmotionServer from "@emotion/server/create-instance";
import theme from "theme";
import createEmotionCache from "theme/createEmotionCache";
import i18nextConfig from "next-i18next.config";
export default class MyDocument extends Document {
render() {
const { locale } = this.props.__NEXT_DATA__;
return (
<Html lang={locale}>
<Head>
{/* PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
<meta name="emotion-insertion-point" content="" />
{this.props.emotionStyleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async ctx => {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App =>
function EnhanceApp(props) {
return <App emotionCache={cache} {...props} />;
},
});
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map(style => (
<style
data-emotion={`${style.key} ${style.ids.join(" ")}`}
key={style.key}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
emotionStyleTags,
};
};
_app
import Head from "next/head";
import { appWithTranslation, useTranslation } from "next-i18next";
import { ThemeProvider } from "@mui/material/styles";
import { CacheProvider } from "@emotion/react";
import CssBaseline from "@mui/material/CssBaseline";
import createEmotionCache from "theme/createEmotionCache";
import theme from "theme";
import "styles/globals.scss";
import { useEffect } from "react";
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = dir => createEmotionCache(dir);
function MyApp(props) {
const { i18n } = useTranslation();
const {
Component,
emotionCache = clientSideEmotionCache(i18n.dir()),
pageProps,
} = props;
useEffect(() => {
document.body.dir = i18n.dir();
}, [i18n]);
return (
<CacheProvider value={emotionCache}>
<ThemeProvider theme={{ ...theme, direction: i18n.dir() }}>
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
</Head>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
export default appWithTranslation(MyApp);
createEmotionCache.js
import createCache from "@emotion/cache";
import { prefixer } from "stylis";
import rtlPlugin from "stylis-plugin-rtl";
const isBrowser = typeof document !== "undefined";
// On the client side, Create a meta tag at the top of the <head> and set it as insertionPoint.
// This assures that MUI styles are loaded first.
// It allows developers to easily override MUI styles with other styling solutions, like CSS modules.
export default function createEmotionCache(direction) {
let insertionPoint;
if (isBrowser) {
const emotionInsertionPoint = document.querySelector(
'meta[name="emotion-insertion-point"]',
);
insertionPoint = emotionInsertionPoint ?? undefined;
}
return createCache({
key: direction === "rtl" ? "mui-style-rtl" : "mui-style-ltr",
stylisPlugins: direction === "rtl" ? [prefixer, rtlPlugin] : [],
insertionPoint,
});
// return createCache({ key: "mui-style-ltr", insertionPoint });
}
theme.js
import { createTheme } from '@mui/material/styles';
// Create a theme instance.
const theme = createTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
},
typography: {
fontFamily: "'Open Sans', Roboto, sans-serif",
},
});
export default theme;
index.js
import Head from "next/head";
import { useRouter } from "next/router";
import LocaleSwitcher from "../ponents/LocaleSwitcher";
import { useTranslation, Trans } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Box from "@mui/material/Box";
import { TextField } from "@mui/material";
export default function Home(props) {
const router = useRouter();
const { locale, locales, defaultLocale } = router;
const { t, i18n } = useTranslation("home");
return (
<div>
<h1>{t("title")}</h1>
<p>
{t("Current")}: {locale}
</p>
<p>
{t("Default")}: {defaultLocale}
</p>
<p>
{t("Configured")}: {JSON.stringify(locales)}
</p>
<LocaleSwitcher />
<Box m={2}>
<TextField label={t("Email")} />
</Box>
</div>
);
}
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ["home"])),
},
};
}
LocaleSwitcher.js
import Link from "next/link";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
export default function LocaleSwitcher({ title }) {
const router = useRouter();
const { locales, locale: activeLocale } = router;
const otherLocales = locales.filter(locale => locale !== activeLocale);
const { t } = useTranslation("home");
return (
<div>
<p>{t("Locale")}:</p>
<ul>
{otherLocales.map(locale => {
const { pathname, query, asPath } = router;
return (
<li key={locale}>
<Link href={{ pathname, query }} as={asPath} locale={locale}>
<a>
{locale === "en"
? "English"
: locale === "fa"
? "Farsi"
: null}
</a>
</Link>
</li>
);
})}
</ul>
</div>
);
}
I am using nextjs v12.2, @mui/material v5.9, next-i18next v11.3
I am trying to implement dynamic Rtl/Ltr support based on changing language
I used this example for integration of Next.JS with Material UI v5 and this document to change the direction of MUI ponents
The problem is "each time when I switch between language generated class for mui ponent Like below (screenshot) for all mui ponent, in this example created a repeated class for mui input ponent"
My current implementation is as follows:
_document
import * as React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import createEmotionServer from "@emotion/server/create-instance";
import theme from "theme";
import createEmotionCache from "theme/createEmotionCache";
import i18nextConfig from "next-i18next.config";
export default class MyDocument extends Document {
render() {
const { locale } = this.props.__NEXT_DATA__;
return (
<Html lang={locale}>
<Head>
{/* PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
<meta name="emotion-insertion-point" content="" />
{this.props.emotionStyleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async ctx => {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App =>
function EnhanceApp(props) {
return <App emotionCache={cache} {...props} />;
},
});
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map(style => (
<style
data-emotion={`${style.key} ${style.ids.join(" ")}`}
key={style.key}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
emotionStyleTags,
};
};
_app
import Head from "next/head";
import { appWithTranslation, useTranslation } from "next-i18next";
import { ThemeProvider } from "@mui/material/styles";
import { CacheProvider } from "@emotion/react";
import CssBaseline from "@mui/material/CssBaseline";
import createEmotionCache from "theme/createEmotionCache";
import theme from "theme";
import "styles/globals.scss";
import { useEffect } from "react";
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = dir => createEmotionCache(dir);
function MyApp(props) {
const { i18n } = useTranslation();
const {
Component,
emotionCache = clientSideEmotionCache(i18n.dir()),
pageProps,
} = props;
useEffect(() => {
document.body.dir = i18n.dir();
}, [i18n]);
return (
<CacheProvider value={emotionCache}>
<ThemeProvider theme={{ ...theme, direction: i18n.dir() }}>
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
</Head>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
export default appWithTranslation(MyApp);
createEmotionCache.js
import createCache from "@emotion/cache";
import { prefixer } from "stylis";
import rtlPlugin from "stylis-plugin-rtl";
const isBrowser = typeof document !== "undefined";
// On the client side, Create a meta tag at the top of the <head> and set it as insertionPoint.
// This assures that MUI styles are loaded first.
// It allows developers to easily override MUI styles with other styling solutions, like CSS modules.
export default function createEmotionCache(direction) {
let insertionPoint;
if (isBrowser) {
const emotionInsertionPoint = document.querySelector(
'meta[name="emotion-insertion-point"]',
);
insertionPoint = emotionInsertionPoint ?? undefined;
}
return createCache({
key: direction === "rtl" ? "mui-style-rtl" : "mui-style-ltr",
stylisPlugins: direction === "rtl" ? [prefixer, rtlPlugin] : [],
insertionPoint,
});
// return createCache({ key: "mui-style-ltr", insertionPoint });
}
theme.js
import { createTheme } from '@mui/material/styles';
// Create a theme instance.
const theme = createTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
},
typography: {
fontFamily: "'Open Sans', Roboto, sans-serif",
},
});
export default theme;
index.js
import Head from "next/head";
import { useRouter } from "next/router";
import LocaleSwitcher from "../ponents/LocaleSwitcher";
import { useTranslation, Trans } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Box from "@mui/material/Box";
import { TextField } from "@mui/material";
export default function Home(props) {
const router = useRouter();
const { locale, locales, defaultLocale } = router;
const { t, i18n } = useTranslation("home");
return (
<div>
<h1>{t("title")}</h1>
<p>
{t("Current")}: {locale}
</p>
<p>
{t("Default")}: {defaultLocale}
</p>
<p>
{t("Configured")}: {JSON.stringify(locales)}
</p>
<LocaleSwitcher />
<Box m={2}>
<TextField label={t("Email")} />
</Box>
</div>
);
}
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ["home"])),
},
};
}
LocaleSwitcher.js
import Link from "next/link";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
export default function LocaleSwitcher({ title }) {
const router = useRouter();
const { locales, locale: activeLocale } = router;
const otherLocales = locales.filter(locale => locale !== activeLocale);
const { t } = useTranslation("home");
return (
<div>
<p>{t("Locale")}:</p>
<ul>
{otherLocales.map(locale => {
const { pathname, query, asPath } = router;
return (
<li key={locale}>
<Link href={{ pathname, query }} as={asPath} locale={locale}>
<a>
{locale === "en"
? "English"
: locale === "fa"
? "Farsi"
: null}
</a>
</Link>
</li>
);
})}
</ul>
</div>
);
}
Share
Improve this question
edited Aug 16, 2022 at 6:03
hmd.fullstack
asked Aug 9, 2022 at 20:19
hmd.fullstackhmd.fullstack
5682 gold badges8 silver badges24 bronze badges
0
1 Answer
Reset to default 8_app.js
import Head from "next/head";
import { appWithTranslation, useTranslation } from "next-i18next";
import { ThemeProvider } from "@mui/material/styles";
import { CacheProvider } from "@emotion/react";
import CssBaseline from "@mui/material/CssBaseline";
import theme from "theme";
import "styles/globals.scss";
import { useEffect } from "react";
import createCache from "@emotion/cache";
import { prefixer } from "stylis";
import rtlPlugin from "stylis-plugin-rtl";
const isBrowser = typeof document !== "undefined";
let insertionPoint;
if (isBrowser) {
const emotionInsertionPoint = document.querySelector(
'meta[name="emotion-insertion-point"]',
);
insertionPoint = emotionInsertionPoint ?? undefined;
}
const cacheRtl = createCache({
key: "mui-style-rtl",
stylisPlugins: [prefixer, rtlPlugin],
insertionPoint,
});
const cacheLtr = createCache({
key: "mui-style-ltr",
insertionPoint,
});
function MyApp(props) {
const { i18n } = useTranslation();
const { Component, emotionCache, pageProps } = props;
useEffect(() => {
document.body.dir = i18n.dir();
}, [i18n]);
return (
<>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Head>
<CacheProvider value={i18n.dir() === "rtl" ? cacheRtl : cacheLtr}>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
</>
);
}
export default appWithTranslation(MyApp);
@MarijaNajdova explained the problem at https://github./mui/material-ui/issues/33892