ant-design / pro-components

🏆 Use Ant Design like a Pro!
https://pro-components.antdigital.dev
MIT License
4.26k stars 1.35k forks source link

🧐[问题] 纯粹 react 使用 SettingDrawer 国际化没有效果 #7964

Closed InfernalAzazel closed 9 months ago

InfernalAzazel commented 9 months ago

提问前先看看:

https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md

🧐 问题描述

代码仓库

adminPro

💻 示例代码

export const AppWith = () => { const appTheme = useAppStore((state: any) => state.theme); const locale = useAppStore((state: any) => state.locale);

useEffect(() => {
    i18n.changeLanguage(locale).catch((error) => console.error('Failed to change language:', error));
}, [i18n]);
return (
    <React.StrictMode>
        <I18nextProvider i18n={i18n}>
            <ConfigProvider
                theme={{
                    algorithm: appTheme.navTheme === 'light' ? theme.defaultAlgorithm : theme.darkAlgorithm,
                    token: {
                        colorPrimary: appTheme.colorPrimary
                    }
                }}
                locale={getLocale(locale)}
            >
                <App>
                    <EscapeAntd/>
                    <RouterProvider router={router}/>
                </App>
            </ConfigProvider>
        </I18nextProvider>
    </React.StrictMode>
);

};

ReactDOM.createRoot(document.getElementById('root')!).render()

