diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index 80f6e8908..544abdc7f 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -236,7 +236,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost(() => this.componentsAsset); readonly injectionConsumer = new ResourceConsumer(() => { - return {}; + return { + i18n: this.project.i18n, + }; }); readonly asyncLibraryMap: { [key: string]: {} } = {}; diff --git a/packages/designer/src/project/project.ts b/packages/designer/src/project/project.ts index 27e04a89e..73ac686d2 100644 --- a/packages/designer/src/project/project.ts +++ b/packages/designer/src/project/project.ts @@ -10,7 +10,7 @@ export class Project { @obx.val readonly documents: DocumentModel[] = []; - private data: ProjectSchema = { version: '1.0.0', componentsMap: [], componentsTree: [] }; + private data: ProjectSchema = { version: '1.0.0', componentsMap: [], componentsTree: [], i18n: {} }; private _simulator?: ISimulatorHost; @@ -40,15 +40,24 @@ export class Project { this._config = value; } + @obx.ref private _i18n: any = {}; + get i18n(): any { + return this._i18n; + } + set i18n(value: any) { + this._i18n = value || {}; + } + /** * 获取项目整体 schema */ getSchema(): ProjectSchema { return { ...this.data, - // todo: future change this filter + // TODO: future change this filter componentsMap: this.currentDocument?.getComponentsMap(), componentsTree: this.documents.filter((doc) => !doc.isBlank()).map((doc) => doc.schema), + i18n: this._i18n || {}, }; } @@ -57,6 +66,7 @@ export class Project { * @param schema */ setSchema(schema?: ProjectSchema) { + // FIXME: 这里的行为和 getSchema 并不对等,感觉不太对 const doc = this.documents.find((doc) => doc.actived); doc && doc.import(schema?.componentsTree[0]); } @@ -73,9 +83,11 @@ export class Project { version: '1.0.0', componentsMap: [], componentsTree: [], + i18n: {}, ...schema, }; this.config = schema?.config || this.config; + this.i18n = schema?.i18n || this.i18n; if (autoOpen) { if (autoOpen === true) { @@ -137,6 +149,9 @@ export class Project { if (key === 'config') { this.config = value; } + if (key === 'i18n') { + this.i18n = value; + } Object.assign(this.data, { [key]: value }); } @@ -160,6 +175,9 @@ export class Project { if (key === 'config') { return this.config; } + if (key === 'i18n') { + return this.i18n; + } return Reflect.get(this.data, key); } diff --git a/packages/react-simulator-renderer/src/renderer-view.tsx b/packages/react-simulator-renderer/src/renderer-view.tsx index 263a71e56..43776a051 100644 --- a/packages/react-simulator-renderer/src/renderer-view.tsx +++ b/packages/react-simulator-renderer/src/renderer-view.tsx @@ -7,6 +7,8 @@ import { SimulatorRendererContainer, DocumentInstance } from './renderer'; import { Router, Route, Switch } from 'react-router'; import './renderer.less'; +const DEFAULT_SIMULATOR_LOCALE = 'zh-CN'; + // patch cloneElement avoid lost keyProps const originCloneElement = window.React.cloneElement; (window as any).React.cloneElement = (child: any, { _leaf, ...props }: any = {}, ...rest: any[]) => { @@ -137,8 +139,13 @@ class Renderer extends Component<{ const { documentInstance, rendererContainer: renderer } = this.props; const { container } = documentInstance; const { designMode, device } = container; + const messages = container.context?.utils?.i18n?.messages || {}; + const locale = container.context?.utils?.i18n?.currentLocale || Object.keys(messages)[0] || DEFAULT_SIMULATOR_LOCALE; + return ( { + const newCtx = { + ...this._appContext, + }; + newCtx.utils.i18n.currentLocale = loc; + this._appContext = newCtx; + }, + currentLocale: undefined, + messages: {}, + }, }, constants: {}, requestHandlersMap: this._requestHandlersMap, }; host.injectionConsumer.consume((data) => { - // sync utils, i18n, contants,... config + // TODO: sync utils, i18n, contants,... config + const newCtx = { + ...this._appContext, + }; + newCtx.utils.i18n.messages = data.i18n || {}; + this._appContext = newCtx; }); } diff --git a/packages/renderer-core/src/renderer/base.tsx b/packages/renderer-core/src/renderer/base.tsx index 3135b4b5c..dae19e59a 100644 --- a/packages/renderer-core/src/renderer/base.tsx +++ b/packages/renderer-core/src/renderer/base.tsx @@ -10,6 +10,7 @@ import { getValue, parseData, parseExpression, + parseI18n, isEmpty, isSchema, isFileSchema, @@ -19,11 +20,11 @@ import { transformArrayToMap, transformStringToFunction, checkPropTypes, - generateI18n, + getI18n, acceptsRef, getFileCssName, capitalizeFirstLetter, - DataHelper, + DataHelper, isI18n, isVariable } from '../utils'; @@ -77,9 +78,8 @@ export default function baseRenererFactory() { this.appHelper = props.__appHelper; this.__compScopes = {}; this.__instanceMap = {}; - const { locale, messages } = props; - this.i18n = generateI18n(locale, messages); this.__bindCustomMethods(props); + this.__initI18nAPIs(props); } __afterInit(props: IRendererProps) { } @@ -259,6 +259,15 @@ export default function baseRenererFactory() { ); */ }; + __initI18nAPIs = () => { + this.i18n = (key: string, values = {}) => { + const { locale, messages } = this.props; + return getI18n(key, values, locale, messages); + }; + this.getLocale = () => this.props.locale; + this.setLocale = (loc: string) => this.appHelper?.utils?.i18n?.setLocale && this.appHelper?.utils?.i18n?.setLocale(loc); + }; + __render = () => { const schema = this.props.__schema; this.__setLifeCycleMethods('render'); @@ -328,6 +337,9 @@ export default function baseRenererFactory() { if (isJSExpression(schema)) { return parseExpression(schema, self); } + if (isI18n(schema)) { + return parseI18n(schema, self); + } if (isJSSlot(schema)) { return this.__createVirtualDom(schema.value, self, parentInfo); } @@ -581,11 +593,16 @@ export default function baseRenererFactory() { if (!isSchema(props) && !isJSSlot(props)) return checkProps(props); } - const handleI18n = (props: any) => props[props.use || 'zh_CN']; + const handleLegaoI18n = (props: any) => props[props.use || 'zh_CN']; // 兼容乐高设计态 i18n 数据 if (isI18n(props)) { - props = handleI18n(props); + const i18nProp = handleLegaoI18n(props); + if (i18nProp) { + props = i18nProp; + } else { + return parseI18n(props, self); + } } // 兼容乐高设计态的变量绑定 diff --git a/packages/renderer-core/src/utils/common.ts b/packages/renderer-core/src/utils/common.ts index c8f9f63d8..ec2ec4eb4 100644 --- a/packages/renderer-core/src/utils/common.ts +++ b/packages/renderer-core/src/utils/common.ts @@ -46,7 +46,8 @@ const EXPRESSION_TYPE = { JSEXPRESSION: 'JSExpression', JSFUNCTION: 'JSFunction', JSSLOT: 'JSSlot', - JSBLOCK: 'JSBlock' + JSBLOCK: 'JSBlock', + I18N: 'i18n', }; const EXPRESSION_REG = /^\{\{(\{.*\}|.*?)\}\}$/; const hasSymbol = typeof Symbol === 'function' && Symbol.for; @@ -112,6 +113,10 @@ export function isJSExpression(obj: any) { return isJSExpressionObj || isJSExpressionStr; } +export function isI18n(obj) { + return obj && typeof obj === 'object' && EXPRESSION_TYPE.I18N === obj.type; +} + /** * @name wait * @description 等待函数 @@ -180,6 +185,19 @@ export function generateI18n(locale = 'zh-CN', messages: any = {}) { }; } +/** + * 用于处理国际化字符串 + * @param {*} key 语料标识 + * @param {*} values 字符串模版变量 + * @param {*} locale 国际化标识,例如 zh-CN、en-US + * @param {*} messages 国际化语言包 + */ +export function getI18n(key: string, values = {}, locale = 'zh-CN', messages = {}) { + if (!messages || !messages[locale] || !messages[locale][key]) return ''; + const formater = new IntlMessageFormat(messages[locale][key], locale); + return formater.format(values); +} + /** * 判断当前组件是否能够设置ref * @param {*} Comp 需要判断的组件 @@ -373,6 +391,8 @@ export function transformStringToFunction(str: string) { export function parseData(schema: any, self: any): any { if (isJSExpression(schema)) { return parseExpression(schema, self); + } else if (isI18n(schema)) { + return parseI18n(schema, self); } else if (typeof schema === 'string') { return schema.trim(); } else if (Array.isArray(schema)) { @@ -423,10 +443,14 @@ export function capitalizeFirstLetter(word: string) { return word[0].toUpperCase() + word.slice(1); } -export function isI18n(obj: any) { - return obj && typeof obj === 'object' && obj?.type === 'i18n'; -} - export function isVariable(obj: any) { return obj && typeof obj === 'object' && obj?.type === 'variable'; } + +/* 将 i18n 结构,降级解释为对 i18n 接口的调用 */ +export function parseI18n(i18nInfo: any, self: any) { + return parseExpression({ + type: EXPRESSION_TYPE.JSEXPRESSION, + value: `this.i18n('${i18nInfo.key}')`, + }, self); +}