commit
efd914a426
|
@ -13,7 +13,7 @@
|
|||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
const {preset} = require("./jest.config");
|
||||
const { preset } = require('./jest.config');
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
|
@ -23,19 +23,15 @@ module.exports = {
|
|||
browsers: ['> 1%', 'last 2 versions', 'not ie <= 8'],
|
||||
node: 'current',
|
||||
},
|
||||
useBuiltIns: 'usage',
|
||||
corejs: 3,
|
||||
},
|
||||
],
|
||||
['@babel/preset-typescript'],
|
||||
[
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
[
|
||||
"@babel/preset-react",
|
||||
'@babel/preset-react',
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"importSource": "openinula"
|
||||
}
|
||||
]
|
||||
runtime: 'automatic',
|
||||
importSource: 'openinula',
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import dts from 'rollup-plugin-dts';
|
||||
|
||||
function deleteFolder(filePath) {
|
||||
if (fs.existsSync(filePath)) {
|
||||
if (fs.lstatSync(filePath).isDirectory()) {
|
||||
const files = fs.readdirSync(filePath);
|
||||
files.forEach(file => {
|
||||
const nectFilePath = path.join(filePath, file);
|
||||
const states = fs.lstatSync(nectFilePath);
|
||||
if (states.isDirectory()) {
|
||||
deleteFolder(nectFilePath);
|
||||
} else {
|
||||
fs.unlinkSync(nectFilePath);
|
||||
}
|
||||
});
|
||||
fs.rmdirSync(filePath);
|
||||
} else if (fs.lstatSync(filePath).isFile()) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param folders {string[]}
|
||||
* @returns {{buildEnd(): void, name: string}}
|
||||
*/
|
||||
export function cleanUp(folders) {
|
||||
return {
|
||||
name: 'clean-up',
|
||||
buildEnd() {
|
||||
folders.forEach(folder => deleteFolder(folder));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function builderTypeConfig() {
|
||||
return {
|
||||
input: './build/@types/index.d.ts',
|
||||
output: {
|
||||
file: './build/@types/index.d.ts',
|
||||
format: 'es',
|
||||
},
|
||||
plugins: [dts(), cleanUp(['./build/@types/example', './build/@types/src'])],
|
||||
};
|
||||
}
|
||||
|
||||
export default [builderTypeConfig()];
|
|
@ -39,7 +39,7 @@ const App = () => {
|
|||
<div className='container'>
|
||||
<Example1/>
|
||||
<Example2/>
|
||||
<Example3/>
|
||||
<Example3 locale={locale} setLocale={setLocale}/>
|
||||
</div>
|
||||
<div className='container'>
|
||||
<Example4 locale={locale} messages={message}/>
|
||||
|
|
|
@ -17,9 +17,7 @@ import App from './App'
|
|||
|
||||
function render() {
|
||||
Inula.render(
|
||||
<>
|
||||
<App/>
|
||||
</>,
|
||||
<App/>,
|
||||
document.querySelector('#root') as any
|
||||
)
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ export default {
|
|||
IntlProvider: I18nProvider,
|
||||
injectIntl: injectIntl,
|
||||
RawIntlProvider: InjectProvider,
|
||||
}
|
||||
};
|
||||
|
||||
// 用于定义文本
|
||||
export function defineMessages<K extends keyof any, T = MessageDescriptor, U = Record<K, T>>(msgs: U): U {
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
"description": "",
|
||||
"main": "build/intl.umd.js",
|
||||
"type": "commonjs",
|
||||
"types": "build/index.d.ts",
|
||||
"types": "build/@types/index.d.ts",
|
||||
"scripts": {
|
||||
"demo-serve": "webpack serve --mode=development",
|
||||
"build": "rollup --config rollup.config.js",
|
||||
"rollup-build": "rollup --config rollup.config.js && npm run build-types",
|
||||
"build-types": "tsc -p tsconfig.json && rollup -c build-type.js",
|
||||
"test": "jest --config jest.config.js",
|
||||
"test-c": "jest --coverage"
|
||||
},
|
||||
|
@ -33,6 +34,7 @@
|
|||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-node-resolve": "^7.1.3",
|
||||
"@rollup/plugin-typescript": "^11.0.0",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/node": "^16.18.27",
|
||||
|
@ -40,7 +42,6 @@
|
|||
"babel": "^6.23.0",
|
||||
"babel-jest": "^29.5.0",
|
||||
"babel-loader": "^9.1.2",
|
||||
"core-js": "3.31.0",
|
||||
"html-webpack-plugin": "^5.5.1",
|
||||
"jest": "29.3.1",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
|
@ -56,8 +57,6 @@
|
|||
"typescript": "4.9.3",
|
||||
"webpack": "^5.81.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.13.3",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"webpack-dev-server": "^4.13.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,51 +14,45 @@
|
|||
*/
|
||||
|
||||
import ruleUtils from '../utils/parseRuleUtils';
|
||||
import { LexerInterface } from "../types/interfaces";
|
||||
|
||||
const getMatch = ruleUtils.checkSticky()
|
||||
? // 正则表达式具有 sticky 标志
|
||||
(regexp, buffer) => regexp.exec(buffer)
|
||||
: // 正则表达式具有 global 标志,匹配的字符串长度为 0,则表示匹配失败
|
||||
(regexp, buffer) => (regexp.exec(buffer)[0].length === 0 ? null : regexp.exec(buffer));
|
||||
import { LexerInterface } from '../types/interfaces';
|
||||
|
||||
class Lexer<T> implements LexerInterface<T> {
|
||||
readonly startState: string;
|
||||
readonly states: Record<string, any>;
|
||||
private buffer: string = '';
|
||||
private stack: string[] = [];
|
||||
private index;
|
||||
private line;
|
||||
private col;
|
||||
private queuedText;
|
||||
private state;
|
||||
private groups;
|
||||
private error;
|
||||
private index: number = 0;
|
||||
private line: number = 1;
|
||||
private col: number = 1;
|
||||
private queuedText: string = '';
|
||||
private state: string = '';
|
||||
private groups: string[] = [];
|
||||
private error: Record<string, any> | undefined;
|
||||
private regexp;
|
||||
private fast;
|
||||
private queuedGroup;
|
||||
private value;
|
||||
private fast: object = {};
|
||||
private queuedGroup: string | null = '';
|
||||
private value: string = '';
|
||||
|
||||
constructor(states, state) {
|
||||
this.startState = state;
|
||||
this.states = states;
|
||||
constructor(unionReg: Record<string, any>, startState: string) {
|
||||
this.startState = startState;
|
||||
this.states = unionReg;
|
||||
this.buffer = '';
|
||||
this.stack = [];
|
||||
this.reset();
|
||||
}
|
||||
|
||||
public reset(data?, info?) {
|
||||
public reset(data?: string) {
|
||||
this.buffer = data || '';
|
||||
this.index = 0;
|
||||
this.line = info ? info.line : 1;
|
||||
this.col = info ? info.col : 1;
|
||||
this.queuedText = info ? info.queuedText : '';
|
||||
this.setState(info ? info.state : this.startState);
|
||||
this.stack = info && info.stack ? info.stack.slice() : [];
|
||||
this.line = 1;
|
||||
this.col = 1;
|
||||
this.queuedText = '';
|
||||
this.setState(this.startState);
|
||||
this.stack = [];
|
||||
return this;
|
||||
}
|
||||
|
||||
private setState(state) {
|
||||
private setState(state: string) {
|
||||
if (!state || this.state === state) {
|
||||
return;
|
||||
}
|
||||
|
@ -71,15 +65,15 @@ class Lexer<T> implements LexerInterface<T> {
|
|||
}
|
||||
|
||||
private popState() {
|
||||
this.setState(this.stack.pop());
|
||||
this.setState(<string>this.stack.pop());
|
||||
}
|
||||
|
||||
private pushState(state) {
|
||||
private pushState(state: string) {
|
||||
this.stack.push(this.state);
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
private getGroup(match) {
|
||||
private getGroup(match: Record<string, any>) {
|
||||
const groupCount = this.groups.length;
|
||||
for (let i = 0; i < groupCount; i++) {
|
||||
if (match[i + 1] !== undefined) {
|
||||
|
@ -127,7 +121,7 @@ class Lexer<T> implements LexerInterface<T> {
|
|||
const group = this.getGroup(match);
|
||||
const text = match[0];
|
||||
|
||||
if (error.fallback && match.index !== index) {
|
||||
if (error?.fallback && match.index !== index) {
|
||||
this.queuedGroup = group;
|
||||
this.queuedText = text;
|
||||
return this.getToken(error, buffer.slice(index, match.index), index);
|
||||
|
@ -136,7 +130,14 @@ class Lexer<T> implements LexerInterface<T> {
|
|||
return this.getToken(group, text, index);
|
||||
}
|
||||
|
||||
private getToken(group, text, offset) {
|
||||
/**
|
||||
* 獲取Token
|
||||
* @param group 解析模板后獲得的屬性值
|
||||
* @param text 文本屬性的信息
|
||||
* @param offset 偏移量
|
||||
* @private
|
||||
*/
|
||||
private getToken(group: any, text: string, offset: number) {
|
||||
let lineNum = 0;
|
||||
let last = 1; // 最后一个换行符的索引位置
|
||||
if (group.lineBreaks) {
|
||||
|
@ -192,9 +193,14 @@ class Lexer<T> implements LexerInterface<T> {
|
|||
next: (): IteratorResult<T> => {
|
||||
const token = this.next();
|
||||
return { value: token, done: !token } as IteratorResult<T>;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const getMatch = ruleUtils.checkSticky()
|
||||
? // 正则表达式具有 sticky 标志
|
||||
(regexp, buffer) => regexp.exec(buffer)
|
||||
: // 正则表达式具有 global 标志,匹配的字符串长度为 0,则表示匹配失败
|
||||
(regexp, buffer) => (regexp.exec(buffer)[0].length === 0 ? null : regexp.exec(buffer));
|
||||
export default Lexer;
|
||||
|
|
|
@ -70,5 +70,5 @@ const select: Record<string, any> = {
|
|||
export const mappingRule: Record<string, any> = {
|
||||
body,
|
||||
arg,
|
||||
select
|
||||
select,
|
||||
};
|
||||
|
|
|
@ -16,16 +16,16 @@
|
|||
import Lexer from './Lexer';
|
||||
import { mappingRule } from './mappingRule';
|
||||
import ruleUtils from '../utils/parseRuleUtils';
|
||||
import { RawToken } from "../types/types";
|
||||
import { RawToken } from '../types/types';
|
||||
|
||||
const defaultErrorRule = ruleUtils.getRuleOptions('error', { lineBreaks: true, shouldThrow: true });
|
||||
|
||||
// 解析规则并生成词法分析器所需的数据结构,以便进行词法分析操作
|
||||
function parseRules(rules: Record<string, any>, hasStates: boolean): Record<string, any> {
|
||||
let errorRule: Record<string, any> | null = null;
|
||||
const fast = {};
|
||||
let enableFast = true;
|
||||
let unicodeFlag = null;
|
||||
const fast: object = {};
|
||||
let enableFast: boolean = true;
|
||||
let unicodeFlag: boolean | null = null;
|
||||
const groups: Record<string, any>[] = [];
|
||||
const parts: string[] = [];
|
||||
|
||||
|
@ -108,11 +108,11 @@ export function checkStateGroup(group: Record<string, any>, name: string, map: R
|
|||
}
|
||||
|
||||
// 将国际化解析规则注入分词器中
|
||||
function parseMappingRule(mappingRule: Record<string, any>, start?: string): Lexer<RawToken> {
|
||||
function parseMappingRule(mappingRule: Record<string, any>, startState?: string): Lexer<RawToken> {
|
||||
const keys = Object.getOwnPropertyNames(mappingRule);
|
||||
|
||||
if (!start) {
|
||||
start = keys[0];
|
||||
if (!startState) {
|
||||
startState = keys[0];
|
||||
}
|
||||
|
||||
// 将每个状态的规则解析为规则数组,并存储在 ruleMap 对象中
|
||||
|
@ -153,27 +153,27 @@ function parseMappingRule(mappingRule: Record<string, any>, start?: string): Lex
|
|||
}
|
||||
}
|
||||
|
||||
const map = {};
|
||||
const mappingAllRules = {};
|
||||
|
||||
// 将规则映射为词法分析器数据结构,并存储在 map 对象中
|
||||
// 将规则映射为词法分析器数据结构,并存储在 mappingAllRules 对象中
|
||||
keys.forEach(key => {
|
||||
map[key] = parseRules(ruleMap[key], true);
|
||||
mappingAllRules[key] = parseRules(ruleMap[key], true);
|
||||
});
|
||||
|
||||
// 检查状态组中的规则是否正确引用了其他状态
|
||||
keys.forEach(name => {
|
||||
const state = map[name];
|
||||
const state = mappingAllRules[name];
|
||||
const groups = state.groups;
|
||||
groups.forEach(group => {
|
||||
checkStateGroup(group, name, map);
|
||||
checkStateGroup(group, name, mappingAllRules);
|
||||
});
|
||||
const fastKeys = Object.getOwnPropertyNames(state.fast);
|
||||
fastKeys.forEach(fastKey => {
|
||||
checkStateGroup(state.fast[fastKey], name, map);
|
||||
checkStateGroup(state.fast[fastKey], name, mappingAllRules);
|
||||
});
|
||||
});
|
||||
|
||||
return new Lexer(map, start);
|
||||
return new Lexer(mappingAllRules, startState);
|
||||
}
|
||||
|
||||
function processFast(match, fast: {}, options) {
|
||||
|
|
|
@ -17,7 +17,7 @@ import { lexer } from './parseMappingRule';
|
|||
import { RawToken, Token } from '../types/types';
|
||||
import { DEFAULT_PLURAL_KEYS } from '../constants';
|
||||
import { Content, FunctionArg, PlainArg, Select, TokenContext } from '../types/interfaces';
|
||||
import Lexer from "./Lexer";
|
||||
import Lexer from './Lexer';
|
||||
|
||||
const getContext = (lt: Record<string, any>): TokenContext => ({
|
||||
offset: lt.offset,
|
||||
|
@ -29,15 +29,14 @@ const getContext = (lt: Record<string, any>): TokenContext => ({
|
|||
|
||||
export const checkSelectType = (value: string): boolean => {
|
||||
return value === 'plural' || value === 'select' || value === 'selectordinal';
|
||||
}
|
||||
};
|
||||
|
||||
class Parser {
|
||||
lexer: Lexer<RawToken>;
|
||||
cardinalKeys: string[] = DEFAULT_PLURAL_KEYS;
|
||||
ordinalKeys: string[] = DEFAULT_PLURAL_KEYS;
|
||||
|
||||
constructor(message: string) {
|
||||
this.lexer = lexer.reset(message);
|
||||
lexer.reset(message);
|
||||
}
|
||||
|
||||
isSelectKeyValid(token: RawToken, type: Select['type'], value: string) {
|
||||
|
@ -60,7 +59,7 @@ class Parser {
|
|||
isPlural = true;
|
||||
}
|
||||
|
||||
for (const token of this.lexer) {
|
||||
for (const token of lexer) {
|
||||
switch (token.type) {
|
||||
case 'offset': {
|
||||
if (type === 'select') {
|
||||
|
@ -97,7 +96,7 @@ class Parser {
|
|||
|
||||
parseToken(token: RawToken, isPlural: boolean): PlainArg | FunctionArg | Select {
|
||||
const context = getContext(token);
|
||||
const nextToken = this.lexer.next();
|
||||
const nextToken = lexer.next();
|
||||
|
||||
if (!nextToken) {
|
||||
throw new Error('The message end position is invalid.');
|
||||
|
@ -111,7 +110,7 @@ class Parser {
|
|||
return { type: 'argument', arg: token.value, ctx: context };
|
||||
}
|
||||
case 'func-simple': {
|
||||
const end = this.lexer.next();
|
||||
const end = lexer.next();
|
||||
if (!end) {
|
||||
throw new Error('The message end position is invalid.');
|
||||
}
|
||||
|
@ -159,7 +158,7 @@ class Parser {
|
|||
const tokens: any[] = [];
|
||||
let content: string | Content | null = null;
|
||||
|
||||
for (const token of this.lexer) {
|
||||
for (const token of lexer) {
|
||||
if (token.type === 'argument') {
|
||||
if (content) {
|
||||
content = null;
|
||||
|
@ -175,7 +174,7 @@ class Parser {
|
|||
} else if (token.type === 'doubleapos') {
|
||||
tokens.push(token.value);
|
||||
} else if (token.type === 'quoted') {
|
||||
tokens.push(token.value)
|
||||
tokens.push(token.value);
|
||||
} else if (token.type === 'content') {
|
||||
tokens.push(token.value);
|
||||
} else {
|
||||
|
|
|
@ -21,9 +21,10 @@ import {
|
|||
Select,
|
||||
FunctionArg,
|
||||
I18nContextProps,
|
||||
configProps
|
||||
configProps,
|
||||
InjectedIntl,
|
||||
} from './interfaces';
|
||||
import I18n from "../core/I18n";
|
||||
import I18n from '../core/I18n';
|
||||
|
||||
export type Error = string | ((message, id, context) => string);
|
||||
|
||||
|
@ -71,11 +72,15 @@ export type RawToken = {
|
|||
col: number;
|
||||
};
|
||||
|
||||
export type I18nProviderProps = I18nContextProps & configProps
|
||||
export type I18nProviderProps = I18nContextProps & configProps;
|
||||
|
||||
export type IntlType = {
|
||||
i18n: I18n;
|
||||
formatMessage: Function,
|
||||
formatNumber: Function,
|
||||
formatDate: Function,
|
||||
formatMessage: Function;
|
||||
formatNumber: Function;
|
||||
formatDate: Function;
|
||||
};
|
||||
|
||||
export interface InjectedIntlProps {
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
"declaration": true,
|
||||
"experimentalDecorators": true,
|
||||
"downlevelIteration": true,
|
||||
"declarationDir": "./build/@types",
|
||||
// 赋值为空数组使@types/node不会起作用
|
||||
"lib": [
|
||||
"dom",
|
||||
|
@ -53,19 +54,22 @@
|
|||
}
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*",
|
||||
"./src/format/**/*.ts",
|
||||
"./example/**/*"
|
||||
"./index.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib",
|
||||
"**/*.spec.ts",
|
||||
"dev"
|
||||
"dev",
|
||||
"./example/**/*",
|
||||
"./tsconfig.json"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
"jest",
|
||||
"@testing-library/jest-dom"
|
||||
],
|
||||
"files": [
|
||||
"./index.ts"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue