最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to add Dynamic RTL support for NEXT.js Material UI Emotion - Stack Overflow

programmeradmin0浏览0评论

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
Add a ment  | 

1 Answer 1

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

发布评论

评论列表(0)

  1. 暂无评论