feat: 🎸 为 Rax 出码增加对 i18n 的支持

This commit is contained in:
牧毅 2020-08-21 13:01:22 +08:00
parent 1970e75c28
commit 8d198bd2a9
32 changed files with 735 additions and 56 deletions

View File

@ -159,8 +159,8 @@ export class ProjectBuilder implements IProjectBuilder {
}
// i18n?
if (parseResult.globalI18n && builders.i18n && this.template.slots.i18n) {
const { files } = await builders.i18n.generateModule(parseResult.globalI18n);
if (builders.i18n && this.template.slots.i18n) {
const { files } = await builders.i18n.generateModule(parseResult.project);
buildResult.push({
path: this.template.slots.i18n.path,

View File

@ -32,6 +32,15 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
});
// TODO: i18n 是可选的,如果没有 i18n 这个文件怎么办?该怎么判断?
next.chunks.push({
type: ChunkType.STRING,
fileType: cfg.fileType,
name: COMMON_CHUNK_NAME.InternalDepsImport,
content: `import * as __$$i18n from '../../i18n';`,
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
});
next.chunks.push({
type: ChunkType.STRING,
fileType: cfg.fileType,
@ -78,6 +87,16 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
get constants() {
return __$$constants;
},
get i18n() {
return self._i18n;
},
getLocale() {
return __$$i18n.getLocale();
},
setLocale(locale) {
__$$i18n.setLocale(locale);
self.forceUpdate();
},
...this._methods,
};
@ -87,6 +106,34 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd],
});
next.chunks.push({
type: ChunkType.STRING,
fileType: cfg.fileType,
name: CLASS_DEFINE_CHUNK_NAME.InsVar,
content: `
_i18n = this._createI18nDelegate();
`,
linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start],
});
next.chunks.push({
type: ChunkType.STRING,
fileType: cfg.fileType,
name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod,
content: `
_createI18nDelegate() {
return new Proxy(
{},
{
get(target, prop) {
return __$$i18n.i18n(prop);
},
},
);
}
`,
linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd],
});
return next;
};
return plugin;

View File

