feat: add i18n support for react simulator & render

This commit is contained in:
春希 2021-01-29 17:45:23 +08:00
parent c9e0b21b44
commit 694651262a
6 changed files with 99 additions and 15 deletions

View File

@ -236,7 +236,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
readonly componentsConsumer = new ResourceConsumer<Asset | undefined>(() => this.componentsAsset);
readonly injectionConsumer = new ResourceConsumer(() => {
return {};
return {
i18n: this.project.i18n,
};
});
readonly asyncLibraryMap: { [key: string]: {} } = {};

View File

@ -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);
}

View File

@ -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 (
<LowCodeRenderer
locale={locale}
messages={messages}
schema={documentInstance.schema}
components={container.components}
appHelper={container.context}

View File

@ -267,12 +267,28 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
return parseQuery(search);
},
},
i18n: {
setLocale: (loc: string) => {
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;
});
}

View File

@ -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);
}
}
// 兼容乐高设计态的变量绑定

View File

@ -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-CNen-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);
}