支持 mobile 和 en-US 替换配置;这里有个小 breaking change, 内置翻译文件 locale/en 换成了 locale/en-US (#1349)

* 环境配置覆盖初步

* 换成最靠前替换来支持 validations

* 文档描述更清晰些
This commit is contained in:
吴多益 2021-01-10 11:22:18 +08:00 committed by GitHub
parent 1933250052
commit c092ccecac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 161 additions and 37 deletions

View File

@ -2,9 +2,16 @@
title: 多语言
---
amis 内置对英文的支持,同时你也可以扩展其他语言。
amis 中对多语言的支持有两方面:
## JS SDK
1. amis 内部组件的多语言,比如日期组件中的日期
1. JSON 配置中的多语言,比如配置中的 label 值
## amis 内部组件多语言
分为 JS SDK 和 React 两种用法。
### JS SDK
从 1.1.0 版本开始已经自带英文翻译,所以只需要在 props 里设置 locale 即可。
@ -28,20 +35,20 @@ let amisScoped = amis.embed(
}
},
{
locale: 'en'
locale: 'en-US'
}
);
```
## React
### React
React 中没有内置英文版本,需要自己 import使用如下方法
React 版本中没有内置英文翻译,需要自己 import使用如下方法
```javascript
import 'amis/lib/locale/en';
import 'amis/lib/locale/en-US';
```
在渲染 amis 组件的时候设置 locale 为 en
在渲染 amis 组件的时候设置 locale 为 en-US
```javascript
{
@ -52,13 +59,33 @@ import 'amis/lib/locale/en';
body: '内容'
},
{
locale: 'en'
locale: 'en-US'
}
);
}
```
## 扩展其它语言
## JSON 配置中设置多语言
在 JSON 配置中,也可以设置不同语言下的不同展现,比如前面设置了 `locale``en-US`,这时在任意 JSON 配置中都能使用 `en-US` 对象来覆盖这个语言下的效果。
```schema: scope="body"
{
"type": "form",
"controls": [{
"type": "text",
"name": "name",
"label": "姓名:",
"en-US": {
"label": "username: "
}
}]
}
```
请点击上方的切换语言下拉框切换到英文,就能看到 `label` 属性被替换了,除了 `label` 以外,还可以覆盖其他任意属性,比如将 type 换成其他。
## 扩展内置组件的语言
如果想扩展其他语言,首先参考 `https://github.com/baidu/amis/blob/master/src/locale/en.ts` 文件,了解需要翻译哪些文字,以中文为 key然后参考后面的示例注册新语言未翻译的文字都将使用默认语言即中文。

View File

@ -0,0 +1,51 @@
---
title: 移动端定制
---
有时候我们需要在移动端下展示不同效果,可以通过 `mobile` 属性来在移动端下覆盖部分属性。
```schema: scope="body"
{
"type": "form",
"controls": [{
"name": "email",
"type": "email",
"label": "邮箱:",
"mobile": {
"name": "phone",
"type": "text",
"label": "电话:",
"validations": {
"isPhoneNumber": true
}
}
}]
}
```
请点击上方切换到移动端预览效果。
`mobile` 属性可以出现在配置中的任意地方,替换父节点的任意属性,比如前面的例子可以写成放在 `form` 上替换所有 `controls`
```schema: scope="body"
{
"type": "form",
"controls": [{
"name": "email",
"type": "email",
"label": "邮箱:"
}],
"mobile": {
"controls": [{
"name": "phone",
"type": "text",
"label": "电话:",
"validations": {
"isPhoneNumber": true
}
}]
}
}
```
> 注意这里对于移动端的判断是根据页面宽度,和 CSS 保持一致,所以即便是在 PC 上,如果页面宽度很小也会切换到 mobile 配置

View File

