Skip to content

Commit 7d5eebe

Browse files
authored
feat: locale 支持 (#2605)
* feat(locale): 添加国际化 & 单测 * refactor: 暂时不暴露 GLOBAL 变量,修改 cr 建议 * fix(locale): 找不到翻译时,返回 key 值 & 修复单测
1 parent 4a9d83f commit 7d5eebe

19 files changed

+283
-13
lines changed

__tests__/unit/core/global-spec.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { GLOBAL, setGlobal } from '../../../src/core/global';
2+
3+
describe('global variables', () => {
4+
it('global locale', () => {
5+
// 单测环境,默认 中文
6+
expect(GLOBAL.locale).toBe('zh-CN');
7+
});
8+
9+
it('setGlobal', () => {
10+
setGlobal({ locale: 'en-US' });
11+
expect(GLOBAL.locale).toBe('en-US');
12+
});
13+
});

__tests__/unit/core/locale-spec.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// 得引一下 src,否则无法注册国际化
2+
import '../../../src';
3+
import { GLOBAL, setGlobal } from '../../../src/core/global';
4+
import { getLocale, registerLocale } from '../../../src/core/locale';
5+
import { EN_US_LOCALE } from '../../../src/locales/en_US';
6+
import { ZH_CN_LOCALE } from '../../../src/locales/zh_CN';
7+
8+
describe('locale', () => {
9+
it('getLocale', () => {
10+
// 单测环境,默认 中文
11+
expect(GLOBAL.locale).toBe('zh-CN');
12+
expect(getLocale('xxx').get('general')).toEqual(ZH_CN_LOCALE.general);
13+
14+
expect(getLocale('en-US').get('general')).toEqual(EN_US_LOCALE.general);
15+
16+
setGlobal({ locale: 'en-US' });
17+
expect(getLocale('xxx').get('general')).toEqual(EN_US_LOCALE.general);
18+
});
19+
20+
it('registerLocale', () => {
21+
registerLocale('custom', {
22+
locale: 'custom',
23+
general: { increase: 'INCREASE', decrease: 'DECREASE', root: 'ROOT' },
24+
statistic: { total: '统计' },
25+
conversionTag: { label: '转化率' },
26+
waterfall: { total: '累计值' },
27+
});
28+
expect(getLocale('custom').get(['statistic', 'total'])).toBe('统计');
29+
expect(getLocale('custom').get('statistic.total')).toBe('统计');
30+
// 返回 key 值
31+
expect(getLocale('custom').get('statistic-total')).toBe('statistic-total');
32+
// 找不到语言包,则使用全局语言包
33+
expect(getLocale('---').get('locale')).toBe('en-US');
34+
expect(getLocale('---').get('locale')).toBe('en-US');
35+
36+
setGlobal({ locale: 'custom' });
37+
expect(getLocale('---').get('locale')).toBe('custom');
38+
expect(getLocale('---').get(['statistic', 'total'])).toBe('统计');
39+
expect(getLocale('---').get('statistic.total')).toBe('统计');
40+
expect(getLocale('---').get('statistic-total')).toBe('statistic-total');
41+
});
42+
});

__tests__/unit/utils/template-spec.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { template } from '../../../src/utils/template';
22

33
describe('template', () => {
4-
it('template', () => {
4+
it('default', () => {
55
const data = { name: 'item1', value: 1, percentage: 0.23 };
66
expect(template('{name}: {value}', data)).toBe('item1: 1');
77
expect(template('{ name }: { value }', data)).toBe('item1: 1');
@@ -13,4 +13,11 @@ describe('template', () => {
1313
// 没有 match 的 data 原路返回
1414
expect(template('{name}: {value}({percentage1})', data)).toBe('item1: 1({percentage1})');
1515
});
16+
17+
it('data: empty', () => {
18+
// 没有 match 的 data 原路返回
19+
expect(template('{name}: {value}({percentage1})', {})).toBe('{name}: {value}({percentage1})');
20+
expect(template('{name}: {value}({percentage1})', null)).toBe('{name}: {value}({percentage1})');
21+
expect(template('{name}: {value}({percentage1})', undefined)).toBe('{name}: {value}({percentage1})');
22+
});
1623
});

jest.setup.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const { setGlobal } = require('./src/core/global');
2+
3+
setGlobal({ locale: 'zh-CN' });

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@
113113
"testEnvironment": "jest-electron/environment",
114114
"testTimeout": 30000,
115115
"setupFilesAfterEnv": [
116-
"jest-extended"
116+
"jest-extended",
117+
"./jest.setup.js"
117118
],
118119
"preset": "ts-jest",
119120
"collectCoverage": false,
@@ -138,7 +139,7 @@
138139
"limit-size": [
139140
{
140141
"path": "dist/g2plot.min.js",
141-
"limit": "910 Kb"
142+
"limit": "920 Kb"
142143
},
143144
{
144145
"path": "dist/g2plot.min.js",

src/core/global.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { each } from '@antv/util';
2+
/**
3+
* @file 全局的一些变量定义:含国际化、主题...
4+
*/
5+
export const GLOBAL = {
6+
/** 全局语言 */
7+
locale: 'en-US',
8+
};
9+
10+
/**
11+
* 全局变量设置
12+
* @param key
13+
* @param value
14+
*/
15+
export function setGlobal(datum: Record<string, any>): void {
16+
each(datum, (v, k) => (GLOBAL[k] = v));
17+
}

src/core/locale.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { get } from '@antv/util';
2+
import { Locale } from '../types/locale';
3+
import { template } from '../utils';
4+
import { GLOBAL } from './global';
5+
6+
const LocaleMap = {};
7+
8+
/**
9+
* register a locale
10+
* @param locale
11+
* @param localeObj
12+
*/
13+
export function registerLocale(locale: string, localeObj: Locale) {
14+
LocaleMap[locale] = localeObj;
15+
}
16+
17+
/**
18+
* get locale of specific language
19+
* @param lang
20+
* @returns
21+
*/
22+
export function getLocale(locale: string) {
23+
return {
24+
get: (key: string | string[], obj?: Record<string, any>) => {
25+
return template(
26+
get(LocaleMap[locale], key) || get(LocaleMap[GLOBAL.locale], key) || get(LocaleMap['en-US'], key) || key,
27+
obj
28+
);
29+
},
30+
};
31+
}

src/core/plot.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type PickOptions = Pick<
1919
| 'syncViewPadding'
2020
| 'supportCSSTransform'
2121
| 'limitInPlot'
22+
| 'locale'
2223
>;
2324

2425
const SOURCE_ATTRIBUTE_NAME = 'data-chart-source-type';

src/index.ts

+13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ export const version = '2.3.21';
44
import * as G2 from '@antv/g2';
55
export { G2 };
66

7+
// 国际化处理
8+
import { registerLocale } from './core/locale';
9+
import { EN_US_LOCALE } from './locales/en_US';
10+
import { ZH_CN_LOCALE } from './locales/zh_CN';
11+
/** default locale register */
12+
registerLocale('en-US', EN_US_LOCALE);
13+
registerLocale('zh-CN', ZH_CN_LOCALE);
14+
/** 透出 国际化 工具函数,便于使用 */
15+
export { registerLocale };
16+
17+
/** 全局变量 */
18+
export { setGlobal } from './core/global';
19+
720
/** G2Plot 的 Plot 基类 */
821
export { Plot } from './core/plot';
922

src/locales/en_US.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Locale } from '../types/locale';
2+
3+
export const EN_US_LOCALE: Locale = {
4+
locale: 'en-US',
5+
6+
// General
7+
general: {
8+
increase: 'Increase',
9+
decrease: 'Decrease',
10+
root: 'Root',
11+
},
12+
13+
// Plot Components
14+
/** statistic text component */
15+
statistic: {
16+
total: 'Total',
17+
},
18+
/** conversionTag component */
19+
conversionTag: {
20+
label: 'Conversion rate',
21+
},
22+
legend: {},
23+
tooltip: {},
24+
slider: {},
25+
scrollbar: {},
26+
27+
// Plots
28+
waterfall: {
29+
total: 'Total',
30+
},
31+
};

src/locales/zh_CN.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Locale } from '../types/locale';
2+
3+
export const ZH_CN_LOCALE: Locale = {
4+
locale: 'zh-CN',
5+
6+
// 通用
7+
general: {
8+
increase: '增加',
9+
decrease: '减少',
10+
root: '初始',
11+
},
12+
13+
// 按照图表组件
14+
/** 中心文本 */
15+
statistic: {
16+
total: '总计',
17+
},
18+
/** 转化率组件 */
19+
conversionTag: {
20+
label: '转化率',
21+
},
22+
legend: {},
23+
tooltip: {},
24+
slider: {},
25+
scrollbar: {},
26+
27+
// 按照图表类型
28+
waterfall: {
29+
total: '总计',
30+
},
31+
};

src/plots/funnel/adaptor.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Params } from '../../core/adaptor';
22
import { interaction, animation, theme, scale, annotation, tooltip } from '../../adaptor/common';
3+
import { getLocale } from '../../core/locale';
34
import { flow, deepAssign } from '../../utils';
45
import { conversionTagFormatter } from '../../utils/conversion';
56
import { FunnelOptions } from './types';
@@ -24,7 +25,9 @@ import { FUNNEL_CONVERSATION, FUNNEL_MAPPING_VALUE, FUNNEL_PERCENT } from './con
2425
*/
2526
function defaultOptions(params: Params<FunnelOptions>): Params<FunnelOptions> {
2627
const { options } = params;
27-
const { compareField, xField, yField } = options;
28+
const { compareField, xField, yField, locale } = options;
29+
const i18n = getLocale(locale);
30+
2831
const defaultOption = {
2932
minSize: 0,
3033
maxSize: 1,
@@ -68,7 +71,10 @@ function defaultOptions(params: Params<FunnelOptions>): Params<FunnelOptions> {
6871
offsetY: 0,
6972
style: {},
7073
// conversionTag 的计算和显示逻辑统一保持一致
71-
formatter: (datum) => `转化率: ${conversionTagFormatter(...(datum[FUNNEL_CONVERSATION] as [number, number]))}`,
74+
formatter: (datum) =>
75+
`${i18n.get(['conversionTag', 'label'])}: ${conversionTagFormatter(
76+
...(datum[FUNNEL_CONVERSATION] as [number, number])
77+
)}`,
7278
},
7379
};
7480

src/plots/pie/adaptor.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Params } from '../../core/adaptor';
33
import { legend, animation, theme, state, annotation } from '../../adaptor/common';
44
import { getMappingFunction } from '../../adaptor/geometries/base';
55
import { interval } from '../../adaptor/geometries';
6+
import { getLocale } from '../../core/locale';
67
import { Interaction } from '../../types/interaction';
78
import { flow, template, transformLabel, deepAssign, renderStatistic, processIllegalData } from '../../utils';
89
import { Data, Datum } from '../../types';
@@ -176,7 +177,9 @@ function label(params: Params<PieOptions>): Params<PieOptions> {
176177
* 2. 默认使用 meta 的 formatter
177178
*/
178179
export function transformStatisticOptions(options: PieOptions): PieOptions {
179-
const { innerRadius, statistic, angleField, colorField, meta } = options;
180+
const { innerRadius, statistic, angleField, colorField, meta, locale } = options;
181+
182+
const i18n = getLocale(locale);
180183

181184
if (innerRadius && statistic) {
182185
let { title: titleOpt, content: contentOpt } = deepAssign({}, DEFAULT_OPTIONS.statistic, statistic);
@@ -189,7 +192,7 @@ export function transformStatisticOptions(options: PieOptions): PieOptions {
189192
if (datum) {
190193
return datum[colorField];
191194
}
192-
return !isNil(titleOpt.content) ? titleOpt.content : '总计';
195+
return !isNil(titleOpt.content) ? titleOpt.content : i18n.get(['statistic', 'total']);
193196
},
194197
},
195198
titleOpt

src/plots/waterfall/adaptor.ts

+39-4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,31 @@ import { Datum } from '../../types';
44
import { Params } from '../../core/adaptor';
55
import { interaction, animation, theme, state, scale, annotation } from '../../adaptor/common';
66
import { interval } from '../../adaptor/geometries';
7+
import { getLocale } from '../../core/locale';
78
import { findGeometry, flow, transformLabel, deepAssign } from '../../utils';
89
import { Y_FIELD, ABSOLUTE_FIELD, DIFF_FIELD, IS_TOTAL } from './constant';
910
import { WaterfallOptions } from './types';
1011
import { transformData } from './utils';
1112
import './shape';
1213

14+
/**
15+
* 处理默认配置项
16+
* @param params
17+
* @returns
18+
*/
19+
function defaultOptions(params: Params<WaterfallOptions>): Params<WaterfallOptions> {
20+
const { locale, total } = params.options;
21+
22+
const localeTotalLabel = getLocale(locale).get(['waterfall', 'total']);
23+
24+
if (total && typeof total.label !== 'string' && localeTotalLabel) {
25+
// @ts-ignore
26+
params.options.total.label = localeTotalLabel;
27+
}
28+
29+
return params;
30+
}
31+
1332
/**
1433
* 字段
1534
* @param params
@@ -109,23 +128,26 @@ function axis(params: Params<WaterfallOptions>): Params<WaterfallOptions> {
109128
*/
110129
function legend(params: Params<WaterfallOptions>): Params<WaterfallOptions> {
111130
const { chart, options } = params;
112-
const { legend, total, risingFill, fallingFill } = options;
131+
const { legend, total, risingFill, fallingFill, locale } = options;
132+
133+
const i18n = getLocale(locale);
113134

114135
if (legend === false) {
115136
chart.legend(false);
116137
} else {
117138
const items = [
118139
{
119-
name: '增加',
140+
name: i18n.get(['general', 'increase']),
120141
value: 'increase',
121142
marker: { symbol: 'square', style: { r: 5, fill: risingFill } },
122143
},
123144
{
124-
name: '减少',
145+
name: i18n.get(['general', 'decrease']),
125146
value: 'decrease',
126147
marker: { symbol: 'square', style: { r: 5, fill: fallingFill } },
127148
},
128149
];
150+
129151
if (total) {
130152
items.push({
131153
name: total.label || '',
@@ -209,5 +231,18 @@ export function tooltip(params: Params<WaterfallOptions>): Params<WaterfallOptio
209231
* @param params
210232
*/
211233
export function adaptor(params: Params<WaterfallOptions>) {
212-
return flow(geometry, meta, axis, legend, tooltip, label, state, theme, interaction, animation, annotation())(params);
234+
return flow(
235+
defaultOptions,
236+
theme,
237+
geometry,
238+
meta,
239+
axis,
240+
legend,
241+
tooltip,
242+
label,
243+
state,
244+
interaction,
245+
animation,
246+
annotation()
247+
)(params);
213248
}

src/plots/waterfall/constant.ts

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export const DEFAULT_OPTIONS = {
1919
},
2020
/** default: show total */
2121
total: {
22-
label: '总计',
2322
style: {
2423
fill: 'rgba(0, 0, 0, 0.25)',
2524
},

0 commit comments

Comments
 (0)