diff --git a/packages/app/client/src/plugins/localization-management.ts b/packages/app/client/src/plugins/localization-management.ts
new file mode 100644
index 000000000..c22674881
--- /dev/null
+++ b/packages/app/client/src/plugins/localization-management.ts
@@ -0,0 +1 @@
+export { default } from '@nocobase/plugin-localization-management/client';
\ No newline at end of file
diff --git a/packages/core/auth/src/auth-manager.ts b/packages/core/auth/src/auth-manager.ts
index ac3c324e2..e028fba9e 100644
--- a/packages/core/auth/src/auth-manager.ts
+++ b/packages/core/auth/src/auth-manager.ts
@@ -98,7 +98,7 @@ export class AuthManager {
ctx.auth = authenticator;
} catch (err) {
ctx.auth = {} as Auth;
- ctx.app.logger.warn(`auth, ${err.message}, ${err.stack}`);
+ ctx.app.logger.warn(`auth, ${err.message}`);
return next();
}
if (authenticator) {
diff --git a/packages/core/client/src/acl/Configuration/MenuConfigure.tsx b/packages/core/client/src/acl/Configuration/MenuConfigure.tsx
index dcd35d459..4f64db6d6 100644
--- a/packages/core/client/src/acl/Configuration/MenuConfigure.tsx
+++ b/packages/core/client/src/acl/Configuration/MenuConfigure.tsx
@@ -86,6 +86,24 @@ export const MenuConfigure = () => {
}
message.success(t('Saved successfully'));
};
+
+ const translateTitle = (menus: any[]) => {
+ return menus.map((menu) => {
+ const title = t(menu.title);
+ if (menu.children) {
+ return {
+ ...menu,
+ title,
+ children: translateTitle(menu.children),
+ };
+ }
+ return {
+ ...menu,
+ title,
+ };
+ });
+ };
+
return (
{
},
},
]}
- dataSource={items}
+ dataSource={translateTitle(items)}
/>
);
};
diff --git a/packages/core/client/src/auth/SigninPage.tsx b/packages/core/client/src/auth/SigninPage.tsx
index ccb32e0f2..264bec23e 100644
--- a/packages/core/client/src/auth/SigninPage.tsx
+++ b/packages/core/client/src/auth/SigninPage.tsx
@@ -134,7 +134,7 @@ export const SigninPage = () => {
`}
>
{tabs.length > 1 ? (
- ({ label: tab.tabTitle, key: tab.name, children: tab.component }))} />
+ ({ label: t(tab.tabTitle), key: tab.name, children: tab.component }))} />
) : tabs.length ? (
{tabs[0].component}
) : (
diff --git a/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx b/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx
index a8164e52e..098f8a9dc 100644
--- a/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx
+++ b/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx
@@ -1,6 +1,7 @@
import { Spin } from 'antd';
import { keyBy } from 'lodash';
import React, { useContext, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { useAPIClient, useRequest } from '../api-client';
import { templateOptions } from '../collection-manager/Configuration/templates';
import { useCollectionHistory } from './CollectionHistoryProvider';
@@ -31,6 +32,7 @@ export const CollectionManagerProvider: React.FC = (pr
};
export const RemoteCollectionManagerProvider = (props: any) => {
+ const { t } = useTranslation();
const api = useAPIClient();
const [contentLoading, setContentLoading] = useState(false);
const { refreshCH } = useCollectionHistory();
@@ -85,11 +87,32 @@ export const RemoteCollectionManagerProvider = (props: any) => {
service.mutate({ data: collection });
};
+ const collections = (service?.data?.data || []).map(({ rawTitle, title, fields, ...collection }) => ({
+ ...collection,
+ title: rawTitle ? title : t(title),
+ rawTitle: rawTitle || title,
+ fields: fields.map(({ uiSchema, ...field }) => {
+ if (uiSchema?.title) {
+ const title = uiSchema.title;
+ uiSchema.title = uiSchema.rawTitle ? title : t(title);
+ uiSchema.rawTitle = uiSchema.rawTitle || title;
+ }
+ if (uiSchema?.enum) {
+ uiSchema.enum = uiSchema.enum.map((item) => ({
+ ...item,
+ label: item.rawLabel ? item.label : t(item.label),
+ rawLabel: item.rawLabel || item.label,
+ }));
+ }
+ return { uiSchema, ...field };
+ }),
+ }));
+
return (
{
const columns: TableColumnProps[] = [
{
- dataIndex: ['uiSchema', 'title'],
+ dataIndex: ['uiSchema', 'rawTitle'],
title: t('Field display name'),
render: (value) => {compile(value)}
,
},
@@ -177,7 +177,7 @@ const InheritFields = (props) => {
const columns: TableColumnProps[] = [
{
- dataIndex: ['uiSchema', 'title'],
+ dataIndex: ['uiSchema', 'rawTitle'],
title: t('Field display name'),
render: (value) => {compile(value)}
,
},
diff --git a/packages/core/client/src/collection-manager/Configuration/ConfigurationTable.tsx b/packages/core/client/src/collection-manager/Configuration/ConfigurationTable.tsx
index ee7cb8208..88318a493 100644
--- a/packages/core/client/src/collection-manager/Configuration/ConfigurationTable.tsx
+++ b/packages/core/client/src/collection-manager/Configuration/ConfigurationTable.tsx
@@ -2,6 +2,7 @@ import { useForm } from '@formily/react';
import { action } from '@formily/reactive';
import { uid } from '@formily/shared';
import React, { useContext, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { CollectionFieldsTable } from '.';
import { useAPIClient } from '../../api-client';
import { useCurrentAppInfo } from '../../appInfo';
@@ -81,6 +82,7 @@ const useNewId = (prefix) => {
};
export const ConfigurationTable = () => {
+ const { t } = useTranslation();
const { collections = [], interfaces } = useCollectionManager();
const {
data: { database },
@@ -149,7 +151,7 @@ export const ConfigurationTable = () => {
.then(({ data }) => {
return data?.data?.map((item: any) => {
return {
- label: compile(item.title),
+ label: t(compile(item.title)),
value: item.name,
};
});
diff --git a/packages/core/client/src/collection-manager/Configuration/ConfigurationTabs.tsx b/packages/core/client/src/collection-manager/Configuration/ConfigurationTabs.tsx
index 938d67f1a..35de41ad8 100644
--- a/packages/core/client/src/collection-manager/Configuration/ConfigurationTabs.tsx
+++ b/packages/core/client/src/collection-manager/Configuration/ConfigurationTabs.tsx
@@ -14,6 +14,7 @@ import { uid } from '@formily/shared';
import { App, Badge, Card, Dropdown, Tabs } from 'antd';
import _ from 'lodash';
import React, { useContext, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../../api-client';
import { SchemaComponent, SchemaComponentOptions, useCompile } from '../../schema-component';
import { useResourceActionContext } from '../ResourceActionProvider';
@@ -67,11 +68,12 @@ const TabTitle = observer(
);
const TabBar = ({ item }) => {
+ const { t } = useTranslation();
const compile = useCompile();
return (
- {compile(item.name)}
+ {t(compile(item.name))}
);
};
@@ -118,6 +120,7 @@ const DndProvider = observer(
{ displayName: 'DndProvider' },
);
export const ConfigurationTabs = () => {
+ const { t } = useTranslation();
const { data, refresh } = useContext(CollectionCategroriesContext);
const { refresh: refreshCM, run, defaultRequest, setState } = useResourceActionContext();
const [key, setKey] = useState('all');
@@ -179,7 +182,7 @@ export const ConfigurationTabs = () => {
const loadCategories = async () => {
return data.map((item: any) => ({
- label: compile(item.name),
+ label: t(compile(item.name)),
value: item.id,
}));
};
diff --git a/packages/core/client/src/collection-manager/interfaces/markdown.ts b/packages/core/client/src/collection-manager/interfaces/markdown.ts
index 43bf92a9f..918c4fbd6 100644
--- a/packages/core/client/src/collection-manager/interfaces/markdown.ts
+++ b/packages/core/client/src/collection-manager/interfaces/markdown.ts
@@ -1,12 +1,12 @@
import { ISchema } from '@formily/react';
+import { i18n } from '../../i18n';
import { defaultProps } from './properties';
import { IField } from './types';
-import { i18n } from '../../i18n';
export const markdown: IField = {
name: 'markdown',
type: 'object',
- title: 'Markdown',
+ title: '{{t("Markdown")}}',
group: 'media',
default: {
type: 'text',
diff --git a/packages/core/client/src/document-title/index.tsx b/packages/core/client/src/document-title/index.tsx
index 9678df749..65ced248e 100644
--- a/packages/core/client/src/document-title/index.tsx
+++ b/packages/core/client/src/document-title/index.tsx
@@ -1,5 +1,6 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
import { Plugin } from '../application/Plugin';
import { useSystemSettings } from '../system-settings';
@@ -15,9 +16,10 @@ export const DocumentTitleContext = createContext({
export const DocumentTitleProvider: React.FC<{ addonBefore?: string; addonAfter?: string }> = (props) => {
const { addonBefore, addonAfter } = props;
+ const { t } = useTranslation();
const [title, setTitle] = useState('');
- const documentTitle = `${addonBefore ? ` - ${addonBefore}` : ''}${title || ''}${
- addonAfter ? ` - ${addonAfter}` : ''
+ const documentTitle = `${addonBefore ? ` - ${t(addonBefore)}` : ''}${t(title || '')}${
+ addonAfter ? ` - ${t(addonAfter)}` : ''
}`;
return (
= ({ app, error }) => (
);
export class NocoBaseBuildInPlugin extends Plugin {
- async afterAdd(): Promise {
+ async afterAdd() {
this.app.addComponents({
AppSpin,
AppError,
});
this.addPlugins();
}
+
async load() {
this.addComponents();
this.addRoutes();
@@ -62,6 +63,7 @@ export class NocoBaseBuildInPlugin extends Plugin {
this.app.use(CSSVariableProvider);
this.app.use(CurrentUserSettingsMenuProvider);
}
+
addRoutes() {
this.router.add('root', {
path: '/',
@@ -102,8 +104,8 @@ export class NocoBaseBuildInPlugin extends Plugin {
});
}
addPlugins() {
+ this.app.pm.add(LocalePlugin, { name: 'locale' });
this.app.pm.add(AdminLayoutPlugin, { name: 'admin-layout' });
- this.app.pm.add(AntdConfigPlugin, { name: 'antd-config', config: { remoteLocale: true } });
this.app.pm.add(SystemSettingsPlugin, { name: 'system-setting' });
this.app.pm.add(PinnedListPlugin, {
name: 'pinned-list',
diff --git a/packages/core/client/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts b/packages/core/client/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts
new file mode 100644
index 000000000..a5baafacf
--- /dev/null
+++ b/packages/core/client/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts
@@ -0,0 +1,35 @@
+import { dayjs } from '@nocobase/utils/client';
+import { ConfigProvider } from 'antd';
+import { loadConstrueLocale } from '../../antd-config-provider/loadConstrueLocale';
+import { Plugin } from '../../application/Plugin';
+
+export class LocalePlugin extends Plugin {
+ locales: any = {};
+ async afterAdd() {
+ const api = this.app.apiClient;
+ const locale = api.auth.locale;
+ try {
+ const { data } = await api.request({
+ url: 'app:getLang',
+ params: {
+ locale,
+ },
+ });
+ this.locales = data?.data || {};
+ this.app.use(ConfigProvider, { locale: this.locales.antd, popupMatchSelectWidth: false });
+ if (data?.data?.lang && !locale) {
+ api.auth.setLocale(data?.data?.lang);
+ this.app.i18n.changeLanguage(data?.data?.lang);
+ }
+ Object.keys(data?.data?.resources || {}).forEach((key) => {
+ this.app.i18n.addResources(data?.data?.lang, key, data?.data?.resources[key] || {});
+ });
+ loadConstrueLocale(data?.data);
+ dayjs.locale(data?.data?.moment);
+ window['cronLocale'] = data?.data?.cron;
+ } catch (error) {
+ (() => {})();
+ throw error;
+ }
+ }
+}
diff --git a/packages/core/client/src/schema-component/antd/action/Action.tsx b/packages/core/client/src/schema-component/antd/action/Action.tsx
index 4a14657fb..22359c5b9 100644
--- a/packages/core/client/src/schema-component/antd/action/Action.tsx
+++ b/packages/core/client/src/schema-component/antd/action/Action.tsx
@@ -3,6 +3,7 @@ import { observer, RecursionField, useField, useFieldSchema, useForm } from '@fo
import { App, Button, Popover } from 'antd';
import classnames from 'classnames';
import React, { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { useActionContext } from '../..';
import { useDesignable } from '../../';
import { Icon } from '../../../icon';
@@ -79,6 +80,7 @@ export const Action: ComposedAction = observer(
title,
...others
} = props;
+ const { t } = useTranslation();
const { onClick } = useProps(props);
const [visible, setVisible] = useState(false);
const [formValueChanged, setFormValueChanged] = useState(false);
@@ -146,7 +148,7 @@ export const Action: ComposedAction = observer(
className={classnames(actionDesignerCss, className)}
type={props.type === 'danger' ? undefined : props.type}
>
- {title || compile(fieldSchema.title)}
+ {t(title || compile(fieldSchema.title))}
);
diff --git a/packages/core/client/src/schema-component/antd/form-v2/Templates.tsx b/packages/core/client/src/schema-component/antd/form-v2/Templates.tsx
index 5b109d06e..479d63562 100644
--- a/packages/core/client/src/schema-component/antd/form-v2/Templates.tsx
+++ b/packages/core/client/src/schema-component/antd/form-v2/Templates.tsx
@@ -63,7 +63,7 @@ const useDataTemplates = () => {
key: 'none',
title: t('None'),
},
- ].concat(items.map((t, i) => ({ key: i, ...t })));
+ ].concat(items.map((item, i) => ({ key: i, ...item })));
const defaultTemplate = items.find((item) => item.default);
return {
diff --git a/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx b/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx
index c3397f0e0..9f135b83e 100644
--- a/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx
+++ b/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx
@@ -47,6 +47,16 @@ const InsertMenuItems = (props) => {
if (!isSubMenu && insertPosition === 'beforeEnd') {
return null;
}
+ const serverHooks = [
+ {
+ type: 'onSelfCreate',
+ method: 'bindMenuToRole',
+ },
+ {
+ type: 'onSelfSave',
+ method: 'extractTextToLocale',
+ },
+ ];
return (
{
'x-component-props': {
icon,
},
- 'x-server-hooks': [
- {
- type: 'onSelfCreate',
- method: 'bindMenuToRole',
- },
- ],
+ 'x-server-hooks': serverHooks,
});
}}
/>
@@ -123,12 +128,7 @@ const InsertMenuItems = (props) => {
'x-component-props': {
icon,
},
- 'x-server-hooks': [
- {
- type: 'onSelfCreate',
- method: 'bindMenuToRole',
- },
- ],
+ 'x-server-hooks': serverHooks,
properties: {
page: {
type: 'void',
@@ -184,12 +184,7 @@ const InsertMenuItems = (props) => {
icon,
href,
},
- 'x-server-hooks': [
- {
- type: 'onSelfCreate',
- method: 'bindMenuToRole',
- },
- ],
+ 'x-server-hooks': serverHooks,
});
}}
/>
@@ -263,6 +258,12 @@ export const MenuDesigner = () => {
onSubmit={({ title, icon, href }) => {
const schema = {
['x-uid']: fieldSchema['x-uid'],
+ 'x-server-hooks': [
+ {
+ type: 'onSelfSave',
+ method: 'extractTextToLocale',
+ },
+ ],
};
if (title) {
fieldSchema.title = title;
diff --git a/packages/core/client/src/schema-component/antd/menu/Menu.tsx b/packages/core/client/src/schema-component/antd/menu/Menu.tsx
index 4ea80293c..a11fde6c7 100644
--- a/packages/core/client/src/schema-component/antd/menu/Menu.tsx
+++ b/packages/core/client/src/schema-component/antd/menu/Menu.tsx
@@ -463,6 +463,7 @@ export const Menu: ComposedMenu = observer(
Menu.Item = observer(
(props) => {
+ const { t } = useTranslation();
const { pushMenuItem } = useCollectMenuItems();
const { icon, children, ...others } = props;
const schema = useFieldSchema();
@@ -489,7 +490,7 @@ Menu.Item = observer(
verticalAlign: 'middle',
}}
>
- {field.title}
+ {t(field.title)}
@@ -512,6 +513,7 @@ Menu.Item = observer(
Menu.URL = observer(
(props) => {
+ const { t } = useTranslation();
const { pushMenuItem } = useCollectMenuItems();
const { icon, children, ...others } = props;
const schema = useFieldSchema();
@@ -547,7 +549,7 @@ Menu.URL = observer(
verticalAlign: 'middle',
}}
>
- {field.title}
+ {t(field.title)}
@@ -565,6 +567,7 @@ Menu.URL = observer(
Menu.SubMenu = observer(
(props) => {
+ const { t } = useTranslation();
const { Component, getMenuItems } = useMenuItem();
const { pushMenuItem } = useCollectMenuItems();
const { icon, children, ...others } = props;
@@ -583,7 +586,7 @@ Menu.SubMenu = observer(
- {field.title}
+ {t(field.title)}
diff --git a/packages/core/client/src/schema-component/antd/menu/MenuItemInitializers/index.tsx b/packages/core/client/src/schema-component/antd/menu/MenuItemInitializers/index.tsx
index 062dfa21c..1005ab8a5 100644
--- a/packages/core/client/src/schema-component/antd/menu/MenuItemInitializers/index.tsx
+++ b/packages/core/client/src/schema-component/antd/menu/MenuItemInitializers/index.tsx
@@ -93,6 +93,10 @@ export const GroupItem = itemWrap((props) => {
type: 'onSelfCreate',
method: 'bindMenuToRole',
},
+ {
+ type: 'onSelfSave',
+ method: 'extractTextToLocale',
+ },
],
});
}, [theme]);
@@ -152,6 +156,10 @@ export const PageMenuItem = itemWrap((props) => {
type: 'onSelfCreate',
method: 'bindMenuToRole',
},
+ {
+ type: 'onSelfSave',
+ method: 'extractTextToLocale',
+ },
],
properties: {
page: {
@@ -232,6 +240,10 @@ export const LinkMenuItem = itemWrap((props) => {
type: 'onSelfCreate',
method: 'bindMenuToRole',
},
+ {
+ type: 'onSelfSave',
+ method: 'extractTextToLocale',
+ },
],
});
}, [theme]);
diff --git a/packages/core/client/src/schema-component/antd/page/Page.tsx b/packages/core/client/src/schema-component/antd/page/Page.tsx
index ed9193bf2..b8ad9457c 100644
--- a/packages/core/client/src/schema-component/antd/page/Page.tsx
+++ b/packages/core/client/src/schema-component/antd/page/Page.tsx
@@ -25,6 +25,7 @@ import { useStyles } from './style';
export const Page = (props) => {
const { children, ...others } = props;
+ const { t } = useTranslation();
const compile = useCompile();
const { title, setTitle } = useDocumentTitle();
const fieldSchema = useFieldSchema();
@@ -41,13 +42,12 @@ export const Page = (props) => {
useEffect(() => {
if (!title) {
- setTitle(fieldSchema.title);
+ setTitle(t(fieldSchema.title));
}
}, [fieldSchema.title, title]);
const disablePageHeader = fieldSchema['x-component-props']?.disablePageHeader;
const enablePageTabs = fieldSchema['x-component-props']?.enablePageTabs;
const hidePageTitle = fieldSchema['x-component-props']?.hidePageTitle;
- const { t } = useTranslation();
const options = useContext(SchemaOptionsContext);
const [searchParams, setSearchParams] = useSearchParams();
const [loading, setLoading] = useState(false);
@@ -77,7 +77,7 @@ export const Page = (props) => {
className={classNames('pageHeaderCss', pageHeaderTitle || enablePageTabs ? '' : 'height0')}
ghost={false}
// 如果标题为空的时候会导致 PageHeader 不渲染,所以这里设置一个空白字符,然后再设置高度为 0
- title={pageHeaderTitle || ' '}
+ title={t(pageHeaderTitle || ' ')}
{...others}
footer={
enablePageTabs && (
diff --git a/packages/core/client/src/schema-component/antd/tabs/Tabs.tsx b/packages/core/client/src/schema-component/antd/tabs/Tabs.tsx
index c8bc35d03..5aaab19bc 100644
--- a/packages/core/client/src/schema-component/antd/tabs/Tabs.tsx
+++ b/packages/core/client/src/schema-component/antd/tabs/Tabs.tsx
@@ -1,8 +1,9 @@
import { css } from '@emotion/css';
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
-import { Tabs as AntdTabs, TabPaneProps, TabsProps } from 'antd';
+import { TabPaneProps, Tabs as AntdTabs, TabsProps } from 'antd';
import classNames from 'classnames';
import React, { useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
import { Icon } from '../../../icon';
import { useSchemaInitializer } from '../../../schema-initializer';
import { DndContext, SortableItem } from '../../common';
diff --git a/packages/core/database/src/collection.ts b/packages/core/database/src/collection.ts
index 134bf760a..5afcb6256 100644
--- a/packages/core/database/src/collection.ts
+++ b/packages/core/database/src/collection.ts
@@ -1,6 +1,6 @@
import merge from 'deepmerge';
import { EventEmitter } from 'events';
-import { default as lodash, default as _ } from 'lodash';
+import { default as _, default as lodash } from 'lodash';
import {
ModelOptions,
ModelStatic,
@@ -399,7 +399,6 @@ export class Collection<
updateOptions(options: CollectionOptions, mergeOptions?: any) {
let newOptions = lodash.cloneDeep(options);
newOptions = merge(this.options, newOptions, mergeOptions);
-
this.context.database.emit('beforeUpdateCollection', this, newOptions);
this.options = newOptions;
@@ -409,7 +408,6 @@ export class Collection<
}
this.context.database.emit('afterUpdateCollection', this);
-
return this;
}
diff --git a/packages/core/database/src/fields/field.ts b/packages/core/database/src/fields/field.ts
index 58a5fb677..891afb920 100644
--- a/packages/core/database/src/fields/field.ts
+++ b/packages/core/database/src/fields/field.ts
@@ -1,5 +1,4 @@
import _ from 'lodash';
-
import {
DataType,
ModelAttributeColumnOptions,
@@ -22,6 +21,7 @@ export interface FieldContext {
export interface BaseFieldOptions {
name?: string;
hidden?: boolean;
+ translation?: boolean;
[key: string]: any;
}
diff --git a/packages/plugins/acl/src/server/collections/roles.ts b/packages/plugins/acl/src/server/collections/roles.ts
index 8e1b67fcd..e48ee4297 100644
--- a/packages/plugins/acl/src/server/collections/roles.ts
+++ b/packages/plugins/acl/src/server/collections/roles.ts
@@ -36,6 +36,7 @@ export default {
title: '{{t("Role name")}}',
'x-component': 'Input',
},
+ translation: true,
},
{
type: 'boolean',
@@ -85,5 +86,16 @@ export default {
name: 'snippets',
defaultValue: ['!ui.*', '!pm', '!pm.*'],
},
+ {
+ type: 'belongsToMany',
+ name: 'users',
+ target: 'users',
+ foreignKey: 'roleName',
+ otherKey: 'userId',
+ onDelete: 'CASCADE',
+ sourceKey: 'name',
+ targetKey: 'id',
+ through: 'rolesUsers',
+ },
],
} as CollectionOptions;
diff --git a/packages/plugins/api-keys/package.json b/packages/plugins/api-keys/package.json
index 5e058d51b..5edac1980 100644
--- a/packages/plugins/api-keys/package.json
+++ b/packages/plugins/api-keys/package.json
@@ -36,4 +36,4 @@
"react-i18next": "^11.15.1"
},
"gitHead": "ce588eefb0bfc50f7d5bbee575e0b5e843bf6644"
-}
+}
\ No newline at end of file
diff --git a/packages/plugins/auth/src/server/actions/authenticators.ts b/packages/plugins/auth/src/server/actions/authenticators.ts
index c1f384ab5..73d18b4e1 100644
--- a/packages/plugins/auth/src/server/actions/authenticators.ts
+++ b/packages/plugins/auth/src/server/actions/authenticators.ts
@@ -1,8 +1,8 @@
import { Context, Next } from '@nocobase/actions';
-import { namespace } from '../../preset';
import { Model, Repository } from '@nocobase/database';
+import { namespace } from '../../preset';
-async function checkCount(repository: Repository, id: number) {
+async function checkCount(repository: Repository, id: number[]) {
// TODO(yangqia): This is a temporary solution, may cause concurrency problem.
const count = await repository.count({
filter: {
diff --git a/packages/plugins/auth/src/server/collections/authenticators.ts b/packages/plugins/auth/src/server/collections/authenticators.ts
index da968a463..64a6e3b52 100644
--- a/packages/plugins/auth/src/server/collections/authenticators.ts
+++ b/packages/plugins/auth/src/server/collections/authenticators.ts
@@ -56,6 +56,7 @@ export default {
title: '{{t("Title")}}',
'x-component': 'Input',
},
+ translation: true,
},
{
interface: 'textarea',
diff --git a/packages/plugins/client/src/index.ts b/packages/plugins/client/src/index.ts
index 7ddad5814..ce9f71d9f 100644
--- a/packages/plugins/client/src/index.ts
+++ b/packages/plugins/client/src/index.ts
@@ -1 +1 @@
-export { default } from './server';
+export { default, getResourceLocale } from './server';
diff --git a/packages/plugins/client/src/server/index.ts b/packages/plugins/client/src/server/index.ts
index 7ddad5814..1d7c99f42 100644
--- a/packages/plugins/client/src/server/index.ts
+++ b/packages/plugins/client/src/server/index.ts
@@ -1 +1,2 @@
+export { getResourceLocale } from './resource';
export { default } from './server';
diff --git a/packages/plugins/collection-manager/src/server/collections/collectionCategories.ts b/packages/plugins/collection-manager/src/server/collections/collectionCategories.ts
index 0e17cf091..8fad4a1a8 100644
--- a/packages/plugins/collection-manager/src/server/collections/collectionCategories.ts
+++ b/packages/plugins/collection-manager/src/server/collections/collectionCategories.ts
@@ -13,6 +13,7 @@ export default {
{
type: 'string',
name: 'name',
+ translation: true,
},
{
type: 'string',
diff --git a/packages/plugins/collection-manager/src/server/collections/collections.ts b/packages/plugins/collection-manager/src/server/collections/collections.ts
index 698326454..7693fd35b 100644
--- a/packages/plugins/collection-manager/src/server/collections/collections.ts
+++ b/packages/plugins/collection-manager/src/server/collections/collections.ts
@@ -27,6 +27,7 @@ export default {
type: 'string',
name: 'title',
required: true,
+ translation: true,
},
{
type: 'boolean',
diff --git a/packages/plugins/collection-manager/src/server/collections/fields.ts b/packages/plugins/collection-manager/src/server/collections/fields.ts
index 7277221e5..feebbe039 100644
--- a/packages/plugins/collection-manager/src/server/collections/fields.ts
+++ b/packages/plugins/collection-manager/src/server/collections/fields.ts
@@ -63,6 +63,7 @@ export default {
type: 'json',
name: 'options',
defaultValue: {},
+ translation: true,
},
],
} as CollectionOptions;
diff --git a/packages/plugins/data-visualization/src/client/block/schemas/configure.ts b/packages/plugins/data-visualization/src/client/block/schemas/configure.ts
index 8c060664d..8df16f65c 100644
--- a/packages/plugins/data-visualization/src/client/block/schemas/configure.ts
+++ b/packages/plugins/data-visualization/src/client/block/schemas/configure.ts
@@ -154,7 +154,7 @@ export const querySchema: ISchema = {
'x-component-props': {
options: '{{ collectionOptions }}',
onChange: '{{ onCollectionChange }}',
- placeholder: lang('Collection'),
+ placeholder: '{{t("Collection")}}',
},
},
},
@@ -206,11 +206,11 @@ export const querySchema: ISchema = {
placeholder: '{{t("Aggregation")}}',
},
enum: [
- { label: lang('Sum'), value: 'sum' },
- { label: lang('Count'), value: 'count' },
- { label: lang('Avg'), value: 'avg' },
- { label: lang('Max'), value: 'max' },
- { label: lang('Min'), value: 'min' },
+ { label: '{{t("Sum")}}', value: 'sum' },
+ { label: '{{t("Count")}}', value: 'count' },
+ { label: '{{t("Avg")}}', value: 'avg' },
+ { label: '{{t("Max")}}', value: 'max' },
+ { label: '{{t("Min")}}', value: 'min' },
],
},
alias: {
diff --git a/packages/plugins/data-visualization/src/client/index.tsx b/packages/plugins/data-visualization/src/client/index.tsx
index d690529d5..2eec05f13 100644
--- a/packages/plugins/data-visualization/src/client/index.tsx
+++ b/packages/plugins/data-visualization/src/client/index.tsx
@@ -14,7 +14,7 @@ const Chart: React.FC = (props) => {
children.push({
key: 'chart-v2',
type: 'item',
- title: t('Chart'),
+ title: t('Charts'),
component: 'ChartV2BlockInitializer',
});
}
diff --git a/packages/plugins/data-visualization/src/client/locale/index.ts b/packages/plugins/data-visualization/src/client/locale/index.ts
index 428b9d68f..1ab22a13d 100644
--- a/packages/plugins/data-visualization/src/client/locale/index.ts
+++ b/packages/plugins/data-visualization/src/client/locale/index.ts
@@ -1,10 +1,9 @@
import { i18n } from '@nocobase/client';
import { useTranslation } from 'react-i18next';
-import zhCN from './zh-CN';
-export const NAMESPACE = 'charts-v2';
+export const NAMESPACE = 'data-visualization';
-i18n.addResources('zh-CN', NAMESPACE, zhCN);
+// i18n.addResources('zh-CN', NAMESPACE, zhCN);
// i18n.addResources('en-US', NAMESPACE, enUS);
// i18n.addResources('ja-JP', NAMESPACE, jaJP);
// i18n.addResources('ru-RU', NAMESPACE, ruRU);
diff --git a/packages/plugins/data-visualization/src/client/locale/zh-CN.ts b/packages/plugins/data-visualization/src/client/locale/zh-CN.ts
index 6bb209cab..462bdd8ae 100644
--- a/packages/plugins/data-visualization/src/client/locale/zh-CN.ts
+++ b/packages/plugins/data-visualization/src/client/locale/zh-CN.ts
@@ -68,4 +68,5 @@ export default {
Min: '最小值',
Max: '最大值',
'Please select a chart type.': '请选择图表类型',
+ Collection: '数据表',
};
diff --git a/packages/plugins/data-visualization/src/client/renderer/ChartLibrary.tsx b/packages/plugins/data-visualization/src/client/renderer/ChartLibrary.tsx
index e420fccbc..54a9cf844 100644
--- a/packages/plugins/data-visualization/src/client/renderer/ChartLibrary.tsx
+++ b/packages/plugins/data-visualization/src/client/renderer/ChartLibrary.tsx
@@ -77,7 +77,7 @@ export const useChartTypes = (): {
const children = Object.entries(l.charts).map(([type, chart]) => ({
...chart,
key: type,
- label: chart.name,
+ label: lang(chart.name),
value: type,
}));
return [
diff --git a/packages/plugins/data-visualization/src/client/renderer/library/G2PlotLibrary.tsx b/packages/plugins/data-visualization/src/client/renderer/library/G2PlotLibrary.tsx
index bf8aaadfa..28ceab0b1 100644
--- a/packages/plugins/data-visualization/src/client/renderer/library/G2PlotLibrary.tsx
+++ b/packages/plugins/data-visualization/src/client/renderer/library/G2PlotLibrary.tsx
@@ -1,5 +1,4 @@
import { Area, Bar, Column, DualAxes, Line, Pie, Scatter } from '@ant-design/plots';
-import { lang } from '../../locale';
import { Charts, commonInit, infer, usePropsFunc } from '../ChartLibrary';
const init = commonInit;
@@ -7,7 +6,7 @@ const basicSchema = {
type: 'object',
properties: {
xField: {
- title: lang('xField'),
+ title: '{{t("xField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
@@ -15,7 +14,7 @@ const basicSchema = {
required: true,
},
yField: {
- title: lang('yField'),
+ title: '{{t("yField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
@@ -23,7 +22,7 @@ const basicSchema = {
required: true,
},
seriesField: {
- title: lang('seriesField'),
+ title: '{{t("seriesField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
@@ -50,40 +49,40 @@ const useProps: usePropsFunc = ({ data, fieldProps, general, advanced }) => {
export const G2PlotLibrary: Charts = {
line: {
- name: lang('Line Chart'),
+ name: 'Line Chart',
component: Line,
schema: basicSchema,
init,
useProps,
reference: {
- title: lang('Line Chart'),
+ title: 'Line Chart',
link: 'https://g2plot.antv.antgroup.com/api/plots/bar',
},
},
area: {
- name: lang('Area Chart'),
+ name: 'Area Chart',
component: Area,
schema: basicSchema,
init,
useProps,
reference: {
- title: lang('Area Chart'),
+ title: 'Area Chart',
link: 'https://g2plot.antv.antgroup.com/api/plots/area',
},
},
column: {
- name: lang('Column Chart'),
+ name: 'Column Chart',
component: Column,
schema: basicSchema,
init,
useProps,
reference: {
- title: lang('Column Chart'),
+ title: 'Column Chart',
link: 'https://g2plot.antv.antgroup.com/api/plots/column',
},
},
bar: {
- name: lang('Bar Chart'),
+ name: 'Bar Chart',
component: Bar,
schema: basicSchema,
init: (fields, { measures, dimensions }) => {
@@ -98,18 +97,18 @@ export const G2PlotLibrary: Charts = {
},
useProps,
reference: {
- title: lang('Bar Chart'),
+ title: 'Bar Chart',
link: 'https://g2plot.antv.antgroup.com/api/plots/bar',
},
},
pie: {
- name: lang('Pie Chart'),
+ name: 'Pie Chart',
component: Pie,
schema: {
type: 'object',
properties: {
angleField: {
- title: lang('angleField'),
+ title: '{{t("angleField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
@@ -117,7 +116,7 @@ export const G2PlotLibrary: Charts = {
required: true,
},
colorField: {
- title: lang('colorField'),
+ title: '{{t("colorField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
@@ -137,12 +136,12 @@ export const G2PlotLibrary: Charts = {
},
useProps,
reference: {
- title: lang('Pie Chart'),
+ title: 'Pie Chart',
link: 'https://g2plot.antv.antgroup.com/api/plots/pie',
},
},
dualAxes: {
- name: lang('Dual Axes Chart'),
+ name: 'Dual Axes Chart',
component: DualAxes,
useProps: ({ data, fieldProps, general, advanced }) => {
return {
@@ -154,7 +153,7 @@ export const G2PlotLibrary: Charts = {
type: 'object',
properties: {
xField: {
- title: lang('xField'),
+ title: '{{t("xField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
@@ -162,7 +161,7 @@ export const G2PlotLibrary: Charts = {
required: true,
},
yField: {
- title: lang('yField'),
+ title: '{{t("yField")}}',
type: 'array',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems',
@@ -197,7 +196,7 @@ export const G2PlotLibrary: Charts = {
properties: {
add: {
type: 'void',
- title: lang('Add'),
+ title: '{{t("Add")}}',
'x-component': 'ArrayItems.Addition',
},
},
@@ -214,22 +213,22 @@ export const G2PlotLibrary: Charts = {
};
},
reference: {
- title: lang('Dual Axes Chart'),
+ title: 'Dual Axes Chart',
link: 'https://g2plot.antv.antgroup.com/api/plots/dual-axes',
},
},
// gauge: {
- // name: lang('Gauge Chart'),
+ // name: 'Gauge Chart',
// component: Gauge,
// },
scatter: {
- name: lang('Scatter Chart'),
+ name: 'Scatter Chart',
component: Scatter,
schema: basicSchema,
init,
useProps,
reference: {
- title: lang('Scatter Chart'),
+ title: 'Scatter Chart',
link: 'https://g2plot.antv.antgroup.com/api/plots/scatter',
},
},
diff --git a/packages/plugins/file-manager/src/server/collections/storages.ts b/packages/plugins/file-manager/src/server/collections/storages.ts
index 836094118..49ef011a3 100644
--- a/packages/plugins/file-manager/src/server/collections/storages.ts
+++ b/packages/plugins/file-manager/src/server/collections/storages.ts
@@ -11,6 +11,7 @@ export default {
comment: '存储引擎名称',
type: 'string',
name: 'title',
+ translation: true,
},
{
title: '英文标识',
diff --git a/packages/plugins/localization-management/README.md b/packages/plugins/localization-management/README.md
new file mode 100644
index 000000000..07f5ef46c
--- /dev/null
+++ b/packages/plugins/localization-management/README.md
@@ -0,0 +1,35 @@
+# Localization Management
+
+支持管理应用程序的多语言资源。
+
+## 使用方法
+
+### 在`系统设置`中添加对应语言
+
+
+
+### 切换到对应语言
+
+
+
+### 管理多语言资源
+
+1. 同步需要翻译的原文内容
+
+
+
+- 目前支持的内容
+ - 菜单
+ - 系统和插件提供的语言包
+ - 数据表名、字段名、字段选项标签
+
+> **Note**
+> 新增菜单、数据表名、字段名、字段选项标签会自动同步
+> 已有内容需要点击同步按钮手动同步
+
+2. 编辑翻译内容,点击`发布`按钮即可生效
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/plugins/localization-management/client.d.ts b/packages/plugins/localization-management/client.d.ts
new file mode 100755
index 000000000..bd53a2f77
--- /dev/null
+++ b/packages/plugins/localization-management/client.d.ts
@@ -0,0 +1,3 @@
+// @ts-nocheck
+export * from './lib/client';
+export { default } from './lib/client';
diff --git a/packages/plugins/localization-management/client.js b/packages/plugins/localization-management/client.js
new file mode 100755
index 000000000..c83e7e450
--- /dev/null
+++ b/packages/plugins/localization-management/client.js
@@ -0,0 +1,65 @@
+'use strict';
+
+function _getRequireWildcardCache(nodeInterop) {
+ if (typeof WeakMap !== 'function') return null;
+ var cacheBabelInterop = new WeakMap();
+ var cacheNodeInterop = new WeakMap();
+ return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) {
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
+ })(nodeInterop);
+}
+
+function _interopRequireWildcard(obj, nodeInterop) {
+ if (!nodeInterop && obj && obj.__esModule) {
+ return obj;
+ }
+ if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
+ return { default: obj };
+ }
+ var cache = _getRequireWildcardCache(nodeInterop);
+ if (cache && cache.has(obj)) {
+ return cache.get(obj);
+ }
+ var newObj = {};
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
+ for (var key in obj) {
+ if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
+ if (desc && (desc.get || desc.set)) {
+ Object.defineProperty(newObj, key, desc);
+ } else {
+ newObj[key] = obj[key];
+ }
+ }
+ }
+ newObj.default = obj;
+ if (cache) {
+ cache.set(obj, newObj);
+ }
+ return newObj;
+}
+
+var _index = _interopRequireWildcard(require('./lib/client'));
+
+Object.defineProperty(exports, '__esModule', {
+ value: true,
+});
+var _exportNames = {};
+Object.defineProperty(exports, 'default', {
+ enumerable: true,
+ get: function get() {
+ return _index.default;
+ },
+});
+
+Object.keys(_index).forEach(function (key) {
+ if (key === 'default' || key === '__esModule') return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _index[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function get() {
+ return _index[key];
+ },
+ });
+});
diff --git a/packages/plugins/localization-management/package.json b/packages/plugins/localization-management/package.json
new file mode 100644
index 000000000..e49d7a74d
--- /dev/null
+++ b/packages/plugins/localization-management/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@nocobase/plugin-localization-management",
+ "version": "0.11.0-alpha.1",
+ "main": "lib/server/index.js",
+ "devDependencies": {
+ "@nocobase/cache": "0.11.0-alpha.1",
+ "@nocobase/client": "0.11.0-alpha.1",
+ "@nocobase/database": "0.11.0-alpha.1",
+ "@nocobase/server": "0.11.0-alpha.1",
+ "@nocobase/test": "0.11.0-alpha.1",
+ "@nocobase/plugin-client": "0.11.0-alpha.1",
+ "@nocobase/plugin-ui-schema-storage": "0.11.0-alpha.1"
+ },
+ "dependencies": {
+ "deepmerge": "^4.3.1"
+ },
+ "displayName": "Localization management",
+ "displayName.zh-CN": "多语言管理",
+ "description": "Allows to manage localization resources of the application.",
+ "description.zh-CN": "支持管理应用程序的多语言资源。"
+}
\ No newline at end of file
diff --git a/packages/plugins/localization-management/server.d.ts b/packages/plugins/localization-management/server.d.ts
new file mode 100755
index 000000000..4d922a91b
--- /dev/null
+++ b/packages/plugins/localization-management/server.d.ts
@@ -0,0 +1,3 @@
+// @ts-nocheck
+export * from './lib/server';
+export { default } from './lib/server';
diff --git a/packages/plugins/localization-management/server.js b/packages/plugins/localization-management/server.js
new file mode 100755
index 000000000..4f16903a6
--- /dev/null
+++ b/packages/plugins/localization-management/server.js
@@ -0,0 +1,65 @@
+'use strict';
+
+function _getRequireWildcardCache(nodeInterop) {
+ if (typeof WeakMap !== 'function') return null;
+ var cacheBabelInterop = new WeakMap();
+ var cacheNodeInterop = new WeakMap();
+ return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) {
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
+ })(nodeInterop);
+}
+
+function _interopRequireWildcard(obj, nodeInterop) {
+ if (!nodeInterop && obj && obj.__esModule) {
+ return obj;
+ }
+ if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
+ return { default: obj };
+ }
+ var cache = _getRequireWildcardCache(nodeInterop);
+ if (cache && cache.has(obj)) {
+ return cache.get(obj);
+ }
+ var newObj = {};
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
+ for (var key in obj) {
+ if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
+ if (desc && (desc.get || desc.set)) {
+ Object.defineProperty(newObj, key, desc);
+ } else {
+ newObj[key] = obj[key];
+ }
+ }
+ }
+ newObj.default = obj;
+ if (cache) {
+ cache.set(obj, newObj);
+ }
+ return newObj;
+}
+
+var _index = _interopRequireWildcard(require('./lib/server'));
+
+Object.defineProperty(exports, '__esModule', {
+ value: true,
+});
+var _exportNames = {};
+Object.defineProperty(exports, 'default', {
+ enumerable: true,
+ get: function get() {
+ return _index.default;
+ },
+});
+
+Object.keys(_index).forEach(function (key) {
+ if (key === 'default' || key === '__esModule') return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _index[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function get() {
+ return _index[key];
+ },
+ });
+});
diff --git a/packages/plugins/localization-management/src/client/Localization.tsx b/packages/plugins/localization-management/src/client/Localization.tsx
new file mode 100644
index 000000000..435213bca
--- /dev/null
+++ b/packages/plugins/localization-management/src/client/Localization.tsx
@@ -0,0 +1,253 @@
+import { SyncOutlined } from '@ant-design/icons';
+import { Form, createForm } from '@formily/core';
+import { Field, useField, useForm } from '@formily/react';
+import {
+ FormProvider,
+ Input,
+ Radio,
+ SchemaComponent,
+ locale,
+ useAPIClient,
+ useActionContext,
+ useRecord,
+ useResourceActionContext,
+ useResourceContext,
+} from '@nocobase/client';
+import { Input as AntdInput, Button, Card, Checkbox, Col, Divider, Popover, Row, Tag, Typography, message } from 'antd';
+import React, { useMemo, useState } from 'react';
+import { useLocalTranslation } from './locale';
+import { localizationSchema } from './schemas/localization';
+const { Text } = Typography;
+
+const useUpdateTranslationAction = () => {
+ const field = useField();
+ const form = useForm();
+ const ctx = useActionContext();
+ const { refresh } = useResourceActionContext();
+ const { targetKey } = useResourceContext();
+ const { [targetKey]: textId } = useRecord();
+ const api = useAPIClient();
+ const locale = api.auth.getLocale();
+ return {
+ async run() {
+ await form.submit();
+ field.data = field.data || {};
+ field.data.loading = true;
+ try {
+ await api.resource('localizationTranslations').updateOrCreate({
+ filterKeys: ['textId', 'locale'],
+ values: {
+ textId,
+ locale,
+ translation: form.values.translation,
+ },
+ });
+ ctx.setVisible(false);
+ await form.reset();
+ refresh();
+ } catch (e) {
+ console.log(e);
+ } finally {
+ field.data.loading = false;
+ }
+ },
+ };
+};
+
+const useDestroyTranslationAction = () => {
+ const { refresh } = useResourceActionContext();
+ const api = useAPIClient();
+ const { translationId: filterByTk } = useRecord();
+ return {
+ async run() {
+ await api.resource('localizationTranslations').destroy({ filterByTk });
+ refresh();
+ },
+ };
+};
+
+const useBulkDestroyTranslationAction = () => {
+ const { state, setState, refresh } = useResourceActionContext();
+ const api = useAPIClient();
+ const { t } = useLocalTranslation();
+ return {
+ async run() {
+ if (!state?.selectedRowKeys?.length) {
+ return message.error(t('Please select the records you want to delete'));
+ }
+ await api.resource('localizationTranslations').destroy({ filterByTk: state?.selectedRowKeys });
+ setState?.({ selectedRowKeys: [] });
+ refresh();
+ },
+ };
+};
+
+const usePublishAction = () => {
+ const api = useAPIClient();
+ return {
+ async run() {
+ await api.resource('localization').publish();
+ window.location.reload();
+ },
+ };
+};
+
+const Sync = () => {
+ const { t } = useLocalTranslation();
+ const { refresh } = useResourceActionContext();
+ const api = useAPIClient();
+ const [loading, setLoading] = useState(false);
+ const plainOptions = ['local', 'menu', 'db'];
+ const [checkedList, setCheckedList] = useState(plainOptions);
+ const [indeterminate, setIndeterminate] = useState(false);
+ const [checkAll, setCheckAll] = useState(true);
+ const onChange = (list: any[]) => {
+ setCheckedList(list);
+ setIndeterminate(!!list.length && list.length < plainOptions.length);
+ setCheckAll(list.length === plainOptions.length);
+ };
+
+ const onCheckAllChange = (e) => {
+ setCheckedList(e.target.checked ? plainOptions : []);
+ setIndeterminate(false);
+ setCheckAll(e.target.checked);
+ };
+
+ return (
+
+
+ {t('All')}
+
+
+
+
+
+ {t('System & Plugins')}
+
+
+ {t('Collections & Fields')}
+
+
+ {t('Menu')}
+
+
+
+ >
+ }
+ >
+ }
+ loading={loading}
+ onClick={async () => {
+ if (!checkedList.length) {
+ return message.error(t('Please select the resources you want to synchronize'));
+ }
+ setLoading(true);
+ await api.resource('localization').sync({
+ values: {
+ type: checkedList,
+ },
+ });
+ setLoading(false);
+ refresh();
+ }}
+ >
+ {t('Sync')}
+
+
+ );
+};
+
+const Filter = () => {
+ const { t } = useLocalTranslation();
+ const { run, refresh } = useResourceActionContext();
+ const api = useAPIClient();
+ const locale = api.auth.getLocale();
+ const form = useMemo