diff --git a/modules/code-generator/jest.config.js b/modules/code-generator/jest.config.js index 0b213cd36..0f908143d 100644 --- a/modules/code-generator/jest.config.js +++ b/modules/code-generator/jest.config.js @@ -6,4 +6,5 @@ module.exports = { collectCoverage: false, collectCoverageFrom: ['src/**/*.{ts,tsx}', '!**/node_modules/**', '!**/vendor/**'], testMatch: ['/tests/**/*.test.ts'], + setupFiles: ['./jest.setup.js'], }; diff --git a/modules/code-generator/jest.setup.js b/modules/code-generator/jest.setup.js new file mode 100644 index 000000000..67da56145 --- /dev/null +++ b/modules/code-generator/jest.setup.js @@ -0,0 +1,12 @@ +// 对于 standalone 模式的专门 polyfills +if (process.env.TEST_TARGET === 'standalone') { + // 模拟浏览器环境 + global.window = global; + global.self = global; + + // 将所有测试用例里面的 './src' 都替换为 './dist/standalone' + jest.mock('./src', () => require('./dist/standalone')); +} + +// 如果在调试模式下,则不限制超时时间 +jest.setTimeout(typeof v8debug === 'object' ? Infinity : 30 * 1000); diff --git a/modules/code-generator/package.json b/modules/code-generator/package.json index 27253b3ee..039b87f84 100644 --- a/modules/code-generator/package.json +++ b/modules/code-generator/package.json @@ -85,6 +85,7 @@ "lodash-es": "^4.17.21", "mock-fs": "^5.1.2", "moment": "^2.29.1", + "nanomatch": "^1.2.13", "node-fetch": "2.x", "path-browserify": "^1.0.1", "prettier": "^2.5.1", diff --git a/modules/code-generator/scripts/run-demo-project b/modules/code-generator/scripts/run-demo-project new file mode 100755 index 000000000..1ad7dce23 --- /dev/null +++ b/modules/code-generator/scripts/run-demo-project @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +// @ts-check +const program = require('commander'); +const { spawnSync } = require('child_process'); +const glob = require('glob'); +const fs = require('fs'); +const path = require('path'); +const _ = require('lodash'); + +program + .option('--npm ', 'specify the npm command location or alias') + .arguments('') + .action((project, options) => { + try { + if (!fs.existsSync(project)) { + throw new Error(`Project ${project} does not exist`); + } + + const getProjectActualPath = [ + () => path.resolve(process.cwd(), project), + () => + path.resolve( + process.cwd(), + path.join( + project, + path.dirname(glob.sync('*/package.json', { cwd: project })[0] || ''), + ), + ), + ] + .map((x) => _.memoize(x)) + .find((x) => fs.existsSync(path.join(x(), 'package.json'))); + + if (!getProjectActualPath) { + throw new Error(`Project ${project} is not a valid project(no package.json)`); + } + + const projectActualPath = getProjectActualPath(); + if (path.resolve(process.cwd(), project) !== projectActualPath) { + console.log('Changing directory to', path.relative(process.cwd(), projectActualPath)); + } + + process.chdir(projectActualPath); + + const npm = options.npm || 'npm'; + const cmd = `${npm} install && ${npm} start`; + console.log('# %s', cmd); + spawnSync(cmd, { stdio: 'inherit', shell: true }); + } catch (err) { + console.log(err); + process.exit(1); + } + }); + +program.parse(process.argv); diff --git a/modules/code-generator/src/cli/solutions/example-solution.ts b/modules/code-generator/src/cli/solutions/example-solution.ts index df87c9f97..2efff780c 100644 --- a/modules/code-generator/src/cli/solutions/example-solution.ts +++ b/modules/code-generator/src/cli/solutions/example-solution.ts @@ -638,6 +638,7 @@ export default function createHelloWorldProjectBuilder() { CodeGen.plugins.react.reactCommonDeps(), CodeGen.plugins.common.esmodule({ fileType: 'jsx' }), CodeGen.plugins.react.containerClass(), + CodeGen.plugins.react.containerInjectContext(), CodeGen.plugins.react.containerInjectUtils(), CodeGen.plugins.react.containerInjectDataSourceEngine(), CodeGen.plugins.react.containerInjectI18n(), @@ -659,6 +660,7 @@ export default function createHelloWorldProjectBuilder() { CodeGen.plugins.react.reactCommonDeps(), CodeGen.plugins.common.esmodule({ fileType: 'jsx' }), CodeGen.plugins.react.containerClass(), + CodeGen.plugins.react.containerInjectContext(), CodeGen.plugins.react.containerInjectUtils(), CodeGen.plugins.react.containerInjectDataSourceEngine(), CodeGen.plugins.react.containerInjectI18n(), diff --git a/modules/code-generator/src/generator/ModuleBuilder.ts b/modules/code-generator/src/generator/ModuleBuilder.ts index 447e593b8..f4554ef61 100644 --- a/modules/code-generator/src/generator/ModuleBuilder.ts +++ b/modules/code-generator/src/generator/ModuleBuilder.ts @@ -5,6 +5,7 @@ import { CodeGeneratorError, ICodeChunk, ICompiledModule, + IContextData, IModuleBuilder, IParseResult, ISchemaParser, @@ -23,6 +24,7 @@ export function createModuleBuilder( plugins: BuilderComponentPlugin[]; postProcessors: PostProcessor[]; mainFileName?: string; + contextData?: IContextData; } = { plugins: [], postProcessors: [], @@ -41,7 +43,13 @@ export function createModuleBuilder( let files: ResultFile[] = []; - const { chunks } = await chunkGenerator.run(input); + const { chunks } = await chunkGenerator.run(input, { + ir: input, + chunks: [], + depNames: [], + contextData: options.contextData || {}, + }); + chunks.forEach((fileChunkList) => { const content = linker.link(fileChunkList); const file = createResultFile( diff --git a/modules/code-generator/src/generator/ProjectBuilder.ts b/modules/code-generator/src/generator/ProjectBuilder.ts index 68cc8a30c..ad513b70a 100644 --- a/modules/code-generator/src/generator/ProjectBuilder.ts +++ b/modules/code-generator/src/generator/ProjectBuilder.ts @@ -14,7 +14,7 @@ import { SchemaParser } from '../parser/SchemaParser'; import { createResultDir, addDirectory, addFile } from '../utils/resultHelper'; import { createModuleBuilder } from './ModuleBuilder'; -import { ProjectPreProcessor, ProjectPostProcessor } from '../types/core'; +import { ProjectPreProcessor, ProjectPostProcessor, IContextData } from '../types/core'; import { CodeGeneratorError } from '../types/error'; interface IModuleInfo { @@ -36,6 +36,10 @@ export interface ProjectBuilderInitOptions { projectPreProcessors?: ProjectPreProcessor[]; /** 项目级别的后置处理器 */ projectPostProcessors?: ProjectPostProcessor[]; + /** 是否处于严格模式 */ + inStrictMode?: boolean; + /** 一些额外的上下文数据 */ + extraContextData?: Record; } export class ProjectBuilder implements IProjectBuilder { @@ -57,6 +61,12 @@ export class ProjectBuilder implements IProjectBuilder { /** 项目级别的后置处理器 */ private projectPostProcessors: ProjectPostProcessor[]; + /** 是否处于严格模式 */ + public readonly inStrictMode: boolean; + + /** 一些额外的上下文数据 */ + public readonly extraContextData: IContextData; + constructor({ template, plugins, @@ -64,6 +74,8 @@ export class ProjectBuilder implements IProjectBuilder { schemaParser = new SchemaParser(), projectPreProcessors = [], projectPostProcessors = [], + inStrictMode = false, + extraContextData = {}, }: ProjectBuilderInitOptions) { this.template = template; this.plugins = plugins; @@ -71,6 +83,8 @@ export class ProjectBuilder implements IProjectBuilder { this.schemaParser = schemaParser; this.projectPreProcessors = projectPreProcessors; this.projectPostProcessors = projectPostProcessors; + this.inStrictMode = inStrictMode; + this.extraContextData = extraContextData; } async generateProject(originalSchema: ProjectSchema | string): Promise { @@ -264,6 +278,10 @@ export class ProjectBuilder implements IProjectBuilder { builders[pluginName] = createModuleBuilder({ plugins: this.plugins[pluginName], postProcessors: this.postProcessors, + contextData: { + inStrictMode: this.inStrictMode, + ...this.extraContextData, + }, ...options, }); } diff --git a/modules/code-generator/src/index.ts b/modules/code-generator/src/index.ts index d928c4ffc..7cf861e6f 100644 --- a/modules/code-generator/src/index.ts +++ b/modules/code-generator/src/index.ts @@ -25,16 +25,8 @@ import i18n from './plugins/project/i18n'; import utils from './plugins/project/utils'; import prettier from './postprocessor/prettier'; -// 引入常用工具 -import * as utilsCommon from './utils/common'; -import * as utilsCompositeType from './utils/compositeType'; -import * as utilsJsExpression from './utils/jsExpression'; -import * as utilsJsSlot from './utils/jsSlot'; -import * as utilsNodeToJSX from './utils/nodeToJSX'; -import * as utilsResultHelper from './utils/resultHelper'; -import * as utilsTemplateHelper from './utils/templateHelper'; -import * as utilsValidate from './utils/validate'; -import * as utilsSchema from './utils/schema'; +// 引入全局常用工具 +import * as globalUtils from './utils'; import * as CONSTANTS from './const'; @@ -85,17 +77,7 @@ export default { postprocessor: { prettier, }, - utils: { - common: utilsCommon, - compositeType: utilsCompositeType, - jsExpression: utilsJsExpression, - jsSlot: utilsJsSlot, - nodeToJSX: utilsNodeToJSX, - resultHelper: utilsResultHelper, - templateHelper: utilsTemplateHelper, - validate: utilsValidate, - schema: utilsSchema, - }, + utils: globalUtils, chunkNames: { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, diff --git a/modules/code-generator/src/plugins/component/rax/containerClass.ts b/modules/code-generator/src/plugins/component/rax/containerClass.ts index 6b90f4fff..5e923635b 100644 --- a/modules/code-generator/src/plugins/component/rax/containerClass.ts +++ b/modules/code-generator/src/plugins/component/rax/containerClass.ts @@ -134,19 +134,6 @@ const pluginFactory: BuilderComponentPluginFactory = () => { ], }); - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, - content: ` - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - `, - linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd], - }); - next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, diff --git a/modules/code-generator/src/plugins/component/rax/containerInjectContext.ts b/modules/code-generator/src/plugins/component/rax/containerInjectContext.ts index 149339ec3..fed943aab 100644 --- a/modules/code-generator/src/plugins/component/rax/containerInjectContext.ts +++ b/modules/code-generator/src/plugins/component/rax/containerInjectContext.ts @@ -10,6 +10,7 @@ import { IContainerInfo, } from '../../../types'; import { RAX_CHUNK_NAME } from './const'; +import { DEFAULT_LINK_AFTER } from '../../../const'; export interface PluginConfig { fileType: string; @@ -46,6 +47,16 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, + content: ` + __$$i18n._inject2(this); + `, + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]], + }); + next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, diff --git a/modules/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts b/modules/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts index 47d654b90..446b1d3f8 100644 --- a/modules/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts +++ b/modules/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts @@ -29,6 +29,11 @@ import { RAX_CHUNK_NAME } from './const'; export interface PluginConfig extends RaxFrameworkOptions { fileType?: string; + + /** + * 数据源的 handlers 的映射配置 + * @deprecated 请使用 datasourceConfig.handlersPackages 来配置 + */ dataSourceHandlersPackageMap?: Record; } diff --git a/modules/code-generator/src/plugins/component/react/containerInjectContext.ts b/modules/code-generator/src/plugins/component/react/containerInjectContext.ts new file mode 100644 index 000000000..eb687ef0e --- /dev/null +++ b/modules/code-generator/src/plugins/component/react/containerInjectContext.ts @@ -0,0 +1,62 @@ +import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; + +import { Scope } from '../../../utils/Scope'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, + IContainerInfo, +} from '../../../types'; + +export interface PluginConfig { + fileType: string; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + ...config, + }; + + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IContainerInfo; + const scope = Scope.createRootScope(); + + const { inStrictMode } = next.contextData; + if (inStrictMode) { + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: ` + _context = this._createContext(); + `, + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], + }); + // TODO: createContext...... + } else { + // 非严格模式下,上下文就是自己 + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: ` + _context = this; + `, + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], + }); + } + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/modules/code-generator/src/plugins/component/react/containerInjectDataSourceEngine.ts b/modules/code-generator/src/plugins/component/react/containerInjectDataSourceEngine.ts index 860b9e755..20302dea9 100644 --- a/modules/code-generator/src/plugins/component/react/containerInjectDataSourceEngine.ts +++ b/modules/code-generator/src/plugins/component/react/containerInjectDataSourceEngine.ts @@ -31,13 +31,34 @@ import { isContainerSchema } from '../../../utils/schema'; import { REACT_CHUNK_NAME } from './const'; export interface PluginConfig { - fileType: string; + fileType?: string; + + /** + * 数据源配置 + */ + datasourceConfig?: { + /** 数据源引擎的版本 */ + engineVersion?: string; + + /** 数据源引擎的包名 */ + enginePackage?: string; + + /** 数据源 handlers 的版本 */ + handlersVersion?: { + [key: string]: string; + }; + + /** 数据源 handlers 的包名 */ + handlersPackages?: { + [key: string]: string; + }; + }; } const pluginFactory: BuilderComponentPluginFactory = (config?) => { - const cfg: PluginConfig = { - fileType: FileType.JSX, + const cfg = { ...config, + fileType: config?.fileType || FileType.JSX, }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { @@ -65,7 +86,9 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => }; const handlerFactoryExportName = `create${changeCase.pascal(dsType)}Handler`; - const handlerPkgName = `@alilc/lowcode-datasource-${changeCase.kebab(dsType)}-handler`; + const handlerPkgName = + cfg.datasourceConfig?.handlersPackages?.[dsType] || + `@alilc/lowcode-datasource-${changeCase.kebab(dsType)}-handler`; next.chunks.push({ type: ChunkType.STRING, diff --git a/modules/code-generator/src/plugins/component/react/containerInjectI18n.ts b/modules/code-generator/src/plugins/component/react/containerInjectI18n.ts index 6a6c39e05..da04029c0 100644 --- a/modules/code-generator/src/plugins/component/react/containerInjectI18n.ts +++ b/modules/code-generator/src/plugins/component/react/containerInjectI18n.ts @@ -33,7 +33,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => name: COMMON_CHUNK_NAME.InternalDepsImport, // TODO: 下面这个路径有没有更好的方式来获取?而非写死 content: ` - import { i18n as _$$i18n } from '../../i18n'; + import * as __$$i18n from '../../i18n'; `, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); @@ -41,13 +41,11 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, - name: CLASS_DEFINE_CHUNK_NAME.InsMethod, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: ` - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - } + __$$i18n._inject2(this); `, - linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]], }); return next; diff --git a/modules/code-generator/src/plugins/component/react/jsx.ts b/modules/code-generator/src/plugins/component/react/jsx.ts index 67c18e177..d6f7cf932 100644 --- a/modules/code-generator/src/plugins/component/react/jsx.ts +++ b/modules/code-generator/src/plugins/component/react/jsx.ts @@ -100,8 +100,8 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => fileType: cfg.fileType, name: REACT_CHUNK_NAME.ClassRenderJSX, content: ` - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return ${jsxContent}; `, linkAfter: [REACT_CHUNK_NAME.ClassRenderStart, REACT_CHUNK_NAME.ClassRenderPre], diff --git a/modules/code-generator/src/plugins/component/react/reactCommonDeps.ts b/modules/code-generator/src/plugins/component/react/reactCommonDeps.ts index 2e71f8d90..f42aaf61b 100644 --- a/modules/code-generator/src/plugins/component/react/reactCommonDeps.ts +++ b/modules/code-generator/src/plugins/component/react/reactCommonDeps.ts @@ -18,7 +18,10 @@ const pluginFactory: BuilderComponentPluginFactory = () => { type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.ExternalDepsImport, - content: 'import React from \'react\';', + content: ` +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 +import React from \'react\';`, linkAfter: [], }); diff --git a/modules/code-generator/src/plugins/project/i18n.ts b/modules/code-generator/src/plugins/project/i18n.ts index eaac073c8..a1fbcdf36 100644 --- a/modules/code-generator/src/plugins/project/i18n.ts +++ b/modules/code-generator/src/plugins/project/i18n.ts @@ -23,11 +23,9 @@ const pluginFactory: BuilderComponentPluginFactory = () => { fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileMainContent, content: ` - import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = ${i18nStr}; - let locale = 'en-US'; + let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -35,23 +33,65 @@ const pluginFactory: BuilderComponentPluginFactory = () => { locale = target; }; - const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = i18nConfig && i18nConfig[locale] && i18nConfig[locale][id] || defaultMessage; + const isEmptyVariables = variables => ( + Array.isArray(variables) && variables.length === 0 + || typeof variables === 'object' && (!variables || Object.keys(variables).length === 0) + ); + + // 按低代码规范里面的要求进行变量替换 + const format = (msg, variables) => ( + typeof msg === 'string' + ? msg.replace(/\\\$\\{(\\w+)\\}/g, (match, key) => variables?.[key] ?? '') + : msg + ); + + const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return \`\${id}\`; + return fallback === undefined ? \`\${id}\` : fallback; } - if (!variables || !variables.length) { - return msg; - } - - return new IntlMessageFormat(msg, locale).format(variables); + return format(msg, variables); } - const i18n = id => { - return i18nFormat({ id }); + const i18n = (id, params) => { + return i18nFormat({ id }, params); }; + + // 将国际化的一些方法注入到目标对象&上下文中 + const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')] + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + } + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, setLocale: target.setLocale + }); + } + } `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, @@ -72,6 +112,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { setLocale, i18n, i18nFormat, + _inject2, }; `, linkAfter: [ diff --git a/modules/code-generator/src/solutions/icejs.ts b/modules/code-generator/src/solutions/icejs.ts index 07ead1b7a..1b90f0b54 100644 --- a/modules/code-generator/src/solutions/icejs.ts +++ b/modules/code-generator/src/solutions/icejs.ts @@ -5,6 +5,7 @@ import { createProjectBuilder } from '../generator/ProjectBuilder'; import esmodule from '../plugins/common/esmodule'; import containerClass from '../plugins/component/react/containerClass'; import containerInitState from '../plugins/component/react/containerInitState'; +import containerInjectContext from '../plugins/component/react/containerInjectContext'; import containerInjectUtils from '../plugins/component/react/containerInjectUtils'; import containerInjectDataSourceEngine from '../plugins/component/react/containerInjectDataSourceEngine'; import containerInjectI18n from '../plugins/component/react/containerInjectI18n'; @@ -31,6 +32,7 @@ export default function createIceJsProjectBuilder(): IProjectBuilder { fileType: 'jsx', }), containerClass(), + containerInjectContext(), containerInjectUtils(), containerInjectDataSourceEngine(), containerInjectI18n(), @@ -53,6 +55,7 @@ export default function createIceJsProjectBuilder(): IProjectBuilder { fileType: 'jsx', }), containerClass(), + containerInjectContext(), containerInjectUtils(), containerInjectDataSourceEngine(), containerInjectI18n(), @@ -86,6 +89,7 @@ export default function createIceJsProjectBuilder(): IProjectBuilder { export const plugins = { containerClass, containerInitState, + containerInjectContext, containerInjectUtils, containerInjectI18n, containerInjectDataSourceEngine, diff --git a/modules/code-generator/src/standalone.ts b/modules/code-generator/src/standalone.ts index 2b33b6d81..2f15515cf 100644 --- a/modules/code-generator/src/standalone.ts +++ b/modules/code-generator/src/standalone.ts @@ -25,16 +25,8 @@ import i18n from './plugins/project/i18n'; import utils from './plugins/project/utils'; import prettier from './postprocessor/prettier'; -// 引入常用工具 -import * as utilsCommon from './utils/common'; -import * as utilsCompositeType from './utils/compositeType'; -import * as utilsJsExpression from './utils/jsExpression'; -import * as utilsJsSlot from './utils/jsSlot'; -import * as utilsNodeToJSX from './utils/nodeToJSX'; -import * as utilsResultHelper from './utils/resultHelper'; -import * as utilsTemplateHelper from './utils/templateHelper'; -import * as utilsValidate from './utils/validate'; -import * as utilsSchema from './utils/schema'; +// 引入全局常用工具 +import * as globalUtils from './utils'; import * as CONSTANTS from './const'; @@ -54,7 +46,6 @@ export default { rax, }, publishers: { - // TODO: 增加 web 端的 zip publisher zip: createZipPublisher, }, plugins: { @@ -85,17 +76,7 @@ export default { postprocessor: { prettier, }, - utils: { - common: utilsCommon, - compositeType: utilsCompositeType, - jsExpression: utilsJsExpression, - jsSlot: utilsJsSlot, - nodeToJSX: utilsNodeToJSX, - resultHelper: utilsResultHelper, - templateHelper: utilsTemplateHelper, - validate: utilsValidate, - schema: utilsSchema, - }, + utils: globalUtils, chunkNames: { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, diff --git a/modules/code-generator/src/types/core.ts b/modules/code-generator/src/types/core.ts index abc7d1d32..841b8fe02 100644 --- a/modules/code-generator/src/types/core.ts +++ b/modules/code-generator/src/types/core.ts @@ -59,8 +59,22 @@ export interface IBaseCodeStruct { export interface ICodeStruct extends IBaseCodeStruct { ir: any; - // FIXME: 这个方案不太好,想清楚场景,结构化表达,不要那么通用 - contextData: Record; + contextData: IContextData; +} + +/** 上下文数据,用来在插件之间共享一些数据 */ +export interface IContextData { + /** 是否处于严格模式 */ + inStrictMode?: boolean; + + /** 是否使用了 Ref 的 API (this.$/this.$$) */ + useRefApi?: boolean; + + /** + * 其他自定义数据 + * (三方自定义插件也可以在此放一些数据,建议起个长一点的名称,用自己的插件名做前缀,以防冲突) + */ + [key: string]: any; } export type BuilderComponentPlugin = (initStruct: ICodeStruct) => Promise; diff --git a/modules/code-generator/src/typings.d.ts b/modules/code-generator/src/typings.d.ts new file mode 100644 index 000000000..be8d00776 --- /dev/null +++ b/modules/code-generator/src/typings.d.ts @@ -0,0 +1 @@ +declare module 'nanomatch'; diff --git a/modules/code-generator/src/utils/compositeType.ts b/modules/code-generator/src/utils/compositeType.ts index e876ec6e5..d4885e224 100644 --- a/modules/code-generator/src/utils/compositeType.ts +++ b/modules/code-generator/src/utils/compositeType.ts @@ -57,7 +57,11 @@ function generateObject( options: CompositeValueGeneratorOptions = {}, ): string { if (value.type === 'i18n') { - return `this._i18nText(${JSON.stringify(value)})`; + // params 可能会绑定变量,这里需要处理下 + if (value.params && typeof value.params === 'object') { + return `this._i18nText(${generateUnknownType(_.omit(value, 'type'), scope, options)})`; + } + return `this._i18nText(${JSON.stringify(_.omit(value, 'type'))})`; // TODO: 优化:这里可以考虑提取成个常量... } const body = Object.keys(value) diff --git a/modules/code-generator/src/utils/index.ts b/modules/code-generator/src/utils/index.ts new file mode 100644 index 000000000..ff5194172 --- /dev/null +++ b/modules/code-generator/src/utils/index.ts @@ -0,0 +1,28 @@ +// 本文件是要导出到外面的,注意只导出比较稳定的东西 +import * as common from './common'; +import * as compositeType from './compositeType'; +import * as jsExpression from './jsExpression'; +import * as jsSlot from './jsSlot'; +import * as nodeToJSX from './nodeToJSX'; +import * as resultHelper from './resultHelper'; +import * as templateHelper from './templateHelper'; +import * as validate from './validate'; +import * as schema from './schema'; +import * as version from './version'; +import * as scope from './Scope'; +import * as expressionParser from './expressionParser'; + +export { + common, + compositeType, + jsExpression, + jsSlot, + nodeToJSX, + resultHelper, + templateHelper, + validate, + schema, + version, + scope, + expressionParser, +}; diff --git a/modules/code-generator/src/utils/resultHelper.ts b/modules/code-generator/src/utils/resultHelper.ts index 627004656..d7a3ef565 100644 --- a/modules/code-generator/src/utils/resultHelper.ts +++ b/modules/code-generator/src/utils/resultHelper.ts @@ -1,4 +1,5 @@ import { ResultFile, ResultDir } from '@alilc/lowcode-types'; +import nm from 'nanomatch'; import { CodeGeneratorError } from '../types/error'; import { FlattenFile } from '../types/file'; @@ -47,12 +48,204 @@ export function flattenResult(dir: ResultDir, cwd = ''): FlattenFile[] { return [ ...dir.files.map( (file): FlattenFile => ({ - pathName: `${cwd ? `${cwd}/` : ''}${file.name}${file.ext ? `.${file.ext}` : ''}`, + pathName: joinPath(cwd, `${file.name}${file.ext ? `.${file.ext}` : ''}`), content: file.content, }), ), - ].concat( - ...dir.dirs.map((subDir) => - flattenResult(subDir, [cwd, subDir.name].filter((x) => x !== '' && x !== '.').join('/'))), - ); + ].concat(...dir.dirs.map((subDir) => flattenResult(subDir, joinPath(cwd, subDir.name)))); +} + +export type GlobOptions = { + /** 是否查找 ".xxx" 文件, 默认: 否 */ + dot?: boolean; +}; + +/** + * 查找文件 + * @param result 出码结果 + * @param fileGlobExpr 文件名匹配表达式 + * @param resultDirPath 出码结果的路径(默认是 '.') + * @returns 匹配的第一个文件或 null (找不到) + */ +export function findFile( + result: ResultDir, + fileGlobExpr: string, + options: GlobOptions = {}, + resultDirPath = getResultNameOrDefault(result, ''), +): ResultFile | null { + const maxDepth = !/\/|\*\*/.test(fileGlobExpr) ? 1 : undefined; // 如果 glob 表达式里面压根不会匹配子目录,则深度限制为 1 + const files = scanFiles(result, resultDirPath, maxDepth); + + for (let [filePath, file] of files) { + if (nm.isMatch(filePath, fileGlobExpr, options)) { + return file; + } + } + + return null; +} + +/** + * 使用 glob 语法查找多个文件 + * @param result 出码结果 + * @param fileGlobExpr 文件名匹配表达式 + * @param resultDirPath 出码结果的路径(默认是 '.') + * @returns 找到的文件列表的迭代器 [ [文件路径, 文件信息], ... ] + */ +export function* globFiles( + result: ResultDir, + fileGlobExpr: string, + options: GlobOptions = {}, + resultDirPath = getResultNameOrDefault(result, ''), +): IterableIterator<[string, ResultFile]> { + const files = scanFiles(result, resultDirPath); + + for (let [filePath, file] of files) { + if (nm.isMatch(filePath, fileGlobExpr, options)) { + yield [filePath, file]; + } + } +} + +/** + * 遍历所有的文件 + */ +export function* scanFiles( + result: ResultDir, + resultDirPath = getResultNameOrDefault(result, ''), + maxDepth = 10000, +): IterableIterator<[string, ResultFile]> { + for (let file of result.files) { + const fileName = getFileNameWithExt(file); + yield [joinPath(resultDirPath, fileName), file]; + } + + for (let subDir of result.dirs) { + yield* scanFiles(subDir, joinPath(resultDirPath, subDir.name), maxDepth - 1); + } +} + +export function getFileNameWithExt(file: ResultFile) { + return `${file.name}${file.ext ? `.${file.ext}` : ''}`; +} + +function getResultNameOrDefault(result: ResultDir, defaultDir = '/') { + return result.name && result.name !== '.' ? result.name : defaultDir; +} + +function joinPath(...pathParts: string[]): string { + return pathParts + .filter((x) => x !== '' && x !== '.') + .join('/') + .replace(/\\+/g, '/') + .replace(/\/+/g, '/'); +} + +export function* scanDirs( + result: ResultDir, + resultDirPath = getResultNameOrDefault(result, ''), + maxDepth = 10000, +): IterableIterator<[string, ResultDir]> { + yield [resultDirPath, result]; + + for (let subDir of result.dirs) { + yield* scanDirs(subDir, joinPath(resultDirPath, subDir.name), maxDepth - 1); + } +} + +export function* globDirs( + result: ResultDir, + dirGlobExpr: string, + options: GlobOptions = {}, + resultDirPath = getResultNameOrDefault(result, ''), +): IterableIterator<[string, ResultDir]> { + const dirs = scanDirs(result, resultDirPath); + + for (let [dirPath, dir] of dirs) { + if (nm.isMatch(dirPath, dirGlobExpr, options)) { + yield [dirPath, dir]; + } + } +} + +export function findDir( + result: ResultDir, + dirGlobExpr: string, + options: GlobOptions = {}, + resultDirPath = getResultNameOrDefault(result, ''), +): ResultDir | null { + const dirs = scanDirs(result, resultDirPath); + + for (let [dirPath, dir] of dirs) { + if (nm.isMatch(dirPath, dirGlobExpr, options)) { + return dir; + } + } + + return null; +} + +/** + * 从结果中移除一些文件 + * @param result 出码结果目录 + * @param filePathGlobExpr 要移除的文件路径(glob 表达式) + * @param globOptions glob 参数 + * @returns 移除了多少文件 + */ +export function removeFilesFromResult( + result: ResultDir, + filePathGlobExpr: string, + globOptions: GlobOptions = {}, +): number { + let removedCount = 0; + const [dirPath, fileName] = splitPath(filePathGlobExpr); + + const dirs = dirPath ? globDirs(result, dirPath) : [['', result] as const]; + for (let [, dir] of dirs) { + const files = globFiles(dir, fileName, globOptions, '.'); + for (let [, file] of files) { + dir.files.splice(dir.files.indexOf(file), 1); + removedCount += 1; + } + } + + return removedCount; +} + +/** + * 从结果中移除一些目录 + * @param result 出码结果目录 + * @param dirPathGlobExpr 要移除的目录路径(glob 表达式) + * @param globOptions glob 参数 + * @returns 移除了多少文件 + */ +export function removeDirsFromResult( + result: ResultDir, + dirPathGlobExpr: string, + globOptions: GlobOptions = {}, +): number { + let removedCount = 0; + const [dirPath, fileName] = splitPath(dirPathGlobExpr); + + const dirs = dirPath ? globDirs(result, dirPath) : [['', result] as const]; + for (let [, dir] of dirs) { + const foundDirs = globDirs(dir, fileName, globOptions, '.'); + for (let [, foundDir] of foundDirs) { + dir.dirs.splice(dir.dirs.indexOf(foundDir), 1); + removedCount += 1; + } + } + + return removedCount; +} + +/** + * 将文件路径拆分为目录路径和文件名 + * @param filePath + * @returns [fileDirPath, fileName] + */ +function splitPath(filePath: string) { + const parts = filePath.split('/'); + const fileName = parts.pop() || ''; + return [joinPath(...parts), fileName]; } diff --git a/modules/code-generator/test-cases/rax-app/demo01/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo01/expected/demo-project/src/i18n.js index 8f0de31e0..f9c486fc1 100644 --- a/modules/code-generator/test-cases/rax-app/demo01/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo01/expected/demo-project/src/i18n.js @@ -1,8 +1,6 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = {}; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -10,22 +8,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo01/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo01/expected/demo-project/src/pages/Home/index.jsx index eacd2ae69..5e39ecbac 100644 --- a/modules/code-generator/test-cases/rax-app/demo01/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo01/expected/demo-project/src/pages/Home/index.jsx @@ -33,6 +33,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -63,11 +65,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo02/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo02/expected/demo-project/src/i18n.js index 8f0de31e0..f9c486fc1 100644 --- a/modules/code-generator/test-cases/rax-app/demo02/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo02/expected/demo-project/src/i18n.js @@ -1,8 +1,6 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = {}; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -10,22 +8,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo02/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo02/expected/demo-project/src/pages/Home/index.jsx index b3c234b27..caaa64567 100644 --- a/modules/code-generator/test-cases/rax-app/demo02/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo02/expected/demo-project/src/pages/Home/index.jsx @@ -68,6 +68,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -166,11 +168,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/i18n.js index 8f0de31e0..f9c486fc1 100644 --- a/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/i18n.js @@ -1,8 +1,6 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = {}; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -10,22 +8,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/Detail/index.jsx b/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/Detail/index.jsx index 2571a3e80..f4cff2fc6 100644 --- a/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/Detail/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/Detail/index.jsx @@ -37,6 +37,8 @@ class Detail$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -72,11 +74,6 @@ class Detail$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/Home/index.jsx index ad8ed68f0..8b7719f92 100644 --- a/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/Home/index.jsx @@ -37,6 +37,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -72,11 +74,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/List/index.jsx b/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/List/index.jsx index 4b61409ac..9759b157d 100644 --- a/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/List/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo03/expected/demo-project/src/pages/List/index.jsx @@ -37,6 +37,8 @@ class List$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -75,11 +77,6 @@ class List$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo04/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo04/expected/demo-project/src/i18n.js index 8f0de31e0..f9c486fc1 100644 --- a/modules/code-generator/test-cases/rax-app/demo04/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo04/expected/demo-project/src/i18n.js @@ -1,8 +1,6 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = {}; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -10,22 +8,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo04/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo04/expected/demo-project/src/pages/Home/index.jsx index fcc9d46fa..f28ab5284 100644 --- a/modules/code-generator/test-cases/rax-app/demo04/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo04/expected/demo-project/src/pages/Home/index.jsx @@ -35,6 +35,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -67,11 +69,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo05/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo05/expected/demo-project/src/i18n.js index c3e7c955d..d5ba5759d 100644 --- a/modules/code-generator/test-cases/rax-app/demo05/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo05/expected/demo-project/src/i18n.js @@ -1,5 +1,3 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = { 'zh-CN': { 'hello-world': '你好,世界!', @@ -9,7 +7,7 @@ const i18nConfig = { }, }; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -17,22 +15,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo05/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo05/expected/demo-project/src/pages/Home/index.jsx index ea0ce733d..6dee43f56 100644 --- a/modules/code-generator/test-cases/rax-app/demo05/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo05/expected/demo-project/src/pages/Home/index.jsx @@ -33,6 +33,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -69,11 +71,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo06-jsslot/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo06-jsslot/expected/demo-project/src/i18n.js index c3e7c955d..d5ba5759d 100644 --- a/modules/code-generator/test-cases/rax-app/demo06-jsslot/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo06-jsslot/expected/demo-project/src/i18n.js @@ -1,5 +1,3 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = { 'zh-CN': { 'hello-world': '你好,世界!', @@ -9,7 +7,7 @@ const i18nConfig = { }, }; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -17,22 +15,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo06-jsslot/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo06-jsslot/expected/demo-project/src/pages/Home/index.jsx index 368253370..42d65d8ec 100644 --- a/modules/code-generator/test-cases/rax-app/demo06-jsslot/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo06-jsslot/expected/demo-project/src/pages/Home/index.jsx @@ -35,6 +35,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -75,11 +77,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo07-newline-in-props/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo07-newline-in-props/expected/demo-project/src/i18n.js index c3e7c955d..d5ba5759d 100644 --- a/modules/code-generator/test-cases/rax-app/demo07-newline-in-props/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo07-newline-in-props/expected/demo-project/src/i18n.js @@ -1,5 +1,3 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = { 'zh-CN': { 'hello-world': '你好,世界!', @@ -9,7 +7,7 @@ const i18nConfig = { }, }; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -17,22 +15,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo07-newline-in-props/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo07-newline-in-props/expected/demo-project/src/pages/Home/index.jsx index c669b4fe5..cc14e59b6 100644 --- a/modules/code-generator/test-cases/rax-app/demo07-newline-in-props/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo07-newline-in-props/expected/demo-project/src/pages/Home/index.jsx @@ -33,6 +33,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -69,11 +71,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo08-jsslot-with-multiple-children/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo08-jsslot-with-multiple-children/expected/demo-project/src/i18n.js index c3e7c955d..d5ba5759d 100644 --- a/modules/code-generator/test-cases/rax-app/demo08-jsslot-with-multiple-children/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo08-jsslot-with-multiple-children/expected/demo-project/src/i18n.js @@ -1,5 +1,3 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = { 'zh-CN': { 'hello-world': '你好,世界!', @@ -9,7 +7,7 @@ const i18nConfig = { }, }; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -17,22 +15,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo08-jsslot-with-multiple-children/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo08-jsslot-with-multiple-children/expected/demo-project/src/pages/Home/index.jsx index ef0e1a848..3b2ba46ea 100644 --- a/modules/code-generator/test-cases/rax-app/demo08-jsslot-with-multiple-children/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo08-jsslot-with-multiple-children/expected/demo-project/src/pages/Home/index.jsx @@ -35,6 +35,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -75,11 +77,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo09-jsslot-with-conditional-children/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo09-jsslot-with-conditional-children/expected/demo-project/src/i18n.js index c3e7c955d..d5ba5759d 100644 --- a/modules/code-generator/test-cases/rax-app/demo09-jsslot-with-conditional-children/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo09-jsslot-with-conditional-children/expected/demo-project/src/i18n.js @@ -1,5 +1,3 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = { 'zh-CN': { 'hello-world': '你好,世界!', @@ -9,7 +7,7 @@ const i18nConfig = { }, }; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -17,22 +15,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo09-jsslot-with-conditional-children/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo09-jsslot-with-conditional-children/expected/demo-project/src/pages/Home/index.jsx index 353fa084e..0b8f800ab 100644 --- a/modules/code-generator/test-cases/rax-app/demo09-jsslot-with-conditional-children/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo09-jsslot-with-conditional-children/expected/demo-project/src/pages/Home/index.jsx @@ -35,6 +35,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -78,11 +80,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo10-jsslot-with-loop-children/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo10-jsslot-with-loop-children/expected/demo-project/src/i18n.js index c3e7c955d..d5ba5759d 100644 --- a/modules/code-generator/test-cases/rax-app/demo10-jsslot-with-loop-children/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo10-jsslot-with-loop-children/expected/demo-project/src/i18n.js @@ -1,5 +1,3 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = { 'zh-CN': { 'hello-world': '你好,世界!', @@ -9,7 +7,7 @@ const i18nConfig = { }, }; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -17,22 +15,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo10-jsslot-with-loop-children/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo10-jsslot-with-loop-children/expected/demo-project/src/pages/Home/index.jsx index 8e457c7e3..5514f581a 100644 --- a/modules/code-generator/test-cases/rax-app/demo10-jsslot-with-loop-children/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo10-jsslot-with-loop-children/expected/demo-project/src/pages/Home/index.jsx @@ -35,6 +35,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -80,11 +82,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo11-utils-name-alias/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo11-utils-name-alias/expected/demo-project/src/i18n.js index 8f0de31e0..f9c486fc1 100644 --- a/modules/code-generator/test-cases/rax-app/demo11-utils-name-alias/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo11-utils-name-alias/expected/demo-project/src/i18n.js @@ -1,8 +1,6 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = {}; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -10,22 +8,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo11-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx b/modules/code-generator/test-cases/rax-app/demo11-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx index 3fa72f414..c88f22314 100644 --- a/modules/code-generator/test-cases/rax-app/demo11-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo11-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx @@ -40,6 +40,8 @@ class Aaaa$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -70,11 +72,6 @@ class Aaaa$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo12-refs/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo12-refs/expected/demo-project/src/i18n.js index c3e7c955d..d5ba5759d 100644 --- a/modules/code-generator/test-cases/rax-app/demo12-refs/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo12-refs/expected/demo-project/src/i18n.js @@ -1,5 +1,3 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = { 'zh-CN': { 'hello-world': '你好,世界!', @@ -9,7 +7,7 @@ const i18nConfig = { }, }; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -17,22 +15,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo12-refs/expected/demo-project/src/pages/Home/index.jsx b/modules/code-generator/test-cases/rax-app/demo12-refs/expected/demo-project/src/pages/Home/index.jsx index 0475ae5c2..309e6cd96 100644 --- a/modules/code-generator/test-cases/rax-app/demo12-refs/expected/demo-project/src/pages/Home/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo12-refs/expected/demo-project/src/pages/Home/index.jsx @@ -33,6 +33,8 @@ class Home$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -70,11 +72,6 @@ class Home$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/rax-app/demo13-datasource-prop/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/rax-app/demo13-datasource-prop/expected/demo-project/src/i18n.js index 8f0de31e0..f9c486fc1 100644 --- a/modules/code-generator/test-cases/rax-app/demo13-datasource-prop/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/rax-app/demo13-datasource-prop/expected/demo-project/src/i18n.js @@ -1,8 +1,6 @@ -import IntlMessageFormat from 'intl-messageformat'; - const i18nConfig = {}; -let locale = 'en-US'; +let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN'; const getLocale = () => locale; @@ -10,22 +8,61 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { - const msg = (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || defaultMessage; +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === 'object' && (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === 'string' ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? '') : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { + const msg = i18nConfig[locale]?.[id] ?? i18nConfig[locale.replace('-', '_')]?.[id] ?? defaultMessage; if (msg == null) { console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace('-', '_')]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || 'zh_CN'] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/rax-app/demo13-datasource-prop/expected/demo-project/src/pages/Example/index.jsx b/modules/code-generator/test-cases/rax-app/demo13-datasource-prop/expected/demo-project/src/pages/Example/index.jsx index 60a8766f4..370aab992 100644 --- a/modules/code-generator/test-cases/rax-app/demo13-datasource-prop/expected/demo-project/src/pages/Example/index.jsx +++ b/modules/code-generator/test-cases/rax-app/demo13-datasource-prop/expected/demo-project/src/pages/Example/index.jsx @@ -36,6 +36,8 @@ class Example$$Page extends Component { constructor(props, context) { super(props); + + __$$i18n._inject2(this); } /* end of constructor */ componentDidMount() { @@ -72,11 +74,6 @@ class Example$$Page extends Component { ); } /* end of render */ - _i18nText(t) { - const locale = this._context.getLocale(); - return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US; - } - _createContext() { const self = this; const context = { diff --git a/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/i18n.js index 316a16ac5..85ae1e019 100644 --- a/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/i18n.js @@ -1,8 +1,9 @@ -import IntlMessageFormat from "intl-messageformat"; - const i18nConfig = {}; -let locale = "en-US"; +let locale = + typeof navigator === "object" && typeof navigator.language === "string" + ? navigator.language + : "zh-CN"; const getLocale = () => locale; @@ -10,24 +11,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/pages/Test/index.jsx index 32a5fbc94..2d712d5ec 100644 --- a/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/pages/Test/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo1/expected/demo-project/src/pages/Test/index.jsx @@ -1,3 +1,5 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import { Form, Input, NumberPicker, Select, Button } from "@alifd/next"; @@ -10,11 +12,13 @@ import { create as __$$createDataSourceEngine } from "@alilc/lowcode-datasource- import utils, { RefsManager } from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; class Test$$Page extends React.Component { + _context = this; + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this, { runtimeConfig: true, @@ -39,6 +43,8 @@ class Test$$Page extends React.Component { this._refsManager = new RefsManager(); + __$$i18n._inject2(this); + this.state = { text: "outter" }; } @@ -113,10 +119,6 @@ class Test$$Page extends React.Component { }; } - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentDidMount() { this._dataSourceEngine.reloadDataSource(); @@ -124,8 +126,8 @@ class Test$$Page extends React.Component { } render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
locale; @@ -10,24 +11,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo2-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx b/modules/code-generator/test-cases/react-app/demo2-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx index 37804ac96..91e1630a7 100644 --- a/modules/code-generator/test-cases/react-app/demo2-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo2-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx @@ -1,3 +1,5 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import { Page } from "@alilc/b6-page"; @@ -10,11 +12,13 @@ import { create as __$$createDataSourceEngine } from "@alilc/lowcode-datasource- import utils from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; class Aaaa$$Page extends React.Component { + _context = this; + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this, { runtimeConfig: true, @@ -36,6 +40,8 @@ class Aaaa$$Page extends React.Component { this.utils = utils; + __$$i18n._inject2(this); + this.state = {}; } @@ -60,17 +66,13 @@ class Aaaa$$Page extends React.Component { }; } - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentDidMount() { this._dataSourceEngine.reloadDataSource(); } render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
locale; @@ -19,24 +20,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo2/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/test-cases/react-app/demo2/expected/demo-project/src/pages/Test/index.jsx index ee2b503ba..c9f737575 100644 --- a/modules/code-generator/test-cases/react-app/demo2/expected/demo-project/src/pages/Test/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo2/expected/demo-project/src/pages/Test/index.jsx @@ -1,14 +1,18 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import { Form, Input, NumberPicker, Select, Button } from "@alifd/next"; import utils, { RefsManager } from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; class Test$$Page extends React.Component { + _context = this; + constructor(props, context) { super(props); @@ -16,6 +20,8 @@ class Test$$Page extends React.Component { this._refsManager = new RefsManager(); + __$$i18n._inject2(this); + this.state = { text: "outter" }; } @@ -27,17 +33,13 @@ class Test$$Page extends React.Component { return this._refsManager.getAll(refName); }; - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentDidMount() { console.log("componentDidMount"); } render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
locale; @@ -19,24 +20,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo3/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/test-cases/react-app/demo3/expected/demo-project/src/pages/Test/index.jsx index adf65c1b7..ec43915d2 100644 --- a/modules/code-generator/test-cases/react-app/demo3/expected/demo-project/src/pages/Test/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo3/expected/demo-project/src/pages/Test/index.jsx @@ -1,3 +1,5 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import Super, { @@ -13,7 +15,7 @@ import SuperOther from "@alifd/next"; import utils from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; @@ -24,23 +26,23 @@ const SelectOption = Select.Option; const SearchTable = SearchTableExport.default; class Test$$Page extends React.Component { + _context = this; + constructor(props, context) { super(props); this.utils = utils; + __$$i18n._inject2(this); + this.state = {}; } - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentDidMount() {} render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
diff --git a/modules/code-generator/test-cases/react-app/demo4/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/react-app/demo4/expected/demo-project/src/i18n.js index 316a16ac5..85ae1e019 100644 --- a/modules/code-generator/test-cases/react-app/demo4/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/react-app/demo4/expected/demo-project/src/i18n.js @@ -1,8 +1,9 @@ -import IntlMessageFormat from "intl-messageformat"; - const i18nConfig = {}; -let locale = "en-US"; +let locale = + typeof navigator === "object" && typeof navigator.language === "string" + ? navigator.language + : "zh-CN"; const getLocale = () => locale; @@ -10,24 +11,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo4/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/test-cases/react-app/demo4/expected/demo-project/src/pages/Test/index.jsx index 86cb40fb6..479882e5b 100644 --- a/modules/code-generator/test-cases/react-app/demo4/expected/demo-project/src/pages/Test/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo4/expected/demo-project/src/pages/Test/index.jsx @@ -1,3 +1,5 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import { @@ -15,7 +17,7 @@ import { create as __$$createDataSourceEngine } from "@alilc/lowcode-datasource- import utils, { RefsManager } from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; @@ -24,6 +26,8 @@ const NextBlockCell = NextBlock.Cell; const AliSearchTable = AliSearchTableExport.default; class Test$$Page extends React.Component { + _context = this; + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this, { runtimeConfig: true, @@ -45,6 +49,8 @@ class Test$$Page extends React.Component { this._refsManager = new RefsManager(); + __$$i18n._inject2(this); + this.state = { text: "outter", isShowDialog: false }; } @@ -81,10 +87,6 @@ class Test$$Page extends React.Component { }; } - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentWillUnmount() { console.log("will umount"); } @@ -133,8 +135,8 @@ class Test$$Page extends React.Component { } render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
locale; @@ -10,24 +11,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo5/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/test-cases/react-app/demo5/expected/demo-project/src/pages/Test/index.jsx index 3d1d2ecde..cf6b31158 100644 --- a/modules/code-generator/test-cases/react-app/demo5/expected/demo-project/src/pages/Test/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo5/expected/demo-project/src/pages/Test/index.jsx @@ -1,3 +1,5 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import { @@ -22,7 +24,7 @@ import { AliAutoSearchTable } from "@alife/mc-assets-1935/build/lowcode/index.js import utils, { RefsManager } from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; @@ -31,6 +33,8 @@ const NextBlockCell = NextBlock.Cell; const AliAutoSearchTableDefault = AliAutoSearchTable.default; class Test$$Page extends React.Component { + _context = this; + constructor(props, context) { super(props); @@ -38,6 +42,8 @@ class Test$$Page extends React.Component { this._refsManager = new RefsManager(); + __$$i18n._inject2(this); + this.state = { name: "nongzhou", gateways: [], @@ -55,10 +61,6 @@ class Test$$Page extends React.Component { return this._refsManager.getAll(refName); }; - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentWillUnmount() { /* ... */ } @@ -90,8 +92,8 @@ class Test$$Page extends React.Component { componentDidMount() {} render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
locale; @@ -10,24 +11,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo6-literal-condition/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/test-cases/react-app/demo6-literal-condition/expected/demo-project/src/pages/Test/index.jsx index c351e3a56..510685aea 100644 --- a/modules/code-generator/test-cases/react-app/demo6-literal-condition/expected/demo-project/src/pages/Test/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo6-literal-condition/expected/demo-project/src/pages/Test/index.jsx @@ -1,3 +1,5 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import { Form, Input, NumberPicker, Select, Button } from "@alifd/next"; @@ -10,11 +12,13 @@ import { create as __$$createDataSourceEngine } from "@alilc/lowcode-datasource- import utils, { RefsManager } from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; class Test$$Page extends React.Component { + _context = this; + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this, { runtimeConfig: true, @@ -39,6 +43,8 @@ class Test$$Page extends React.Component { this._refsManager = new RefsManager(); + __$$i18n._inject2(this); + this.state = { text: "outter" }; } @@ -113,10 +119,6 @@ class Test$$Page extends React.Component { }; } - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentDidMount() { this._dataSourceEngine.reloadDataSource(); @@ -124,8 +126,8 @@ class Test$$Page extends React.Component { } render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
locale; @@ -10,24 +11,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo7-literal-condition2/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/test-cases/react-app/demo7-literal-condition2/expected/demo-project/src/pages/Test/index.jsx index e12ec9bd2..591da2fcf 100644 --- a/modules/code-generator/test-cases/react-app/demo7-literal-condition2/expected/demo-project/src/pages/Test/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo7-literal-condition2/expected/demo-project/src/pages/Test/index.jsx @@ -1,3 +1,5 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import { @@ -21,13 +23,15 @@ import { import utils, { RefsManager } from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; const NextBlockCell = NextBlock.Cell; class Test$$Page extends React.Component { + _context = this; + constructor(props, context) { super(props); @@ -35,6 +39,8 @@ class Test$$Page extends React.Component { this._refsManager = new RefsManager(); + __$$i18n._inject2(this); + this.state = { books: [], currentStep: 0, @@ -97,10 +103,6 @@ class Test$$Page extends React.Component { return this._refsManager.getAll(refName); }; - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentDidUpdate(prevProps, prevState, snapshot) {} componentWillUnmount() {} @@ -189,8 +191,8 @@ class Test$$Page extends React.Component { componentDidMount() {} render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
locale; @@ -10,24 +11,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo8-datasource-prop/expected/demo-project/src/pages/Example/index.jsx b/modules/code-generator/test-cases/react-app/demo8-datasource-prop/expected/demo-project/src/pages/Example/index.jsx index d16656be7..6b75712e6 100644 --- a/modules/code-generator/test-cases/react-app/demo8-datasource-prop/expected/demo-project/src/pages/Example/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo8-datasource-prop/expected/demo-project/src/pages/Example/index.jsx @@ -1,3 +1,5 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import { Page, Table } from "@alilc/lowcode-components"; @@ -8,11 +10,13 @@ import { create as __$$createDataSourceEngine } from "@alilc/lowcode-datasource- import utils from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; class Example$$Page extends React.Component { + _context = this; + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this, { runtimeConfig: true, @@ -32,6 +36,8 @@ class Example$$Page extends React.Component { this.utils = utils; + __$$i18n._inject2(this); + this.state = {}; } @@ -56,17 +62,13 @@ class Example$$Page extends React.Component { }; } - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentDidMount() { this._dataSourceEngine.reloadDataSource(); } render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
{ - return _$$i18n(i18nKey); - }; - componentDidMount() { this._dataSourceEngine.reloadDataSource(); } render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
diff --git a/modules/code-generator/test-cases/react-app/demo9-datasource-engine/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/react-app/demo9-datasource-engine/expected/demo-project/src/i18n.js index 316a16ac5..85ae1e019 100644 --- a/modules/code-generator/test-cases/react-app/demo9-datasource-engine/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/react-app/demo9-datasource-engine/expected/demo-project/src/i18n.js @@ -1,8 +1,9 @@ -import IntlMessageFormat from "intl-messageformat"; - const i18nConfig = {}; -let locale = "en-US"; +let locale = + typeof navigator === "object" && typeof navigator.language === "string" + ? navigator.language + : "zh-CN"; const getLocale = () => locale; @@ -10,24 +11,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo_10-jsslot/expected/demo-project/src/i18n.js b/modules/code-generator/test-cases/react-app/demo_10-jsslot/expected/demo-project/src/i18n.js index 316a16ac5..85ae1e019 100644 --- a/modules/code-generator/test-cases/react-app/demo_10-jsslot/expected/demo-project/src/i18n.js +++ b/modules/code-generator/test-cases/react-app/demo_10-jsslot/expected/demo-project/src/i18n.js @@ -1,8 +1,9 @@ -import IntlMessageFormat from "intl-messageformat"; - const i18nConfig = {}; -let locale = "en-US"; +let locale = + typeof navigator === "object" && typeof navigator.language === "string" + ? navigator.language + : "zh-CN"; const getLocale = () => locale; @@ -10,24 +11,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo_10-jsslot/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/test-cases/react-app/demo_10-jsslot/expected/demo-project/src/pages/Test/index.jsx index d95e13b89..34bda3646 100644 --- a/modules/code-generator/test-cases/react-app/demo_10-jsslot/expected/demo-project/src/pages/Test/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo_10-jsslot/expected/demo-project/src/pages/Test/index.jsx @@ -1,3 +1,5 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import { @@ -25,7 +27,7 @@ import { import utils, { RefsManager } from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; @@ -36,6 +38,8 @@ const AliAutoSearchTableDefault = AliAutoSearchTable.default; const NextBlockCell = NextBlock.Cell; class Test$$Page extends React.Component { + _context = this; + constructor(props, context) { super(props); @@ -43,6 +47,8 @@ class Test$$Page extends React.Component { this._refsManager = new RefsManager(); + __$$i18n._inject2(this); + this.state = { pkgs: [], total: 0, @@ -71,10 +77,6 @@ class Test$$Page extends React.Component { return this._refsManager.getAll(refName); }; - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentDidUpdate(prevProps, prevState, snapshot) {} componentWillUnmount() {} @@ -181,8 +183,8 @@ class Test$$Page extends React.Component { } render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
locale; @@ -10,24 +11,67 @@ const setLocale = (target) => { locale = target; }; -const i18nFormat = ({ id, defaultMessage }, variables) => { +const isEmptyVariables = (variables) => + (Array.isArray(variables) && variables.length === 0) || + (typeof variables === "object" && + (!variables || Object.keys(variables).length === 0)); + +// 按低代码规范里面的要求进行变量替换 +const format = (msg, variables) => + typeof msg === "string" + ? msg.replace(/\$\{(\w+)\}/g, (match, key) => variables?.[key] ?? "") + : msg; + +const i18nFormat = ({ id, defaultMessage, fallback }, variables) => { const msg = - (i18nConfig && i18nConfig[locale] && i18nConfig[locale][id]) || + i18nConfig[locale]?.[id] ?? + i18nConfig[locale.replace("-", "_")]?.[id] ?? defaultMessage; if (msg == null) { console.warn("[i18n]: unknown message id: %o (locale=%o)", id, locale); - return `${id}`; + return fallback === undefined ? `${id}` : fallback; } - if (!variables || !variables.length) { - return msg; + return format(msg, variables); +}; + +const i18n = (id, params) => { + return i18nFormat({ id }, params); +}; + +// 将国际化的一些方法注入到目标对象&上下文中 +const _inject2 = (target) => { + target.i18n = i18n; + target.getLocale = getLocale; + target.setLocale = (locale) => { + setLocale(locale); + target.forceUpdate(); + }; + target._i18nText = (t) => { + // 优先取直接传过来的语料 + const localMsg = t[locale] ?? t[String(locale).replace("-", "_")]; + if (localMsg != null) { + return format(localMsg, variables); + } + + // 其次用项目级别的 + const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params); + if (projectMsg != null) { + return projectMsg; + } + + // 兜底用 use 指定的或默认语言的 + return format(t[t.use || "zh_CN"] ?? t.en_US, t.params); + }; + + // 注入到上下文中去 + if (target._context && target._context !== target) { + Object.assign(target._context, { + i18n, + getLocale, + setLocale: target.setLocale, + }); } - - return new IntlMessageFormat(msg, locale).format(variables); }; -const i18n = (id) => { - return i18nFormat({ id }); -}; - -export { getLocale, setLocale, i18n, i18nFormat }; +export { getLocale, setLocale, i18n, i18nFormat, _inject2 }; diff --git a/modules/code-generator/test-cases/react-app/demo_11-jsslot-2/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/test-cases/react-app/demo_11-jsslot-2/expected/demo-project/src/pages/Test/index.jsx index 4dacde34e..dd38043fd 100644 --- a/modules/code-generator/test-cases/react-app/demo_11-jsslot-2/expected/demo-project/src/pages/Test/index.jsx +++ b/modules/code-generator/test-cases/react-app/demo_11-jsslot-2/expected/demo-project/src/pages/Test/index.jsx @@ -1,3 +1,5 @@ +// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 +// 例外:react 框架的导出名和各种组件名除外。 import React from "react"; import { @@ -25,7 +27,7 @@ import { import utils, { RefsManager } from "../../utils"; -import { i18n as _$$i18n } from "../../i18n"; +import * as __$$i18n from "../../i18n"; import "./index.css"; @@ -36,6 +38,8 @@ const AliAutoSearchTableDefault = AliAutoSearchTable.default; const NextBlockCell = NextBlock.Cell; class Test$$Page extends React.Component { + _context = this; + constructor(props, context) { super(props); @@ -43,6 +47,8 @@ class Test$$Page extends React.Component { this._refsManager = new RefsManager(); + __$$i18n._inject2(this); + this.state = { pkgs: [], total: 0, @@ -79,10 +85,6 @@ class Test$$Page extends React.Component { return this._refsManager.getAll(refName); }; - i18n = (i18nKey) => { - return _$$i18n(i18nKey); - }; - componentDidUpdate(prevProps, prevState, snapshot) {} componentWillUnmount() {} @@ -207,8 +209,8 @@ class Test$$Page extends React.Component { } render() { - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (
{ + test('default import', async () => { + await exportProject(inputSchemaJsonFile, outputDir, {}); + + const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx'); + expect(generatedPageFileContent).toContain( + ` + + `.trim(), + ); + }); +}); + +function exportProject( + importPath: string, + outputPath: string, + mergeSchema?: Partial, +) { + const schemaJsonStr = fs.readFileSync(importPath, { encoding: 'utf8' }); + const schema = { ...JSON.parse(schemaJsonStr), ...mergeSchema }; + const builder = CodeGenerator.solutions.icejs(); + + return builder.generateProject(schema).then(async (result) => { + const publisher = createDiskPublisher(); + await publisher.publish({ + project: result, + outputPath, + projectSlug: 'demo-project', + createProjectFolder: true, + }); + return result; + }); +} + +function readOutputTextFile(outputFilePath: string): string { + return fs.readFileSync(path.resolve(outputDir, outputFilePath), 'utf-8'); +} diff --git a/modules/code-generator/tests/bugfix/icejs-import-wrong-naming.test.ts b/modules/code-generator/tests/bugfix/icejs-import-wrong-naming.test.ts index 92003788f..51e22b3a9 100644 --- a/modules/code-generator/tests/bugfix/icejs-import-wrong-naming.test.ts +++ b/modules/code-generator/tests/bugfix/icejs-import-wrong-naming.test.ts @@ -2,6 +2,7 @@ import CodeGenerator from '../../src'; import * as fs from 'fs'; import * as path from 'path'; import { ProjectSchema } from '@alilc/lowcode-types'; +import { createDiskPublisher } from '../helpers/solutionHelper'; const testCaseBaseName = path.basename(__filename, '.test.ts'); const inputSchemaJsonFile = path.join(__dirname, `${testCaseBaseName}.schema.json`); @@ -205,7 +206,7 @@ function exportProject( return builder.generateProject(schema).then(async (result) => { // displayResultInConsole(result); - const publisher = CodeGenerator.publishers.disk(); + const publisher = createDiskPublisher(); await publisher.publish({ project: result, outputPath, diff --git a/modules/code-generator/tests/bugfix/icejs-js-function1.test.ts b/modules/code-generator/tests/bugfix/icejs-js-function1.test.ts index a1a107c49..aed08c6d8 100644 --- a/modules/code-generator/tests/bugfix/icejs-js-function1.test.ts +++ b/modules/code-generator/tests/bugfix/icejs-js-function1.test.ts @@ -1,6 +1,7 @@ import CodeGenerator from '../../src'; import * as fs from 'fs'; import * as path from 'path'; +import { createDiskPublisher } from '../helpers/solutionHelper'; const testCaseBaseName = path.basename(__filename, '.test.ts'); @@ -31,7 +32,7 @@ function exportProject(inputPath: string, outputPath: string) { return builder.generateProject(newSchema).then(async (result) => { // displayResultInConsole(result); - const publisher = CodeGenerator.publishers.disk(); + const publisher = createDiskPublisher(); await publisher.publish({ project: result, outputPath, diff --git a/modules/code-generator/tests/bugfix/icejs-missing-imports-1.test.ts b/modules/code-generator/tests/bugfix/icejs-missing-imports-1.test.ts index 184e31c46..17c40a4fa 100644 --- a/modules/code-generator/tests/bugfix/icejs-missing-imports-1.test.ts +++ b/modules/code-generator/tests/bugfix/icejs-missing-imports-1.test.ts @@ -1,6 +1,7 @@ import CodeGenerator from '../../src'; import * as fs from 'fs'; import * as path from 'path'; +import { createDiskPublisher } from '../helpers/solutionHelper'; const testCaseBaseName = path.basename(__filename, '.test.ts'); @@ -31,7 +32,7 @@ function exportProject(inputPath: string, outputPath: string) { return builder.generateProject(newSchema).then(async (result) => { // displayResultInConsole(result); - const publisher = CodeGenerator.publishers.disk(); + const publisher = createDiskPublisher(); await publisher.publish({ project: result, outputPath, diff --git a/modules/code-generator/tests/bugfix/icejs-package-json-dependencies.test.ts b/modules/code-generator/tests/bugfix/icejs-package-json-dependencies.test.ts index 3aaff3ae4..6edd6b912 100644 --- a/modules/code-generator/tests/bugfix/icejs-package-json-dependencies.test.ts +++ b/modules/code-generator/tests/bugfix/icejs-package-json-dependencies.test.ts @@ -1,6 +1,7 @@ import CodeGenerator from '../../src'; import * as fs from 'fs'; import * as path from 'path'; +import { createDiskPublisher } from '../helpers/solutionHelper'; const testCaseBaseName = path.basename(__filename, '.test.ts'); @@ -37,7 +38,7 @@ function exportProject(inputPath: string, outputPath: string) { return builder.generateProject(newSchema).then(async (result) => { // displayResultInConsole(result); - const publisher = CodeGenerator.publishers.disk(); + const publisher = createDiskPublisher(); await publisher.publish({ project: result, outputPath, diff --git a/modules/code-generator/tests/bugfix/icejs-page-map1.test.ts b/modules/code-generator/tests/bugfix/icejs-page-map1.test.ts index 5e2d747a6..8c2e63b45 100644 --- a/modules/code-generator/tests/bugfix/icejs-page-map1.test.ts +++ b/modules/code-generator/tests/bugfix/icejs-page-map1.test.ts @@ -1,6 +1,7 @@ import CodeGenerator from '../../src'; import * as fs from 'fs'; import * as path from 'path'; +import { createDiskPublisher } from '../helpers/solutionHelper'; const testCaseBaseName = path.basename(__filename, '.test.ts'); @@ -25,7 +26,7 @@ function exportProject(inputPath: string, outputPath: string) { return builder.generateProject(newSchema).then(async (result) => { // displayResultInConsole(result); - const publisher = CodeGenerator.publishers.disk(); + const publisher = createDiskPublisher(); await publisher.publish({ project: result, outputPath, diff --git a/modules/code-generator/tests/bugfix/page-element1.test.ts b/modules/code-generator/tests/bugfix/page-element1.test.ts index 71b4bfc60..2aa27c025 100644 --- a/modules/code-generator/tests/bugfix/page-element1.test.ts +++ b/modules/code-generator/tests/bugfix/page-element1.test.ts @@ -1,6 +1,7 @@ import CodeGenerator from '../../src'; import * as fs from 'fs'; import * as path from 'path'; +import { createDiskPublisher } from '../helpers/solutionHelper'; test('page-element1', async () => { const inputSchemaJsonFile = path.join(__dirname, 'page-element1.schema.json'); @@ -67,7 +68,7 @@ function exportProject(inputPath: string, outputPath: string) { return builder.generateProject(newSchema).then(async (result) => { // displayResultInConsole(result); - const publisher = CodeGenerator.publishers.disk(); + const publisher = createDiskPublisher(); await publisher.publish({ project: result, outputPath, diff --git a/modules/code-generator/tests/bugfix/page-element2.test.ts b/modules/code-generator/tests/bugfix/page-element2.test.ts index cb739b0df..d0bff34e6 100644 --- a/modules/code-generator/tests/bugfix/page-element2.test.ts +++ b/modules/code-generator/tests/bugfix/page-element2.test.ts @@ -1,6 +1,7 @@ import CodeGenerator from '../../src'; import * as fs from 'fs'; import * as path from 'path'; +import { createDiskPublisher } from '../helpers/solutionHelper'; test('page-element2', async () => { const inputSchemaJsonFile = path.join(__dirname, 'page-element2.schema.json'); @@ -67,7 +68,7 @@ function exportProject(inputPath: string, outputPath: string) { return builder.generateProject(newSchema).then(async (result) => { // displayResultInConsole(result); - const publisher = CodeGenerator.publishers.disk(); + const publisher = createDiskPublisher(); await publisher.publish({ project: result, outputPath, diff --git a/modules/code-generator/tests/cli.test.ts b/modules/code-generator/tests/cli.test.ts index 2a785c293..8e1598334 100644 --- a/modules/code-generator/tests/cli.test.ts +++ b/modules/code-generator/tests/cli.test.ts @@ -1,6 +1,14 @@ import { run } from '../src/cli'; describe('cli', () => { + // standalone 模式下不需要测试 cli + if (process.env.TEST_TARGET === 'standalone') { + it('should ignore', () => { + expect(true).toBe(true); + }); + return; + } + it('should works for the default example-schema.json', async () => { const res = await run(['example-schema.json'], { solution: 'icejs', diff --git a/modules/code-generator/tests/plugins/jsx/__snapshots__/p0-condition-at-root.test.ts.snap b/modules/code-generator/tests/plugins/jsx/__snapshots__/p0-condition-at-root.test.ts.snap index 534b19495..9d9fe0389 100644 --- a/modules/code-generator/tests/plugins/jsx/__snapshots__/p0-condition-at-root.test.ts.snap +++ b/modules/code-generator/tests/plugins/jsx/__snapshots__/p0-condition-at-root.test.ts.snap @@ -5,8 +5,8 @@ Object { "chunks": Array [ Object { "content": " - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return (this.state.otherThings).map((item, index) => ((__$$context) => (!!(__$$context.state.something) && (Hello world!)))(__$$createChildContext(__$$context, { item, index }))); ", "fileType": "jsx", @@ -66,8 +66,8 @@ Object { "chunks": Array [ Object { "content": " - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return !!(this.state.something) && (Hello world!); ", "fileType": "jsx", @@ -123,8 +123,8 @@ Object { "chunks": Array [ Object { "content": " - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return Hello world!; ", "fileType": "jsx", @@ -177,8 +177,8 @@ Object { "chunks": Array [ Object { "content": " - const __$$context = this; - const { state } = this; + const __$$context = this._context || this; + const { state } = __$$context; return Hello world!; ", "fileType": "jsx", diff --git a/modules/code-generator/tests/utils/resultHelper/example-result.json b/modules/code-generator/tests/utils/resultHelper/example-result.json new file mode 100644 index 000000000..804faee83 --- /dev/null +++ b/modules/code-generator/tests/utils/resultHelper/example-result.json @@ -0,0 +1,46 @@ +{ + "name": ".", + "dirs": [ + { + "name": "src", + "dirs": [ + { + "name": "components", + "dirs": [ + { + "name": "Hello", + "dirs": [], + "files": [ + { + "name": "index", + "ext": "js", + "content": "export default () =>
Hello
" + }, + { + "name": "index", + "ext": "css", + "content": ".hello {color: red}" + } + ] + } + ], + "files": [ + { + "name": "index", + "ext": "js", + "content": "export * from \"./Hello\";" + } + ] + } + ], + "files": [ + { "name": "index", "ext": "js", "content": "console.log(\"Hello\")" }, + { "name": "index", "ext": "css", "content": "html,body{ padding: 0; }" } + ] + } + ], + "files": [ + { "name": ".eslintrc", "ext": "", "content": "{}" }, + { "name": "package", "ext": "json", "content": "{ \"name\": \"demo\", \"version\":\"1.0.0\" }" } + ] +} diff --git a/modules/code-generator/tests/utils/resultHelper/findFile.test.ts b/modules/code-generator/tests/utils/resultHelper/findFile.test.ts new file mode 100644 index 000000000..f6d1e2be4 --- /dev/null +++ b/modules/code-generator/tests/utils/resultHelper/findFile.test.ts @@ -0,0 +1,34 @@ +import _ from 'lodash'; +import CodeGen from '../../../src'; + +describe('CodeGen.utils.resultHelper.findFile', () => { + it('could package.json by "package.json"', () => { + const result = require('./example-result.json') as any; + const found = CodeGen.utils.resultHelper.findFile(result, 'package.json'); + expect(found).toMatchInlineSnapshot(` + Object { + "content": "{ \\"name\\": \\"demo\\", \\"version\\":\\"1.0.0\\" }", + "ext": "json", + "name": "package", + } + `); + }); + + it('could find a internal component by src/components/*/index.js', () => { + const result = require('./example-result.json') as any; + const found = CodeGen.utils.resultHelper.findFile(result, 'src/components/*/index.js'); + expect(found).toMatchInlineSnapshot(` + Object { + "content": "export default () =>
Hello
", + "ext": "js", + "name": "index", + } + `); + }); + + it('could not find non-existing file', () => { + const result = require('./example-result.json') as any; + const found = CodeGen.utils.resultHelper.findFile(result, 'something-not-exist.js'); + expect(found).toBeNull(); + }); +}); diff --git a/modules/code-generator/tests/utils/resultHelper/globFiles.test.ts b/modules/code-generator/tests/utils/resultHelper/globFiles.test.ts new file mode 100644 index 000000000..a524d94e9 --- /dev/null +++ b/modules/code-generator/tests/utils/resultHelper/globFiles.test.ts @@ -0,0 +1,67 @@ +import _ from 'lodash'; +import CodeGen from '../../../src'; + +describe('CodeGen.utils.resultHelper.globFiles', () => { + it('could find all files exclude dot files by **/*', () => { + const result = require('./example-result.json') as any; + const files = CodeGen.utils.resultHelper.globFiles(result, '**/*'); + expect(Array.from(files).map(_.first)).toMatchInlineSnapshot(` + Array [ + "package.json", + "src/index.js", + "src/index.css", + "src/components/index.js", + "src/components/Hello/index.js", + "src/components/Hello/index.css", + ] + `); + }); + + it('could find all files by **/* with option.dot = true ', () => { + const result = require('./example-result.json') as any; + const files = CodeGen.utils.resultHelper.globFiles(result, '**/*', { dot: true }); + expect(Array.from(files).map(_.first)).toMatchInlineSnapshot(` + Array [ + ".eslintrc", + "package.json", + "src/index.js", + "src/index.css", + "src/components/index.js", + "src/components/Hello/index.js", + "src/components/Hello/index.css", + ] + `); + }); + + it('could find all js files by **/*.js', () => { + const result = require('./example-result.json') as any; + const files = CodeGen.utils.resultHelper.globFiles(result, '**/*.js'); + expect(Array.from(files).map(_.first)).toMatchInlineSnapshot(` + Array [ + "src/index.js", + "src/components/index.js", + "src/components/Hello/index.js", + ] + `); + }); + + it('could find package.json by package.json', () => { + const result = require('./example-result.json') as any; + const files = CodeGen.utils.resultHelper.globFiles(result, 'package.json'); + expect(Array.from(files).map(_.first)).toMatchInlineSnapshot(` + Array [ + "package.json", + ] + `); + }); + + it('could find all index.js in components by **/components/*/index.js', () => { + const result = require('./example-result.json') as any; + const files = CodeGen.utils.resultHelper.globFiles(result, '**/components/*/index.js'); + expect(Array.from(files).map(_.first)).toMatchInlineSnapshot(` + Array [ + "src/components/Hello/index.js", + ] + `); + }); +}); diff --git a/modules/code-generator/tests/utils/resultHelper/removeDirsFromResult.test.ts b/modules/code-generator/tests/utils/resultHelper/removeDirsFromResult.test.ts new file mode 100644 index 000000000..642ef4b18 --- /dev/null +++ b/modules/code-generator/tests/utils/resultHelper/removeDirsFromResult.test.ts @@ -0,0 +1,101 @@ +import type { ResultDir } from '@alilc/lowcode-types'; +import _ from 'lodash'; +import CodeGen from '../../../src'; + +const loadResult = (): ResultDir => _.cloneDeep(require('./example-result.json')); + +describe('CodeGen.utils.resultHelper.removeDirsFromResult', () => { + it('could remove src by "src"', () => { + const result = loadResult(); + expect(listAllDirs(result)).toMatchInlineSnapshot(` + Array [ + "", + "src", + "src/components", + "src/components/Hello", + ] + `); + + const removed = CodeGen.utils.resultHelper.removeDirsFromResult(result, 'src'); + + expect(listAllDirs(result)).toMatchInlineSnapshot(` + Array [ + "", + ] + `); + + expect(removed).toBe(1); + }); + + it('could remove src/components/Hello by "*/components/*"', () => { + const result = loadResult(); + expect(listAllDirs(result)).toMatchInlineSnapshot(` + Array [ + "", + "src", + "src/components", + "src/components/Hello", + ] + `); + + const removed = CodeGen.utils.resultHelper.removeDirsFromResult(result, '*/components/*'); + + expect(listAllDirs(result)).toMatchInlineSnapshot(` + Array [ + "", + "src", + "src/components", + ] + `); + + expect(removed).toBe(1); + }); + + it('could remove all dirs by "*"', () => { + const result = loadResult(); + expect(listAllDirs(result)).toMatchInlineSnapshot(` + Array [ + "", + "src", + "src/components", + "src/components/Hello", + ] + `); + + const removed = CodeGen.utils.resultHelper.removeDirsFromResult(result, '*'); + + expect(listAllDirs(result)).toMatchInlineSnapshot(` + Array [ + "", + ] + `); + + expect(removed).toBe(1); + }); + + it('could remove all dirs by "**"', () => { + const result = loadResult(); + expect(listAllDirs(result)).toMatchInlineSnapshot(` + Array [ + "", + "src", + "src/components", + "src/components/Hello", + ] + `); + + const removed = CodeGen.utils.resultHelper.removeDirsFromResult(result, '**'); + + expect(listAllDirs(result)).toMatchInlineSnapshot(` + Array [ + "", + ] + `); + + expect(removed).toBe(3); + }); +}); + +function listAllDirs(result: ResultDir): string[] { + return Array.from(CodeGen.utils.resultHelper.scanDirs(result)).map(([dirPath]) => dirPath); +} diff --git a/modules/code-generator/tests/utils/resultHelper/removeFilesFromResult.test.ts b/modules/code-generator/tests/utils/resultHelper/removeFilesFromResult.test.ts new file mode 100644 index 000000000..d34964f37 --- /dev/null +++ b/modules/code-generator/tests/utils/resultHelper/removeFilesFromResult.test.ts @@ -0,0 +1,102 @@ +import type { ResultDir } from '@alilc/lowcode-types'; +import _ from 'lodash'; +import CodeGen from '../../../src'; + +const loadResult = (): ResultDir => _.cloneDeep(require('./example-result.json')); + +describe('CodeGen.utils.resultHelper.removeFilesFromResult', () => { + it('could remove package.json by "package.json"', () => { + const result = loadResult(); + expect(listAllFiles(result)).toMatchInlineSnapshot(` + Array [ + ".eslintrc", + "package.json", + "src/index.js", + "src/index.css", + "src/components/index.js", + "src/components/Hello/index.js", + "src/components/Hello/index.css", + ] + `); + + const removed = CodeGen.utils.resultHelper.removeFilesFromResult(result, 'package.json'); + + expect(listAllFiles(result)).toMatchInlineSnapshot(` + Array [ + ".eslintrc", + "src/index.js", + "src/index.css", + "src/components/index.js", + "src/components/Hello/index.js", + "src/components/Hello/index.css", + ] + `); + + expect(removed).toBe(1); + }); + + it('could remove .eslintrc.json by ".eslintrc" with dot=true', () => { + const result = loadResult(); + expect(listAllFiles(result)).toMatchInlineSnapshot(` + Array [ + ".eslintrc", + "package.json", + "src/index.js", + "src/index.css", + "src/components/index.js", + "src/components/Hello/index.js", + "src/components/Hello/index.css", + ] + `); + + const removed = CodeGen.utils.resultHelper.removeFilesFromResult(result, '.eslintrc', { + dot: true, + }); + + expect(listAllFiles(result)).toMatchInlineSnapshot(` + Array [ + "package.json", + "src/index.js", + "src/index.css", + "src/components/index.js", + "src/components/Hello/index.js", + "src/components/Hello/index.css", + ] + `); + + expect(removed).toBe(1); + }); + + it('could remove all css files by "**/*.css"', () => { + const result = loadResult(); + expect(listAllFiles(result)).toMatchInlineSnapshot(` + Array [ + ".eslintrc", + "package.json", + "src/index.js", + "src/index.css", + "src/components/index.js", + "src/components/Hello/index.js", + "src/components/Hello/index.css", + ] + `); + + const removed = CodeGen.utils.resultHelper.removeFilesFromResult(result, '**/*.css'); + + expect(listAllFiles(result)).toMatchInlineSnapshot(` + Array [ + ".eslintrc", + "package.json", + "src/index.js", + "src/components/index.js", + "src/components/Hello/index.js", + ] + `); + + expect(removed).toBe(2); + }); +}); + +function listAllFiles(result: ResultDir): string[] { + return Array.from(CodeGen.utils.resultHelper.scanFiles(result)).map(([filePath]) => filePath); +} diff --git a/modules/code-generator/tests/utils/resultHelper/scanFiles.test.ts b/modules/code-generator/tests/utils/resultHelper/scanFiles.test.ts new file mode 100644 index 000000000..f849e6eb3 --- /dev/null +++ b/modules/code-generator/tests/utils/resultHelper/scanFiles.test.ts @@ -0,0 +1,68 @@ +import CodeGen from '../../../src'; + +describe('CodeGen.utils.resultHelper.scanFiles', () => { + it('should works', () => { + const result = require('./example-result.json') as any; + const files = CodeGen.utils.resultHelper.scanFiles(result); + expect(Array.from(files)).toMatchInlineSnapshot(` + Array [ + Array [ + ".eslintrc", + Object { + "content": "{}", + "ext": "", + "name": ".eslintrc", + }, + ], + Array [ + "package.json", + Object { + "content": "{ \\"name\\": \\"demo\\", \\"version\\":\\"1.0.0\\" }", + "ext": "json", + "name": "package", + }, + ], + Array [ + "src/index.js", + Object { + "content": "console.log(\\"Hello\\")", + "ext": "js", + "name": "index", + }, + ], + Array [ + "src/index.css", + Object { + "content": "html,body{ padding: 0; }", + "ext": "css", + "name": "index", + }, + ], + Array [ + "src/components/index.js", + Object { + "content": "export * from \\"./Hello\\";", + "ext": "js", + "name": "index", + }, + ], + Array [ + "src/components/Hello/index.js", + Object { + "content": "export default () =>
Hello
", + "ext": "js", + "name": "index", + }, + ], + Array [ + "src/components/Hello/index.css", + Object { + "content": ".hello {color: red}", + "ext": "css", + "name": "index", + }, + ], + ] + `); + }); +});