Merge branch 'next' into develop

# Conflicts:
#	packages/presets/nocobase/package.json
This commit is contained in:
chenos 2025-03-28 15:02:25 +08:00
commit 68f63b359b
23 changed files with 973 additions and 10 deletions

View File

@ -20,7 +20,6 @@ import { FilterBlockProvider } from '../../../filter-provider/FilterProvider';
import { import {
NocoBaseRecursionField, NocoBaseRecursionField,
RefreshComponentProvider, RefreshComponentProvider,
useRefreshComponent,
useRefreshFieldSchema, useRefreshFieldSchema,
} from '../../../formily/NocoBaseRecursionField'; } from '../../../formily/NocoBaseRecursionField';
import { DndContext, DndContextProps } from '../../common/dnd-context'; import { DndContext, DndContextProps } from '../../common/dnd-context';
@ -379,11 +378,9 @@ export const Grid: any = observer(
}, [fieldSchema, render, InitializerComponent, showDivider]); }, [fieldSchema, render, InitializerComponent, showDivider]);
const refreshFieldSchema = useRefreshFieldSchema(); const refreshFieldSchema = useRefreshFieldSchema();
const refreshComponent = useRefreshComponent();
const refresh = useCallback(() => { const refresh = useCallback(() => {
refreshFieldSchema?.(); refreshFieldSchema?.();
refreshComponent?.(); }, [refreshFieldSchema]);
}, [refreshComponent, refreshFieldSchema]);
return ( return (
<RefreshComponentProvider refresh={refresh}> <RefreshComponentProvider refresh={refresh}>

View File

@ -0,0 +1,34 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { expect, test } from '@nocobase/test/e2e';
import { afterConfiguringTheModalWhenReopeningItTheContentShouldPersist } from './utils';
test.describe('refresh', () => {
test('After configuring the modal, when reopening it, the content should persist', async ({ mockPage, page }) => {
await mockPage(afterConfiguringTheModalWhenReopeningItTheContentShouldPersist).goto();
// 1. 点击 Bulk edit 按钮,打开弹窗
await page.getByLabel('action-Action-Bulk edit-').click();
// 2. 新增一个表单区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Form' }).click();
// 3. 新增一个名为 Nickname 的字段
await page.getByLabel('schema-initializer-Grid-bulkEditForm:configureFields-users').hover();
await page.getByRole('menuitem', { name: 'Nickname' }).click();
// 4. 关闭弹窗,然后再打开,刚才新增的字段应该还在
await page.getByLabel('drawer-Action.Container-users-Bulk edit-mask').click();
await page.getByLabel('action-Action-Bulk edit-').click();
await expect(page.getByLabel('block-item-BulkEditField-').getByText('Nickname')).toBeVisible();
await page.getByLabel('block-item-BulkEditField-').click();
});
});

View File

@ -1243,3 +1243,388 @@ export const theAddBlockButtonInDrawerShouldBeVisible = {
'x-index': 1, 'x-index': 1,
}, },
}; };
export const afterConfiguringTheModalWhenReopeningItTheContentShouldPersist = {
pageSchema: {
type: 'void',
'x-component': 'Page',
name: 'rjzvy4bmawn',
'x-uid': '1rs9caegbf2',
'x-async': false,
properties: {
tab: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
properties: {
bmsmf8futai: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'x84k7qs6jko',
'x-async': false,
'x-index': 4,
},
noe2oca30hc: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 't7jxa830ps6',
'x-async': false,
'x-index': 5,
},
w2hnq7rau9p: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': '0fjjtg8z7ws',
'x-async': false,
'x-index': 7,
},
fcfs4oot86g: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'nklv7lonpgn',
'x-async': false,
'x-index': 8,
},
i22fydav355: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'fz4g6cr9jvr',
'x-async': false,
'x-index': 10,
},
row_6u7y7uccrvz: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-index': 12,
'x-uid': '7tzumo4nec7',
'x-async': false,
},
higfesvgj7g: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': '8hpa6qf3sez',
'x-async': false,
'x-index': 13,
},
'37myao9n0wc': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'uw1dp2qxd3y',
'x-async': false,
'x-index': 14,
},
uvfd76q4ye9: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'nc56fu33m42',
'x-async': false,
'x-index': 15,
},
miidizeqgot: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'jf4qarrcs0z',
'x-async': false,
'x-index': 16,
},
hxmr87i5imu: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'l3kdiqd9a7k',
'x-async': false,
'x-index': 17,
},
pa8dwdi4h5a: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'uz5wcet83qn',
'x-async': false,
'x-index': 18,
},
pno0a05tbnp: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'b4bakhhasp3',
'x-async': false,
'x-index': 19,
},
uj09g5xgnr1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'qks035fnfl6',
'x-async': false,
'x-index': 20,
},
giobcwj316k: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
'x-uid': 'awwsb89nyso',
'x-async': false,
'x-index': 22,
},
oznewtbvuyw: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.6.11',
properties: {
bwtax0bnnp3: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.6.11',
properties: {
c0bypj7wg5q: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'users:list',
'x-use-decorator-props': 'useTableBlockDecoratorProps',
'x-decorator-props': {
collection: 'users',
dataSource: 'main',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
'x-app-version': '1.6.11',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-app-version': '1.6.11',
properties: {
'1dlvhzr308c': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Bulk edit")}}',
'x-component': 'Action',
'x-action': 'customize:bulkEdit',
'x-action-settings': {
updateMode: 'selected',
},
'x-component-props': {
openMode: 'drawer',
icon: 'EditOutlined',
},
'x-align': 'right',
'x-decorator': 'BulkEditActionDecorator',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:bulkEdit',
'x-acl-action': 'update',
'x-acl-action-props': {
skipScopeCheck: true,
},
'x-app-version': '1.6.11',
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Bulk edit")}}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
'x-app-version': '1.6.11',
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'popup:addTab',
'x-initializer-props': {
gridInitializer: 'popup:bulkEdit:addBlock',
},
'x-app-version': '1.6.11',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Bulk edit")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
'x-app-version': '1.6.11',
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:bulkEdit:addBlock',
'x-app-version': '1.6.11',
'x-uid': '5ejbu8v5ol8',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'gfheiqtl7f7',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'if2rcx1dy2n',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'cxyi8q6lm3n',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'vbvf13xq15t',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'amlzm32jhwg',
'x-async': false,
'x-index': 1,
},
f232o2ds23n: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
'x-app-version': '1.6.11',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-toolbar': 'TableColumnSchemaToolbar',
'x-initializer': 'table:configureItemActions',
'x-settings': 'fieldSettings:TableColumn',
'x-toolbar-props': {
initializer: 'table:configureItemActions',
},
'x-app-version': '1.6.11',
properties: {
'153lpq30p5f': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
'x-app-version': '1.6.11',
'x-uid': '4mha1dmmyz9',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'afmceivuaf0',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'iu0xkmeuc5z',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'ylor106s9ok',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'rl50hidu14n',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'xxhug2yumqf',
'x-async': false,
'x-index': 23,
},
},
name: 'h63eibc46on',
'x-uid': 'u9g23o0ohgk',
'x-async': true,
'x-index': 1,
},
},
},
};