- AdminLayout.tsx
```ts
import type {MenuDataItem, ProLayoutProps, ProSettings} from '@ant-design/pro-components';
import {
    PageContainer,
    ProCard,
    ProLayout,
    SettingDrawer
} from '@ant-design/pro-components';
import type {AvatarProps, MenuProps} from 'antd';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import {Dropdown} from 'antd';
import type {ReactNode} from 'react';
import React, { useEffect, useRef, useState} from 'react';
// import {menuData} from './defaultProps';
import {Link, Outlet, useLocation} from 'react-router-dom';
import {Icon} from '@iconify/react';
import {useGetUsersRoutesRequest} from '@/services';
import {useAppStore} from "@/store";
import {getTreeDataAndHalfCheckedKeys} from "@/utils";
import {useTranslation} from 'react-i18next';
import i18n from "@/locales";

const AdminLayout: React.FC = () => {

    const {t} = useTranslation();
    const location = useLocation();
    const [pathname, setPathname] = useState('/admin/welcome');
    const [menuData, setMenuData] = useState<MenuDataItem[]>([]);
    const setAccessToken = useAppStore((state: any) => state.setAccessToken);
    const theme = useAppStore((state: any) => state.theme);
    const setLocale = useAppStore((state: any) => state.setLocale);
    const setTheme = useAppStore((state: any) => state.setTheme);
    const actionRef = useRef<{ reload: () => void; }>();

    const {data: dataUsersRoutes} = useGetUsersRoutesRequest()

    useEffect(() => {
        const {treeData} = getTreeDataAndHalfCheckedKeys(dataUsersRoutes?.data || [])
        setMenuData(treeData)
    }, [dataUsersRoutes]);

    useEffect(() => {
        actionRef.current?.reload();
    }, [menuData]);
    const handleSettingsChange = (newSettings: Partial<ProSettings>) => {
        setTheme(newSettings)
    };

    const menuItemRender = (item: MenuDataItem, dom: ReactNode) => {
        if (item.disabled || item.path === undefined) {
            return dom;
        }
        return <Link to={item.path} onClick={() => {
            setPathname(item.path as string);
        }}>{dom}</Link>;
    };

    const loopMenuItem = (menus: any[]): MenuDataItem[] =>
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        menus.map(({uid, key, father, order, depth, create_at, update_at, icon, children, ...item}) => ({
            ...item,
            icon: icon && <Icon icon={icon} width={16} height={16}/>,
            children: children && loopMenuItem(children),
        }));

    const avatarRender = {
        render: (_: AvatarProps, dom: ReactNode) => {
            const onClick: MenuProps['onClick'] = ({key}) => {
                if (key === 'logout') {
                    setAccessToken('')
                }
            };

            const items: MenuProps['items'] = [
                {
                    key: 'logout',
                    icon: <Icon icon='ant-design:logout-outlined' width={16} height={16}/>,
                    label: t(`layouts.admin.lang.logout`),
                },
            ];
            return (
                <Dropdown
                    menu={{items, onClick}}
                    placement="bottom"
                >
                    {dom}
                </Dropdown>
            )
        }
    }

    function actionsRender() {
        const onClick: MenuProps['onClick'] = async ({key}) => {
            setLocale(key)
            await i18n.changeLanguage(key)
        };
        const items: MenuProps['items'] = [
            {
                key: 'zh_cn',
                icon: <Icon icon='mdi:language-swift' width={16} height={16}/>,
                label: t(`layouts.admin.lang.zh_cn`),
            },
            {
                key: 'en_us',
                icon: <Icon icon='mdi:language-swift' width={16} height={16}/>,
                label: t(`layouts.admin.lang.en_us`),
            },
        ];
        return [
            <Dropdown
                menu={{items, onClick}}
                placement="bottom"
            >
                <Icon icon={'grommet-icons:language'} width={32} height={32}/>
            </Dropdown>

        ]
    }

    const props: ProLayoutProps = {
        actionRef: actionRef,
        title: 'adminPro',
        menuItemRender,
        location,
        menu: {
            request: async () => loopMenuItem(menuData)
        },
        actionsRender: actionsRender,
        avatarProps: {
            src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
            size: 'small',
            title: 'panda',
            ...avatarRender
        }
    };

    return (
        <div>
            <ProLayout
                {...props}
                {...theme}
            >
                <PageContainer>
                    <ProCard>
                        <Outlet/>
                    </ProCard>
                </PageContainer>
                <SettingDrawer
                    pathname={pathname}
                    enableDarkTheme
                    settings={theme}
                    onSettingChange={handleSettingsChange}
                    disableUrlParams
                    hideHintAlert
                    hideCopyButton
                />
            </ProLayout>

        </div>
    );
};

export default AdminLayout;

🚑 其他信息

333

chenshuai2144 commented 9 months ago

问题:纯粹 React 使用 SettingDrawer 国际化没有效果的问题如何解决?

解决方案:

  1. 首先,关闭 SettingDrawer 动态设置可以在 src/app.tsx 文件中将其删除。
  2. 关于时间类组件的国际化 locale 设置不生效的问题,请确保正确设置了 dayjs 语言包,并在代码中进行加载:

    import 'dayjs/locale/zh-cn';
    
    dayjs.locale('zh-cn');

    另外,还要检查是否有两个版本的 dayjs 共存,可以通过运行 npm ls dayjs 查看项目中依赖的 dayjs 版本。 如果项目中依赖的 dayjs 版本和 antd 依赖的 dayjs 版本无法兼容,会导致使用两个不同版本的 dayjs 实例,从而导致国际化失效。

  3. 如果想将组件的默认语言切回中文,可以尝试使用 ConfigProvider 组件来包裹应用,并在代码中配置 dayjs.locale('zh-cn')。 如果日期组件的国际化仍未生效,请检查本地的 dayjs 版本和 antd 依赖的 dayjs 版本是否一致。
  4. 关于为什么不直接使用 formatMessage 这个语法糖,虽然 formatMessage 使用方便,但是它脱离了 React 的生命周期, 最严重的问题是切换语言时无法触发 DOM 的重新渲染,从而导致用户体验较差。 推荐使用 useIntl 或者 injectIntl 来实现同样的功能。
  5. 最后,关于格式化字符串的问题,可以将不同语言的配置分别放在相应的文件(如 en-US.tszh-CN.ts)中,在组件中使用 FormattedMessage 组件进行使用:

    import { FormattedMessage } from 'umi';
    
    export default () => {
     return (
       <div>
         <FormattedMessage id="navbar.lang" />
       </div>
     );
    };

    这样,根据当前的语言设置,会自动显示对应的文本内容。

chenshuai2144 commented 9 months ago

他是跟着 antd 的 ConfigProvide 跑的,顺便包一个

InfernalAzazel commented 9 months ago

问题:纯粹 React 使用 SettingDrawer 国际化没有效果的问题如何解决?

解决方案:

  1. 首先,关闭 SettingDrawer 动态设置可以在 src/app.tsx 文件中将其删除。
  2. 关于时间类组件的国际化 locale 设置不生效的问题,请确保正确设置了 dayjs 语言包,并在代码中进行加载:

    import 'dayjs/locale/zh-cn';
    
    dayjs.locale('zh-cn');

    另外,还要检查是否有两个版本的 dayjs 共存,可以通过运行 npm ls dayjs 查看项目中依赖的 dayjs 版本。 如果项目中依赖的 dayjs 版本和 antd 依赖的 dayjs 版本无法兼容,会导致使用两个不同版本的 dayjs 实例,从而导致国际化失效。

  3. 如果想将组件的默认语言切回中文,可以尝试使用 ConfigProvider 组件来包裹应用,并在代码中配置 dayjs.locale('zh-cn')。 如果日期组件的国际化仍未生效,请检查本地的 dayjs 版本和 antd 依赖的 dayjs 版本是否一致。
  4. 关于为什么不直接使用 formatMessage 这个语法糖,虽然 formatMessage 使用方便,但是它脱离了 React 的生命周期, 最严重的问题是切换语言时无法触发 DOM 的重新渲染,从而导致用户体验较差。 推荐使用 useIntl 或者 injectIntl 来实现同样的功能。
  5. 最后,关于格式化字符串的问题,可以将不同语言的配置分别放在相应的文件(如 en-US.tszh-CN.ts)中,在组件中使用 FormattedMessage 组件进行使用:

    import { FormattedMessage } from 'umi';
    
    export default () => {
     return (
       <div>
         <FormattedMessage id="navbar.lang" />
       </div>
     );
    };

    这样,根据当前的语言设置,会自动显示对应的文本内容。

可以适应 react-i18next 国际化库吗?