diff --git a/packages/core/cli/src/commands/index.js b/packages/core/cli/src/commands/index.js index 45daf3c483..3647d07250 100644 --- a/packages/core/cli/src/commands/index.js +++ b/packages/core/cli/src/commands/index.js @@ -18,6 +18,7 @@ module.exports = (cli) => { generateAppDir(); require('./global')(cli); require('./create-nginx-conf')(cli); + require('./locale')(cli); require('./build')(cli); require('./tar')(cli); require('./dev')(cli); diff --git a/packages/core/cli/src/commands/locale.js b/packages/core/cli/src/commands/locale.js new file mode 100644 index 0000000000..6a947d7167 --- /dev/null +++ b/packages/core/cli/src/commands/locale.js @@ -0,0 +1,81 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +const { Command } = require('commander'); +const fg = require('fast-glob'); +const fs = require('fs-extra'); +const path = require('path'); +const _ = require('lodash'); +const deepmerge = require('deepmerge'); +const { getCronstrueLocale } = require('./locale/cronstrue'); +const { getReactJsCron } = require('./locale/react-js-cron'); + +function sortJSON(json) { + if (Array.isArray(json)) { + return json.map(sortJSON); + } else if (typeof json === 'object' && json !== null) { + const sortedKeys = Object.keys(json).sort(); + const sortedObject = {}; + sortedKeys.forEach((key) => { + sortedObject[key] = sortJSON(json[key]); + }); + return sortedObject; + } + return json; +} + +/** + * + * @param {Command} cli + */ +module.exports = (cli) => { + const locale = cli.command('locale'); + locale.command('generate').action(async (options) => { + const cwd = path.resolve(process.cwd(), 'node_modules', '@nocobase'); + const files = await fg('./*/src/locale/*.json', { + cwd, + }); + let locales = {}; + await fs.mkdirp(path.resolve(process.cwd(), 'storage/locales')); + for (const file of files) { + const locale = path.basename(file, '.json'); + const pkg = path.basename(path.dirname(path.dirname(path.dirname(file)))); + _.set(locales, [locale.replace(/_/g, '-'), `@nocobase/${pkg}`], await fs.readJSON(path.resolve(cwd, file))); + if (locale.includes('_')) { + await fs.rename( + path.resolve(cwd, file), + path.resolve(cwd, path.dirname(file), `${locale.replace(/_/g, '-')}.json`), + ); + } + } + const zhCN = locales['zh-CN']; + const enUS = locales['en-US']; + for (const key1 in zhCN) { + for (const key2 in zhCN[key1]) { + if (!_.get(enUS, [key1, key2])) { + _.set(enUS, [key1, key2], key2); + } + } + } + for (const locale of Object.keys(locales)) { + locales[locale] = deepmerge(enUS, locales[locale]); + locales[locale]['cronstrue'] = getCronstrueLocale(locale); + locales[locale]['react-js-cron'] = getReactJsCron(locale); + } + locales = sortJSON(locales); + for (const locale of Object.keys(locales)) { + await fs.writeFile( + path.resolve(process.cwd(), 'storage/locales', `${locale}.json`), + JSON.stringify(sortJSON(locales[locale]), null, 2), + ); + } + }); + + locale.command('sync').action(async (options) => {}); +}; diff --git a/packages/core/cli/src/commands/locale/cronstrue.js b/packages/core/cli/src/commands/locale/cronstrue.js new file mode 100644 index 0000000000..b59d41c8ea --- /dev/null +++ b/packages/core/cli/src/commands/locale/cronstrue.js @@ -0,0 +1,122 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +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', +}; + +exports.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) { + // empty + } + } + return items; +}; diff --git a/packages/core/cli/src/commands/locale/react-js-cron/en-US.json b/packages/core/cli/src/commands/locale/react-js-cron/en-US.json new file mode 100644 index 0000000000..c518d108a4 --- /dev/null +++ b/packages/core/cli/src/commands/locale/react-js-cron/en-US.json @@ -0,0 +1,75 @@ +{ + "everyText": "every", + "emptyMonths": "every month", + "emptyMonthDays": "every day of the month", + "emptyMonthDaysShort": "day of the month", + "emptyWeekDays": "every day of the week", + "emptyWeekDaysShort": "day of the week", + "emptyHours": "every hour", + "emptyMinutes": "every minute", + "emptyMinutesForHourPeriod": "every", + "yearOption": "year", + "monthOption": "month", + "weekOption": "week", + "dayOption": "day", + "hourOption": "hour", + "minuteOption": "minute", + "rebootOption": "reboot", + "prefixPeriod": "Every", + "prefixMonths": "in", + "prefixMonthDays": "on", + "prefixWeekDays": "on", + "prefixWeekDaysForMonthAndYearPeriod": "and", + "prefixHours": "at", + "prefixMinutes": ":", + "prefixMinutesForHourPeriod": "at", + "suffixMinutesForHourPeriod": "minute(s)", + "errorInvalidCron": "Invalid cron expression", + "clearButtonText": "Clear", + "weekDays": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ], + "months": [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" + ], + "altWeekDays": [ + "SUN", + "MON", + "TUE", + "WED", + "THU", + "FRI", + "SAT" + ], + "altMonths": [ + "JAN", + "FEB", + "MAR", + "APR", + "MAY", + "JUN", + "JUL", + "AUG", + "SEP", + "OCT", + "NOV", + "DEC" + ] +} \ No newline at end of file diff --git a/packages/core/cli/src/commands/locale/react-js-cron/index.js b/packages/core/cli/src/commands/locale/react-js-cron/index.js new file mode 100644 index 0000000000..c006afe6a2 --- /dev/null +++ b/packages/core/cli/src/commands/locale/react-js-cron/index.js @@ -0,0 +1,17 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +exports.getReactJsCron = (lang) => { + const langs = { + 'en-US': require('./en-US.json'), + 'zh-CN': require('./zh-CN.json'), + 'z-TW': require('./zh-TW.json'), + } + return langs[lang] || langs['en-US']; +} diff --git a/packages/core/cli/src/commands/locale/react-js-cron/zh-CN.json b/packages/core/cli/src/commands/locale/react-js-cron/zh-CN.json new file mode 100644 index 0000000000..3deea0f35c --- /dev/null +++ b/packages/core/cli/src/commands/locale/react-js-cron/zh-CN.json @@ -0,0 +1,33 @@ +{ + "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/core/cli/src/commands/locale/react-js-cron/zh-TW.json b/packages/core/cli/src/commands/locale/react-js-cron/zh-TW.json new file mode 100644 index 0000000000..ddc825b472 --- /dev/null +++ b/packages/core/cli/src/commands/locale/react-js-cron/zh-TW.json @@ -0,0 +1,33 @@ +{ + "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": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"] +}