@ -113,7 +113,7 @@ let amisScoped = amis.embed(
amisJSON,
{
// 这里是初始 props一般不用传。
// locale: 'en' // props 中可以设置语言,默认是中文
// locale: 'en-US' // props 中可以设置语言,默认是中文
},
{
// 可以不传,用来实现 ajax 请求
@ -272,7 +272,7 @@ class MyComponent extends React.Component<any, any> {
},
{
// props...
// locale: 'en' // 请参考「多语言」的文档
// locale: 'en-US' // 请参考「多语言」的文档
},
{
// 下面三个接口必须实现

View File

@ -10,7 +10,7 @@ import {
} from '../../src/components/index';
import {eachTree, mapTree} from '../../src/utils/helper';
import {Icon} from '../../src/components/icons';
import '../../src/locale/en';
import '../../src/locale/en-US';
import {
Router,
Route,
@ -72,7 +72,7 @@ const locales = [
{
label: 'English',
value: 'en'
value: 'en-US'
}
];

View File

@ -1169,6 +1169,13 @@ export default [
// @ts-ignore
import('../../docs/zh-CN/extend/addon.md').then(makeMarkdownRenderer)
},
{
label: '移动端定制',
path: '/zh-CN/docs/extend/mobile',
getComponent: () =>
// @ts-ignore
import('../../docs/zh-CN/extend/mobile.md').then(makeMarkdownRenderer)
},
{
label: '多语言',
path: '/zh-CN/docs/extend/i18n',

View File

@ -15,7 +15,7 @@ import {
render as renderAmis
} from '../src/index';
import '../src/locale/en';
import '../src/locale/en-US';
export function embed(
container: string | HTMLElement,

View File

@ -9,7 +9,7 @@ import {render} from 'react-dom';
import axios from 'axios';
import copy from 'copy-to-clipboard';
import {toast} from '../src/components/Toast';
import '../src/locale/en';
import '../src/locale/en-US';
import {render as renderAmis} from '../src/index';

30
src/envOverwrite.ts Normal file
View File

@ -0,0 +1,30 @@
/**
* @file 使
*/
import {findObjectsWithKey} from './utils/helper';
const isMobile = window.matchMedia('(max-width: 768px)').matches ? true : false;
export const envOverwrite = (schema: any, locale?: string) => {
if (schema.mobile && isMobile) {
Object.assign(schema, schema.mobile);
delete schema.mobile;
}
if (locale) {
let schemaNodes = findObjectsWithKey(schema, locale);
for (let schemaNode of schemaNodes) {
Object.assign(schemaNode, schemaNode[locale]);
delete schemaNode[locale];
}
}
if (isMobile) {
let schemaNodes = findObjectsWithKey(schema, 'mobile');
for (let schemaNode of schemaNodes) {
Object.assign(schemaNode, schemaNode['mobile']);
delete schemaNode['mobile'];
}
}
};

View File

@ -56,6 +56,7 @@ import {
} from './locale';
import {SchemaCollection, SchemaObject, SchemaTpl} from './Schema';
import {result} from 'lodash';
import {envOverwrite} from './envOverwrite';
export interface TestFunc {
(
@ -156,7 +157,7 @@ export interface RendererConfig extends RendererBasicConfig {
}
export interface RenderSchemaFilter {
(schema: Schema, renderer: RendererConfig, props?: object): Schema;
(schema: Schema, renderer: RendererConfig, props?: any): Schema;
}
export interface RootRenderProps {
@ -425,6 +426,9 @@ export class RootRenderer extends React.Component<RootRendererProps> {
)
: data;
// 根据环境覆盖 schema这个要在最前面做不然就无法覆盖 validations
envOverwrite(schema, locale);
return (
<RootStoreContext.Provider value={rootStore}>
<ThemeContext.Provider value={this.props.theme || 'default'}>
@ -1058,7 +1062,12 @@ export function render(
options: RenderOptions = {},
pathPrefix: string = ''
): JSX.Element {
const locale = props.locale || getDefaultLocale();
let locale = props.locale || getDefaultLocale();
// 兼容 locale 的不同写法
locale = locale.replace('_', '-');
locale = locale === 'en' ? 'en-US' : locale;
locale = locale === 'zh' ? 'zh-CN' : locale;
locale = locale === 'cn' ? 'zh-CN' : locale;
const translate = props.translate || makeTranslator(locale);
let store = stores[options.session || 'global'];

View File

@ -1,6 +1,6 @@
import {register} from '../locale';
register('en', {
register('en-US', {
'确认': 'Confirm',
'取消': 'Cancel',
'YYYY年': 'YYYY',

View File

@ -14,7 +14,7 @@ import {
} from '../utils/tpl-builtin';
import {isApiOutdated, isEffectiveApi} from '../utils/api';
import {ScopedContext, IScopedContext} from '../Scoped';
import {createObject} from '../utils/helper';
import {createObject, findObjectsWithKey} from '../utils/helper';
import Spinner from '../components/Spinner';
import {
BaseSchema,
@ -117,24 +117,6 @@ export interface ChartSchema extends BaseSchema {
unMountOnHidden?: boolean;
}
/**
* key
* @param obj
* @param key
*/
function findObjectsWithKey(obj: any, key: string) {
let objects: any[] = [];
for (const k in obj) {
if (!obj.hasOwnProperty(k)) continue;
if (typeof obj[k] === 'object') {
objects = objects.concat(findObjectsWithKey(obj[k], key));
} else if (k === key) {
objects.push(obj);
}
}
return objects;
}
const EVAL_CACHE: {[key: string]: Function} = {};
/**
* ECharts JSON

View File

@ -1342,3 +1342,21 @@ export const keyToPath = (string: string) => {
return result;
};
/**
* key
* @param obj
* @param key
*/
export function findObjectsWithKey(obj: any, key: string) {
let objects: any[] = [];
for (const k in obj) {
if (!obj.hasOwnProperty(k)) continue;
if (k === key) {
objects.push(obj);
} else if (typeof obj[k] === 'object') {
objects = objects.concat(findObjectsWithKey(obj[k], key));
}
}
return objects;
}