@ -182,17 +182,13 @@ function transformJsExpr(expr: string, handlers: CustomHandlerSet) {
}
const exprAst = parseExpression(expr);
switch (exprAst.type) {
// 对于下面这些比较安全的字面值,可以直接返回对应的表达式,而非包一层
case 'BigIntLiteral':
case 'BooleanLiteral':
case 'DecimalLiteral':
case 'NullLiteral':
case 'NumericLiteral':
case 'RegExpLiteral':
case 'StringLiteral':
return expr;
// 对于下面这些比较安全的字面值,可以直接返回对应的表达式,而非包一层
if (isSimpleStraightLiteral(exprAst)) {
return expr;
}
switch (exprAst.type) {
// 对于直接写个函数的,则不用再包下,因为这样不会抛出异常的
case 'ArrowFunctionExpression':
case 'FunctionExpression':
@ -221,8 +217,11 @@ function isSimpleDirectlyAccessingThis(exprAst: MemberExpression) {
/** this.state.xxx 和 this.utils.xxx 等安全的肯定应该存在的东东 */
function isSimpleDirectlyAccessingSafeProperties(exprAst: MemberExpression): boolean {
const isPropertySimpleStraight =
!exprAst.computed || (exprAst.property.type !== 'PrivateName' && isSimpleStraightLiteral(exprAst.property));
return (
!exprAst.computed &&
isPropertySimpleStraight &&
exprAst.object.type === 'MemberExpression' &&
exprAst.object.object.type === 'ThisExpression' &&
!exprAst.object.computed &&
@ -231,6 +230,22 @@ function isSimpleDirectlyAccessingSafeProperties(exprAst: MemberExpression): boo
);
}
/** 判断是非是一些简单直接的字面值 */
function isSimpleStraightLiteral(expr: Expression): boolean {
switch (expr.type) {
case 'BigIntLiteral':
case 'BooleanLiteral':
case 'DecimalLiteral':
case 'NullLiteral':
case 'NumericLiteral':
case 'RegExpLiteral':
case 'StringLiteral':
return true;
default:
return false;
}
}
function isImportAliasDefineChunk(
chunk: ICodeChunk,
): chunk is ICodeChunk & {

View File

@ -1,5 +1,5 @@
import { COMMON_CHUNK_NAME } from '../../const/generator';
import { generateCompositeType } from '../../utils/compositeType';
import {
BuilderComponentPlugin,
BuilderComponentPluginFactory,
@ -16,53 +16,55 @@ const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
};
const ir = next.ir as IProjectInfo;
if (ir.i18n) {
const [, i18nStr] = generateCompositeType(ir.i18n, {});
next.chunks.push({
type: ChunkType.STRING,
fileType: FileType.JS,
name: COMMON_CHUNK_NAME.FileMainContent,
content: `
const i18nConfig = ${i18nStr};
let locale = 'en_US';
const i18nStr = ir.i18n ? JSON.stringify(ir.i18n, null, 2) : '{}';
const changeLocale = (target) => {
locale = target;
};
next.chunks.push({
type: ChunkType.STRING,
fileType: FileType.JS,
name: COMMON_CHUNK_NAME.FileMainContent,
content: `
const i18nConfig = ${i18nStr};
const i18n = key => i18nConfig && i18nConfig[locale] && i18nConfig[locale][key] || '';
`,
linkAfter: [
COMMON_CHUNK_NAME.ExternalDepsImport,
COMMON_CHUNK_NAME.InternalDepsImport,
COMMON_CHUNK_NAME.ImportAliasDefine,
COMMON_CHUNK_NAME.FileVarDefine,
COMMON_CHUNK_NAME.FileUtilDefine,
],
});
let locale = 'en-US';
next.chunks.push({
type: ChunkType.STRING,
fileType: FileType.JS,
name: COMMON_CHUNK_NAME.FileExport,
content: `
export {
changeLocale,
i18n,
};
`,
linkAfter: [
COMMON_CHUNK_NAME.ExternalDepsImport,
COMMON_CHUNK_NAME.InternalDepsImport,
COMMON_CHUNK_NAME.ImportAliasDefine,
COMMON_CHUNK_NAME.FileVarDefine,
COMMON_CHUNK_NAME.FileUtilDefine,
COMMON_CHUNK_NAME.FileMainContent,
],
});
}
const getLocale = () => locale;
const setLocale = (target) => {
locale = target;
};
const i18n = key => i18nConfig && i18nConfig[locale] && i18nConfig[locale][key] || '';
`,
linkAfter: [
COMMON_CHUNK_NAME.ExternalDepsImport,
COMMON_CHUNK_NAME.InternalDepsImport,
COMMON_CHUNK_NAME.ImportAliasDefine,
COMMON_CHUNK_NAME.FileVarDefine,
COMMON_CHUNK_NAME.FileUtilDefine,
],
});
next.chunks.push({
type: ChunkType.STRING,
fileType: FileType.JS,
name: COMMON_CHUNK_NAME.FileExport,
content: `
export {
getLocale,
setLocale,
i18n,
};
`,
linkAfter: [
COMMON_CHUNK_NAME.ExternalDepsImport,
COMMON_CHUNK_NAME.InternalDepsImport,
COMMON_CHUNK_NAME.ImportAliasDefine,
COMMON_CHUNK_NAME.FileVarDefine,
COMMON_CHUNK_NAME.FileUtilDefine,
COMMON_CHUNK_NAME.FileMainContent,
],
});
return next;
};
return plugin;

View File

@ -0,0 +1,13 @@
const i18nConfig = {};
let locale = 'en-US';
const getLocale = () => locale;
const setLocale = (target) => {
locale = target;
};
const i18n = (key) => (i18nConfig && i18nConfig[locale] && i18nConfig[locale][key]) || '';
export { getLocale, setLocale, i18n };

View File

@ -13,6 +13,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env';
import __$$constants from '../../constants';
import * as __$$i18n from '../../i18n';
import __$$projectUtils from '../../utils';
import './index.css';
@ -22,6 +24,8 @@ class Home$$Page extends Component {
_context = this._createContext();
_i18n = this._createI18nDelegate();
_dataSourceConfig = this._defineDataSourceConfig();
_dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true });
@ -74,12 +78,33 @@ class Home$$Page extends Component {
get constants() {
return __$$constants;
},
get i18n() {
return self._i18n;
},
getLocale() {
return __$$i18n.getLocale();
},
setLocale(locale) {
__$$i18n.setLocale(locale);
self.forceUpdate();
},
...this._methods,
};
return context;
}
_createI18nDelegate() {
return new Proxy(
{},
{
get(target, prop) {
return __$$i18n.i18n(prop);
},
},
);
}
_defineDataSourceConfig() {
const __$$context = this._context;
return { list: [] };

View File

@ -0,0 +1,13 @@
const i18nConfig = {};
let locale = 'en-US';
const getLocale = () => locale;
const setLocale = (target) => {
locale = target;
};
const i18n = (key) => (i18nConfig && i18nConfig[locale] && i18nConfig[locale][key]) || '';
export { getLocale, setLocale, i18n };

View File

@ -19,6 +19,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env';
import __$$constants from '../../constants';
import * as __$$i18n from '../../i18n';
import __$$projectUtils from '../../utils';
import './index.css';
@ -45,6 +47,8 @@ class Home$$Page extends Component {
_context = this._createContext();
_i18n = this._createI18nDelegate();
_dataSourceConfig = this._defineDataSourceConfig();
_dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, {
runtimeConfig: true,
@ -171,12 +175,33 @@ class Home$$Page extends Component {
get constants() {
return __$$constants;
},
get i18n() {
return self._i18n;
},
getLocale() {
return __$$i18n.getLocale();
},
setLocale(locale) {
__$$i18n.setLocale(locale);
self.forceUpdate();
},
...this._methods,
};
return context;
}
_createI18nDelegate() {
return new Proxy(
{},
{
get(target, prop) {
return __$$i18n.i18n(prop);
},
},
);
}
_defineDataSourceConfig() {
const __$$context = this._context;
return {

View File

@ -0,0 +1,13 @@
const i18nConfig = {};
let locale = 'en-US';
const getLocale = () => locale;
const setLocale = (target) => {
locale = target;
};
const i18n = (key) => (i18nConfig && i18nConfig[locale] && i18nConfig[locale][key]) || '';
export { getLocale, setLocale, i18n };

View File

@ -17,6 +17,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env';
import __$$constants from '../../constants';
import * as __$$i18n from '../../i18n';
import __$$projectUtils from '../../utils';
import './index.css';
@ -28,6 +30,8 @@ class Detail$$Page extends Component {
_context = this._createContext();
_i18n = this._createI18nDelegate();
_dataSourceConfig = this._defineDataSourceConfig();
_dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true });
@ -85,12 +89,33 @@ class Detail$$Page extends Component {
get constants() {
return __$$constants;
},
get i18n() {
return self._i18n;
},
getLocale() {
return __$$i18n.getLocale();
},
setLocale(locale) {
__$$i18n.setLocale(locale);
self.forceUpdate();
},
...this._methods,
};
return context;
}
_createI18nDelegate() {
return new Proxy(
{},
{
get(target, prop) {
return __$$i18n.i18n(prop);
},
},
);
}
_defineDataSourceConfig() {
const __$$context = this._context;
return { list: [] };

View File

@ -17,6 +17,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env';
import __$$constants from '../../constants';
import * as __$$i18n from '../../i18n';
import __$$projectUtils from '../../utils';
import './index.css';
@ -28,6 +30,8 @@ class Home$$Page extends Component {
_context = this._createContext();
_i18n = this._createI18nDelegate();
_dataSourceConfig = this._defineDataSourceConfig();
_dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true });
@ -85,12 +89,33 @@ class Home$$Page extends Component {
get constants() {
return __$$constants;
},
get i18n() {
return self._i18n;
},
getLocale() {
return __$$i18n.getLocale();
},
setLocale(locale) {
__$$i18n.setLocale(locale);
self.forceUpdate();
},
...this._methods,
};
return context;
}
_createI18nDelegate() {
return new Proxy(
{},
{
get(target, prop) {
return __$$i18n.i18n(prop);
},
},
);
}
_defineDataSourceConfig() {
const __$$context = this._context;
return { list: [] };

View File

@ -17,6 +17,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env';
import __$$constants from '../../constants';
import * as __$$i18n from '../../i18n';
import __$$projectUtils from '../../utils';
import './index.css';
@ -28,6 +30,8 @@ class List$$Page extends Component {
_context = this._createContext();
_i18n = this._createI18nDelegate();
_dataSourceConfig = this._defineDataSourceConfig();
_dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true });
@ -88,12 +92,33 @@ class List$$Page extends Component {
get constants() {
return __$$constants;
},
get i18n() {
return self._i18n;
},
getLocale() {
return __$$i18n.getLocale();
},
setLocale(locale) {
__$$i18n.setLocale(locale);
self.forceUpdate();
},
...this._methods,
};
return context;
}
_createI18nDelegate() {
return new Proxy(
{},
{
get(target, prop) {
return __$$i18n.i18n(prop);
},
},
);
}
_defineDataSourceConfig() {
const __$$context = this._context;
return { list: [] };

View File

@ -0,0 +1,13 @@
const i18nConfig = {};
let locale = 'en-US';
const getLocale = () => locale;
const setLocale = (target) => {
locale = target;
};
const i18n = (key) => (i18nConfig && i18nConfig[locale] && i18nConfig[locale][key]) || '';
export { getLocale, setLocale, i18n };

View File

@ -17,6 +17,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env';
import __$$constants from '../../constants';
import * as __$$i18n from '../../i18n';
import __$$projectUtils from '../../utils';
import './index.css';
@ -28,6 +30,8 @@ class Home$$Page extends Component {
_context = this._createContext();
_i18n = this._createI18nDelegate();
_dataSourceConfig = this._defineDataSourceConfig();
_dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true });
@ -82,12 +86,33 @@ class Home$$Page extends Component {
get constants() {
return __$$constants;
},
get i18n() {
return self._i18n;
},
getLocale() {
return __$$i18n.getLocale();
},
setLocale(locale) {
__$$i18n.setLocale(locale);
self.forceUpdate();
},
...this._methods,
};
return context;
}
_createI18nDelegate() {
return new Proxy(
{},
{
get(target, prop) {
return __$$i18n.i18n(prop);
},
},
);
}
_defineDataSourceConfig() {
const __$$context = this._context;
return { list: [] };

View File

@ -0,0 +1,12 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@ -0,0 +1,11 @@
# 忽略目录
build/
tests/
demo/
# node 覆盖率文件
coverage/
# 忽略文件
**/*-min.js
**/*.min.js

View File

@ -0,0 +1,3 @@
module.exports = {
extends: ['rax'],
};

View File

@ -0,0 +1,17 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
*~
*.swp
*.log
.DS_Store
.idea/
.temp/
build/
dist/
lib/
coverage/
node_modules/
template.yml

View File

@ -0,0 +1,15 @@
# @ali/rax-component-demo
## Getting Started
### `npm run start`
Runs the app in development mode.
Open [http://localhost:9999](http://localhost:9999) to view it in the browser.
The page will reload if you make edits.
### `npm run build`
Builds the app for production to the `build` folder.

View File

@ -0,0 +1,7 @@
{
"type": "rax",
"builder": "@ali/builder-rax-v1",
"info": {
"raxVersion": "1.x"
}
}

View File

@ -0,0 +1,12 @@
{
"inlineStyle": false,
"plugins": [
[
"build-plugin-rax-app",
{
"targets": ["web", "miniapp"]
}
],
"@ali/build-plugin-rax-app-def"
]
}

View File

@ -0,0 +1,32 @@
{
"name": "@ali/rax-app-demo",
"private": true,
"version": "1.0.0",
"scripts": {
"build": "build-scripts build",
"start": "build-scripts start",
"lint": "eslint --ext .js --ext .jsx ./"
},
"dependencies": {
"@ali/lowcode-datasource-engine": "^0.1.0",
"universal-env": "^3.2.0",
"rax": "^1.1.0",
"rax-app": "^2.0.0",
"rax-document": "^0.1.0",
"rax-view": "^1.0.0",
"rax-text": "^1.0.0"
},
"devDependencies": {
"build-plugin-rax-app": "^5.0.0",
"@alib/build-scripts": "^0.1.0",
"@typescript-eslint/eslint-plugin": "^2.11.0",
"@typescript-eslint/parser": "^2.11.0",
"babel-eslint": "^10.0.3",
"eslint": "^6.8.0",
"eslint-config-rax": "^0.1.0",
"eslint-plugin-import": "^2.20.0",
"eslint-plugin-module": "^0.1.0",
"eslint-plugin-react": "^7.18.0",
"@ali/build-plugin-rax-app-def": "^1.0.0"
}
}

View File

@ -0,0 +1,6 @@
import { runApp } from 'rax-app';
import appConfig from './app.json';
import './global.scss';
runApp(appConfig);

View File

@ -0,0 +1,11 @@
{
"routes": [
{
"path": "/",
"source": "pages/Home/index"
}
],
"window": {
"title": "Rax App Demo"
}
}

View File

@ -0,0 +1,3 @@
const __$$constants = {};
export default __$$constants;

View File

@ -0,0 +1,25 @@
import { createElement } from 'rax';
import { Root, Style, Script } from 'rax-document';
function Document() {
return (
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover"
/>
<title>Rax App Demo</title>
<Style />
</head>
<body>
{/* root container */}
<Root />
<Script />
</body>
</html>
);
}
export default Document;

View File

@ -0,0 +1,3 @@
body {
-webkit-font-smoothing: antialiased;
}

View File

@ -0,0 +1,20 @@
const i18nConfig = {
'zh-CN': {
'hello-world': '你好,世界!',
},
'en-US': {
'hello-world': 'Hello world!',
},
};
let locale = 'en-US';
const getLocale = () => locale;
const setLocale = (target) => {
locale = target;
};
const i18n = (key) => (i18nConfig && i18nConfig[locale] && i18nConfig[locale][key]) || '';
export { getLocale, setLocale, i18n };

View File

@ -0,0 +1,162 @@
// : "__$$" 访
// rax
import { createElement, Component } from 'rax';
import { withRouter as __$$withRouter } from 'rax-app';
import Page from 'rax-view';
import Text from 'rax-text';
import { create as __$$createDataSourceEngine } from '@ali/lowcode-datasource-engine';
import { isMiniApp as __$$isMiniApp } from 'universal-env';
import __$$constants from '../../constants';
import * as __$$i18n from '../../i18n';
import __$$projectUtils from '../../utils';
import './index.css';
class Home$$Page extends Component {
_methods = this._defineMethods();
_context = this._createContext();
_i18n = this._createI18nDelegate();
_dataSourceConfig = this._defineDataSourceConfig();
_dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true });
_utils = this._defineUtils();
componentDidMount() {
this._dataSourceEngine.reloadDataSource();
}
componentWillUnmount() {}
render() {
const __$$context = this._context;
return (
<Page>
<Text
onClick={function () {
__$$context.setLocale(__$$context.getLocale() === 'en-US' ? 'zh-CN' : 'en-US');
}}
>
{__$$context.i18n['hello-world']}
</Text>
</Page>
);
}
_createContext() {
const self = this;
const context = {
get state() {
return self.state;
},
setState(newState) {
self.setState(newState);
},
get dataSourceMap() {
return self._dataSourceEngine.dataSourceMap || {};
},
async reloadDataSource() {
await self._dataSourceEngine.reloadDataSource();
},
get utils() {
return self._utils;
},
get page() {
return context;
},
get component() {
return context;
},
get props() {
return self.props;
},
get constants() {
return __$$constants;
},
get i18n() {
return self._i18n;
},
getLocale() {
return __$$i18n.getLocale();
},
setLocale(locale) {
__$$i18n.setLocale(locale);
self.forceUpdate();
},
...this._methods,
};
return context;
}
_createI18nDelegate() {
return new Proxy(
{},
{
get(target, prop) {
return __$$i18n.i18n(prop);
},
},
);
}
_defineDataSourceConfig() {
const __$$context = this._context;
return { list: [] };
}
_defineUtils() {
const utils = {
...__$$projectUtils,
};
Object.entries(utils).forEach(([name, util]) => {
if (typeof util === 'function') {
utils[name] = util.bind(this._context);
}
});
return utils;
}
_defineMethods() {
const __$$methods = {};
//
Object.entries(__$$methods).forEach(([methodName, method]) => {
if (typeof method === 'function') {
__$$methods[methodName] = (...args) => {
return method.apply(this._context, args);
};
}
});
return __$$methods;
}
}
export default __$$withRouter(Home$$Page);
function __$$eval(expr) {
try {
return expr();
} catch (err) {
console.warn('Failed to evaluate: ', expr, err);
}
}
function __$$evalArray(expr) {
const res = __$$eval(expr);
return Array.isArray(res) ? res : [];
}

View File

@ -0,0 +1 @@
export default {};

View File

@ -0,0 +1,73 @@
{
// 这是一个关于国际化的 schema 示例
// Schema 参见https://yuque.antfin-inc.com/mo/spec/spec-materials#eNCJr
version: '1.0.0',
componentsMap: [
{
componentName: 'Page',
package: 'rax-view',
version: '^1.0.0',
destructuring: false,
exportName: 'Page',
},
{
componentName: 'Text',
package: 'rax-text',
version: '^1.0.0',
destructuring: false,
exportName: 'Text',
},
],
componentsTree: [
{
componentName: 'Page',
props: {},
lifeCycles: {},
fileName: 'home',
meta: {
router: '/',
},
dataSource: {
list: [],
},
children: [
{
componentName: 'Text',
props: {
onClick: {
type: 'JSFunction',
value: "function () {\n this.setLocale(this.getLocale() === 'en-US' ? 'zh-CN' : 'en-US');\n}",
},
},
children: [
{
type: 'JSExpression',
value: 'this.i18n["hello-world"]',
},
],
},
],
},
],
i18n: {
'zh-CN': {
'hello-world': '你好,世界!',
},
'en-US': {
'hello-world': 'Hello world!',
},
},
config: {
sdkVersion: '1.0.3',
historyMode: 'hash',
targetRootID: 'root',
},
meta: {
name: 'Rax App Demo',
git_group: 'demo-group',
project_name: 'demo-project',
description: '这是一个示例应用',
spma: 'spmademo',
creator: '张三',
},
}