fix: i18n parser & setting

This commit is contained in:
kangwei 2020-05-29 01:57:51 +08:00
parent f20bfaa884
commit dbdd9e485a
4 changed files with 402 additions and 15 deletions

View File

@ -1,21 +1,18 @@
import Env from './env';
import { isJSSlot, isI18nData, isJSExpression } from '@ali/lowcode-types';
import { isPlainObject } from '@ali/lowcode-utils';
const I18nUtil = require('@ali/ve-i18n-util');
import i18nUtil from './i18n-util';
interface I18nObject {
type?: string;
use?: string;
key?: string;
[lang: string]: string | undefined;
}
export function i18nReducer(obj?: any): any {
// FIXME: 表达式使用 mock 值未来live 模式直接使用原始值
export function deepValueParser(obj?: any): any {
if (isJSExpression(obj)) {
obj = obj.mock;
}
if (!obj) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => i18nReducer(item));
return obj.map((item) => deepValueParser(item));
}
if (isPlainObject(obj)) {
if (isI18nData(obj)) {
@ -23,19 +20,20 @@ export function i18nReducer(obj?: any): any {
let locale = Env.getLocale();
if (obj.key) {
// FIXME: 此处需要升级I18nUtil改成响应式
return I18nUtil.get(obj.key, locale);
return i18nUtil.get(obj.key, locale);
}
if (locale !== 'zh_CN' && locale !== 'zh_TW' && !obj[locale]) {
locale = 'en_US';
}
return obj[obj.use || locale] || obj.zh_CN;
}
if (isJSSlot(obj) || isJSExpression(obj)) {
if (isJSSlot(obj)) {
return obj;
}
const out: I18nObject = {};
const out: any = {};
Object.keys(obj).forEach((key) => {
out[key] = i18nReducer(obj[key]);
out[key] = deepValueParser(obj[key]);
});
return out;
}

View File

@ -0,0 +1,79 @@
declare enum LANGUAGES {
zh_CN = 'zh_CN',
en_US = 'en_US'
}
export interface I18nRecord {
type?: 'i18n';
[key: string]: string;
/**
* i18n unique key
*/
key?: string;
}
export interface I18nRecordData {
gmtCreate: Date;
gmtModified: Date;
i18nKey: string;
i18nText: I18nRecord;
id: number;
}
export interface II18nUtilConfigs {
items?: {};
/**
*
*/
disableInstantLoad?: boolean;
/**
*
*/
disableFullLoad?: boolean;
loader?: (configs: ILoaderConfigs) => Promise<I18nRecordData[]>;
remover?: (key: string, dic: I18nRecord) => Promise<void>;
saver?: (key: string, dic: I18nRecord) => Promise<void>;
}
export interface ILoaderConfigs {
/**
* search keywords
*/
keyword?: string;
/**
* should load all i18n items
*/
isFull?: boolean;
/**
* search i18n item based on uniqueKey
*/
key?: string;
}
export interface II18nUtil {
init(config: II18nUtilConfigs): void;
isInitialized(): boolean;
isReady(): boolean;
attach(prop: object, value: I18nRecord, updator: () => any);
search(keyword: string, silent?: boolean);
load(configs: ILoaderConfigs): Promise<I18nRecord[]>;
/**
* Get local i18n Record
* @param key
* @param lang
*/
get(key: string, lang: string): string | I18nRecord;
getFromRemote(key: string): Promise<I18nRecord>;
getItem(key: string, forceData?: boolean): any;
getItems(): I18nRecord[];
update(key: string, doc: I18nRecord, lang: LANGUAGES);
create(doc: I18nRecord, lang: LANGUAGES): string;
remove(key: string): Promise<void>;
onReady(func: () => any);
onRowsChange(func: () => any);
onChange(func: (dic: I18nRecord) => any);
}
declare const i18nUtil: II18nUtil;
export default i18nUtil;

View File

@ -0,0 +1,310 @@
import { EventEmitter } from 'events';
import { obx } from '@ali/lowcode-editor-core';
let keybase = Date.now();
function keygen(maps) {
let key;
do {
key = `i18n-${(keybase).toString(36)}`;
keybase += 1;
} while (key in maps);
return key;
}
class DocItem {
constructor(parent, doc, unInitial) {
this.parent = parent;
const { use, ...strings } = doc;
this.doc = obx.val({
type: 'i18n',
...strings,
});
this.emitter = new EventEmitter;
this.inited = unInitial !== true;
}
getKey() {
return this.doc.key;
}
getDoc(lang) {
if (lang) {
return this.doc[lang];
}
return this.doc;
}
setDoc(doc, lang, initial) {
if (lang) {
this.doc[lang] = doc;
} else {
const { use, strings } = doc || {};
Object.assign(this.doc, strings);
}
this.emitter.emit('change', this.doc);
if (initial) {
this.inited = true;
} else if (this.inited) {
this.parent._saveChange(this.doc.key, this.doc);
}
}
remove() {
if (!this.inited) return Promise.reject('not initialized');
const { key, ...doc } = this.doc; // eslint-disable-line
this.emitter.emit('change', doc);
return this.parent.remove(this.getKey());
}
onChange(func) {
this.emitter.on('change', func);
return () => {
this.emitter.removeListener('change', func);
};
}
}
class I18nUtil {
constructor() {
this.emitter = new EventEmitter;
// original data source from remote
this.i18nData = {};
// current i18n records on the left pane
this.items = [];
this.maps = {};
// full list of i18n records for synchronized call
this.fullList = [];
this.fullMap = {};
this.config = {};
this.ready = false;
this.isInited = false;
}
_prepareItems(items, isFull = false, isSilent = false) {
this[isFull ? 'fullList' : 'items'] = items.map((dict) => {
let item = this[isFull ? 'fullMap' : 'maps'][dict.key];
if (item) {
item.setDoc(dict, null, true);
} else {
item = new DocItem(this, dict);
this[isFull ? 'fullMap' : 'maps'][dict.key] = item;
}
return item;
});
if (this.ready && !isSilent) {
this.emitter.emit('rowschange');
this.emitter.emit('change');
} else {
this.ready = true;
this.emitter.emit('ready');
}
}
_load(configs = {}, silent) {
if (!this.config.loader) {
console.error(new Error('Please load loader while init I18nUtil.'));
return Promise.reject();
}
return this.config.loader(configs).then((data) => {
if (configs.i18nKey) {
return Promise.resolve(data.i18nText);
}
this._prepareItems(data.data, configs.isFull, silent);
// set pagination data to i18nData
this.i18nData = data;
if (!silent) {
this.emitter.emit('rowschange');
this.emitter.emit('change');
}
return Promise.resolve(this.items.map(i => i.getDoc()));
});
}
_saveToItems(key, dict) {
let item = null;
item = this.items.find(doc => doc.getKey() === key);
if (!item) {
item = this.fullList.find(doc => doc.getKey() === key);
}
if (item) {
item.setDoc(dict);
} else {
item = new DocItem(this, {
key,
...dict,
});
this.items.unshift(item);
this.fullList.unshift(item);
this.maps[key] = item;
this.fullMap[key] = item;
this._saveChange(key, dict, true);
}
}
_saveChange(key, dict, rowschange) {
if (rowschange) {
this.emitter.emit('rowschange');
}
this.emitter.emit('change');
if (dict === null) {
delete this.maps[key];
delete this.fullMap[key];
}
return this._save(key, dict);
}
_save(key, dict) {
const saver = dict === null ? this.config.remover : this.config.saver;
if (!saver) return Promise.reject('Saver function is not set');
return saver(key, dict);
}
init(config) {
if (this.isInited) return;
this.config = config || {};
if (this.config.items) {
// inject to current page
this._prepareItems(this.config.items);
}
if (!this.config.disableInstantLoad) {
this._load({ isFull: !this.config.disableFullLoad });
}
this.isInited = true;
}
isInitialized() {
return this.isInited;
}
isReady() {
return this.ready;
}
// add events updater when i18n record change
// we should notify engine's view to change
attach(prop, value, updator) {
const isI18nValue = value && value.type === 'i18n' && value.key;
const key = isI18nValue ? value.key : null;
if (prop.i18nLink) {
if (isI18nValue && (key === prop.i18nLink.key)) {
return prop.i18nLink;
}
prop.i18nLink.detach();
}
if (isI18nValue) {
return {
key,
detach: this.getItem(key, value).onChange(updator),
};
}
return null;
}
/**
* 搜索 i18n 词条
*
* @param {any} keyword 搜索关键字
* @param {boolean} [silent=false] 是否刷新左侧的 i18n 数据
* @returns
*
* @memberof I18nUtil
*/
search(keyword, silent = false) {
return this._load({ keyword }, silent);
}
load(configs = {}) {
return this._load(configs);
}
get(key, lang) {
const item = this.getItem(key);
if (item) {
return item.getDoc(lang);
}
return null;
}
getFromRemote(key) {
return this._load({ i18nKey: key });
}
getItem(key, forceData) {
if (forceData && !this.maps[key] && !this.fullList[key]) {
const item = new DocItem(this, {
key,
...forceData,
}, true);
this.maps[key] = item;
this.fullMap[key] = item;
this.fullList.push(item);
this.items.push(item);
}
return this.maps[key] || this.fullMap[key];
}
getItems() {
return this.items;
}
update(key, doc, lang) {
let dict = this.get(key) || {};
if (!lang) {
dict = doc;
} else {
dict[lang] = doc;
}
this._saveToItems(key, dict);
}
create(doc, lang) {
const dict = lang ? { [lang]: doc } : doc;
const key = keygen(this.fullMap);
this._saveToItems(key, dict);
return key;
}
remove(key) {
const index = this.items.findIndex(item => item.getKey() === key);
const indexG = this.fullList.findIndex(item => item.getKey() === key);
if (index > -1) {
this.items.splice(index, 1);
}
if (indexG > -1) {
this.fullList.splice(index, 1);
}
return this._saveChange(key, null, true);
}
onReady(func) {
this.emitter.on('ready', func);
return () => {
this.emitter.removeListener('ready', func);
};
}
onRowsChange(func) {
this.emitter.on('rowschange', func);
return () => {
this.emitter.removeListener('rowschange', func);
};
}
onChange(func) {
this.emitter.on('change', func);
return () => {
this.emitter.removeListener('change', func);
};
}
}
export default new I18nUtil();

View File

@ -3,7 +3,7 @@ import Popup from '@ali/ve-popups';
import Icons from '@ali/ve-icons';
import logger from '@ali/vu-logger';
import { render } from 'react-dom';
import I18nUtil from '@ali/ve-i18n-util';
import I18nUtil from './i18n-util';
import { hotkey as Hotkey } from '@ali/lowcode-editor-core';
import { createElement } from 'react';
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS, VERSION as Version } from './base/const';