From cc47041519f47758a2e77f163208522977eb981f Mon Sep 17 00:00:00 2001 From: chenos Date: Fri, 13 Jan 2023 10:55:04 +0800 Subject: [PATCH] feat: load multiple languages dynamically (#1355) * feat: load multiple languages dynamically * fix: map locale * fix: antd * fix: locale * fix: th * fix: cronstrue locales * fix: improve code * fix: defaults --- Dockerfile | 4 +- Dockerfile.acr | 8 +- packages/core/client/package.json | 3 +- .../client/src/antd-config-provider/index.tsx | 31 +- .../loadConstrueLocale.ts | 172 +++ packages/core/client/src/i18n/i18n.ts | 39 +- packages/core/client/src/index.tsx | 2 +- packages/core/client/src/locale/en_US.ts | 142 +- packages/core/client/src/locale/index.ts | 146 +- .../src/schema-component/antd/cron/Cron.tsx | 96 +- .../antd/cron/locale/index.ts | 7 + .../SystemSettingsShortcut.tsx | 2 +- packages/core/server/package.json | 2 +- packages/core/server/src/middlewares/i18n.ts | 16 +- packages/plugins/client/src/antd.ts | 9 + packages/plugins/client/src/cron.ts | 16 + packages/plugins/client/src/cronstrue.ts | 111 ++ packages/plugins/client/src/locale/index.ts | 3 + .../client/src/locale/zh-CN.example.json | 1237 +++++++++++++++++ packages/plugins/client/src/moment-locale.ts | 141 ++ packages/plugins/client/src/resource.ts | 69 + packages/plugins/client/src/server.ts | 27 + .../plugins/error-handler/src/locale/en_US.ts | 6 +- .../src/client/GraphCollectionProvider.tsx | 13 +- .../src/client/GraphCollectionShortcut.tsx | 6 +- .../src/client/GraphDrawPage.tsx | 27 +- .../src/client/components/Entity.tsx | 22 +- .../src/client/utils.tsx | 9 +- packages/plugins/import/src/client/index.ts | 8 +- .../plugins/import/src/client/locale/en-US.ts | 46 +- .../map/src/client/components/AMap.tsx | 15 +- .../src/client/components/Configuration.tsx | 8 +- .../map/src/client/components/Designer.tsx | 8 +- .../map/src/client/components/Search.tsx | 6 +- packages/plugins/map/src/client/constants.ts | 2 +- .../plugins/map/src/client/fields/circle.ts | 2 +- .../map/src/client/fields/lineString.ts | 2 +- .../plugins/map/src/client/fields/point.ts | 3 +- .../plugins/map/src/client/fields/polygon.ts | 2 +- .../plugins/map/src/client/fields/schema.ts | 2 +- packages/plugins/map/src/client/index.tsx | 2 +- .../plugins/map/src/client/initialize.tsx | 7 +- .../src/client/{locales => locale}/en-US.ts | 0 .../src/client/{locales => locale}/index.ts | 6 +- .../src/client/{locales => locale}/zh-CN.ts | 0 .../plugins/oidc/src/client/locale/index.ts | 15 +- .../plugins/saml/src/client/locale/index.ts | 15 +- .../sequence-field/src/client/locale/en-US.ts | 24 + .../sequence-field/src/client/locale/index.ts | 7 +- .../snapshot-field/src/client/locale/index.ts | 15 +- packages/plugins/users/src/locale/en-US.ts | 8 +- .../verification/src/client/locale/index.ts | 5 +- .../workflow/src/client/locale/en-US.ts | 62 +- .../workflow/src/client/locale/index.ts | 17 +- yarn.lock | 47 +- 55 files changed, 2307 insertions(+), 393 deletions(-) create mode 100644 packages/core/client/src/antd-config-provider/loadConstrueLocale.ts create mode 100644 packages/core/client/src/schema-component/antd/cron/locale/index.ts create mode 100644 packages/plugins/client/src/antd.ts create mode 100644 packages/plugins/client/src/cron.ts create mode 100644 packages/plugins/client/src/cronstrue.ts create mode 100644 packages/plugins/client/src/locale/index.ts create mode 100644 packages/plugins/client/src/locale/zh-CN.example.json create mode 100644 packages/plugins/client/src/moment-locale.ts create mode 100644 packages/plugins/client/src/resource.ts rename packages/plugins/map/src/client/{locales => locale}/en-US.ts (100%) rename packages/plugins/map/src/client/{locales => locale}/index.ts (72%) rename packages/plugins/map/src/client/{locales => locale}/zh-CN.ts (100%) create mode 100644 packages/plugins/sequence-field/src/client/locale/en-US.ts diff --git a/Dockerfile b/Dockerfile index cb0bc2b3c..7fe12a1e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,9 @@ RUN cd /app \ && rm -rf my-nocobase-app/packages/app/client/src/.umi \ && rm -rf nocobase.tar.gz \ && rm -rf ./my-nocobase-app/node_modules/@antv \ - && rm -rf ./my-nocobase-app/node_modules/antd \ + && rm -rf ./my-nocobase-app/node_modules/antd/dist \ + && rm -rf ./my-nocobase-app/node_modules/antd/es \ + && rm -rf ./my-nocobase-app/node_modules/antd/node_modules \ && rm -rf ./my-nocobase-app/node_modules/@ant-design \ && rm -rf ./my-nocobase-app/node_modules/china-division/dist/villages.json \ && find ./my-nocobase-app/node_modules/china-division/dist -name '*.csv' -delete \ diff --git a/Dockerfile.acr b/Dockerfile.acr index 9e3116d6b..7fe12a1e1 100644 --- a/Dockerfile.acr +++ b/Dockerfile.acr @@ -30,9 +30,11 @@ RUN cd /app \ RUN cd /app \ && rm -rf my-nocobase-app/packages/app/client/src/.umi \ && rm -rf nocobase.tar.gz \ - && rm -rf ./my-nocobase-app/nocobase/node_modules/@antv \ - && rm -rf ./my-nocobase-app/nocobase/node_modules/antd \ - && rm -rf ./my-nocobase-app/nocobase/node_modules/@ant-design \ + && rm -rf ./my-nocobase-app/node_modules/@antv \ + && rm -rf ./my-nocobase-app/node_modules/antd/dist \ + && rm -rf ./my-nocobase-app/node_modules/antd/es \ + && rm -rf ./my-nocobase-app/node_modules/antd/node_modules \ + && rm -rf ./my-nocobase-app/node_modules/@ant-design \ && rm -rf ./my-nocobase-app/node_modules/china-division/dist/villages.json \ && find ./my-nocobase-app/node_modules/china-division/dist -name '*.csv' -delete \ && find ./my-nocobase-app/node_modules/china-division/dist -name '*.sqlite' -delete \ diff --git a/packages/core/client/package.json b/packages/core/client/package.json index 437d7e745..a6c0583c4 100644 --- a/packages/core/client/package.json +++ b/packages/core/client/package.json @@ -22,7 +22,8 @@ "cron-parser": "^4.6.0", "cronstrue": "^2.11.0", "file-saver": "^2.0.5", - "i18next": "^21.6.0", + "i18next": "^22.4.9", + "i18next-http-backend": "^2.1.1", "json-templates": "^4.2.0", "marked": "^4.0.12", "mathjs": "^10.6.0", diff --git a/packages/core/client/src/antd-config-provider/index.tsx b/packages/core/client/src/antd-config-provider/index.tsx index 6c7990362..6dd677766 100644 --- a/packages/core/client/src/antd-config-provider/index.tsx +++ b/packages/core/client/src/antd-config-provider/index.tsx @@ -1,14 +1,21 @@ import { ConfigProvider, Spin } from 'antd'; -import React from 'react'; +import moment from 'moment'; +import React, { createContext, useContext } from 'react'; import { useTranslation } from 'react-i18next'; import { useAPIClient, useRequest } from '../api-client'; -import locale from '../locale'; +import { loadConstrueLocale } from './loadConstrueLocale'; + +export const AppLangContext = createContext({}); + +export const useAppLangContext = () => { + return useContext(AppLangContext); +}; export function AntdConfigProvider(props) { const { remoteLocale, ...others } = props; const api = useAPIClient(); const { i18n } = useTranslation(); - const { loading } = useRequest( + const { data, loading } = useRequest( { url: 'app:getLang', }, @@ -19,6 +26,12 @@ export function AntdConfigProvider(props) { api.auth.setLocale(data?.data?.lang); i18n.changeLanguage(data?.data?.lang); } + Object.keys(data?.data?.resources || {}).forEach((key) => { + i18n.addResources(data?.data?.lang, key, data?.data?.resources[key] || {}); + }); + loadConstrueLocale(data?.data); + moment.locale(data?.data?.moment); + window['cronLocale'] = data?.data?.cron; }, manual: !remoteLocale, }, @@ -27,12 +40,10 @@ export function AntdConfigProvider(props) { return ; } return ( - - {props.children} - + + + {props.children} + + ); } diff --git a/packages/core/client/src/antd-config-provider/loadConstrueLocale.ts b/packages/core/client/src/antd-config-provider/loadConstrueLocale.ts new file mode 100644 index 000000000..e25b66103 --- /dev/null +++ b/packages/core/client/src/antd-config-provider/loadConstrueLocale.ts @@ -0,0 +1,172 @@ +import cronstrue from 'cronstrue'; + +class CronstrueLocale { + constructor(protected data: any) {} + atX0SecondsPastTheMinuteGt20(): string | null { + return this.data['atX0SecondsPastTheMinuteGt20']; + } + atX0MinutesPastTheHourGt20(): string | null { + return this.data['atX0MinutesPastTheHourGt20']; + } + commaMonthX0ThroughMonthX1(): string | null { + return this.data['commaMonthX0ThroughMonthX1']; + } + commaYearX0ThroughYearX1(): string | null { + return this.data['commaYearX0ThroughYearX1']; + } + use24HourTimeFormatByDefault() { + return this.data['use24HourTimeFormatByDefault']; + } + anErrorOccuredWhenGeneratingTheExpressionD() { + return this.data['anErrorOccuredWhenGeneratingTheExpressionD']; + } + everyMinute() { + return this.data['everyMinute']; + } + everyHour() { + return this.data['everyHour']; + } + atSpace() { + return this.data['atSpace']; + } + everyMinuteBetweenX0AndX1() { + return this.data['everyMinuteBetweenX0AndX1']; + } + at() { + return this.data['at']; + } + spaceAnd() { + return this.data['spaceAnd']; + } + everySecond() { + return this.data['everySecond']; + } + everyX0Seconds() { + return this.data['everyX0Seconds']; + } + secondsX0ThroughX1PastTheMinute() { + return this.data['secondsX0ThroughX1PastTheMinute']; + } + atX0SecondsPastTheMinute() { + return this.data['atX0SecondsPastTheMinute']; + } + everyX0Minutes() { + return this.data['everyX0Minutes']; + } + minutesX0ThroughX1PastTheHour() { + return this.data['minutesX0ThroughX1PastTheHour']; + } + atX0MinutesPastTheHour() { + return this.data['atX0MinutesPastTheHour']; + } + everyX0Hours() { + return this.data['everyX0Hours']; + } + betweenX0AndX1() { + return this.data['betweenX0AndX1']; + } + atX0() { + return this.data['atX0']; + } + commaEveryDay() { + return this.data['commaEveryDay']; + } + commaEveryX0DaysOfTheWeek() { + return this.data['commaEveryX0DaysOfTheWeek']; + } + commaX0ThroughX1() { + return this.data['commaX0ThroughX1']; + } + commaAndX0ThroughX1() { + return this.data['commaAndX0ThroughX1']; + } + first() { + return this.data['first']; + } + second() { + return this.data['second']; + } + third() { + return this.data['third']; + } + fourth() { + return this.data['fourth']; + } + fifth() { + return this.data['fifth']; + } + commaOnThe() { + return this.data['commaOnThe']; + } + spaceX0OfTheMonth() { + return this.data['spaceX0OfTheMonth']; + } + lastDay() { + return this.data['lastDay']; + } + commaOnTheLastX0OfTheMonth() { + return this.data['commaOnTheLastX0OfTheMonth']; + } + commaOnlyOnX0() { + return this.data['commaOnlyOnX0']; + } + commaAndOnX0() { + return this.data['commaAndOnX0']; + } + commaEveryX0Months() { + return this.data['commaEveryX0Months']; + } + commaOnlyInX0() { + return this.data['commaOnlyInX0']; + } + commaOnTheLastDayOfTheMonth() { + return this.data['commaOnTheLastDayOfTheMonth']; + } + commaOnTheLastWeekdayOfTheMonth() { + return this.data['commaOnTheLastWeekdayOfTheMonth']; + } + commaDaysBeforeTheLastDayOfTheMonth() { + return this.data['commaDaysBeforeTheLastDayOfTheMonth']; + } + firstWeekday() { + return this.data['firstWeekday']; + } + weekdayNearestDayX0() { + return this.data['weekdayNearestDayX0']; + } + commaOnTheX0OfTheMonth() { + return this.data['commaOnTheX0OfTheMonth']; + } + commaEveryX0Days() { + return this.data['commaEveryX0Days']; + } + commaBetweenDayX0AndX1OfTheMonth() { + return this.data['commaBetweenDayX0AndX1OfTheMonth']; + } + commaOnDayX0OfTheMonth() { + return this.data['commaOnDayX0OfTheMonth']; + } + commaEveryHour() { + return this.data['commaEveryHour']; + } + commaEveryX0Years() { + return this.data['commaEveryX0Years']; + } + commaStartingX0() { + return this.data['commaStartingX0']; + } + daysOfTheWeek() { + return this.data['daysOfTheWeek']; + } + monthsOfTheYear() { + return this.data['monthsOfTheYear']; + } +} + +export const loadConstrueLocale = (data) => { + cronstrue.initialize({ + load(availableLocales) { + availableLocales[data?.lang] = new CronstrueLocale(data?.cronstrue); + }, + }); +}; diff --git a/packages/core/client/src/i18n/i18n.ts b/packages/core/client/src/i18n/i18n.ts index 497080192..23b7cf9c0 100644 --- a/packages/core/client/src/i18n/i18n.ts +++ b/packages/core/client/src/i18n/i18n.ts @@ -1,5 +1,4 @@ import i18next from 'i18next'; -import moment from 'moment'; import { initReactI18next } from 'react-i18next'; import locale from '../locale'; const log = require('debug')('i18next'); @@ -12,27 +11,25 @@ Object.keys(locale).forEach((lang) => { resources[lang] = locale[lang].resources; }); -i18n.use(initReactI18next).init({ - lng: localStorage.getItem('NOCOBASE_LOCALE') || 'en-US', - // debug: true, - defaultNS: 'client', - // parseMissingKeyHandler: (key) => { - // console.log('parseMissingKeyHandler', `'${key}': '${key}',`); - // return key; - // }, - // ns: ['client'], - resources, -}); - -function setMomentLng(language) { - const lng = locale[language || 'en-US'].moment || 'en'; - log(lng); - moment.locale(lng); -} - -setMomentLng(localStorage.getItem('NOCOBASE_LOCALE')); +i18n + // .use(Backend) + .use(initReactI18next) + .init({ + lng: localStorage.getItem('NOCOBASE_LOCALE') || 'en-US', + // debug: true, + defaultNS: 'client', + // backend: { + // // for all available options read the backend's repository readme file + // loadPath: '/api/locales/{{lng}}/{{ns}}.json', + // }, + // parseMissingKeyHandler: (key) => { + // console.log('parseMissingKeyHandler', `'${key}': '${key}',`); + // return key; + // }, + // ns: ['client'], + resources: {}, + }); i18n.on('languageChanged', (lng) => { localStorage.setItem('NOCOBASE_LOCALE', lng); - setMomentLng(lng); }); diff --git a/packages/core/client/src/index.tsx b/packages/core/client/src/index.tsx index e76fdf3b7..facc1a0d5 100644 --- a/packages/core/client/src/index.tsx +++ b/packages/core/client/src/index.tsx @@ -4,6 +4,7 @@ import './global.less'; export * from './acl'; export * from './antd-config-provider'; export * from './api-client'; +export * from './appInfo'; export * from './application'; export * from './async-data-provider'; export * from './block-provider'; @@ -26,5 +27,4 @@ export * from './schema-templates'; export * from './settings-form'; export * from './system-settings'; export * from './user'; -export * from './appInfo' diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index 7970dc954..0fe92836d 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -43,6 +43,7 @@ export default { "Super admin": "Super admin", "Language": "Language", "Allow sign up": "Allow sign up", + "Enable SMS authentication": "Enable SMS authentication", "Sign out": "Sign out", "Cancel": "Cancel", "Submit": "Submit", @@ -53,12 +54,12 @@ export default { "Form": "Form", "Select data source": "Select data source", "Calendar": "Calendar", - 'Delete events': 'Delete events', - 'This event': 'This event', - 'This and following events': 'This and following events', - 'All events': 'All events', - 'Delete this event?': 'Delete this event?', - 'Delete Event': 'Delete Event', + "Delete events": "Delete events", + "This event": "This event", + "This and following events": "This and following events", + "All events": "All events", + "Delete this event?": "Delete this event?", + "Delete Event": "Delete Event", "Kanban": "Kanban", "Select grouping field": "Select grouping field", "Media": "Media", @@ -114,11 +115,12 @@ export default { "Collection display name": "Collection display name", "Collection name": "Collection name", "Inherits": "Inherits", - "AutoGenId": "Auto-generated ID field", - "CreatedBy": "Recording a row's created user", - "UpdatedBy": "Recording a row's last updated user", - "CreatedAt": "Recording a row's created time ", - "UpdatedAt": "Recording a row's last updated user", + "Generate ID field automatically": "Generate ID field automatically", + "Store the creation user of each record": "Store the creation user of each record", + "Store the last update user of each record": "Store the last update user of each record", + "Store the creation time of each record": "Store the creation time of each record", + "Store the last update time of each record": "Store the last update time of each record", + "More options": "More options", "Records can be sorted": "Records can be sorted", "Collection template": "Collection template", "Calendar collection": "Calendar collection", @@ -136,9 +138,9 @@ export default { "Association fields filter": "Association fields filter", "PK & FK fields": "PK & FK fields", "Association fields": "Association fields", - "Parent collection fields": "Parent collection fields", "System fields": "System fields", "General fields": "General fields", + "Parent collection fields": "Parent collection fields", "Basic": "Basic", "Single line text": "Single line text", "Long text": "Long text", @@ -183,6 +185,7 @@ export default { "12 hour": "12 hour", "24 hour": "24 hour", "Relationship type": "Relationship type", + "Inverse relationship type": "Inverse relationship type", "Source collection": "Source collection", "Source key": "Source key", "Target collection": "Target collection", @@ -193,12 +196,28 @@ export default { "One to many": "One to many", "Many to one": "Many to one", "Many to many": "Many to many", + "Foreign key 1": "Foreign key 1", + "Foreign key 2": "Foreign key 2", "One to one description": "Used to create one-to-one relationships. For example, a user has a profile.", "One to many description": "Used to create a one-to-many relationship. For example, a country will have many cities and a city can only be in one country. When present as a field, it is a sub-table that displays the records of the associated collection. When created, a Many-to-one field is automatically generated in the associated collection.", "Many to one description": "Used to create many-to-one relationships. For example, a city can belong to only one country and a country can have many cities. When present as a field, it is a drop-down selection used to select record from the associated collection. Once created, a One-to-many field is automatically generated in the associated collection.", "Many to many description": "Used to create many-to-many relationships. For example, a student will have many teachers and a teacher will have many students. When present as a field, it is a drop-down selection used to select records from the associated collection.", - "Foreign key 1": "Foreign key 1", - "Foreign key 2": "Foreign key 2", + "Generated automatically if left blank": "Generated automatically if left blank", + "Display association fields": "Display association fields", + "Field component": "Field component", + "Subtable": "Subtable", + "Subform": "Subform", + "Record picker": "Record picker", + "Toggles the subfield mode": "Toggles the subfield mode", + "Selector mode": "Selector mode", + "Subtable mode": "Subtable mode", + "Subform mode": "Subform mode", + "Edit block title": "Edit block title", + "Block title": "Block title", + "Pattern": "Pattern", + "Editable": "Editable", + "Readonly": "Readonly", + "Easy-reading": "Easy-reading", "Add filter": "Add filter", "Add filter group": "Add filter group", "Comparision": "Comparision", @@ -219,6 +238,7 @@ export default { "Edit button": "Edit button", "Hide": "Hide", "Enable actions": "Enable actions", + "Import": "Import", "Export": "Export", "Customize": "Customize", "Function": "Function", @@ -239,9 +259,30 @@ export default { "Custom column name": "Custom column name", "Edit description": "Edit description", "Required": "Required", + "Unique": "Unique", "Label field": "Label field", "Default is the ID field": "Default is the ID field", "Set default sorting rules": "Set default sorting rules", + "Set validation rules": "Set validation rules", + "Max length": "Max length", + "Min length": "Min length", + "Maximum": "Maximum", + "Minimum": "Minimum", + "Max length must greater than min length": "Max length must greater than min length", + "Min length must less than max length": "Min length must less than max length", + "Maximum must greater than minimum": "Maximum must greater than minimum", + "Minimum must less than maximum": "Minimum must less than maximum", + "Validation rule": "Validation rule", + "Add validation rule": "Add validation rule", + "Format": "Format", + "Regular expression": "Pattern", + "Error message": "Error message", + "Length": "Length", + "The field value cannot be greater than ": "The field value cannot be greater than ", + "The field value cannot be less than ": "The field value cannot be less than ", + "The field value is not an integer number": "The field value is not an integer number", + "Set default value": "Set default value", + "Default value": "Default value", "is before": "is before", "is after": "is after", "is on or after": "is on or after", @@ -275,6 +316,7 @@ export default { "Configure calendar": "Configure calendar", "Title field": "Title field", "Custom title": "Custom title", + "Show lunar": "Show lunar", "Start date field": "Start date field", "End date field": "End date field", "Navigate": "Navigate", @@ -282,10 +324,13 @@ export default { "Description": "Description", "Select view": "Select view", "Reset": "Reset", + "Importable fields": "Importable fields", "Exportable fields": "Exportable fields", "Saved successfully": "Saved successfully", "Nickname": "Nickname", "Sign in": "Sign in", + "Sign in via account": "Sign in via account", + "Sign in via phone": "Sign in via phone", "Create an account": "Create an account", "Sign up": "Sign up", "Confirm password": "Confirm password", @@ -293,6 +338,9 @@ export default { "Signed up successfully. It will jump to the login page.": "Signed up successfully. It will jump to the login page.", "Password mismatch": "Password mismatch", "Users": "Users", + "Verification code": "Verification code", + "Send code": "Send code", + "Retry after {{count}} seconds": "Retry after {{count}} seconds", "Roles": "Roles", "Add role": "Add role", "Role name": "Role name", @@ -355,6 +403,7 @@ export default { "DESC": "DESC", "Add sort field": "Add sort field", "ID": "ID", + "Identifier for program usage. Support letters, numbers and underscores, must start with an letter.": "Identifier for program usage. Support letters, numbers and underscores, must start with an letter.", "Drawer": "Drawer", "Dialog": "Dialog", "Delete action": "Delete action", @@ -377,6 +426,7 @@ export default { "exists": "exists", "not exists": "not exists", "is current logged-in user": "is current logged-in user", + "is not current logged-in user": "is not current logged-in user", "=": "=", "≠": "≠", ">": ">", @@ -406,6 +456,7 @@ export default { "Create calendar block": "Create calendar block", "Create kanban block": "Create kanban block", "Grouping field": "Grouping field", + "Single select and radio fields can be used as the grouping field": "Single select and radio fields can be used as the grouping field", "Tab name": "Tab name", "Current record blocks": "Current record blocks", "Popup message": "Popup message", @@ -419,10 +470,13 @@ export default { "General permissions": "General permissions", "Global action permissions": "Global action permissions", "General action permissions": "General action permissions", - "Plugin settings permissions":"Plugin settings permissions", - "Allow to desgin pages":"Allow to desgin pages", - "Allow to manage plugins":"Allow to manage plugins", - "Allow to configure plugins":"Allow to configure plugins", + "Plugin settings permissions": "Plugin settings permissions", + "Allow to desgin pages": "Allow to desgin pages", + "Allow to manage plugins": "Allow to manage plugins", + "Allow to configure plugins": "Allow to configure plugins", + "Allows to configure interface": "Allows to configure interface", + "Allows to install, activate, disable plugins": "Allows to install, activate, disable plugins", + "Allows to configure plugins": "Allows to configure plugins", "Action display name": "Action display name", "Allow": "Allow", "Data scope": "Data scope", @@ -449,8 +503,11 @@ export default { "Use the built-in static file server": "Use the built-in static file server", "Local storage": "Local storage", "Aliyun OSS": "Aliyun OSS", - "Tencent COS": "Tencent COS", "Amazon S3": "Amazon S3", + "Tencent COS": "Tencent COS", + "Region": "Region", + "Bucket": "Bucket", + "Path": "Path", "Unsaved changes": "Unsaved changes", "Are you sure you don't want to save?": "Are you sure you don't want to save?", "Dragging": "Dragging", @@ -462,6 +519,7 @@ export default { "Dynamic value": "Dynamic value", "Current user": "Current user", "Current record": "Current record", + "Current time": "Current time", "Popup close method": "Popup close method", "Automatic close": "Automatic close", "Manually close": "Manually close", @@ -489,29 +547,40 @@ export default { "Record ID": "Record ID", "User": "User", "Field": "Field", + "Select": "Select", + "Select Field": "Select Field", "Field value changes": "Field value changes", "One to one (has one)": "One to one (has one)", "One to one (belongs to)": "One to one (belongs to)", "Use the same time zone (GMT) for all users": "Use the same time zone (GMT) for all users", - "Block title": "Block title", - "Edit block title": "Edit block title", "Province/city/area name": "Province/city/area name", - "Field component": "Field component", - "Subtable": "Subtable", - "Subform": "Subform", - "Regular expression": "Pattern", "Enabled languages": "Enabled languages", "View all plugins": "View all plugins", "Print": "Print", - 'Single select and radio fields can be used as the grouping field': 'Single select and radio fields can be used as the grouping field', - 'Sign up successfully, and automatically jump to the sign in page': 'Sign up successfully, and automatically jump to the sign in page', + "Done": "Done", + "Sign up successfully, and automatically jump to the sign in page": "Sign up successfully, and automatically jump to the sign in page", + "File manager": "File manager", + "ACL": "ACL", + "Collection manager": "Collection manager", + "Plugin manager": "Plugin manager", + "Local": "Local", + "Built-in": "Built-in", + "Marketplace": "Marketplace", + "Coming soon...": "Coming soon...", + "All plugin settings": "All plugin settings", + "Bookmark": "Bookmark", + "Manage all settings": "Manage all settings", + "Create inverse field in the target collection": "Create inverse field in the target collection", + "Inverse field name": "Inverse field name", + "Inverse field display name": "Inverse field display name", + "Bulk update": "Bulk update", "After successful bulk update": "After successful bulk update", - "All": "All", - "Update selected data?": "Update selected data?", - "Update all data?": "Update all data?", "Bulk edit": "Bulk edit", "Data will be updated": "Data will be updated", "Selected": "Selected", + "All": "All", + "Update selected data?": "Update selected data?", + "Update all data?": "Update all data?", "Remains the same": "Remains the same", "Changed to": "Changed to", "Clear": "Clear", @@ -520,6 +589,13 @@ export default { "Selector": "Selector", "Inner": "Inner", "Search and select collection": "Search and select collection", - 'Please fill in the iframe URL': 'Please fill in the iframe URL', - 'Fix block': 'Fix block' -} + "Please fill in the iframe URL": "Please fill in the iframe URL", + "Fix block": "Fix block", + "Plugin name": "Plugin name", + "Plugin tab name": "Plugin tab name", + "AutoGenId": "Auto-generated ID field", + "CreatedBy": "Recording a row's created user", + "UpdatedBy": "Recording a row's last updated user", + "CreatedAt": "Recording a row's created time ", + "UpdatedAt": "Recording a row's last updated user" +}; diff --git a/packages/core/client/src/locale/index.ts b/packages/core/client/src/locale/index.ts index f0dc9b31c..66e00ede3 100644 --- a/packages/core/client/src/locale/index.ts +++ b/packages/core/client/src/locale/index.ts @@ -1,81 +1,75 @@ -import antdEnUS from 'antd/lib/locale/en_US'; -import antdJaJP from 'antd/lib/locale/ja_JP'; -import antdRuRU from 'antd/lib/locale/ru_RU'; -import antdTrTR from 'antd/lib/locale/tr_TR'; -import antdZhCN from 'antd/lib/locale/zh_CN'; -import enUS from './en_US'; -import jaJP from './ja_JP'; -import ruRU from './ru_RU'; -import trTR from './tr_TR'; -import zhCN from './zh_CN'; - export type LocaleOptions = { label: string; - moment: string; - antd: any; - resources?: any; }; +export { default as cron } from '../schema-component/antd/cron/locale'; + export default { - 'en-US': { - label: 'English', - // https://github.com/moment/moment/blob/develop/locale/en.js - moment: 'en', - // https://github.com/ant-design/ant-design/tree/master/components/locale/en_US - antd: antdEnUS, - resources: { - client: { - ...enUS, - }, - }, - }, - 'ja-JP': { - label: '日本語', - // https://github.com/moment/moment/blob/develop/locale/ja.js - moment: 'ja', - // https://github.com/ant-design/ant-design/tree/master/components/locale/ja_JP - antd: antdJaJP, - resources: { - client: { - ...jaJP, - }, - }, - }, - 'zh-CN': { - label: '简体中文', - // https://github.com/moment/moment/blob/develop/locale/zh-cn.js - moment: 'zh-cn', - // https://github.com/ant-design/ant-design/tree/master/components/locale/zh_CN - antd: antdZhCN, - // i18next - resources: { - client: { - ...zhCN, - }, - }, - }, - 'ru-RU': { - label: 'Русский', - // https://github.com/moment/moment/blob/develop/locale/ru.js - moment: 'ru', - // https://github.com/ant-design/ant-design/tree/master/components/locale/ru_RU - antd: antdRuRU, - resources: { - client: { - ...ruRU, - }, - }, - }, - 'tr-TR': { - label: 'Türkçe', - // https://github.com/moment/moment/blob/develop/locale/tr.js - moment: 'tr', - // https://github.com/ant-design/ant-design/tree/master/components/locale/tr_TR - antd: antdTrTR, - resources: { - client: { - ...trTR, - }, - }, - }, -} as Record; + 'ar-EG': { label: 'العربية' }, + 'az-AZ': { label: 'Azərbaycan dili' }, + 'bg-BG': { label: 'Български' }, + 'bn-BD': { label: 'Bengali' }, + 'by-BY': { label: 'Беларускі' }, + 'ca-ES': { label: 'Сatalà/Espanya' }, + 'cs-CZ': { label: 'Česky' }, + 'da-DK': { label: 'Dansk' }, + 'de-DE': { label: 'Deutsch' }, + 'el-GR': { label: 'Ελληνικά' }, + 'en-GB': { label: 'English(GB)' }, + 'en-US': { label: 'English' }, + 'es-ES': { label: 'Español' }, + 'et-EE': { label: 'Estonian (Eesti)' }, + 'fa-IR': { label: 'فارسی' }, + 'fi-FI': { label: 'Suomi' }, + 'fr-BE': { label: 'Français(BE)' }, + 'fr-CA': { label: 'Français(CA)' }, + 'fr-FR': { label: 'Français' }, + 'ga-IE': { label: 'Gaeilge' }, + 'gl-ES': { label: 'Galego' }, + 'he-IL': { label: 'עברית' }, + 'hi-IN': { label: 'हिन्दी' }, + 'hr-HR': { label: 'Hrvatski jezik' }, + 'hu-HU': { label: 'Magyar' }, + 'hy-AM': { label: 'Հայերեն' }, + 'id-ID': { label: 'Bahasa Indonesia' }, + 'is-IS': { label: 'Íslenska' }, + 'it-IT': { label: 'Italiano' }, + 'ja-JP': { label: '日本語' }, + 'ka-GE': { label: 'ქართული' }, + 'kk-KZ': { label: 'Қазақ тілі' }, + 'km-KH': { label: 'ភាសាខ្មែរ' }, + // 'kmr-IQ': { label: 'kmr_IQ' }, + 'kn-IN': { label: 'ಕನ್ನಡ' }, + 'ko-KR': { label: '한국어' }, + 'ku-IQ': { label: 'کوردی' }, + 'lt-LT': { label: 'lietuvių' }, + 'lv-LV': { label: 'Latviešu valoda' }, + 'mk-MK': { label: 'македонски јазик' }, + 'ml-IN': { label: 'മലയാളം' }, + 'mn-MN': { label: 'Монгол хэл' }, + 'ms-MY': { label: 'بهاس ملايو' }, + 'nb-NO': { label: 'Norsk bokmål' }, + 'ne-NP': { label: 'नेपाली' }, + 'nl-BE': { label: 'Vlaams' }, + 'nl-NL': { label: 'Nederlands' }, + 'pl-PL': { label: 'Polski' }, + 'pt-BR': { label: 'Português brasileiro' }, + 'pt-PT': { label: 'Português' }, + 'ro-RO': { label: 'România' }, + 'ru-RU': { label: 'Русский' }, + 'si-LK': { label: 'සිංහල' }, + 'sk-SK': { label: 'Slovenčina' }, + 'sl-SI': { label: 'Slovenščina' }, + 'sr-RS': { label: 'српски језик' }, + 'sv-SE': { label: 'Svenska' }, + 'ta-IN': { label: 'Tamil' }, + 'th-TH': { label: 'ภาษาไทย' }, + 'tk-TK': { label: 'Turkmen' }, + 'tr-TR': { label: 'Türkçe' }, + 'uk-UA': { label: 'Українська' }, + 'ur-PK': { label: 'Oʻzbekcha' }, + 'vi-VN': { label: 'Tiếng Việt' }, + 'zh-CN': { label: '简体中文' }, + 'zh-HK': { label: '繁體中文(香港)' }, + 'zh-TW': { label: '繁體中文(台湾)' }, +}; diff --git a/packages/core/client/src/schema-component/antd/cron/Cron.tsx b/packages/core/client/src/schema-component/antd/cron/Cron.tsx index 87faceb3a..724e5758f 100644 --- a/packages/core/client/src/schema-component/antd/cron/Cron.tsx +++ b/packages/core/client/src/schema-component/antd/cron/Cron.tsx @@ -1,68 +1,54 @@ -import React from 'react'; -import { connect, mapProps, mapReadPretty } from '@formily/react'; -import { Cron as ReactCron, CronProps } from 'react-js-cron'; -import cronstrue from 'cronstrue'; -import 'cronstrue/locales/zh_CN'; import { css } from '@emotion/css'; +import { connect, mapReadPretty } from '@formily/react'; +import cronstrue from 'cronstrue'; +import React from 'react'; +import { Cron as ReactCron, CronProps } from 'react-js-cron'; +import { useAPIClient } from '../../../api-client'; -import localeZhCN from './locale/zh-CN'; +type ComposedCron = React.FC & {}; -const ComponentLocales = { - 'zh-CN': localeZhCN, -}; - -const ReadPrettyLocales = { - 'en-US': 'en', - 'zh-CN': 'zh_CN' -}; - -type ComposedCron = React.FC & {} - -export const Cron: ComposedCron = connect( - (props: Exclude & { onChange: (value: string) => void }) => { - const { onChange, ...rest } = props; - const locale = ComponentLocales[localStorage.getItem('NOCOBASE_LOCALE') || 'en-US']; - return ( -
& { onChange: (value: string) => void }) => { + const { onChange, ...rest } = props; + return ( +
span{ + > span { flex-shrink: 0; - margin: 0 .5em 0 0; + margin: 0 0.5em 0 0; } - > .react-js-cron-select{ - margin: 0 .5em 0 0; + > .react-js-cron-select { + margin: 0 0.5em 0 0; } } - `}> - -
- ); - }, - mapReadPretty((props) => { - const locale = ReadPrettyLocales[localStorage.getItem('NOCOBASE_LOCALE') || 'en-US']; - return props.value - ? ( - - {cronstrue.toString(props.value, { - locale, - use24HourTimeFormat: true - })} - - ) - : null; - }) -); + } + `} + > + +
+ ); +}; + +const ReadPretty = (props) => { + const api = useAPIClient(); + const locale = api.auth.getLocale(); + return props.value ? ( + + {cronstrue.toString(props.value, { + locale, + use24HourTimeFormat: true, + })} + + ) : null; +}; + +export const Cron: ComposedCron = connect(Input, mapReadPretty(ReadPretty)); export default Cron; diff --git a/packages/core/client/src/schema-component/antd/cron/locale/index.ts b/packages/core/client/src/schema-component/antd/cron/locale/index.ts new file mode 100644 index 000000000..706f4d32e --- /dev/null +++ b/packages/core/client/src/schema-component/antd/cron/locale/index.ts @@ -0,0 +1,7 @@ +import { DEFAULT_LOCALE_EN } from 'react-js-cron/dist/cjs/locale'; +import zhCN from './zh-CN'; + +export default { + 'zh-CN': zhCN, + 'en-US': DEFAULT_LOCALE_EN, +}; diff --git a/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx b/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx index 00ac06f88..881b2e5d2 100644 --- a/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx +++ b/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx @@ -13,7 +13,7 @@ import { ActionContext, SchemaComponent, useActionContext } from '../schema-comp const langs = Object.keys(locale).map((lang) => { return { - label: locale[lang].label, + label: `${locale[lang].label} (${lang})`, value: lang, }; }); diff --git a/packages/core/server/package.json b/packages/core/server/package.json index dc21578cc..0d690e10b 100644 --- a/packages/core/server/package.json +++ b/packages/core/server/package.json @@ -16,7 +16,7 @@ "chalk": "^4.1.1", "commander": "^9.2.0", "find-package-json": "^1.2.0", - "i18next": "^21.6.0", + "i18next": "^22.4.9", "koa": "^2.13.4", "koa-bodyparser": "^4.3.0", "koa-static": "^5.0.0", diff --git a/packages/core/server/src/middlewares/i18n.ts b/packages/core/server/src/middlewares/i18n.ts index c87c173ab..ff78562b3 100644 --- a/packages/core/server/src/middlewares/i18n.ts +++ b/packages/core/server/src/middlewares/i18n.ts @@ -2,12 +2,16 @@ export async function i18n(ctx, next) { const i18n = ctx.app.i18n.cloneInstance({ initImmediate: false }); ctx.i18n = i18n; ctx.t = i18n.t.bind(i18n); - const lng = - ctx.get('X-Locale') || - (ctx.request.query.locale as string) || - ctx.app.i18n.language || - ctx.acceptsLanguages().shift() || - 'en-US'; + ctx.getCurrentLocale = () => { + const lng = + ctx.get('X-Locale') || + (ctx.request.query.locale as string) || + ctx.app.i18n.language || + ctx.acceptsLanguages().shift() || + 'en-US'; + return lng; + }; + const lng = ctx.getCurrentLocale(); if (lng !== '*' && lng) { i18n.changeLanguage(lng); } diff --git a/packages/plugins/client/src/antd.ts b/packages/plugins/client/src/antd.ts new file mode 100644 index 000000000..32d663e35 --- /dev/null +++ b/packages/plugins/client/src/antd.ts @@ -0,0 +1,9 @@ +export const getAntdLocale = (lang) => { + const lng = lang.replace('-', '_'); + try { + require.resolve(`antd/lib/locale/${lng}`); + return require(`antd/lib/locale/${lng}`).default; + } catch (error) { + return require(`antd/lib/locale/en_US`).default; + } +}; diff --git a/packages/plugins/client/src/cron.ts b/packages/plugins/client/src/cron.ts new file mode 100644 index 000000000..48de142ab --- /dev/null +++ b/packages/plugins/client/src/cron.ts @@ -0,0 +1,16 @@ +export const getCronLocale = (lang: string) => { + let packageName = '@nocobase/client'; + let locale = null; + try { + const file = `${packageName}/src/locale`; + require.resolve(file); + locale = require(file).cron?.[lang]; + } catch (error) { + try { + const file = `${packageName}/lib/locale`; + require.resolve(file); + locale = require(file).cron?.[lang]; + } catch (error) {} + } + return locale || require('react-js-cron/dist/cjs/locale').DEFAULT_LOCALE_EN; +}; diff --git a/packages/plugins/client/src/cronstrue.ts b/packages/plugins/client/src/cronstrue.ts new file mode 100644 index 000000000..75de7239d --- /dev/null +++ b/packages/plugins/client/src/cronstrue.ts @@ -0,0 +1,111 @@ +const methods = [ + 'atX0SecondsPastTheMinuteGt20', + 'atX0MinutesPastTheHourGt20', + 'commaMonthX0ThroughMonthX1', + 'commaYearX0ThroughYearX1', + 'use24HourTimeFormatByDefault', + 'anErrorOccuredWhenGeneratingTheExpressionD', + 'everyMinute', + 'everyHour', + 'atSpace', + 'everyMinuteBetweenX0AndX1', + 'at', + 'spaceAnd', + 'everySecond', + 'everyX0Seconds', + 'secondsX0ThroughX1PastTheMinute', + 'atX0SecondsPastTheMinute', + 'everyX0Minutes', + 'minutesX0ThroughX1PastTheHour', + 'atX0MinutesPastTheHour', + 'everyX0Hours', + 'betweenX0AndX1', + 'atX0', + 'commaEveryDay', + 'commaEveryX0DaysOfTheWeek', + 'commaX0ThroughX1', + 'commaAndX0ThroughX1', + 'first', + 'second', + 'third', + 'fourth', + 'fifth', + 'commaOnThe', + 'spaceX0OfTheMonth', + 'lastDay', + 'commaOnTheLastX0OfTheMonth', + 'commaOnlyOnX0', + 'commaAndOnX0', + 'commaEveryX0Months', + 'commaOnlyInX0', + 'commaOnTheLastDayOfTheMonth', + 'commaOnTheLastWeekdayOfTheMonth', + 'commaDaysBeforeTheLastDayOfTheMonth', + 'firstWeekday', + 'weekdayNearestDayX0', + 'commaOnTheX0OfTheMonth', + 'commaEveryX0Days', + 'commaBetweenDayX0AndX1OfTheMonth', + 'commaOnDayX0OfTheMonth', + 'commaEveryHour', + 'commaEveryX0Years', + 'commaStartingX0', + 'daysOfTheWeek', + 'monthsOfTheYear', +]; + +const langs = { + af: 'af', + ar: 'ar', + be: 'be', + ca: 'ca', + cs: 'cs', + da: 'da', + de: 'de', + 'en-US': 'en', + es: 'es', + fa: 'fa', + fi: 'fi', + fr: 'fr', + he: 'he', + hu: 'hu', + id: 'id', + it: 'it', + 'ja-JP': 'ja', + ko: 'ko', + nb: 'nb', + nl: 'nl', + pl: 'pl', + pt_BR: 'pt_BR', + pt_PT: 'pt_PT', + ro: 'ro', + 'ru-RU': 'ru', + sk: 'sk', + sl: 'sl', + sv: 'sv', + sw: 'sw', + 'th-TH': 'th', + 'tr-TR': 'tr', + uk: 'uk', + 'zh-CN': 'zh_CN', + 'zh-TW': 'zh_TW', +}; + +export const getCronstrueLocale = (lang) => { + const lng = langs[lang] || 'en'; + const Locale = require(`cronstrue/locales/${lng}`); + let locale; + if (Locale?.default) { + locale = Locale.default.locales[lng]; + } else { + const L = Locale[lng]; + locale = new L(); + } + const items = {}; + for (const method of methods) { + try { + items[method] = locale[method](); + } catch (error) {} + } + return items; +}; diff --git a/packages/plugins/client/src/locale/index.ts b/packages/plugins/client/src/locale/index.ts new file mode 100644 index 000000000..4958260fc --- /dev/null +++ b/packages/plugins/client/src/locale/index.ts @@ -0,0 +1,3 @@ +export default { + // 'zh-CN': require('./zh-CN.example.json'), +}; diff --git a/packages/plugins/client/src/locale/zh-CN.example.json b/packages/plugins/client/src/locale/zh-CN.example.json new file mode 100644 index 000000000..91af013ac --- /dev/null +++ b/packages/plugins/client/src/locale/zh-CN.example.json @@ -0,0 +1,1237 @@ +{ + "lang": "zh-CN", + "moment": "zh-cn", + "resources": { + "error-handler": { + "unique violation": "{{field}} 字段值是唯一的", + "notNull violation": "{{field}} 字段不能为空", + "Validation error": "{{field}} 字段规则验证失败" + }, + "collection-manager": {}, + "ui-schema-storage": {}, + "sequence-field": { + "Sequence": "自动编码", + "Sequence rules": "编号规则", + "Add rule": "添加规则", + "Inputable": "可输入", + "Match rules": "输入必须匹配规则", + "Type": "类型", + "Autoincrement": "自增数字", + "Fixed text": "固定文本", + "Text content": "文本内容", + "Rule content": "规则内容", + "{{value}} Digits": "{{value}} 位数字", + "Digits": "位数", + "Start from": "起始于", + "Starts from {{value}}": "从 {{value}} 开始", + "Reset cycle": "重置周期", + "No reset": "不重置", + "Daily": "每天", + "Every Monday": "每周一", + "Monthly": "每月", + "Yearly": "每年", + "Operations": "操作", + "Customize": "自定义" + }, + "verification": { + "Verification": "验证码", + "Verification providers": "验证码提供商", + "Provider type": "提供商类型", + "Aliyun SMS": "阿里云短信服务", + "Access Key ID": "Access Key ID", + "Access Key Secret": "Access Key Secret", + "Endpoint": "接入点", + "Sign": "签名", + "Template code": "模板代码", + "Verification send failed, please try later or contact to administrator": "验证码发送失败,请稍后重试或联系管理员", + "Not a valid cellphone number, please re-enter": "不是有效的手机号,请重新输入", + "Please don't retry in {{time}} seconds": "请 {{time}} 秒后再试", + "You are trying so frequently, please slow down": "您的操作太频繁,请稍后再试", + "Verification code is invalid": "无效的验证码" + }, + "system-settings": {}, + "acl": {}, + "china-region": {}, + "export": {}, + "file-manager": {}, + "import": { + "Only one file is allowed to be uploaded": "只允许上传一个文件", + "File size cannot exceed 10M": "文件大小不能超过10M", + "Please upload the file of Excel": "请上传Excel的文件", + "Import Data": "导入数据", + "Start import": "开始导入", + "Import explain": "说明", + "Download template": "下载模板", + "Step 1: Download template": "1.下载模板", + "Step 2: Upload Excel": "2.上传完善后的表格", + "Download tip": "- 下载模板后,按格式填写数据\r\n - 只导入第一张工作表\r\n - 支持单次导入不超过10000行数据\r\n - 请勿改模板表头,防止导入失败", + "Upload placeholder": "将文件拖曳到此处或点击上传,文件大小不超过10M", + "Excel data importing": "数据导入中,请勿关闭窗口", + "Import done, total success have {{successCount}} , total failure have {{failureCount}}": "导入完成,共导入成功{{successCount}}条数据,共导入失败{{failureCount}}条数据", + "To download the failure data": "下载导入失败的数据", + "Add importable field": "添加可导入字段", + "Done": "完成", + "Yes": "是", + "No": "否", + "Field {{fieldName}} does not exist": "字段 {{fieldName}} 不存在", + "can not find value": "找不到对应值", + "password is empty": "密码为空", + "Incorrect time format": "时间格式不正确", + "Incorrect date format": "日期格式不正确", + "Incorrect email format": "邮箱格式不正确", + "Illegal percentage format": "百分比格式有误" + }, + "client": { + "Display <1><0>10<1>20<2>50<3>100 items per page": "每页显示 <1><0>10<1>20<2>50<3>100 条", + "Meet <1><0>All<1>Any conditions in the group": "满足组内 <1><0>全部<1>任意 条件", + "Open in<1><0>Modal<1>Drawer<2>Window": "在 <1><0>对话框<1>抽屉<2>窗口 内打开", + "{{count}} filter items": "{{count}} 个筛选项", + "{{count}} more items": "还有 {{count}} 项", + "Total {{count}} items": "总共 {{count}} 条", + "Today": "今天", + "Month": "月", + "Week": "周", + "Work week": "工作日", + "Day": "天", + "Agenda": "列表", + "Date": "日期", + "Time": "时间", + "Event": "事件", + "None": "无", + "System settings": "系统设置", + "System title": "系统名称", + "Logo": "Logo", + "Add menu item": "添加菜单项", + "Page": "页面", + "Name": "名称", + "Icon": "图标", + "Group": "分组", + "Link": "链接", + "Save conditions": "保存筛选条件", + "Edit menu item": "编辑菜单项", + "Move to": "移动到", + "Insert left": "在左边插入", + "Insert right": "在右边插入", + "Insert inner": "在里面插入", + "Delete": "删除", + "UI editor": "界面配置", + "Collection": "数据表", + "Collections & Fields": "数据表配置", + "Roles & Permissions": "角色和权限", + "Edit profile": "个人资料", + "Change password": "修改密码", + "Old password": "旧密码", + "New password": "新密码", + "Switch role": "切换角色", + "Super admin": "超级管理员", + "Language": "语言设置", + "Allow sign up": "允许注册", + "Enable SMS authentication": "启用短信登录和注册", + "Sign out": "注销", + "Cancel": "取消", + "Submit": "提交", + "Close": "关闭", + "Set the data scope": "设置数据范围", + "Data blocks": "数据区块", + "Table": "表格", + "Form": "表单", + "Select data source": "选择数据源", + "Calendar": "日历", + "Delete events": "删除日程", + "This event": "此日程", + "This and following events": "此日程及后续日程", + "All events": "所有日程", + "Delete this event?": "是否删除这个日程?", + "Delete Event": "删除日程", + "Kanban": "看板", + "Select grouping field": "选择分组字段", + "Media": "多媒体", + "Markdown": "Markdown", + "Wysiwyg": "富文本", + "Chart blocks": "图表区块", + "Column chart": "柱状图", + "Bar chart": "条形图", + "Line chart": "折线图", + "Pie chart": "饼图", + "Area chart": "面积图", + "Other chart": "其他图表", + "Other blocks": "其他区块", + "In configuration": "配置中", + "Chart title": "图表标题", + "Chart type": "图表类型", + "Chart config": "图表配置", + "Templates": "模板", + "Select template": "选择模板", + "Action logs": "操作日志", + "Create template": "创建模板", + "Edit markdown": "编辑 Markdown", + "Add block": "创建区块", + "Add new": "添加", + "Add record": "添加数据", + "Custom field display name": "自定义字段名称", + "Display fields": "显示字段", + "Edit record": "编辑数据", + "Delete menu item": "删除菜单项", + "Add page": "添加页面", + "Add group": "添加分组", + "Add link": "添加链接", + "Insert above": "在上面插入", + "Insert below": "在下面插入", + "Save": "保存", + "Delete block": "删除区块", + "Are you sure you want to delete it?": "你确定要删除吗?", + "This is a demo text, **supports Markdown syntax**.": "这是一段演示文本,**支持 Markdown 语法**。", + "Filter": "筛选", + "Action type": "操作类型", + "Actions": "操作", + "Insert": "新增", + "Update": "更新", + "View": "查看", + "View record": "查看数据", + "Refresh": "刷新", + "Data changes": "数据变更", + "Field name": "字段标识", + "Before change": "变更前", + "After change": "变更后", + "Delete record": "删除数据", + "Create collection": "创建数据表", + "Collection display name": "数据表名称", + "Collection name": "数据表标识", + "Inherits": "继承", + "Generate ID field automatically": "自动生成 ID 字段", + "Store the creation user of each record": "记录创建人", + "Store the last update user of each record": "记录最后更新人", + "Store the creation time of each record": "记录创建时间", + "Store the last update time of each record": "记录最后更新时间", + "More options": "更多选项", + "Records can be sorted": "可以对行记录进行排序", + "Collection template": "数据表模板", + "Calendar collection": "日历数据表", + "General collection": "普通数据表", + "Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "随机生成,可修改。支持英文、数字和下划线,必须以英文字母开头。", + "Storage type": "存储类型", + "Edit": "编辑", + "Edit collection": "编辑数据表", + "Configure fields": "配置字段", + "Configure columns": "配置字段", + "Edit field": "编辑字段", + "Override": "重写", + "Override field": "重写字段", + "Configure fields of {{title}}": "「{{title}}」的字段配置", + "Association fields filter": "关系筛选", + "PK & FK fields": "主外键字段", + "Association fields": "关系字段", + "System fields": "系统字段", + "General fields": "普通字段", + "Parent collection fields": "父表字段", + "Basic": "基本类型", + "Single line text": "单行文本", + "Long text": "多行文本", + "Phone": "手机号码", + "Email": "电子邮箱", + "Number": "数字", + "Integer": "整数", + "Percent": "百分比", + "Password": "密码", + "Advanced type": "高级类型", + "Formula": "公式", + "Formula description": "基于同一条记录中的其他字段计算出一个值。", + "Choices": "选择类型", + "Checkbox": "勾选", + "Single select": "下拉菜单(单选)", + "Multiple select": "下拉菜单(多选)", + "Radio group": "单选框", + "Checkbox group": "复选框", + "China region": "中国行政区", + "Attachment": "附件", + "Date & Time": "日期 & 时间", + "Datetime": "日期", + "Relation": "关系类型", + "Link to": "关联", + "Link to description": "用于快速创建表关系,可兼容大多数普通场景。适合非开发人员使用。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。创建后,将同时在目标数据表中生成当前数据表的关联字段。", + "Sub-table": "子表格", + "System info": "系统信息", + "Created at": "创建日期", + "Last updated at": "最后修改日期", + "Created by": "创建人", + "Last updated by": "最后修改人", + "Add field": "添加字段", + "Field display name": "字段名称", + "Field type": "字段类型", + "Field interface": "字段类型", + "Date format": "日期格式", + "Year/Month/Day": "年/月/日", + "Year-Month-Day": "年-月-日", + "Day/Month/Year": "日/月/年", + "Show time": "显示时间", + "Time format": "时间格式", + "12 hour": "12 小时制", + "24 hour": "24 小时制", + "Relationship type": "关系类型", + "Inverse relationship type": "反向关系类型", + "Source collection": "源数据表", + "Source key": "源数据表字段标识", + "Target collection": "目标数据表", + "Through collection": "中间数据表", + "Target key": "目标数据表字段标识", + "Foreign key": "外键", + "One to one": "一对一", + "One to many": "一对多", + "Many to one": "多对一", + "Many to many": "多对多", + "Foreign key 1": "外键1", + "Foreign key 2": "外键2", + "One to one description": "用于创建一对一关系,比如一个用户会有一套个人资料。", + "One to many description": "用于创建一对多关系,比如一个国家会有多个城市。作为字段存在时,它是一个子表格用于显示目标数据表的数据。创建后,会在目标数据表里自动生成一个多对一字段。", + "Many to one description": "用于创建多对一关系,比如一个城市只能属于一个国家,一个国家可以有多个城市。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。创建后,会在目标数据表里自动生成一个多对一字段。", + "Many to many description": "用于创建多对多关系,比如一个学生会有多个老师,一个老师也会有多个学生。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。", + "Generated automatically if left blank": "留空时,自动生成中间表", + "Display association fields": "显示关联表的字段", + "Field component": "字段组件", + "Subtable": "子表格", + "Subform": "子表单", + "Record picker": "数据选择器", + "Toggles the subfield mode": "切换子字段模式", + "Selector mode": "选择器模式", + "Subtable mode": "子表格模式", + "Subform mode": "子表单模式", + "Edit block title": "编辑区块标题", + "Block title": "区块标题", + "Pattern": "模式", + "Editable": "可编辑", + "Readonly": "只读(禁止编辑)", + "Easy-reading": "只读(阅读模式)", + "Add filter": "添加筛选条件", + "Add filter group": "添加筛选分组", + "Comparision": "值比较", + "is": "等于", + "is not": "不等于", + "is variable": "为动态变量", + "contains": "包含", + "does not contain": "不包含", + "starts with": "开头是", + "not starts with": "开头不是", + "ends with": "结尾是", + "not ends with": "结尾不是", + "is empty": "为空", + "is not empty": "不为空", + "Edit chart": "编辑图表", + "Add text": "添加文本", + "Filterable fields": "可筛选字段", + "Edit button": "编辑按钮", + "Hide": "隐藏", + "Enable actions": "启用操作", + "Import": "导入", + "Export": "导出", + "Customize": "自定义", + "Function": "Function", + "Popup form": "Popup form", + "Flexible popup": "Flexible popup", + "Configure actions": "配置操作", + "Display order number": "显示序号", + "Enable drag and drop sorting": "启用拖拽排序", + "Triggered when the row is clicked": "点击表格行时触发", + "Add tab": "添加标签页", + "Disable tabs": "禁用标签页", + "Details": "详情", + "Edit tab": "编辑标签页", + "Relationship blocks": "关系数据区块", + "Select record": "选择数据", + "Display name": "显示名称", + "Select icon": "选择图标", + "Custom column name": "自定义列名称", + "Edit description": "编辑描述", + "Required": "必填", + "Unique": "不允许重复", + "Label field": "标签字段", + "Default is the ID field": "默认为 ID 字段", + "Set default sorting rules": "设置排序规则", + "Set validation rules": "设置验证规则", + "Max length": "最大长度", + "Min length": "最小长度", + "Maximum": "最大值", + "Minimum": "最小值", + "Max length must greater than min length": "最大长度必须大于最小长度", + "Min length must less than max length": "最小长度必须小于最大长度", + "Maximum must greater than minimum": "最大值必须大于最小值", + "Minimum must less than maximum": "最小值必须小于最大值", + "Validation rule": "验证规则", + "Add validation rule": "新增验证规则", + "Format": "格式", + "Regular expression": "正则表达式", + "Error message": "错误消息", + "Length": "长度", + "The field value cannot be greater than ": "数值不能大于", + "The field value cannot be less than ": "数值不能小于", + "The field value is not an integer number": "数字不是整数", + "Set default value": "设置默认值", + "Default value": "默认值", + "is before": "早于", + "is after": "晚于", + "is on or after": "不早于", + "is on or before": "不晚于", + "Upload": "上传", + "Select level": "选择层级", + "Province": "省", + "City": "市", + "Area": "区/县", + "Street": "乡镇/街道", + "Village": "村/居委会", + "Must select to the last level": "必须选到最后一级", + "Move {{title}} to": "将 {{title}} 移动到", + "Target position": "目标位置", + "After": "之后", + "Before": "之前", + "Add {{type}} before \"{{title}}\"": "在 \"{{title}}\" 前插入{{type}}", + "Add {{type}} after \"{{title}}\"": "在 \"{{title}}\" 前插入{{type}}", + "Add {{type}} in \"{{title}}\"": "在 \"{{title}}\" 里插入{{type}}", + "Original name": "原名称", + "Custom name": "自定义名称", + "Custom Title": "自定义标题", + "Options": "选项", + "Option value": "选项值", + "Option label": "选项标签", + "Color": "颜色", + "Add option": "添加选项", + "Related collection": "关系表", + "Allow linking to multiple records": "允许关联多条记录", + "Allow uploading multiple files": "允许上传多个文件", + "Configure calendar": "配置日历", + "Title field": "标题字段", + "Custom title": "自定义标题", + "Show lunar": "展示农历", + "Start date field": "开始日期字段", + "End date field": "结束日期字段", + "Navigate": "分页", + "Title": "标题", + "Description": "描述", + "Select view": "切换视图", + "Reset": "重置", + "Importable fields": "可导入字段", + "Exportable fields": "可导出字段", + "Saved successfully": "保存成功", + "Nickname": "昵称", + "Sign in": "登录", + "Sign in via account": "账号密码登录", + "Sign in via phone": "手机号登录", + "Create an account": "注册账号", + "Sign up": "注册", + "Confirm password": "确认密码", + "Log in with an existing account": "使用已有账号登录", + "Signed up successfully. It will jump to the login page.": "注册成功,将跳转登录页。", + "Password mismatch": "重复密码不匹配", + "Users": "用户", + "Verification code": "验证码", + "Send code": "发送验证码", + "Retry after {{count}} seconds": "{{count}} 秒后重试", + "Roles": "角色", + "Add role": "添加角色", + "Role name": "角色名称", + "Configure": "配置", + "Configure permissions": "配置权限", + "Edit role": "编辑角色", + "Action permissions": "数据表操作权限", + "Menu permissions": "菜单访问权限", + "Menu item name": "菜单名称", + "Allow access": "允许访问", + "Action name": "操作名称", + "Allow action": "允许操作", + "Action scope": "可操作数据范围", + "Operate on new data": "对新增数据操作", + "Operate on existing data": "对已有数据操作", + "Yes": "是", + "No": "否", + "Red": "薄暮", + "Magenta": "法式洋红", + "Volcano": "火山", + "Orange": "日暮", + "Gold": "金盏花", + "Lime": "青柠", + "Green": "极光绿", + "Cyan": "明青", + "Blue": "拂晓蓝", + "Geek blue": "极客蓝", + "Purple": "酱紫", + "Default": "默认", + "Add card": "添加卡片", + "edit title": "修改标题", + "Turn pages": "翻页", + "Others": "其他", + "Save as template": "保存为模板", + "Block templates": "区块模板", + "Convert reference to duplicate": "模板引用转为复制", + "Template name": "模板名称", + "Block type": "区块类型", + "Action column": "操作列", + "Records per page": "每页显示数量", + "(Fields only)": "(仅字段)", + "Button title": "按钮标题", + "Button icon": "按钮图标", + "Submitted successfully": "提交成功", + "Operation succeeded": "操作成功", + "Operation failed": "操作失败", + "Open mode": "打开方式", + "Popup size": "弹窗尺寸", + "Small": "较窄", + "Middle": "中等", + "Large": "较宽", + "Menu item title": "菜单项名称", + "Menu item icon": "菜单项图标", + "Target": "目标", + "Position": "位置", + "Insert before": "在前面插入", + "Insert after": "在后面插入", + "UI Editor": "界面配置", + "ASC": "升序", + "DESC": "降序", + "Add sort field": "添加排序字段", + "ID": "ID", + "Identifier for program usage. Support letters, numbers and underscores, must start with an letter.": "用于程序使用的标识符,支持字母、数字和下划线,必须以字母开头。", + "Drawer": "抽屉", + "Dialog": "对话框", + "Delete action": "删除操作", + "Custom column title": "自定义列标题", + "Original title: ": "原始标题: ", + "Delete table column": "删除列", + "Skip required validation": "跳过必填校验", + "Form values": "表单值", + "Fields values": "字段值", + "When submitting the following fields, the saved values are": "提交以下字段时,保存的值为", + "After successful submission": "提交成功后", + "Then": "然后", + "Stay on current page": "停留在当前页面", + "Redirect to": "跳转到", + "Save action": "保存操作", + "Exists": "存在", + "Filename": "文件名", + "Add condition": "添加条件", + "Add condition group": "添加条件分组", + "exists": "存在", + "not exists": "不存在", + "is current logged-in user": "为当前登录用户", + "is not current logged-in user": "不为当前登录用户", + "=": "=", + "≠": "≠", + ">": ">", + "≥": "≥", + "<": "<", + "≤": "≤", + "Role UID": "角色标识", + "Precision": "精确度", + "Formula mode": "计算方式", + "Expression": "表达式", + "Input +, -, *, /, ( ) to calculate, input @ to open field variables.": "英文输入+、-、*、/、( ) 进行运算,输入@打开可用字段变量。", + "Formula error.": "公式验证错误。", + "Accept": "文件格式", + "Rich Text": "富文本", + "Junction collection": "中间表", + "Leave it blank, unless you need a custom intermediate table": "默认留空,除非你需要一个自定义的中间表", + "Fields": "字段", + "Edit field title": "编辑字段标题", + "Field title": "字段标题", + "Original field title: ": "原始字段标题:", + "Edit tooltip": "编辑提示信息", + "Delete field": "删除字段", + "Select collection": "选择数据表", + "Blank block": "空区块", + "Duplicate template": "复制模板", + "Reference template": "引用模板", + "Create calendar block": "创建日历区块", + "Create kanban block": "创建看板区块", + "Grouping field": "分组字段", + "Single select and radio fields can be used as the grouping field": "数据表的单选字段可以作为分组字段", + "Tab name": "标签名称", + "Current record blocks": "当前数据区块", + "Popup message": "弹窗提示消息", + "Delete role": "删除角色", + "Role display name": "角色名称", + "Default role": "默认角色", + "All collections use general action permissions by default; permission configured individually will override the default one.": "所有数据表都默认使用通用数据操作权限;同时,可以针对每个数据表单独配置权限。", + "Allows configuration of the whole system, including UI, collections, permissions, etc.": "允许配置系统,包括界面配置、数据表配置、权限配置、系统配置等全部配置项", + "New menu items are allowed to be accessed by default.": "新增菜单项默认允许访问", + "Global permissions": "全局配置", + "General permissions": "通用配置", + "Global action permissions": "全局操作权限", + "General action permissions": "通用操作权限", + "Plugin settings permissions": "插件配置权限", + "Allow to desgin pages": "允许界面配置", + "Allow to manage plugins": "允许管理插件", + "Allow to configure plugins": "允许管理配置中心", + "Allows to configure interface": "允许配置界面", + "Allows to install, activate, disable plugins": "允许安装、激活、禁用插件", + "Allows to configure plugins": "允许配置插件", + "Action display name": "操作名称", + "Allow": "允许", + "Data scope": "数据范围", + "Action on new records": "对新增数据操作", + "Action on existing records": "对已有数据操作", + "All records": "所有数据", + "Own records": "自己的数据", + "Permission policy": "权限策略", + "Individual": "单独配置", + "General": "通用配置", + "Accessible": "允许访问", + "Configure permission": "配置权限", + "Action permission": "操作权限", + "Field permission": "字段权限", + "Scope name": "数据范围名称", + "File storages": "文件存储", + "Storage display name": "文件存储名称", + "Storage name": "文件存储标识", + "Default storage": "默认存储", + "Add storage": "添加文件存储", + "Edit storage": "编辑文件存储", + "Storage base URL": "Base URL", + "Destination": "存储路径", + "Use the built-in static file server": "使用内置静态文件服务", + "Local storage": "本地存储", + "Aliyun OSS": "阿里云 OSS", + "Amazon S3": "亚马逊 S3", + "Tencent COS": "腾讯云 COS", + "Region": "区域", + "Bucket": "存储桶", + "Path": "路径(相对)", + "Unsaved changes": "未保存修改", + "Are you sure you don't want to save?": "你确定不保存修改吗?", + "Dragging": "拖拽中", + "Popup": "打开弹窗", + "Trigger workflow": "触发工作流", + "Request API": "请求API", + "Assign field values": "字段赋值", + "Constant value": "静态值", + "Dynamic value": "动态值", + "Current user": "当前用户", + "Current record": "当前记录", + "Current time": "当前时间", + "Popup close method": "弹窗关闭方式", + "Automatic close": "自动关闭", + "Manually close": "手动关闭", + "After successful update": "更新成功后", + "Save record": "保存数据", + "Updated successfully": "更新成功", + "After successful save": "保存成功后", + "After clicking the custom button, the following field values will be assigned according to the following form.": "点击当前自定义按钮时,以下字段值将按照以下表单赋值。", + "After clicking the custom button, the following fields of the current record will be saved according to the following form.": "点击当前自定义按钮时,当前数据以下字段将按照以下表单保存。", + "Button background color": "按钮颜色", + "Highlight": "高亮", + "Danger red": "红色", + "Custom request": "自定义请求", + "Request settings": "请求设置", + "Request URL": "请求地址", + "Request method": "请求方法", + "Request query parameters": "请求查询参数(JSON格式)", + "Request headers": "请求头参数(JSON格式)", + "Request body": "请求体(JSON格式)", + "Request success": "请求成功", + "Invalid JSON format": "非法JSON格式", + "After successful request": "请求成功之后", + "Add exportable field": "添加可导出字段", + "Audit logs": "操作记录", + "Record ID": "数据 ID", + "User": "用户", + "Field": "字段", + "Select": "选择", + "Select Field": "选择字段", + "Field value changes": "变更记录", + "One to one (has one)": "一对一(has one)", + "One to one (belongs to)": "一对一(belongs to)", + "Use the same time zone (GMT) for all users": "所有用户使用同一时区 (格林尼治标准时间)", + "Province/city/area name": "省市区名称", + "Enabled languages": "启用的语言", + "View all plugins": "查看所有插件", + "Print": "打印", + "Done": "完成", + "Sign up successfully, and automatically jump to the sign in page": "注册成功,即将跳转到登录页面", + "File manager": "文件管理器", + "ACL": "访问控制", + "Collection manager": "数据表管理", + "Plugin manager": "插件管理器", + "Local": "本地", + "Built-in": "内置", + "Marketplace": "插件市场", + "Coming soon...": "敬请期待...", + "All plugin settings": "所有插件配置", + "Bookmark": "书签", + "Manage all settings": "管理所有配置", + "Create inverse field in the target collection": "在目标数据表里创建反向关系字段", + "Inverse field name": "反向关系字段标识", + "Inverse field display name": "反向关系字段名称", + "Bulk update": "批量更新", + "After successful bulk update": "批量成功更新后", + "Bulk edit": "批量编辑", + "Data will be updated": "更新的数据", + "Selected": "选中", + "All": "所有", + "Update selected data?": "更新选中的数据吗?", + "Update all data?": "更新全部数据吗?", + "Remains the same": "不更新", + "Changed to": "修改为", + "Clear": "清空", + "Add attach": "增加关联", + "Please select the records to be updated": "请选择要更新的记录", + "Selector": "选择器", + "Inner": "里面", + "Search and select collection": "搜索并选择数据表", + "Please fill in the iframe URL": "请填写嵌入的地址", + "Fix block": "固定区块", + "Plugin name": "插件", + "Plugin tab name": "插件标签页" + }, + "audit-logs": {}, + "duplicator": { + "Select Import data": "请选择导入数据", + "Select Import Plugins": "请选择导入插件", + "Select User Collections": "请选择用户数据", + "Basic Data": "基础数据", + "Optional Data": "可选数据", + "User Data": "用户数据" + }, + "iframe-block": {}, + "math-formula-field": {}, + "excel-formula-field": {}, + "ui-routes-storage": {}, + "workflow": { + "Workflow": "工作流", + "Execution history": "执行历史", + "Executed": "已执行", + "Trigger type": "触发方式", + "Status": "状态", + "On": "启用", + "Off": "停用", + "Version": "版本", + "Copy to new version": "复制到新版本", + "Duplicate": "复制", + "Loading": "加载中", + "Load failed": "加载失败", + "Trigger": "触发器", + "Trigger variables": "触发器变量", + "Trigger data": "触发数据", + "Trigger time": "触发时间", + "Triggered at": "触发时间", + "Collection event": "数据表事件", + "Trigger on": "触发时机", + "After record added": "新增数据后", + "After record updated": "更新数据后", + "After record added or updated": "新增或更新数据后", + "After record deleted": "删除数据后", + "Changed fields": "发生变动的字段", + "Triggered only if one of the selected fields changes. If unselected, it means that it will be triggered when any field changes. When record is added or deleted, any field is considered to have been changed.": "只有被选中的某个字段发生变动时才会触发。如果不选择,则表示任何字段变动时都会触发。新增或删除数据时,任意字段都被认为发生变动。", + "Only triggers when match conditions": "满足以下条件才触发", + "Schedule event": "定时任务", + "Trigger mode": "触发模式", + "Based on certain date": "自定义时间", + "Based on date field of collection": "根据数据表时间字段", + "Starts on": "开始于", + "Ends on": "结束于", + "No end": "不结束", + "Exactly at": "当时", + "Repeat mode": "重复模式", + "Repeat limit": "重复次数", + "No limit": "不限", + "Seconds": "秒", + "Minutes": "分钟", + "Hours": "小时", + "Days": "天", + "Weeks": "周", + "Months": "月", + "No repeat": "不重复", + "Every": "每", + "By minute": "按分钟", + "By hour": "按小时", + "By day": "按天", + "By week": "按周", + "By month": "按月", + "By field": "数据表字段", + "By custom date": "自定义时间", + "Advanced": "高级模式", + "End": "结束", + "Node result": "节点数据", + "Constant": "常量", + "Null": "空值", + "Boolean": "逻辑值", + "String": "字符串", + "Calculator": "运算", + "Arithmetic calculation": "算术运算", + "String operation": "字符串", + "Executed at": "执行于", + "Queueing": "队列中", + "On going": "进行中", + "Succeeded": "成功", + "Failed": "失败", + "Pending": "等待处理", + "Canceled": "已取消", + "This node contains branches, deleting will also be preformed to them, are you sure?": "节点包含分支,将同时删除其所有分支下的子节点,确定继续?", + "Control": "流程控制", + "Collection operations": "数据表操作", + "Extended types": "扩展类型", + "Node type": "节点类型", + "Calculation": "运算", + "Configure calculation": "配置运算", + "Calculation result": "运算结果", + "True": "真", + "False": "假", + "concat": "连接", + "Condition": "条件判断", + "Mode": "模式", + "Continue when \"Yes\"": "“是”则继续", + "Branch into \"Yes\" and \"No\"": "“是”和“否”分别继续", + "Conditions": "条件配置", + "Parallel branch": "分支", + "Add branch": "增加分支", + "All succeeded": "全部成功", + "Any succeeded": "任意成功", + "Any succeeded or failed": "任意成功或失败", + "Continue after all branches succeeded": "全部分支都成功后才能继续", + "Continue after any branch succeeded": "任意分支成功后就继续", + "Continue after any branch succeeded, or exit after any branch failed": "任意分支成功继续,或失败后退出", + "Delay": "延时", + "Duration": "时长", + "End Status": "到时状态", + "Select status": "选择状态", + "Succeed and continue": "通过并继续", + "Fail and exit": "失败并退出", + "Create record": "新增数据", + "Update record": "更新数据", + "Query record": "查询数据", + "Multiple records": "多条数据", + "Please select collection first": "请先选择数据表", + "Only update records matching conditions": "只更新满足条件的数据", + "Fields that are not assigned a value will be set to the default value, and those that do not have a default value are set to null.": "未被赋值的字段将被设置为默认值,没有默认值的设置为空值。", + "Trigger in executed workflow cannot be modified": "已经执行过工作流的触发器不能被修改", + "Node in executed workflow cannot be modified": "已经执行过工作流中的节点不能被修改", + "Can not delete": "无法删除", + "The result of this node has been referenced by other nodes ({{nodes}}), please remove the usage before deleting.": "该节点的执行结果已被其他节点({{nodes}})引用,删除前请先移除引用。", + "HTTP request": "HTTP 请求", + "URL": "地址", + "You can use the above available variables in URL.": "您可以在地址中使用使用上面的可用变量。", + "Request headers": "请求头", + "Name(e.g. Content-Type)": "名称(例如 Content-Type)", + "Value(e.g. Application/json)": "值(例如 Application/json)", + "Add request header": "添加请求头", + "HTTP method": "HTTP 方法", + "Request data": "请求数据", + "Input request data": "输入请求数据", + "You can use the above available variables in request data.": "您可以在请求数据中使用上面的可用变量。", + "Copy success!": "拷贝成功!", + "Copy variable output template statement": "拷贝变量输出模版语句", + "Default headers is Content-Type: application/json": "默认请求头是 Content-Type: application/json", + "Ignore fail request and continue workflow": "忽略失败请求并继续工作流", + "Syntax see": "语法参考", + "Show available variable tool": "显示可用变量工具" + }, + "users": { + "The email is incorrect, please re-enter": "邮箱有误,请重新输入", + "Please fill in your email address": "请填写邮箱", + "The password is incorrect, please re-enter": "密码有误,请重新输入", + "Not a valid cellphone number, please re-enter": "不是有效的手机号,请重新输入", + "The phone number has been registered, please login directly": "手机号已注册,请直接登录", + "The phone number is not registered, please register first": "手机号未注册,请先注册" + }, + "sample-hello": {}, + "oidc": { + "Issuer": "Issuer", + "Enable": "启用", + "Actions": "操作", + "Delete": "删除", + "Edit": "编辑", + "Button title": "登录按钮标题", + "OIDC manager": "OIDC 管理", + "OIDC Providers": "OIDC 身份提供者", + "Provider name": "名称", + "Client id": "客户端 id", + "Client secret": "客户端 secret", + "Openid configuration": "服务发现地址", + "Authorization endpoint": "授权端点", + "Access token endpoint": "令牌端点", + "JWKS endpoint": "JWKS 公钥端点", + "Userinfo endpoint": "用户信息端点", + "Redirect url": "重定向地址", + "Logout endpoint": "登出端点", + "Id token sign alg": "Id token 签名算法", + "Add provider": "添加", + "Edit provider": "编辑", + "Delete provider": "删除", + "Sign in button name, which will be displayed on the sign in page": "登录按钮名称,将在登录页中显示" + }, + "saml": { + "Edit": "编辑", + "Delete": "删除", + "Cancel": "取消", + "Submit": "提交", + "Actions": "操作", + "Title": "名称", + "Enable": "启用", + "Button title": "登录按钮标题", + "SAML manager": "SAML 管理", + "SAML Providers": "SAML 身份提供者", + "Redirect url": "重定向地址", + "SP entity id": "应用唯一标识(SP Entity ID)", + "Add provider": "添加", + "Edit provider": "编辑", + "Client id": "客户端 id", + "Entity id or issuer": "IdP 唯一标识", + "Login Url": "登录地址", + "Public cert": "公钥", + "Delete provider": "删除", + "Are you sure you want to delete it?": "你确定要删除它吗?", + "Sign in button name, which will be displayed on the sign in page": "登录按钮名称,将在登录页中显示" + }, + "map": { + "Map-based geometry": "基于地图的几何图形", + "Map type": "地图类型", + "Point": "点", + "Line": "线", + "Circle": "圆", + "Polygon": "多边形", + "Access key": "访问密钥", + "securityJsCode or serviceHost": "securityJsCode 或 serviceHost", + "AMap": "高德地图", + "Google Maps": "谷歌地图", + "Clear": "清空", + "Click to select the starting point and double-click to end the drawing": "点击选择起点,双击结束绘制", + "Clear the canvas": "清空画布", + "Are you sure to clear the canvas?": "您确定要清空画布吗?", + "Confirm": "确定", + "Cancel": "取消", + "Enter keywords to search": "输入地方名关键字搜索(必须包含省/市)", + "The AccessKey is incorrect, please check it": "访问密钥不正确,请检查", + "Please configure the AMap securityCode or serviceHost correctly": "请正确配置高德地图 securityCode 或 serviceHost", + "Map Manager": "地图管理", + "Configuration": "配置", + "Saved successfully": "保存成功", + "Saved failed": "保存失败", + "Edit": "编辑", + "Save": "保存", + "Please configure the AccessKey and SecurityJsCode first": "请先配置 AccessKey 和 SecurityJsCode", + "Go to the configuration page": "前往配置页面", + "Zoom": "缩放", + "Set default zoom level": "设置默认缩放级别", + "The default zoom level of the map": "地图默认缩放级别", + "Edit field title": "编辑字段标题", + "Field title": "字段标题", + "Edit tooltip": "编辑提示信息", + "Delete field": "删除字段", + "Required": "必填", + "Pattern": "模式", + "Editable": "可编辑", + "Readonly": "只读(禁止编辑)", + "Easy-reading": "只读(阅读模式)", + "Edit description": "编辑描述" + }, + "snapshot-field": { + "Detail": "详情", + "Snapshot": "快照", + "Add block": "创建区块", + "Snapshot to description": "用于创建表的快照,保存指向的表的当前数据,仅在其所属的记录创建时保存,后续不再更新。", + "View record": "查看数据", + "Allow linking to multiple records": "允许关联多条记录", + "Target collection": "关系表" + }, + "graph-collection-manager": { + "Graph Collection": "图形化数据表", + "Collection List": "数据表列表", + "Full Screen": "全屏", + "Collection Search": "表筛选", + "Create Collection": "创建数据表", + "All Fields": "全部", + "Association Fields": "关系字段", + "All relationships": "所有关系", + "Entity relationship only": "实体关系", + "Inheritance relationship only": "继承关系", + "Graphical interface": "图形化界面", + "Auto layout": "自动布局", + "Selection": "选择模式" + } + }, + "antd": { + "locale": "zh-cn", + "Pagination": { + "items_per_page": "条/页", + "jump_to": "跳至", + "jump_to_confirm": "确定", + "page": "页", + "prev_page": "上一页", + "next_page": "下一页", + "prev_5": "向前 5 页", + "next_5": "向后 5 页", + "prev_3": "向前 3 页", + "next_3": "向后 3 页", + "page_size": "页码" + }, + "DatePicker": { + "lang": { + "placeholder": "请选择日期", + "yearPlaceholder": "请选择年份", + "quarterPlaceholder": "请选择季度", + "monthPlaceholder": "请选择月份", + "weekPlaceholder": "请选择周", + "rangePlaceholder": ["开始日期", "结束日期"], + "rangeYearPlaceholder": ["开始年份", "结束年份"], + "rangeMonthPlaceholder": ["开始月份", "结束月份"], + "rangeQuarterPlaceholder": ["开始季度", "结束季度"], + "rangeWeekPlaceholder": ["开始周", "结束周"], + "locale": "zh_CN", + "today": "今天", + "now": "此刻", + "backToToday": "返回今天", + "ok": "确定", + "timeSelect": "选择时间", + "dateSelect": "选择日期", + "weekSelect": "选择周", + "clear": "清除", + "month": "月", + "year": "年", + "previousMonth": "上个月 (翻页上键)", + "nextMonth": "下个月 (翻页下键)", + "monthSelect": "选择月份", + "yearSelect": "选择年份", + "decadeSelect": "选择年代", + "yearFormat": "YYYY年", + "dayFormat": "D日", + "dateFormat": "YYYY年M月D日", + "dateTimeFormat": "YYYY年M月D日 HH时mm分ss秒", + "previousYear": "上一年 (Control键加左方向键)", + "nextYear": "下一年 (Control键加右方向键)", + "previousDecade": "上一年代", + "nextDecade": "下一年代", + "previousCentury": "上一世纪", + "nextCentury": "下一世纪" + }, + "timePickerLocale": { "placeholder": "请选择时间", "rangePlaceholder": ["开始时间", "结束时间"] } + }, + "TimePicker": { "placeholder": "请选择时间", "rangePlaceholder": ["开始时间", "结束时间"] }, + "Calendar": { + "lang": { + "placeholder": "请选择日期", + "yearPlaceholder": "请选择年份", + "quarterPlaceholder": "请选择季度", + "monthPlaceholder": "请选择月份", + "weekPlaceholder": "请选择周", + "rangePlaceholder": ["开始日期", "结束日期"], + "rangeYearPlaceholder": ["开始年份", "结束年份"], + "rangeMonthPlaceholder": ["开始月份", "结束月份"], + "rangeQuarterPlaceholder": ["开始季度", "结束季度"], + "rangeWeekPlaceholder": ["开始周", "结束周"], + "locale": "zh_CN", + "today": "今天", + "now": "此刻", + "backToToday": "返回今天", + "ok": "确定", + "timeSelect": "选择时间", + "dateSelect": "选择日期", + "weekSelect": "选择周", + "clear": "清除", + "month": "月", + "year": "年", + "previousMonth": "上个月 (翻页上键)", + "nextMonth": "下个月 (翻页下键)", + "monthSelect": "选择月份", + "yearSelect": "选择年份", + "decadeSelect": "选择年代", + "yearFormat": "YYYY年", + "dayFormat": "D日", + "dateFormat": "YYYY年M月D日", + "dateTimeFormat": "YYYY年M月D日 HH时mm分ss秒", + "previousYear": "上一年 (Control键加左方向键)", + "nextYear": "下一年 (Control键加右方向键)", + "previousDecade": "上一年代", + "nextDecade": "下一年代", + "previousCentury": "上一世纪", + "nextCentury": "下一世纪" + }, + "timePickerLocale": { "placeholder": "请选择时间", "rangePlaceholder": ["开始时间", "结束时间"] } + }, + "global": { "placeholder": "请选择" }, + "Table": { + "filterTitle": "筛选", + "filterConfirm": "确定", + "filterReset": "重置", + "filterEmptyText": "无筛选项", + "filterCheckall": "全选", + "filterSearchPlaceholder": "在筛选项中搜索", + "selectAll": "全选当页", + "selectInvert": "反选当页", + "selectNone": "清空所有", + "selectionAll": "全选所有", + "sortTitle": "排序", + "expand": "展开行", + "collapse": "关闭行", + "triggerDesc": "点击降序", + "triggerAsc": "点击升序", + "cancelSort": "取消排序" + }, + "Modal": { "okText": "确定", "cancelText": "取消", "justOkText": "知道了" }, + "Popconfirm": { "cancelText": "取消", "okText": "确定" }, + "Transfer": { + "searchPlaceholder": "请输入搜索内容", + "itemUnit": "项", + "itemsUnit": "项", + "remove": "删除", + "selectCurrent": "全选当页", + "removeCurrent": "删除当页", + "selectAll": "全选所有", + "removeAll": "删除全部", + "selectInvert": "反选当页" + }, + "Upload": { + "uploading": "文件上传中", + "removeFile": "删除文件", + "uploadError": "上传错误", + "previewFile": "预览文件", + "downloadFile": "下载文件" + }, + "Empty": { "description": "暂无数据" }, + "Icon": { "icon": "图标" }, + "Text": { "edit": "编辑", "copy": "复制", "copied": "复制成功", "expand": "展开" }, + "PageHeader": { "back": "返回" }, + "Form": { + "optional": "(可选)", + "defaultValidateMessages": { + "default": "字段验证错误${label}", + "required": "请输入${label}", + "enum": "${label}必须是其中一个[${enum}]", + "whitespace": "${label}不能为空字符", + "date": { + "format": "${label}日期格式无效", + "parse": "${label}不能转换为日期", + "invalid": "${label}是一个无效日期" + }, + "types": { + "string": "${label}不是一个有效的${type}", + "method": "${label}不是一个有效的${type}", + "array": "${label}不是一个有效的${type}", + "object": "${label}不是一个有效的${type}", + "number": "${label}不是一个有效的${type}", + "date": "${label}不是一个有效的${type}", + "boolean": "${label}不是一个有效的${type}", + "integer": "${label}不是一个有效的${type}", + "float": "${label}不是一个有效的${type}", + "regexp": "${label}不是一个有效的${type}", + "email": "${label}不是一个有效的${type}", + "url": "${label}不是一个有效的${type}", + "hex": "${label}不是一个有效的${type}" + }, + "string": { + "len": "${label}须为${len}个字符", + "min": "${label}最少${min}个字符", + "max": "${label}最多${max}个字符", + "range": "${label}须在${min}-${max}字符之间" + }, + "number": { + "len": "${label}必须等于${len}", + "min": "${label}最小值为${min}", + "max": "${label}最大值为${max}", + "range": "${label}须在${min}-${max}之间" + }, + "array": { + "len": "须为${len}个${label}", + "min": "最少${min}个${label}", + "max": "最多${max}个${label}", + "range": "${label}数量须在${min}-${max}之间" + }, + "pattern": { "mismatch": "${label}与模式不匹配${pattern}" } + } + }, + "Image": { "preview": "预览" } + }, + "cronstrue": { + "atX0SecondsPastTheMinuteGt20": null, + "atX0MinutesPastTheHourGt20": null, + "commaMonthX0ThroughMonthX1": null, + "commaYearX0ThroughYearX1": ", 从%s年至%s年", + "use24HourTimeFormatByDefault": false, + "anErrorOccuredWhenGeneratingTheExpressionD": "生成表达式描述时发生了错误,请检查cron表达式语法。", + "everyMinute": "每分钟", + "everyHour": "每小时", + "atSpace": "在", + "everyMinuteBetweenX0AndX1": "在 %s 至 %s 之间的每分钟", + "at": "在", + "spaceAnd": " 和", + "everySecond": "每秒", + "everyX0Seconds": "每隔 %s 秒", + "secondsX0ThroughX1PastTheMinute": "在每分钟的第 %s 到 %s 秒", + "atX0SecondsPastTheMinute": "在每分钟的第 %s 秒", + "everyX0Minutes": "每隔 %s 分钟", + "minutesX0ThroughX1PastTheHour": "在每小时的第 %s 到 %s 分钟", + "atX0MinutesPastTheHour": "在每小时的第 %s 分钟", + "everyX0Hours": "每隔 %s 小时", + "betweenX0AndX1": "在 %s 和 %s 之间", + "atX0": "在%s", + "commaEveryDay": ", 每天", + "commaEveryX0DaysOfTheWeek": ", 每周的每 %s 天", + "commaX0ThroughX1": ", %s至%s", + "commaAndX0ThroughX1": ", 和%s至%s", + "first": "第一个", + "second": "第二个", + "third": "第三个", + "fourth": "第四个", + "fifth": "第五个", + "commaOnThe": ", 限每月的", + "spaceX0OfTheMonth": "%s", + "lastDay": "本月最后一天", + "commaOnTheLastX0OfTheMonth": ", 限每月的最后一个%s", + "commaOnlyOnX0": ", 仅%s", + "commaAndOnX0": ", 并且为%s", + "commaEveryX0Months": ", 每隔 %s 个月", + "commaOnlyInX0": ", 仅限%s", + "commaOnTheLastDayOfTheMonth": ", 限每月的最后一天", + "commaOnTheLastWeekdayOfTheMonth": ", 限每月的最后一个工作日", + "commaDaysBeforeTheLastDayOfTheMonth": ", 限每月最后%s天", + "firstWeekday": "第一个工作日", + "weekdayNearestDayX0": "最接近 %s 号的工作日", + "commaOnTheX0OfTheMonth": ", 限每月的%s", + "commaEveryX0Days": ", 每隔 %s 天", + "commaBetweenDayX0AndX1OfTheMonth": ", 限每月的 %s 至 %s 之间", + "commaOnDayX0OfTheMonth": ", 限每月%s", + "commaEveryX0Years": ", 每隔 %s 年", + "commaStartingX0": ", %s开始", + "daysOfTheWeek": ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"], + "monthsOfTheYear": [ + "一月", + "二月", + "三月", + "四月", + "五月", + "六月", + "七月", + "八月", + "九月", + "十月", + "十一月", + "十二月" + ] + }, + "cron": { + "everyText": "每", + "emptyMonths": "每月", + "emptyMonthDays": "每日(月)", + "emptyMonthDaysShort": "每日", + "emptyWeekDays": "每天(周)", + "emptyWeekDaysShort": "每天(周)", + "emptyHours": "每小时", + "emptyMinutes": "每分钟", + "emptyMinutesForHourPeriod": "每", + "yearOption": "年", + "monthOption": "月", + "weekOption": "周", + "dayOption": "天", + "hourOption": "小时", + "minuteOption": "分钟", + "rebootOption": "重启", + "prefixPeriod": "每", + "prefixMonths": "的", + "prefixMonthDays": "的", + "prefixWeekDays": "的", + "prefixWeekDaysForMonthAndYearPeriod": "并且", + "prefixHours": "的", + "prefixMinutes": ":", + "prefixMinutesForHourPeriod": "的", + "suffixMinutesForHourPeriod": "分钟", + "errorInvalidCron": "不符合 cron 规则的表达式", + "clearButtonText": "清空", + "weekDays": ["周日", "周一", "周二", "周三", "周四", "周五", "周六"], + "months": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], + "altWeekDays": ["周日", "周一", "周二", "周三", "周四", "周五", "周六"], + "altMonths": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"] + } +} diff --git a/packages/plugins/client/src/moment-locale.ts b/packages/plugins/client/src/moment-locale.ts new file mode 100644 index 000000000..932259a1d --- /dev/null +++ b/packages/plugins/client/src/moment-locale.ts @@ -0,0 +1,141 @@ +const locales = { + af: 'af', + 'ar-dz': 'ar-dz', + 'ar-kw': 'ar-kw', + 'ar-ly': 'ar-ly', + 'ar-ma': 'ar-ma', + 'ar-sa': 'ar-sa', + 'ar-tn': 'ar-tn', + ar: 'ar', + az: 'az', + be: 'be', + bg: 'bg', + bm: 'bm', + 'bn-bd': 'bn-bd', + bn: 'bn', + bo: 'bo', + br: 'br', + bs: 'bs', + ca: 'ca', + cs: 'cs', + cv: 'cv', + cy: 'cy', + da: 'da', + 'de-at': 'de-at', + 'de-ch': 'de-ch', + de: 'de', + dv: 'dv', + el: 'el', + 'en-au': 'en-au', + 'en-ca': 'en-ca', + 'en-gb': 'en-gb', + 'en-ie': 'en-ie', + 'en-il': 'en-il', + 'en-in': 'en-in', + 'en-nz': 'en-nz', + 'en-sg': 'en-sg', + eo: 'eo', + 'es-do': 'es-do', + 'es-mx': 'es-mx', + 'es-us': 'es-us', + es: 'es', + et: 'et', + eu: 'eu', + fa: 'fa', + fi: 'fi', + fil: 'fil', + fo: 'fo', + 'fr-ca': 'fr-ca', + 'fr-ch': 'fr-ch', + fr: 'fr', + fy: 'fy', + ga: 'ga', + gd: 'gd', + gl: 'gl', + 'gom-deva': 'gom-deva', + 'gom-latn': 'gom-latn', + gu: 'gu', + he: 'he', + hi: 'hi', + hr: 'hr', + hu: 'hu', + 'hy-am': 'hy-am', + id: 'id', + is: 'is', + 'it-ch': 'it-ch', + it: 'it', + 'ja-JP': 'ja', + jv: 'jv', + ka: 'ka', + kk: 'kk', + km: 'km', + kn: 'kn', + ko: 'ko', + ku: 'ku', + ky: 'ky', + lb: 'lb', + lo: 'lo', + lt: 'lt', + lv: 'lv', + me: 'me', + mi: 'mi', + mk: 'mk', + ml: 'ml', + mn: 'mn', + mr: 'mr', + 'ms-my': 'ms-my', + ms: 'ms', + mt: 'mt', + my: 'my', + nb: 'nb', + ne: 'ne', + 'nl-be': 'nl-be', + nl: 'nl', + nn: 'nn', + 'oc-lnc': 'oc-lnc', + 'pa-in': 'pa-in', + pl: 'pl', + 'pt-br': 'pt-br', + pt: 'pt', + ro: 'ro', + 'ru-RU': 'ru', + sd: 'sd', + se: 'se', + si: 'si', + sk: 'sk', + sl: 'sl', + sq: 'sq', + 'sr-cyrl': 'sr-cyrl', + sr: 'sr', + ss: 'ss', + sv: 'sv', + sw: 'sw', + ta: 'ta', + te: 'te', + tet: 'tet', + tg: 'tg', + 'th-TH': 'th', + tk: 'tk', + 'tl-ph': 'tl-ph', + tlh: 'tlh', + 'tr-TR': 'tr', + tzl: 'tzl', + 'tzm-latn': 'tzm-latn', + tzm: 'tzm', + 'ug-cn': 'ug-cn', + uk: 'uk', + ur: 'ur', + 'uz-latn': 'uz-latn', + uz: 'uz', + vi: 'vi', + 'x-pseudo': 'x-pseudo', + yo: 'yo', + 'zh-CN': 'zh-cn', + 'zh-hk': 'zh-hk', + 'zh-mo': 'zh-mo', + 'zh-TW': 'zh-tw', +}; + +export const getMomentLocale = (lang: string) => { + return locales[lang] || 'en'; +}; diff --git a/packages/plugins/client/src/resource.ts b/packages/plugins/client/src/resource.ts new file mode 100644 index 000000000..aa1080bb3 --- /dev/null +++ b/packages/plugins/client/src/resource.ts @@ -0,0 +1,69 @@ +import { PluginManager } from '@nocobase/server'; + +const arr2obj = (items: any[]) => { + const obj = {}; + for (const item of items) { + Object.assign(obj, item); + } + return obj; +}; + +const getResource = (packageName: string, lang: string) => { + let resources = []; + const prefixes = ['src', 'lib']; + const localeKeys = ['locale', 'client/locale', 'server/locale']; + for (const prefix of prefixes) { + for (const localeKey of localeKeys) { + try { + const file = `${packageName}/${prefix}/${localeKey}/${lang}`; + require.resolve(file); + const resource = require(file).default; + resources.push(resource); + } catch (error) {} + } + if (resources.length) { + break; + } + } + if (resources.length === 0 && lang.replace('-', '_') !== lang) { + return getResource(packageName, lang.replace('-', '_')); + } + return arr2obj(resources); +}; + +export const getResourceLocale = async (lang: string, db: any) => { + const resources = {}; + const res = getResource('@nocobase/client', lang); + const defaults = getResource('@nocobase/client', 'zh-CN'); + for (const key in defaults) { + if (Object.prototype.hasOwnProperty.call(defaults, key)) { + defaults[key] = key; + } + } + if (res) { + resources['client'] = { ...defaults, ...res }; + } else { + resources['client'] = defaults; + } + const plugins = await db.getRepository('applicationPlugins').find({ + filter: { + 'name.$ne': 'client', + }, + }); + for (const plugin of plugins) { + const packageName = PluginManager.getPackageName(plugin.get('name')); + const res = getResource(packageName, lang); + const defaults = getResource(packageName, 'zh-CN'); + for (const key in defaults) { + if (Object.prototype.hasOwnProperty.call(defaults, key)) { + defaults[key] = key; + } + } + if (res) { + resources[plugin.get('name')] = { ...defaults, ...res }; + } else { + resources['client'] = defaults; + } + } + return resources; +}; diff --git a/packages/plugins/client/src/server.ts b/packages/plugins/client/src/server.ts index dfd819d4d..1776339ca 100644 --- a/packages/plugins/client/src/server.ts +++ b/packages/plugins/client/src/server.ts @@ -1,7 +1,13 @@ import { Plugin, PluginManager } from '@nocobase/server'; import send from 'koa-send'; import serve from 'koa-static'; +import isEmpty from 'lodash/isEmpty'; import { isAbsolute, resolve } from 'path'; +import { getAntdLocale } from './antd'; +import { getCronLocale } from './cron'; +import { getCronstrueLocale } from './cronstrue'; +import { getMomentLocale } from './moment-locale'; +import { getResourceLocale } from './resource'; export class ClientPlugin extends Plugin { async beforeLoad() { @@ -23,6 +29,7 @@ export class ClientPlugin extends Plugin { this.app.acl.allow('app', 'getPlugins'); this.app.acl.allow('plugins', 'getPinned', 'loggedIn'); const dialect = this.app.db.sequelize.getDialect(); + const locales = require('./locale').default; this.app.resource({ name: 'app', actions: { @@ -53,8 +60,28 @@ export class ClientPlugin extends Plugin { if (enabledLanguages.includes(currentUser?.appLang)) { lang = currentUser?.appLang; } + if (ctx.request.query.locale) { + lang = ctx.request.query.locale; + } + if (isEmpty(locales[lang])) { + locales[lang] = {}; + } + if (isEmpty(locales[lang].resources)) { + locales[lang].resources = await getResourceLocale(lang, ctx.db); + } + if (isEmpty(locales[lang].antd)) { + locales[lang].antd = getAntdLocale(lang); + } + if (isEmpty(locales[lang].cronstrue)) { + locales[lang].cronstrue = getCronstrueLocale(lang); + } + if (isEmpty(locales[lang].cron)) { + locales[lang].cron = getCronLocale(lang); + } ctx.body = { lang, + moment: getMomentLocale(lang), + ...locales[lang], }; await next(); }, diff --git a/packages/plugins/error-handler/src/locale/en_US.ts b/packages/plugins/error-handler/src/locale/en_US.ts index 7ff61f96e..c3c014f8d 100644 --- a/packages/plugins/error-handler/src/locale/en_US.ts +++ b/packages/plugins/error-handler/src/locale/en_US.ts @@ -1,4 +1,6 @@ export default { - 'unique violation': '{{field}} must be unique', - 'notNull Violation': '{{field}} cannot be null', + "unique violation": "{{field}} must be unique", + "notNull violation": "notNull violation", + "Validation error": "{{field}} validation error", + "notNull Violation": "{{field}} cannot be null" }; diff --git a/packages/plugins/graph-collection-manager/src/client/GraphCollectionProvider.tsx b/packages/plugins/graph-collection-manager/src/client/GraphCollectionProvider.tsx index 630f73c9e..57b883daf 100644 --- a/packages/plugins/graph-collection-manager/src/client/GraphCollectionProvider.tsx +++ b/packages/plugins/graph-collection-manager/src/client/GraphCollectionProvider.tsx @@ -1,17 +1,16 @@ -import { i18n, PluginManagerContext, RouteSwitchContext, SettingsCenterContext, SettingsCenterProvider } from '@nocobase/client'; +import { PluginManagerContext, RouteSwitchContext, SettingsCenterContext, SettingsCenterProvider } from '@nocobase/client'; import React, { useContext } from 'react'; -import { useTranslation } from 'react-i18next'; import { GraphCollectionPane, GraphCollectionShortcut } from './GraphCollectionShortcut'; -import { enUS, jaJP, zhCN } from './locale'; +import { useGCMTranslation } from './utils'; export const GraphCollectionProvider = React.memo((props) => { const ctx = useContext(PluginManagerContext); const { routes, components, ...others } = useContext(RouteSwitchContext); - i18n.addResources('en-US', 'graphPositions', enUS); - i18n.addResources('ja-JP', 'graphPositions', jaJP); - i18n.addResources('zh-CN', 'graphPositions', zhCN); - const { t } = useTranslation('graphPositions'); + // i18n.addResources('en-US', 'graphPositions', enUS); + // i18n.addResources('ja-JP', 'graphPositions', jaJP); + // i18n.addResources('zh-CN', 'graphPositions', zhCN); + const { t } = useGCMTranslation(); const items = useContext(SettingsCenterContext); items['collection-manager']['tabs']['graph'] = { diff --git a/packages/plugins/graph-collection-manager/src/client/GraphCollectionShortcut.tsx b/packages/plugins/graph-collection-manager/src/client/GraphCollectionShortcut.tsx index a4168202e..847b58791 100644 --- a/packages/plugins/graph-collection-manager/src/client/GraphCollectionShortcut.tsx +++ b/packages/plugins/graph-collection-manager/src/client/GraphCollectionShortcut.tsx @@ -1,12 +1,12 @@ import { DeleteOutlined, PartitionOutlined } from '@ant-design/icons'; +import { css } from '@emotion/css'; import { uid } from '@formily/shared'; import { PluginManager, SchemaComponent, useActionContext, useRequest } from '@nocobase/client'; import React, { useEffect } from 'react'; -import { css } from '@emotion/css'; -import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { useCreateActionAndRefreshCM } from './action-hooks'; import { GraphDrawPage } from './GraphDrawPage'; +import { useGCMTranslation } from './utils'; const useCollectionValues = (options) => { const { visible } = useActionContext(); @@ -144,7 +144,7 @@ export const GraphCollectionPane = () => { }; export const GraphCollectionShortcut = () => { - const { t } = useTranslation('graphPositions'); + const { t } = useGCMTranslation(); const history = useHistory(); return ( { const ctx = useContext(CollectionManagerContext); const api = useAPIClient(); const compile = useCompile(); - const { t } = useTranslation('graphPositions'); + const { t } = useGCMTranslation(); const [collectionData, setCollectionData] = useState([]); const [collectionList, setCollectionList] = useState([]); const { refreshCM } = useCollectionManager(); diff --git a/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx b/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx index 335a4caca..60b89b07f 100644 --- a/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx +++ b/packages/plugins/graph-collection-manager/src/client/components/Entity.tsx @@ -1,14 +1,16 @@ -import { DeleteOutlined, EditOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; +import { DeleteOutlined, DownOutlined, EditOutlined, UpOutlined } from '@ant-design/icons'; import '@antv/x6-react-shape'; import { css, cx } from '@emotion/css'; import { uid } from '@formily/shared'; import { Action, Checkbox, + collection, CollectionField, CollectionProvider, Form, FormItem, + Formula, Grid, Input, InputNumber, @@ -19,31 +21,29 @@ import { Select, useCollectionManager, useCompile, - Formula, - useRecord, - collection, useCurrentAppInfo, + useRecord } from '@nocobase/client'; -import { useTranslation } from 'react-i18next'; -import { Dropdown, Popover, Tag, Badge } from 'antd'; -import React, { useRef, useState } from 'react'; +import { Badge, Dropdown, Popover, Tag } from 'antd'; import { groupBy } from 'lodash'; +import React, { useRef, useState } from 'react'; import { useAsyncDataSource, useCancelAction, useDestroyActionAndRefreshCM, useDestroyFieldActionAndRefreshCM, useUpdateCollectionActionAndRefreshCM, - useValuesFromRecord, + useValuesFromRecord } from '../action-hooks'; import { collectiionPopoverClass, entityContainer, headClass, tableBtnClass, tableNameClass } from '../style'; -import { FieldSummary } from './FieldSummary'; +import { useGCMTranslation } from '../utils'; import { AddFieldAction } from './AddFieldAction'; import { CollectionNodeProvder } from './CollectionNodeProvder'; +import { EditCollectionAction } from './EditCollectionAction'; import { EditFieldAction } from './EditFieldAction'; +import { FieldSummary } from './FieldSummary'; import { OverrideFieldAction } from './OverrideFieldAction'; import { ViewFieldAction } from './ViewFieldAction'; -import { EditCollectionAction } from './EditCollectionAction'; const Entity: React.FC<{ node?: Node | any; @@ -166,7 +166,7 @@ const PortsCom = React.memo(({ targetGraph, collectionData, setTargetNode, }, } = node; const [collapse, setCollapse] = useState(false); - const { t } = useTranslation('graphPositions'); + const { t } = useGCMTranslation(); const compile = useCompile(); const portsData = groupBy(ports.items, (v) => { if ( diff --git a/packages/plugins/graph-collection-manager/src/client/utils.tsx b/packages/plugins/graph-collection-manager/src/client/utils.tsx index 358a0863a..03e19c930 100644 --- a/packages/plugins/graph-collection-manager/src/client/utils.tsx +++ b/packages/plugins/graph-collection-manager/src/client/utils.tsx @@ -1,8 +1,15 @@ -import { uniqBy, groupBy, reduce, uniq } from 'lodash'; +import { groupBy, reduce, uniq, uniqBy } from 'lodash'; +import { useTranslation } from 'react-i18next'; + const shape = { ER: 'er-rect', EDGE: 'edge', }; + +export const useGCMTranslation = () => { + return useTranslation('graph-collection-manager'); +}; + export const getInheritCollections = (collections, name) => { const parents = []; const getParents = (name) => { diff --git a/packages/plugins/import/src/client/index.ts b/packages/plugins/import/src/client/index.ts index f08ea893d..01e3ae58a 100644 --- a/packages/plugins/import/src/client/index.ts +++ b/packages/plugins/import/src/client/index.ts @@ -1,9 +1,6 @@ -import { i18n } from '@nocobase/client'; -import { NAMESPACE } from './constants'; -import { enUS, zhCN } from './locale'; -i18n.addResources('zh-CN', NAMESPACE, zhCN); -i18n.addResources('en-US', NAMESPACE, enUS); +// i18n.addResources('zh-CN', NAMESPACE, zhCN); +// i18n.addResources('en-US', NAMESPACE, enUS); export * from './ImportActionInitializer'; export * from './ImportDesigner'; @@ -11,3 +8,4 @@ export * from './ImportInitializerProvider'; export * from './ImportPluginProvider'; export { ImportPluginProvider as default } from './ImportPluginProvider'; export * from './useImportAction'; + diff --git a/packages/plugins/import/src/client/locale/en-US.ts b/packages/plugins/import/src/client/locale/en-US.ts index 570c3876f..434b2625d 100644 --- a/packages/plugins/import/src/client/locale/en-US.ts +++ b/packages/plugins/import/src/client/locale/en-US.ts @@ -1,23 +1,27 @@ export default { - 'Only one file is allowed to be uploaded': 'Only one file is allowed to be uploaded', - 'File size cannot exceed 10M': 'File size cannot exceed 10M', - 'Please upload the file of Excel': 'Please upload the file of Excel', - 'Import Data': 'Import Data', - 'Start import': 'Start import', - 'Import explain': 'Guide', - 'Download template': 'Download template', - 'Step 1: Download template': 'Step 1: Download template', - 'Step 2: Upload Excel': 'Step 2: Upload Excel', - 'Download tip': - '- Download the template and fill in the data according to the format \r\n - Import only the first worksheet \r\n - Support single import of up to 10,000 rows of data \r\n - Do not change the header of the template to prevent import failure', - 'Upload placeholder': 'Drag and drop the file here or click to upload, file size should not exceed 10M', - 'Excel data importing': 'Excel data importing', - 'Import done, total success have {{successCount}} , total failure have {{failureCount}}': - 'Import is complete, with a total of {{successCount}} successful and {{failureCount}} failed', - 'To download the failure data': 'To download the failure data', - 'Add importable field': 'Add importable field', - Done: 'Done', - Yes: 'Yes', - No: 'No', - 'Field {{fieldName}} does not exist': 'Field {{fieldName}} does not exist', + "Only one file is allowed to be uploaded": "Only one file is allowed to be uploaded", + "File size cannot exceed 10M": "File size cannot exceed 10M", + "Please upload the file of Excel": "Please upload the file of Excel", + "Import Data": "Import Data", + "Start import": "Start import", + "Import explain": "Guide", + "Download template": "Download template", + "Step 1: Download template": "Step 1: Download template", + "Step 2: Upload Excel": "Step 2: Upload Excel", + "Download tip": "- Download the template and fill in the data according to the format \r\n - Import only the first worksheet \r\n - Support single import of up to 10,000 rows of data \r\n - Do not change the header of the template to prevent import failure", + "Upload placeholder": "Drag and drop the file here or click to upload, file size should not exceed 10M", + "Excel data importing": "Excel data importing", + "Import done, total success have {{successCount}} , total failure have {{failureCount}}": "Import is complete, with a total of {{successCount}} successful and {{failureCount}} failed", + "To download the failure data": "To download the failure data", + "Add importable field": "Add importable field", + "Done": "Done", + "Yes": "Yes", + "No": "No", + "Field {{fieldName}} does not exist": "Field {{fieldName}} does not exist", + "can not find value": "can not find value", + "password is empty": "password is empty", + "Incorrect time format": "Incorrect time format", + "Incorrect date format": "Incorrect date format", + "Incorrect email format": "Incorrect email format", + "Illegal percentage format": "Illegal percentage format" }; diff --git a/packages/plugins/map/src/client/components/AMap.tsx b/packages/plugins/map/src/client/components/AMap.tsx index a06220b40..788cd399a 100644 --- a/packages/plugins/map/src/client/components/AMap.tsx +++ b/packages/plugins/map/src/client/components/AMap.tsx @@ -1,17 +1,16 @@ -import React from 'react'; -import { useEffect, useRef, useState } from 'react'; import AMapLoader from '@amap/amap-jsapi-loader'; import '@amap/amap-jsapi-types'; +import { SyncOutlined } from '@ant-design/icons'; +import { css } from '@emotion/css'; import { useFieldSchema } from '@formily/react'; import { useCollection } from '@nocobase/client'; -import { css } from '@emotion/css'; -import { Alert, Button, Modal } from 'antd'; -import { useMapTranslation } from '../locales'; -import Search from './Search'; import { useMemoizedFn } from 'ahooks'; -import { useMapConfiguration } from '../hooks'; +import { Alert, Button, Modal } from 'antd'; +import React, { useEffect, useRef, useState } from 'react'; import { useHistory } from 'react-router'; -import { SyncOutlined } from '@ant-design/icons'; +import { useMapConfiguration } from '../hooks'; +import { useMapTranslation } from '../locale'; +import Search from './Search'; interface AMapComponentProps { accessKey: string; diff --git a/packages/plugins/map/src/client/components/Configuration.tsx b/packages/plugins/map/src/client/components/Configuration.tsx index 66adaccee..6e00a9990 100644 --- a/packages/plugins/map/src/client/components/Configuration.tsx +++ b/packages/plugins/map/src/client/components/Configuration.tsx @@ -1,10 +1,10 @@ -import { useAPIClient, useCompile, useRequest } from '@nocobase/client'; +import { useAPIClient, useCompile } from '@nocobase/client'; import { useBoolean } from 'ahooks'; -import { Form, Input, Tabs, Button, Card, message } from 'antd'; -import React, { useMemo, useEffect } from 'react'; +import { Button, Card, Form, Input, message, Tabs } from 'antd'; +import React, { useEffect, useMemo } from 'react'; import { MapTypes } from '../constants'; import { MapConfigurationResourceKey, useMapConfiguration } from '../hooks'; -import { useMapTranslation } from '../locales'; +import { useMapTranslation } from '../locale'; const AMapConfiguration = ({ type }) => { const { t } = useMapTranslation(); diff --git a/packages/plugins/map/src/client/components/Designer.tsx b/packages/plugins/map/src/client/components/Designer.tsx index 468083b65..1b725664b 100644 --- a/packages/plugins/map/src/client/components/Designer.tsx +++ b/packages/plugins/map/src/client/components/Designer.tsx @@ -6,11 +6,11 @@ import { useCollection, useCollectionManager, useDesignable, - useFormBlockContext, + useFormBlockContext } from '@nocobase/client'; -import _ from 'lodash'; +import set from 'lodash/set'; import React from 'react'; -import { useMapTranslation } from '../locales'; +import { useMapTranslation } from '../locale'; const Designer = () => { const { getCollectionJoinField } = useCollectionManager(); @@ -230,7 +230,7 @@ const Designer = () => { } onSubmit={({ zoom }) => { if (zoom) { - _.set(fieldSchema, 'x-component-props.zoom', zoom); + set(fieldSchema, 'x-component-props.zoom', zoom); Object.assign(field.componentProps, fieldSchema['x-component-props']); dn.emit('patch', { diff --git a/packages/plugins/map/src/client/components/Search.tsx b/packages/plugins/map/src/client/components/Search.tsx index 9be304b8a..5ad9d87a7 100644 --- a/packages/plugins/map/src/client/components/Search.tsx +++ b/packages/plugins/map/src/client/components/Search.tsx @@ -1,8 +1,8 @@ -import { message, Select } from 'antd'; import { css } from '@emotion/css'; -import React, { useEffect, useRef, useState } from 'react'; import { useDebounceFn } from 'ahooks'; -import { useMapTranslation } from '../locales'; +import { message, Select } from 'antd'; +import React, { useEffect, useRef, useState } from 'react'; +import { useMapTranslation } from '../locale'; interface SearchProps { aMap: any; diff --git a/packages/plugins/map/src/client/constants.ts b/packages/plugins/map/src/client/constants.ts index 36f6ec7b4..ba8ef3c2c 100644 --- a/packages/plugins/map/src/client/constants.ts +++ b/packages/plugins/map/src/client/constants.ts @@ -1,4 +1,4 @@ -import { generateNTemplate } from "./locales"; +import { generateNTemplate } from "./locale"; export const MapTypes = [ { label: generateNTemplate('AMap'), value: 'amap' }, diff --git a/packages/plugins/map/src/client/fields/circle.ts b/packages/plugins/map/src/client/fields/circle.ts index c06878d96..29298ef9a 100644 --- a/packages/plugins/map/src/client/fields/circle.ts +++ b/packages/plugins/map/src/client/fields/circle.ts @@ -1,5 +1,5 @@ import { IField } from '@nocobase/client'; -import { generateNTemplate } from '../locales'; +import { generateNTemplate } from '../locale'; import { commonSchema } from './schema'; export const circle: IField = { diff --git a/packages/plugins/map/src/client/fields/lineString.ts b/packages/plugins/map/src/client/fields/lineString.ts index 02d03f8f7..35af95c69 100644 --- a/packages/plugins/map/src/client/fields/lineString.ts +++ b/packages/plugins/map/src/client/fields/lineString.ts @@ -1,5 +1,5 @@ import { IField } from '@nocobase/client'; -import { generateNTemplate } from '../locales'; +import { generateNTemplate } from '../locale'; import { commonSchema } from './schema'; export const lineString: IField = { diff --git a/packages/plugins/map/src/client/fields/point.ts b/packages/plugins/map/src/client/fields/point.ts index db3d40def..8029693d0 100644 --- a/packages/plugins/map/src/client/fields/point.ts +++ b/packages/plugins/map/src/client/fields/point.ts @@ -1,6 +1,5 @@ -import { ISchema } from '@formily/react'; import { IField } from '@nocobase/client'; -import { generateNTemplate } from '../locales'; +import { generateNTemplate } from '../locale'; import { commonSchema } from './schema'; export const point: IField = { diff --git a/packages/plugins/map/src/client/fields/polygon.ts b/packages/plugins/map/src/client/fields/polygon.ts index c359d5577..799ad8fb9 100644 --- a/packages/plugins/map/src/client/fields/polygon.ts +++ b/packages/plugins/map/src/client/fields/polygon.ts @@ -1,5 +1,5 @@ import { IField } from '@nocobase/client'; -import { generateNTemplate } from '../locales'; +import { generateNTemplate } from '../locale'; import { commonSchema } from './schema'; export const polygon: IField = { diff --git a/packages/plugins/map/src/client/fields/schema.ts b/packages/plugins/map/src/client/fields/schema.ts index 21711bdf4..bd2f73a78 100644 --- a/packages/plugins/map/src/client/fields/schema.ts +++ b/packages/plugins/map/src/client/fields/schema.ts @@ -1,7 +1,7 @@ import { ISchema } from '@formily/react'; import { interfacesProperties } from '@nocobase/client'; import { MapTypes } from '../constants'; -import { generateNTemplate } from '../locales'; +import { generateNTemplate } from '../locale'; const { defaultProps } = interfacesProperties; diff --git a/packages/plugins/map/src/client/index.tsx b/packages/plugins/map/src/client/index.tsx index 803fb29ed..16ab0d9c1 100644 --- a/packages/plugins/map/src/client/index.tsx +++ b/packages/plugins/map/src/client/index.tsx @@ -9,7 +9,7 @@ import Configuration from './components/Configuration'; import Map from './components/Map'; import { interfaces } from './fields'; import { Initialize } from './initialize'; -import { useMapTranslation } from './locales'; +import { useMapTranslation } from './locale'; export default React.memo((props) => { const ctx = useContext(CollectionManagerContext); diff --git a/packages/plugins/map/src/client/initialize.tsx b/packages/plugins/map/src/client/initialize.tsx index ac1908eca..8e1daf8dd 100644 --- a/packages/plugins/map/src/client/initialize.tsx +++ b/packages/plugins/map/src/client/initialize.tsx @@ -1,9 +1,8 @@ -import React from 'react'; import { registerField, registerGroup, useCurrentAppInfo } from '@nocobase/client'; -import { generateNTemplate } from './locales'; -import './locales'; +import React, { useEffect } from 'react'; import { fields } from './fields'; -import { useEffect } from 'react'; +import './locale'; +import { generateNTemplate } from './locale'; export const useRegisterInterface = () => { const { data } = useCurrentAppInfo() || {}; diff --git a/packages/plugins/map/src/client/locales/en-US.ts b/packages/plugins/map/src/client/locale/en-US.ts similarity index 100% rename from packages/plugins/map/src/client/locales/en-US.ts rename to packages/plugins/map/src/client/locale/en-US.ts diff --git a/packages/plugins/map/src/client/locales/index.ts b/packages/plugins/map/src/client/locale/index.ts similarity index 72% rename from packages/plugins/map/src/client/locales/index.ts rename to packages/plugins/map/src/client/locale/index.ts index 90cda6d2d..55e629f62 100644 --- a/packages/plugins/map/src/client/locales/index.ts +++ b/packages/plugins/map/src/client/locale/index.ts @@ -1,13 +1,11 @@ import { i18n } from '@nocobase/client'; import { useTranslation } from 'react-i18next'; -import enUS from './en-US'; -import zhCN from './zh-CN'; export const NAMESPACE = 'map'; -i18n.addResources('zh-CN', NAMESPACE, zhCN); -i18n.addResources('en-US', NAMESPACE, enUS); +// i18n.addResources('zh-CN', NAMESPACE, zhCN); +// i18n.addResources('en-US', NAMESPACE, enUS); export function lang(key: string) { return i18n.t(key, { ns: NAMESPACE }); diff --git a/packages/plugins/map/src/client/locales/zh-CN.ts b/packages/plugins/map/src/client/locale/zh-CN.ts similarity index 100% rename from packages/plugins/map/src/client/locales/zh-CN.ts rename to packages/plugins/map/src/client/locale/zh-CN.ts diff --git a/packages/plugins/oidc/src/client/locale/index.ts b/packages/plugins/oidc/src/client/locale/index.ts index 7357ede64..4cb197b15 100644 --- a/packages/plugins/oidc/src/client/locale/index.ts +++ b/packages/plugins/oidc/src/client/locale/index.ts @@ -1,19 +1,14 @@ import { i18n } from '@nocobase/client'; import { useTranslation } from 'react-i18next'; -import enUS from './en-US'; -import jaJP from './ja-JP'; -import ruRU from './ru-RU'; -import trTR from './tr-TR'; -import zhCN from './zh-CN'; export const NAMESPACE = 'oidc'; -i18n.addResources('zh-CN', NAMESPACE, zhCN); -i18n.addResources('en-US', NAMESPACE, enUS); -i18n.addResources('ja-JP', NAMESPACE, jaJP); -i18n.addResources('ru-RU', NAMESPACE, ruRU); -i18n.addResources('tr-TR', NAMESPACE, trTR); +// i18n.addResources('zh-CN', NAMESPACE, zhCN); +// i18n.addResources('en-US', NAMESPACE, enUS); +// i18n.addResources('ja-JP', NAMESPACE, jaJP); +// i18n.addResources('ru-RU', NAMESPACE, ruRU); +// i18n.addResources('tr-TR', NAMESPACE, trTR); export function lang(key: string) { return i18n.t(key, { ns: NAMESPACE }); diff --git a/packages/plugins/saml/src/client/locale/index.ts b/packages/plugins/saml/src/client/locale/index.ts index 5dc989a88..8e1b6004b 100644 --- a/packages/plugins/saml/src/client/locale/index.ts +++ b/packages/plugins/saml/src/client/locale/index.ts @@ -1,19 +1,14 @@ import { i18n } from '@nocobase/client'; import { useTranslation } from 'react-i18next'; -import enUS from './en-US'; -import jaJP from './ja-JP'; -import ruRU from './ru-RU'; -import trTR from './tr-TR'; -import zhCN from './zh-CN'; export const NAMESPACE = 'saml'; -i18n.addResources('zh-CN', NAMESPACE, zhCN); -i18n.addResources('en-US', NAMESPACE, enUS); -i18n.addResources('ja-JP', NAMESPACE, jaJP); -i18n.addResources('ru-RU', NAMESPACE, ruRU); -i18n.addResources('tr-TR', NAMESPACE, trTR); +// i18n.addResources('zh-CN', NAMESPACE, zhCN); +// i18n.addResources('en-US', NAMESPACE, enUS); +// i18n.addResources('ja-JP', NAMESPACE, jaJP); +// i18n.addResources('ru-RU', NAMESPACE, ruRU); +// i18n.addResources('tr-TR', NAMESPACE, trTR); export function lang(key: string) { return i18n.t(key, { ns: NAMESPACE }); diff --git a/packages/plugins/sequence-field/src/client/locale/en-US.ts b/packages/plugins/sequence-field/src/client/locale/en-US.ts new file mode 100644 index 000000000..db763230a --- /dev/null +++ b/packages/plugins/sequence-field/src/client/locale/en-US.ts @@ -0,0 +1,24 @@ +export default { + "Sequence": "Sequence", + "Sequence rules": "Sequence rules", + "Add rule": "Add rule", + "Inputable": "Inputable", + "Match rules": "Match rules", + "Type": "Type", + "Autoincrement": "Autoincrement", + "Fixed text": "Fixed text", + "Text content": "Text content", + "Rule content": "Rule content", + "{{value}} Digits": "{{value}} Digits", + "Digits": "Digits", + "Start from": "Start from", + "Starts from {{value}}": "Starts from {{value}}", + "Reset cycle": "Reset cycle", + "No reset": "No reset", + "Daily": "Daily", + "Every Monday": "Every Monday", + "Monthly": "Monthly", + "Yearly": "Yearly", + "Operations": "Operations", + "Customize": "Customize" +}; diff --git a/packages/plugins/sequence-field/src/client/locale/index.ts b/packages/plugins/sequence-field/src/client/locale/index.ts index f970a8c53..82fec5110 100644 --- a/packages/plugins/sequence-field/src/client/locale/index.ts +++ b/packages/plugins/sequence-field/src/client/locale/index.ts @@ -1,11 +1,10 @@ -import { useTranslation } from 'react-i18next'; import { i18n } from '@nocobase/client'; +import { useTranslation } from 'react-i18next'; -import zhCN from './zh-CN'; -export const NAMESPACE = 'sequence_field'; +export const NAMESPACE = 'sequence-field'; -i18n.addResources('zh-CN', NAMESPACE, zhCN); +// i18n.addResources('zh-CN', NAMESPACE, zhCN); export function lang(key: string, options = {}) { return i18n.t(key, { ...options, ns: NAMESPACE }); diff --git a/packages/plugins/snapshot-field/src/client/locale/index.ts b/packages/plugins/snapshot-field/src/client/locale/index.ts index abff8ed44..0ff793a4d 100644 --- a/packages/plugins/snapshot-field/src/client/locale/index.ts +++ b/packages/plugins/snapshot-field/src/client/locale/index.ts @@ -1,19 +1,14 @@ import { i18n } from '@nocobase/client'; import { useTranslation } from 'react-i18next'; -import enUS from './en-US'; -import jaJP from './ja-JP'; -import ruRU from './ru-RU'; -import trTR from './tr-TR'; -import zhCN from './zh-CN'; export const NAMESPACE = 'snapshot-field'; -i18n.addResources('zh-CN', NAMESPACE, zhCN); -i18n.addResources('en-US', NAMESPACE, enUS); -i18n.addResources('ja-JP', NAMESPACE, jaJP); -i18n.addResources('ru-RU', NAMESPACE, ruRU); -i18n.addResources('tr-TR', NAMESPACE, trTR); +// i18n.addResources('zh-CN', NAMESPACE, zhCN); +// i18n.addResources('en-US', NAMESPACE, enUS); +// i18n.addResources('ja-JP', NAMESPACE, jaJP); +// i18n.addResources('ru-RU', NAMESPACE, ruRU); +// i18n.addResources('tr-TR', NAMESPACE, trTR); export function lang(key: string) { return i18n.t(key, { ns: NAMESPACE }); diff --git a/packages/plugins/users/src/locale/en-US.ts b/packages/plugins/users/src/locale/en-US.ts index 69f3e7de9..665bd8e77 100644 --- a/packages/plugins/users/src/locale/en-US.ts +++ b/packages/plugins/users/src/locale/en-US.ts @@ -1,4 +1,8 @@ export default { - 'Please fill in your email address': 'Please fill in your email address', - 'The password is incorrect, please re-enter': 'The password is incorrect, please re-enter', + "The email is incorrect, please re-enter": "The email is incorrect, please re-enter", + "Please fill in your email address": "Please fill in your email address", + "The password is incorrect, please re-enter": "The password is incorrect, please re-enter", + "Not a valid cellphone number, please re-enter": "Not a valid cellphone number, please re-enter", + "The phone number has been registered, please login directly": "The phone number has been registered, please login directly", + "The phone number is not registered, please register first": "The phone number is not registered, please register first" }; diff --git a/packages/plugins/verification/src/client/locale/index.ts b/packages/plugins/verification/src/client/locale/index.ts index e8f0d5007..f12acde31 100644 --- a/packages/plugins/verification/src/client/locale/index.ts +++ b/packages/plugins/verification/src/client/locale/index.ts @@ -1,11 +1,10 @@ -import { useTranslation } from 'react-i18next'; import { i18n } from '@nocobase/client'; +import { useTranslation } from 'react-i18next'; -import zhCN from './zh-CN'; export const NAMESPACE = 'verification'; -i18n.addResources('zh-CN', NAMESPACE, zhCN); +// i18n.addResources('zh-CN', NAMESPACE, zhCN); export function lang(key: string) { return i18n.t(key, { ns: NAMESPACE }); diff --git a/packages/plugins/workflow/src/client/locale/en-US.ts b/packages/plugins/workflow/src/client/locale/en-US.ts index ddd9241c0..90343707e 100644 --- a/packages/plugins/workflow/src/client/locale/en-US.ts +++ b/packages/plugins/workflow/src/client/locale/en-US.ts @@ -1,14 +1,21 @@ export default { "Workflow": "Workflow", "Execution history": "Execution history", + "Executed": "Executed", "Trigger type": "Trigger type", "Status": "Status", "On": "On", "Off": "Off", "Version": "Version", "Copy to new version": "Copy to new version", + "Duplicate": "Duplicate", + "Loading": "Loading", "Load failed": "Load failed", "Trigger": "Trigger", + "Trigger variables": "Trigger variables", + "Trigger data": "Trigger data", + "Trigger time": "Trigger time", + "Triggered at": "Triggered at", "Collection event": "Collection event", "Trigger on": "Trigger on", "After record added": "After record added", @@ -24,6 +31,7 @@ export default { "Based on date field of collection": "Based on date field of collection", "Starts on": "Starts on", "Ends on": "Ends on", + "No end": "No end", "Exactly at": "Exactly at", "Repeat mode": "Repeat mode", "Repeat limit": "Repeat limit", @@ -45,36 +53,51 @@ export default { "By custom date": "By custom date", "Advanced": "Advanced", "End": "End", - "Trigger variables": "Trigger variables", "Node result": "Node result", "Constant": "Constant", + "Null": "Null", "Boolean": "Boolean", "String": "String", + "Calculator": "Calculator", "Arithmetic calculation": "Arithmetic calculation", "String operation": "String operation", + "Executed at": "Executed at", + "Queueing": "Queueing", "On going": "On going", "Succeeded": "Succeeded", "Failed": "Failed", + "Pending": "Pending", "Canceled": "Canceled", "This node contains branches, deleting will also be preformed to them, are you sure?": "This node contains branches, deleting will also be preformed to them, are you sure?", "Control": "Control", "Collection operations": "Collection operations", + "Extended types": "Extended types", "Node type": "Node type", "Calculation": "Calculation", "Configure calculation": "Configure calculation", "Calculation result": "Calculation result", "True": "True", "False": "False", + "concat": "concat", "Condition": "Condition", "Mode": "Mode", "Continue when \"Yes\"": "Continue when \"Yes\"", "Branch into \"Yes\" and \"No\"": "Branch into \"Yes\" and \"No\"", "Conditions": "Conditions", "Parallel branch": "Parallel branch", + "Add branch": "Add branch", "All succeeded": "All succeeded", "Any succeeded": "Any succeeded", + "Any succeeded or failed": "Any succeeded or failed", "Continue after all branches succeeded": "Continue after all branches succeeded", "Continue after any branch succeeded": "Continue after any branch succeeded", + "Continue after any branch succeeded, or exit after any branch failed": "Continue after any branch succeeded, or exit after any branch failed", + "Delay": "Delay", + "Duration": "Duration", + "End Status": "End Status", + "Select status": "Select status", + "Succeed and continue": "Succeed and continue", + "Fail and exit": "Fail and exit", "Create record": "Create record", "Update record": "Update record", "Query record": "Query record", @@ -84,22 +107,23 @@ export default { "Fields that are not assigned a value will be set to the default value, and those that do not have a default value are set to null.": "Fields that are not assigned a value will be set to the default value, and those that do not have a default value are set to null.", "Trigger in executed workflow cannot be modified": "Trigger in executed workflow cannot be modified", "Node in executed workflow cannot be modified": "Node in executed workflow cannot be modified", - - 'HTTP request': 'HTTP request', - 'URL': 'URL', - 'You can use the above available variables in URL.': 'You can use the above available variables in URL.', - 'Request headers': 'Request headers', - 'Name(e.g. Content-Type)': 'Name(e.g. Content-Type)', - 'Value(e.g. Application/json)': 'Value(e.g. Application/json)', - 'Add request header': 'Add request header', - 'HTTP method': 'HTTP method', - 'Request data': 'Request data', - 'Input request data': 'Input request data', - 'You can use the above available variables in request data.': 'You can use the above available variables in request data.', - 'Copy success!': 'Copy success!', - 'Copy variable output template statement': 'Copy variable output template statement', - 'Default headers is Content-Type: application/json': 'Default headers is Content-Type: application/json', - 'Ignore fail request and continue workflow': 'Ignore fail request and continue workflow', - 'Syntax see': 'Syntax see', - 'Show available variable tool': 'Show available variable tool' + "Can not delete": "Can not delete", + "The result of this node has been referenced by other nodes ({{nodes}}), please remove the usage before deleting.": "The result of this node has been referenced by other nodes ({{nodes}}), please remove the usage before deleting.", + "HTTP request": "HTTP request", + "URL": "URL", + "You can use the above available variables in URL.": "You can use the above available variables in URL.", + "Request headers": "Request headers", + "Name(e.g. Content-Type)": "Name(e.g. Content-Type)", + "Value(e.g. Application/json)": "Value(e.g. Application/json)", + "Add request header": "Add request header", + "HTTP method": "HTTP method", + "Request data": "Request data", + "Input request data": "Input request data", + "You can use the above available variables in request data.": "You can use the above available variables in request data.", + "Copy success!": "Copy success!", + "Copy variable output template statement": "Copy variable output template statement", + "Default headers is Content-Type: application/json": "Default headers is Content-Type: application/json", + "Ignore fail request and continue workflow": "Ignore fail request and continue workflow", + "Syntax see": "Syntax see", + "Show available variable tool": "Show available variable tool" }; diff --git a/packages/plugins/workflow/src/client/locale/index.ts b/packages/plugins/workflow/src/client/locale/index.ts index eafdadf59..457e0cde5 100644 --- a/packages/plugins/workflow/src/client/locale/index.ts +++ b/packages/plugins/workflow/src/client/locale/index.ts @@ -1,19 +1,14 @@ -import { useTranslation } from 'react-i18next'; import { i18n } from '@nocobase/client'; +import { useTranslation } from 'react-i18next'; -import zhCN from './zh-CN'; -import enUS from './en-US'; -import jaJP from './ja-JP'; -import ruRU from './ru-RU'; -import trTR from './tr-TR'; export const NAMESPACE = 'workflow'; -i18n.addResources('zh-CN', NAMESPACE, zhCN); -i18n.addResources('en-US', NAMESPACE, enUS); -i18n.addResources('ja-JP', NAMESPACE, jaJP); -i18n.addResources('ru-RU', NAMESPACE, ruRU); -i18n.addResources('tr-TR', NAMESPACE, trTR); +// i18n.addResources('zh-CN', NAMESPACE, zhCN); +// i18n.addResources('en-US', NAMESPACE, enUS); +// i18n.addResources('ja-JP', NAMESPACE, jaJP); +// i18n.addResources('ru-RU', NAMESPACE, ruRU); +// i18n.addResources('tr-TR', NAMESPACE, trTR); export function lang(key: string, options = {}) { return i18n.t(key, { ...options, ns: NAMESPACE }); diff --git a/yarn.lock b/yarn.lock index 1bfa16618..757e86e4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3597,7 +3597,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4": version "7.16.3" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ== @@ -3646,6 +3646,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.20.6": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" + integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.10.4", "@babel/template@^7.16.7", "@babel/template@^7.4.0", "@babel/template@^7.4.4": version "7.16.7" resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -9980,6 +9987,13 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" +cross-fetch@3.1.5: + version "3.1.5" + resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + cross-spawn-async@^2.1.1: version "2.2.5" resolved "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc" @@ -13487,12 +13501,19 @@ humanize-ms@^1.2.0, humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -i18next@^21.6.0: - version "21.6.0" - resolved "https://registry.npmjs.org/i18next/-/i18next-21.6.0.tgz#257abf455b24136640a20728b44cf59f60cdeb5c" - integrity sha512-RjNuACL35wWZgtkyMcjcCmK7R72u3P6jTNbGKzrvHGI9M0iK5Vn1DsBIwOByppaXLIbe0viJ79Nz2h8w1UwPoQ== +i18next-http-backend@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.1.1.tgz#72a21d61c2e96eea9ad45ba1b9dd0090e119709a" + integrity sha512-jByfUCDVgQ8+/Wens7queQhYYvMcGTW/lR4IJJNEDDXnmqjLrwi8ubXKpmp76/JIWEZHffNdWqnxFJcTVGeaOw== dependencies: - "@babel/runtime" "^7.12.0" + cross-fetch "3.1.5" + +i18next@^22.4.9: + version "22.4.9" + resolved "https://registry.npmjs.org/i18next/-/i18next-22.4.9.tgz#98c8384c6bd41ff937da98b1e809ba03d3b41053" + integrity sha512-8gWMmUz460KJDQp/ob3MNUX84cVuDRY9PLFPnV8d+Qezz/6dkjxwOaH70xjrCNDO+JrUL25iXfAIN9wUkInNZw== + dependencies: + "@babel/runtime" "^7.20.6" iconv-lite@0.4.24, iconv-lite@^0.4.15, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" @@ -17441,6 +17462,13 @@ node-fetch@2.6.0: resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== +node-fetch@2.6.7, node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^2.6.1: version "2.6.6" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" @@ -17448,13 +17476,6 @@ node-fetch@^2.6.1: dependencies: whatwg-url "^5.0.0" -node-fetch@^2.6.7: - version "2.6.7" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - node-gyp@8.x: version "8.4.1" resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937"