View File

@ -9,13 +9,13 @@
import { FileImageOutlined, LeftOutlined } from '@ant-design/icons'; import { FileImageOutlined, LeftOutlined } from '@ant-design/icons';
import { useActionContext } from '@nocobase/client'; import { useActionContext } from '@nocobase/client';
import { Html5Qrcode } from 'html5-qrcode'; import { Html5Qrcode } from 'html5-qrcode';
import React, { useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ScanBox } from './ScanBox'; import { ScanBox } from './ScanBox';
import { useScanner } from './useScanner'; import { useScanner } from './useScanner';
const qrcodeEleId = 'qrcode'; const qrcodeEleId = 'qrcode';
export const QRCodeScannerInner = (props) => { export const QRCodeScannerInner = ({ setVisible }) => {
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
const imgUploaderRef = useRef<HTMLInputElement>(); const imgUploaderRef = useRef<HTMLInputElement>();
const { t } = useTranslation('block-workbench'); const { t } = useTranslation('block-workbench');
@ -23,9 +23,17 @@ export const QRCodeScannerInner = (props) => {
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0); const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
const onScanSuccess = useCallback(
(text) => {
setVisible(false);
},
[setVisible],
);
const { startScanFile } = useScanner({ const { startScanFile } = useScanner({
onScannerSizeChanged: setOriginVideoSize, onScannerSizeChanged: setOriginVideoSize,
elementId: qrcodeEleId, elementId: qrcodeEleId,
onScanSuccess,
}); });
const getBoxStyle = (): React.CSSProperties => { const getBoxStyle = (): React.CSSProperties => {
@ -174,7 +182,7 @@ export const QRCodeScanner = (props) => {
return visible && cameraAvaliable ? ( return visible && cameraAvaliable ? (
<div style={style}> <div style={style}>
<QRCodeScannerInner /> <QRCodeScannerInner setVisible={setVisible} />
<LeftOutlined style={backIconStyle} onClick={() => setVisible(false)} /> <LeftOutlined style={backIconStyle} onClick={() => setVisible(false)} />
<div style={titleStyle}>{t('Scan QR code')}</div> <div style={titleStyle}>{t('Scan QR code')}</div>
</div> </div>

View File

@ -20,7 +20,7 @@ function removeStringIfStartsWith(text: string, prefix: string): string {
return text; return text;
} }
export function useScanner({ onScannerSizeChanged, elementId }) { export function useScanner({ onScannerSizeChanged, elementId, onScanSuccess }) {
const app = useApp(); const app = useApp();
const mobileManager = app.pm.get(MobileManager); const mobileManager = app.pm.get(MobileManager);
const basename = mobileManager.mobileRouter.basename.replace(/\/+$/, ''); const basename = mobileManager.mobileRouter.basename.replace(/\/+$/, '');
@ -50,12 +50,17 @@ export function useScanner({ onScannerSizeChanged, elementId }) {
}, },
}, },
(text) => { (text) => {
if (text?.startsWith('http')) {
window.location.href = text;
return;
}
navigate(removeStringIfStartsWith(text, basename)); navigate(removeStringIfStartsWith(text, basename));
onScanSuccess && onScanSuccess(text);
}, },
undefined, undefined,
); );
}, },
[navigate, onScannerSizeChanged, viewPoint, basename], [navigate, onScannerSizeChanged, viewPoint, basename, onScanSuccess],
); );
const stopScanner = useCallback(async (scanner: Html5Qrcode) => { const stopScanner = useCallback(async (scanner: Html5Qrcode) => {
const state = scanner.getState(); const state = scanner.getState();
@ -69,13 +74,18 @@ export function useScanner({ onScannerSizeChanged, elementId }) {
await stopScanner(scanner); await stopScanner(scanner);
try { try {
const { decodedText } = await scanner.scanFileV2(file, false); const { decodedText } = await scanner.scanFileV2(file, false);
if (decodedText?.startsWith('http')) {
window.location.href = decodedText;
return;
}
navigate(removeStringIfStartsWith(decodedText, basename)); navigate(removeStringIfStartsWith(decodedText, basename));
onScanSuccess && onScanSuccess(decodedText);
} catch (error) { } catch (error) {
alert(t('QR code recognition failed, please scan again')); alert(t('QR code recognition failed, please scan again'));
startScanCamera(scanner); startScanCamera(scanner);
} }
}, },
[stopScanner, scanner, navigate, basename, t, startScanCamera], [stopScanner, scanner, navigate, basename, t, startScanCamera, onScanSuccess],
); );
useEffect(() => { useEffect(() => {

View File

@ -0,0 +1,2 @@
/node_modules
/src

View File

@ -0,0 +1 @@
# @nocobase/plugin-locale-tester

View File

@ -0,0 +1,2 @@
export * from './dist/client';
export { default } from './dist/client';

View File

@ -0,0 +1 @@
module.exports = require('./dist/client/index.js');

View File

@ -0,0 +1,14 @@
{
"name": "@nocobase/plugin-locale-tester",
"displayName": "Locale tester",
"displayName.zh-CN": "翻译测试工具",
"version": "1.7.0-alpha.10",
"homepage": "https://github.com/nocobase/locales",
"main": "dist/server/index.js",
"dependencies": {},
"peerDependencies": {
"@nocobase/client": "1.x",
"@nocobase/server": "1.x",
"@nocobase/test": "1.x"
}
}

View File

@ -0,0 +1,2 @@
export * from './dist/server';
export { default } from './dist/server';

View File

@ -0,0 +1 @@
module.exports = require('./dist/server/index.js');

View File

@ -0,0 +1,249 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
// CSS modules
type CSSModuleClasses = { readonly [key: string]: string };
declare module '*.module.css' {
const classes: CSSModuleClasses;
export default classes;
}
declare module '*.module.scss' {
const classes: CSSModuleClasses;
export default classes;
}
declare module '*.module.sass' {
const classes: CSSModuleClasses;
export default classes;
}
declare module '*.module.less' {
const classes: CSSModuleClasses;
export default classes;
}
declare module '*.module.styl' {
const classes: CSSModuleClasses;
export default classes;
}
declare module '*.module.stylus' {
const classes: CSSModuleClasses;
export default classes;
}
declare module '*.module.pcss' {
const classes: CSSModuleClasses;
export default classes;
}
declare module '*.module.sss' {
const classes: CSSModuleClasses;
export default classes;
}
// CSS
declare module '*.css' { }
declare module '*.scss' { }
declare module '*.sass' { }
declare module '*.less' { }
declare module '*.styl' { }
declare module '*.stylus' { }
declare module '*.pcss' { }
declare module '*.sss' { }
// Built-in asset types
// see `src/node/constants.ts`
// images
declare module '*.apng' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.jpeg' {
const src: string;
export default src;
}
declare module '*.jfif' {
const src: string;
export default src;
}
declare module '*.pjpeg' {
const src: string;
export default src;
}
declare module '*.pjp' {
const src: string;
export default src;
}
declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.svg' {
const src: string;
export default src;
}
declare module '*.ico' {
const src: string;
export default src;
}
declare module '*.webp' {
const src: string;
export default src;
}
declare module '*.avif' {
const src: string;
export default src;
}
// media
declare module '*.mp4' {
const src: string;
export default src;
}
declare module '*.webm' {
const src: string;
export default src;
}
declare module '*.ogg' {
const src: string;
export default src;
}
declare module '*.mp3' {
const src: string;
export default src;
}
declare module '*.wav' {
const src: string;
export default src;
}
declare module '*.flac' {
const src: string;
export default src;
}
declare module '*.aac' {
const src: string;
export default src;
}
declare module '*.opus' {
const src: string;
export default src;
}
declare module '*.mov' {
const src: string;
export default src;
}
declare module '*.m4a' {
const src: string;
export default src;
}
declare module '*.vtt' {
const src: string;
export default src;
}
// fonts
declare module '*.woff' {
const src: string;
export default src;
}
declare module '*.woff2' {
const src: string;
export default src;
}
declare module '*.eot' {
const src: string;
export default src;
}
declare module '*.ttf' {
const src: string;
export default src;
}
declare module '*.otf' {
const src: string;
export default src;
}
// other
declare module '*.webmanifest' {
const src: string;
export default src;
}
declare module '*.pdf' {
const src: string;
export default src;
}
declare module '*.txt' {
const src: string;
export default src;
}
// wasm?init
declare module '*.wasm?init' {
const initWasm: (options?: WebAssembly.Imports) => Promise<WebAssembly.Instance>;
export default initWasm;
}
// web worker
declare module '*?worker' {
const workerConstructor: {
new(options?: { name?: string }): Worker;
};
export default workerConstructor;
}
declare module '*?worker&inline' {
const workerConstructor: {
new(options?: { name?: string }): Worker;
};
export default workerConstructor;
}
declare module '*?worker&url' {
const src: string;
export default src;
}
declare module '*?sharedworker' {
const sharedWorkerConstructor: {
new(options?: { name?: string }): SharedWorker;
};
export default sharedWorkerConstructor;
}
declare module '*?sharedworker&inline' {
const sharedWorkerConstructor: {
new(options?: { name?: string }): SharedWorker;
};
export default sharedWorkerConstructor;
}
declare module '*?sharedworker&url' {
const src: string;
export default src;
}
declare module '*?raw' {
const src: string;
export default src;
}
declare module '*?url' {
const src: string;
export default src;
}
declare module '*?inline' {
const src: string;
export default src;
}

View File

@ -0,0 +1,124 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { useForm } from '@formily/react';
import { ActionProps, ISchema, Plugin, SchemaComponent, useAPIClient, useApp, useRequest } from '@nocobase/client';
import { Alert, App as AntdApp, Card, Spin } from 'antd';
import React from 'react';
import { useT } from './locale';
function LocaleTester() {
const { data, loading } = useRequest<any>({
url: 'localeTester:get',
});
const t = useT();
const schema: ISchema = {
type: 'void',
name: 'root',
properties: {
test: {
type: 'void',
'x-component': 'FormV2',
properties: {
locale: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input.JSON',
'x-component-props': {
autoSize: { minRows: 20, maxRows: 30 },
},
default: data?.data?.locale,
title: t('Translations'),
},
button: {
type: 'void',
'x-component': 'Action',
title: t('Submit'),
'x-use-component-props': 'useSubmitActionProps',
},
},
},
},
};
const useSubmitActionProps = () => {
const form = useForm();
const api = useAPIClient();
const { message } = AntdApp.useApp();
const app = useApp();
return {
type: 'primary',
htmlType: 'submit',
async onClick() {
await form.submit();
const values = form.values;
await api.request({
url: 'localeTester:updateOrCreate',
method: 'post',
params: {
filterKeys: ['id'],
},
data: {
id: data?.data?.id,
locale: values.locale,
},
});
message.success(app.i18n.t('Saved successfully!'));
window.location.reload();
},
};
};
if (loading) {
return <Spin />;
}
return (
<Card>
<Alert
style={{ marginBottom: 12 }}
description={
<div
dangerouslySetInnerHTML={{
__html: t(
`Please go to <a target="_blank" href="https://github.com/nocobase/locales">nocobase/locales</a> to get the language file that needs translation, then paste it below and provide the translation.`,
),
}}
></div>
}
/>
<SchemaComponent schema={schema} scope={{ useSubmitActionProps }} />
</Card>
);
}
export class PluginLocaleTesterClient extends Plugin {
async afterAdd() {
// await this.app.pm.add()
}
async beforeLoad() {}
// You can get and modify the app instance here
async load() {
this.app.pluginSettingsManager.add('locale-tester', {
title: this.t('Locale tester'),
icon: 'TranslationOutlined',
Component: LocaleTester,
});
// this.app.addComponents({})
// this.app.addScopes({})
// this.app.addProvider()
// this.app.addProviders()
// this.app.router.add()
}
}
export default PluginLocaleTesterClient;

View File

@ -0,0 +1,21 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
// @ts-ignore
import pkg from './../../package.json';
import { useApp } from '@nocobase/client';
export function useT() {
const app = useApp();
return (str: string) => app.i18n.t(str, { ns: [pkg.name, 'client'] });
}
export function tStr(key: string) {
return `{{t(${JSON.stringify(key)}, { ns: ['${pkg.name}', 'client'], nsMode: 'fallback' })}}`;
}

View File

@ -0,0 +1,11 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
export * from './server';
export { default } from './server';

View File

@ -0,0 +1,5 @@
{
"Locale": "Locale",
"Locale tester": "Locale tester",
"Please go to <a target=\"_blank\" href=\"https://github.com/nocobase/locales\">nocobase/locales</a> to get the language file that needs translation, then paste it below and provide the translation.": "Please go to <a target=\"_blank\" href=\"https://github.com/nocobase/locales\">nocobase/locales</a> to get the language file that needs translation, then paste it below and provide the translation."
}

View File

@ -0,0 +1,5 @@
{
"Translations": "翻译",
"Locale tester": "翻译测试工具",
"Please go to <a target=\"_blank\" href=\"https://github.com/nocobase/locales\">nocobase/locales</a> to get the language file that needs translation, then paste it below and provide the translation.": "请前往 <a target=\"_blank\" href=\"https://github.com/nocobase/locales\">nocobase/locales</a> 获取需要翻译的语言文件,粘贴到下方并进行翻译。"
}

View File

@ -0,0 +1,21 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { defineCollection } from '@nocobase/database';
export default defineCollection({
name: 'localeTester',
autoGenId: true,
fields: [
{
type: 'json',
name: 'locale',
},
],
});

View File

@ -0,0 +1,10 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
export { default } from './plugin';

View File

@ -0,0 +1,59 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Plugin } from '@nocobase/server';
import _ from 'lodash';
export class PluginLocaleTesterServer extends Plugin {
async afterAdd() {}
async beforeLoad() {
this.app.acl.registerSnippet({
name: `pm.${this.name}`,
actions: ['localeTester:*'],
});
}
async load() {
this.app.resourceManager.use(async (ctx, next) => {
await next();
const { resourceName, actionName } = ctx.action;
if (resourceName === 'app' && actionName === 'getLang') {
const repository = this.db.getRepository('localeTester');
const record = await repository.findOne();
const locale = record?.locale || {};
if (locale['cronstrue']) {
_.set(ctx.body, 'cronstrue', locale['cronstrue']);
}
if (locale['react-js-cron']) {
_.set(ctx.body, 'cron', locale['react-js-cron']);
}
Object.keys(locale).forEach((key) => {
if (key === 'cronstrue' || key === 'react-js-cron') {
return;
}
const value = locale[key];
_.set(ctx.body, ['resources', key], value);
const k = key.replace('@nocobase/', '').replace('@nocobase/plugin-', '');
_.set(ctx.body, ['resources', k], value);
});
}
});
}
async install() {}
async afterEnable() {}
async afterDisable() {}
async remove() {}
}
export default PluginLocaleTesterServer;

View File

@ -44,6 +44,7 @@
"@nocobase/plugin-gantt": "1.7.0-alpha.10", "@nocobase/plugin-gantt": "1.7.0-alpha.10",
"@nocobase/plugin-graph-collection-manager": "1.7.0-alpha.10", "@nocobase/plugin-graph-collection-manager": "1.7.0-alpha.10",
"@nocobase/plugin-kanban": "1.7.0-alpha.10", "@nocobase/plugin-kanban": "1.7.0-alpha.10",
"@nocobase/plugin-locale-tester": "1.7.0-alpha.10",
"@nocobase/plugin-localization": "1.7.0-alpha.10", "@nocobase/plugin-localization": "1.7.0-alpha.10",
"@nocobase/plugin-logger": "1.7.0-alpha.10", "@nocobase/plugin-logger": "1.7.0-alpha.10",
"@nocobase/plugin-map": "1.7.0-alpha.10", "@nocobase/plugin-map": "1.7.0-alpha.10",