Match-id-bfcb7929ddfbd97f026ce9f735b5dce734781e26

This commit is contained in:
* 2023-09-04 16:03:24 +08:00
parent e143aeba6d
commit 3d2b561616
219 changed files with 0 additions and 14943 deletions

View File

@ -1,74 +0,0 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
root: true,
plugins: ['jest', 'no-for-of-loops', 'no-function-declare-after-return', 'react', '@typescript-eslint'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 8,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
modules: true,
experimentalObjectRestSpread: true,
},
},
env: {
browser: true,
jest: true,
node: true,
es6: true,
},
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-empty-function': 'off',
semi: ['warn', 'always'],
quotes: ['warn', 'single'],
'accessor-pairs': 'off',
'brace-style': ['error', '1tbs'],
'func-style': ['warn', 'declaration', { allowArrowFunctions: true }],
'max-lines-per-function': 'off',
'object-curly-newline': 'off',
// 尾随逗号
'comma-dangle': ['error', 'only-multiline'],
'no-constant-condition': 'off',
'no-for-of-loops/no-for-of-loops': 'error',
'no-function-declare-after-return/no-function-declare-after-return': 'error',
},
globals: {
isDev: true,
isTest: true,
},
overrides: [
{
files: ['scripts/tests/**/*.js'],
globals: {
container: true,
},
},
],
};

View File

@ -1,15 +0,0 @@
module.exports = {
printWidth: 120, // 一行120字符数如果超过会进行换行
tabWidth: 2, // tab等2个空格
useTabs: false, // 用空格缩进行
semi: true, // 行尾使用分号
singleQuote: true, // 字符串使用单引号
quoteProps: 'as-needed', // 仅在需要时在对象属性添加引号
jsxSingleQuote: false, // 在JSX中使用双引号
trailingComma: 'es5', // 使用尾逗号(对象、数组等)
bracketSpacing: true, // 对象的括号间增加空格
bracketSameLine: false, // 将多行JSX元素的>放在最后一行的末尾
arrowParens: 'avoid', // 在唯一的arrow函数参数周围省略括号
vueIndentScriptAndStyle: false, // 不缩进Vue文件中的<script>和<style>标记内的代码
endOfLine: 'lf', // 仅限换行(\n
};

View File

@ -1,3 +0,0 @@
# Inula-Intl
Inula-intl 是 inula 提供的生态组件,主要提供了国际化功能,涵盖了基本的国际化组件和钩子函数,便于用户构建具备国际化能力的前端界面。在 Inula-intl 中使用国际化时,无论是组件或者 Hooks其目的就是获取当前应用程序的国际化实例该实例提供了处理多语言文本、日期、时间等功能。

View File

@ -1,26 +0,0 @@
const {preset} = require("./jest.config");
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
browsers: ['> 1%', 'last 2 versions', 'not ie <= 8'],
node: 'current',
},
useBuiltIns: 'usage',
corejs: 3,
},
],
[
'@babel/preset-typescript',
],
[
"@babel/preset-react",
{
"runtime": "automatic",
"importSource": "inulajs"
}
]
],
};

View File

@ -1,45 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Inula, { useState } from 'inulajs';
import { IntlProvider } from "../index";
import zh from "./locale/zh";
import en from "./locale/en";
import Example1 from "./components/Example1";
import Example2 from "./components/Example2";
import Example3 from "./components/Example3";
import Example4 from "./components/Example4";
import Example5 from "./components/Example5";
import Example6 from "./components/Example6";
const App = () => {
const [locale, setLocale] = useState('zh');
const handleChange = () => {
locale === 'zh' ? setLocale('en') : setLocale('zh');
};
const message = locale === 'zh' ? zh : en
return (
<IntlProvider locale={locale} messages={locale === 'zh' ? zh : en}>
<header>Inula-Intl API Test Demo</header>
<div className='container'>
<Example1/>
<Example2/>
<Example3/>
</div>
<div className='container'>
<Example4 locale={locale} messages={message}/>
<Example5/>
<Example6 locale={{ locale }} messages={message}/>
</div>
<div className='button'>
<button onClick={handleChange}></button>
</div>
</IntlProvider>
);
}
export default App

View File

@ -1,19 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Inula from "inulajs";
import { useIntl } from "../../index";
const Example1 = () => {
const { i18n } = useIntl();
return (
<div className="card">
<h2>useIntl方式测试Demo</h2>
<pre>{i18n.formatMessage({ id: 'text1' })}</pre>
</div>
);
};
export default Example1;

View File

@ -1,19 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Inula from "inulajs";
import { FormattedMessage } from "../../index";
const Example2= () => {
return (
<div className="card">
<h2>FormattedMessage方式测试Demo</h2>
<pre>
<FormattedMessage id='text2'/>
</pre>
</div>
);
};
export default Example2;

View File

@ -1,25 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Inula from 'inulajs';
import { FormattedMessage } from "../../index";
const Example3 = (props) => {
const { locale, setLocale } = props;
return (
<div className="card">
<h2>FormattedMessage方式测试Demo</h2>
<pre>
<button className="testButton" onClick={() => {
setLocale(locale === 'zh' ? 'en' : 'zh')
}}>
<FormattedMessage id={'button'}/>
</button>
<br/>
</pre>
</div>
);
}
export default Example3;

View File

@ -1,21 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Inula from "inulajs";
import { createIntl } from "../../index";
const Example4 = (props) => {
// 受渲染时机影响createIntl方式需控制时序否则慢一拍
const intl = createIntl({ ...props });
const msg = intl.formatMessage({ id: 'text3' });
return (
<div className="card">
<h2>createIntl方式测试Demo</h2>
<pre>{msg}</pre>
</div>
);
};
export default Example4;

View File

@ -1,24 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Inula, { Component } from 'inulajs';
import { injectIntl } from '../../index';
class Example5 extends Component<any, any, any> {
public constructor(props: any, context) {
super(props, context);
}
render() {
const { intl } = this.props as any;
return (
<div className="card">
<h2>injectIntl方式测试Demo</h2>
<pre>{intl.formatMessage({ id: 'text4' })}</pre>
</div>
);
}
}
export default injectIntl(Example5);

View File

@ -1,26 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Inula from "inulajs";
import { createIntl, createIntlCache, RawIntlProvider } from "../../index";
import Example6Child from "./Example6Child";
const Example6 = (props: any) => {
const { locale, messages } = props;
const cache = createIntlCache();
let i18n = createIntl(
{ locale: locale, messages: messages },
cache
);
return (
<RawIntlProvider value={i18n}>
<Example6Child/>
</RawIntlProvider>
);
}
export default Example6;

View File

@ -1,19 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { useIntl } from "../../index";
const Example6Child = (props: any) => {
const {formatMessage} = useIntl();
return (
<div className="card">
<h2>RawIntlProvider方式测试Demo</h2>
<pre>{formatMessage({ id: 'text4' })}</pre>
</div>
);
}
export default Example6Child;

View File

@ -1,102 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Inula-Intl API Test</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f8f8;
margin: 0;
padding: 0;
}
header {
display: flex;
justify-content: center;
align-items: center;
height: 80px;
background-color: #007bff;
color: #fff;
font-size: 24px;
font-weight: bold;
}
h1 {
text-align: center;
margin-top: 50px;
color: #2c3e50;
font-size: 36px;
}
.container {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin-bottom: 50px;
}
.card {
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 5px #aaa;
width: 400px;
padding: 20px;
margin: 20px;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 0 15px #aaa;
transform: translateY(-5px);
}
.card h2 {
margin-bottom: 10px;
font-size: 24px;
color: #2c3e50;
}
.card pre {
background-color: #f0f0f0;
padding: 10px;
font-size: 14px;
border-radius: 5px;
white-space: pre-wrap;
word-wrap: break-word;
}
.button {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
}
.testButton {
width: 350px;
}
button {
font-family: Arial, sans-serif;
font-size: 16px;
font-weight: bold;
color: #fff;
background-color: #007bff;
border: none;
border-radius: 4px;
padding: 10px 20px;
cursor: pointer;
transition: background-color 0.3s ease-in-out;
}
button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@ -1,15 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import * as Inula from 'inulajs';
import App from './App'
function render() {
Inula.render(
<>
<App/>
</>,
document.querySelector('#root') as any
)
}
render();

View File

@ -1,11 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
export default {
button: 'Welcome to the Horizon-Intl component!',
text1: 'Welcome to the Horizon-Intl component!',
text2: 'Welcome to the Horizon-Intl component!',
text3: 'Welcome to the Horizon-Intl component!',
text4: 'Welcome to the Horizon-Intl component!',
}

View File

@ -1,10 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
export default {
button: '欢迎使用国际化组件!',
text1: '欢迎使用国际化组件!',
text2: '欢迎使用国际化组件!',
text3: '欢迎使用国际化组件!',
text4: '欢迎使用国际化组件!',
};

View File

@ -1,45 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import DateTimeFormatter from './src/format/fomatters/DateTimeFormatter';
import NumberFormatter from './src/format/fomatters/NumberFormatter';
import I18n from './src/core/I18n';
import createI18nCache from './src/format/cache/cache';
import FormattedMessage from './src/core/components/FormattedMessage';
import I18nProvider from './src/core/components/I18nProvider';
import injectIntl, { I18nContext, InjectProvider } from './src/core/components/InjectI18n';
import useI18n from './src/core/hook/useI18n';
import createI18n from './src/core/createI18n';
import { InjectedIntl, MessageDescriptor } from './src/types/interfaces';
// 函数API
export {
I18n,
createI18nCache as createIntlCache,
createI18n as createIntl,
DateTimeFormatter,
NumberFormatter,
useI18n as useIntl,
};
// 组件
export {
FormattedMessage,
I18nContext,
I18nProvider as IntlProvider,
injectIntl as injectIntl,
InjectProvider as RawIntlProvider,
};
// 用于定义文本
export function defineMessages<K extends keyof any, T = MessageDescriptor, U = Record<K, T>>(msgs: U): U {
return msgs;
}
export function defineMessage<T>(msg: T): T {
return msg;
}
export interface InjectedIntlProps {
intl: InjectedIntl;
}

View File

@ -1,24 +0,0 @@
module.exports = {
coverageDirectory: 'coverage',
resetModules: true,
preset: 'ts-jest/presets/js-with-ts',
rootDir: process.cwd(),
testMatch: [
'<rootDir>/tests/**/*.test.[jt]s?(x)'
],
moduleFileExtensions: ['ts', 'js', 'jsx', 'tsx'],
moduleDirectories: ['node_modules', 'src'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/tests/$1',
},
transform: {
"^.+\\.(ts|js|jsx|tsx)$": "babel-jest"
},
globals: {
"ts-jest": {
"tsconfig": "tsconfig.json"
}
},
testEnvironment: 'jsdom',
};

View File

@ -1,63 +0,0 @@
{
"name": "Inula-intl",
"version": "0.0.1",
"description": "",
"main": "build/intl.umd.js",
"type": "commonjs",
"types": "build/index.d.ts",
"scripts": {
"demo-serve": "webpack serve --mode=development",
"rollup-build": "rollup --config rollup.config.js",
"test": "jest --config jest.config.js",
"test-c": "jest --coverage"
},
"repository": {
"type": "git",
"url": "https://szv-open.codehub.huawei.com/innersource/fenghuang/horizon/horizon-core.git"
},
"files": [
"build",
"example"
],
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"inulajs": "^0.0.11"
},
"devDependencies": {
"@babel/core": "7.21.3",
"@babel/preset-env": "^7.16.7",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "7.16.7",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-node-resolve": "^7.1.3",
"@rollup/plugin-typescript": "^11.0.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@types/node": "^16.18.27",
"@types/react": "18.0.25",
"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",
"jsdom": "^21.1.1",
"prettier": "^2.8.7",
"rollup": "^2.0.0",
"rollup-plugin-livereload": "^2.0.5",
"rollup-plugin-serve": "^1.1.0",
"rollup-plugin-terser": "^5.3.0",
"tslib": "^2.6.1",
"ts-jest": "29.0.3",
"ts-node": "10.9.1",
"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"
}
}

View File

@ -1,51 +0,0 @@
import path from 'path';
import { fileURLToPath } from 'url';
import babel from '@rollup/plugin-babel';
import nodeResolve from '@rollup/plugin-node-resolve';
import typescript from "@rollup/plugin-typescript";
import { terser } from 'rollup-plugin-terser';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const entry = path.join(__dirname, '/index.ts');
const output = path.join(__dirname, '/build');
const extensions = ['.js', '.ts', '.tsx'];
export default {
input: entry,
output: [
{
file: path.resolve(output, 'intl.umd.js'),
sourcemap: 'inline',
name: 'I18n',
format: 'umd',
},
],
plugins: [
nodeResolve({
extensions,
modulesOnly: true,
}),
babel({
exclude: 'node_modules/**',
configFile: path.join(__dirname, '/babel.config.js'),
extensions,
}),
typescript(
{
tsconfig: 'tsconfig.json',
include: ['./**/*.ts', './**/*.tsx'],
}
),
terser(),
],
external:[
"inulajs",
"react",
"react-dom"
]
};

View File

@ -1,68 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
/**
* \\(?:u\{[a-fA-F0-9]+}) \u{0020} Unicode
* \\x[a-fA-F0-9]{2} \x0A
* [nrtf'"] 匹配常见的转义字符:\n换行符、\r回车符、\t制表符、\f换页符、\' \"
*/
export const UNICODE_REG = /\\(?:u\{[a-fA-F0-9]+}|x[a-fA-F0-9]{2}|[nrtf'"])/g;
// Horizon需要被保留静态常量
export const HORIZON_STATICS = {
childContextTypes: true,
contextType: true,
contextTypes: true,
defaultProps: true,
displayName: true,
getDefaultProps: true,
getDerivedStateFromError: true,
getDerivedStateFromProps: true,
mixins: true,
propTypes: true,
type: true,
};
// JavaScript 需要被保留原生静态属性
export const NATIVE_STATICS = {
name: true,
length: true,
prototype: true,
caller: true,
callee: true,
arguments: true,
arity: true,
};
// Horizon ForwardRef 组件的静态属性需要被保留
export const HORIZON_FORWARD_REF_STATICS = {
vtype: true,
render: true,
defaultProps: true,
key: true,
type: true,
};
// React ForwardRef 组件的静态属性需要被保留
export const REACT_FORWARD_REF_STATICS = {
$$typeof: true, // horizon 'vtype': true
render: true, // render
defaultProps: true, // props
displayName: true,
propTypes: true, // type: type,
};
export const FORWARD_REF_STATICS = {...HORIZON_FORWARD_REF_STATICS, ...REACT_FORWARD_REF_STATICS};
// Horizon Memo 组件的静态属性需要被保留
export const HORIZON_MEMO_STATICS = {
vtype: true, // horizon 'vtype': true
compare: true,
defaultProps: true,
type: true,
};
// 默认复数规则
export const DEFAULT_PLURAL_KEYS = ['zero', 'one', 'two', 'few', 'many', 'other'];

View File

@ -1,127 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import EventDispatcher from '../utils/eventListener';
import DateTimeFormatter from "../format/fomatters/DateTimeFormatter";
import NumberFormatter from "../format/fomatters/NumberFormatter";
import { getFormatMessage } from '../format/getFormatMessage';
import { I18nProps, MessageDescriptor, MessageOptions } from '../types/interfaces';
import { Locale, Locales, Messages, AllLocaleConfig, AllMessages, LocaleConfig, Error, Events } from '../types/types';
export class I18n extends EventDispatcher<Events> {
public locale: Locale;
public locales: Locales;
private readonly _localeConfig: AllLocaleConfig;
private readonly allMessages: AllMessages;
public readonly error?: Error;
public readonly useMemorize?: boolean;
constructor(props: I18nProps) {
super();
this.locale = 'en';
this.locales = this.locale || '';
this.allMessages = {};
this._localeConfig = {};
this.error = props.error;
this.loadMessage(props.messages);
if (props.localeConfig) {
this.loadLocaleConfig(props.localeConfig);
}
if (props.locale || props.locales) {
this.changeLanguage(props.locale!, props.locales);
}
this.formatMessage = this.formatMessage.bind(this);
this.formatDate = this.formatDate.bind(this);
this.formatNumber = this.formatNumber.bind(this);
}
get messages(): string | Messages | AllMessages {
if (this.locale in this.allMessages) {
return this.allMessages[this.locale] ?? {};
} else {
return this.allMessages ?? {};
}
}
get localeConfig(): LocaleConfig {
return this._localeConfig[this.locale] ?? {};
}
setLocaleConfig(locale: Locale, localeData: LocaleConfig) {
if (this._localeConfig[locale]) {
Object.assign(this._localeConfig, localeData);
} else {
this._localeConfig[locale] = localeData;
}
}
// 将热语言环境的本地化数据加载
loadLocaleConfig(localeOrAllData: Locale | AllLocaleConfig, localeConfig?: LocaleConfig) {
if (localeConfig) {
this.setLocaleConfig(localeOrAllData as Locale, localeConfig);
} else {
Object.keys(localeOrAllData).forEach(locale => {
this.setLocaleConfig(locale, localeOrAllData[locale]);
});
}
this.emit('change');
}
setMessage(locale: Locale, messages: Messages) {
if (this.allMessages[locale]) {
Object.assign(this.allMessages[locale], messages);
} else {
this.allMessages[locale] = messages;
}
}
// 加载messages
loadMessage(localeOrMessages: Locale | AllMessages | undefined, messages?: Messages) {
if (messages) {
//当 message 为空的时候加载单一的message信息
this.setMessage(localeOrMessages as string, messages);
} else {
// 加载多对locale-message信息
localeOrMessages &&
Object.keys(localeOrMessages!).forEach(locale => this.setMessage(locale, localeOrMessages![locale]));
}
this.emit('change');
}
// 改变当前的语言环境
changeLanguage(locale: Locale, locales?: Locales) {
this.locale = locale;
if (locales) {
this.locales = locales;
}
this.emit('change');
}
formatMessage(
id: MessageDescriptor | string,
values: Object | undefined = {},
{ message, context, formatOptions, useMemorize}: MessageOptions = {},
) {
return getFormatMessage(this, id, values, { message, context, formatOptions, useMemorize});
}
formatDate(value: string | Date, formatOptions?: Intl.DateTimeFormatOptions): string {
const dateTimeFormatter = new DateTimeFormatter(this.locale || this.locales, formatOptions, this.useMemorize);
return dateTimeFormatter.dateTimeFormat(value);
}
formatNumber(value: number, formatOptions?: Intl.NumberFormatOptions): string {
const numberFormatter = new NumberFormatter(this.locale || this.locales, formatOptions, this.useMemorize);
return numberFormatter.numberFormat(value);
}
}
export default I18n;
export function createI18nInstance(i18nProps: I18nProps = {}): I18n {
return new I18n(i18nProps);
}

View File

@ -1,43 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Horizon, { Children, Fragment } from 'inulajs';
import { FormattedMessageProps } from '../../types/interfaces';
import useI18n from '../hook/useI18n';
/**
* FormattedMessage组件,
* @param props
* @constructor
*/
function FormattedMessage(props: FormattedMessageProps) {
const { i18n } = useI18n();
const { id, values, messages, formatOptions, context, tagName: TagName = Fragment, children, comment, useMemorize }: any = props;
const formatMessageOptions = {
comment,
messages,
context,
useMemorize,
formatOptions,
};
let formattedMessage = i18n.formatMessage(id, values, formatMessageOptions);
if (typeof children === 'function') {
const childNodes = Array.isArray(formattedMessage) ? formattedMessage : [formattedMessage];
return children(childNodes);
}
if (TagName) {
return (
<TagName>{Children.toArray(formattedMessage)}</TagName>
);
}
return (
<>{formattedMessage}</>
);
}
export default FormattedMessage;

View File

@ -1,52 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Horizon, {useRef, useState, useEffect, useMemo, Component} from 'inulajs';
import utils from '../../utils/utils';
import { InjectProvider } from './InjectI18n';
import { I18nProviderProps} from '../../types/interfaces';
import I18n, {createI18nInstance} from "../I18n";
/**
*
* @param props
* @constructor
*/
const I18nProvider = (props: I18nProviderProps)=> {
const { locale, messages, children } = props;
const i18n = useMemo(() => {
return createI18nInstance({
locale: locale,
messages: messages,
});
}, [locale, messages]);
// 使用useRef保存上次的locale值
const localeRef = useRef<string | undefined>(i18n.locale);
const [context, setContext] = useState<I18n>(i18n);
useEffect(() => {
const handleChange = () => {
if (localeRef.current !== i18n.locale) {
localeRef.current = i18n.locale;
setContext(i18n);
}
};
let removeListener = i18n.on('change', handleChange);
// 手动触发一次 handleChange以确保 context 的正确性
handleChange();
// 在组件卸载时取消事件监听
return () => {
removeListener();
};
}, [i18n]);
// 提供一个Provider组件
return <InjectProvider value={context}>{children}</InjectProvider>;
};
export default I18nProvider;

View File

@ -1,53 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Horizon , { createContext, forwardRef } from 'inulajs';
import { isVariantI18n } from '../../utils/utils';
import copyStaticProps from '../../utils/copyStaticProps';
import { InjectOptions } from '../../types/interfaces';
import I18n from "../I18n";
// 创建国际化组件对象上下文
export const I18nContext : any = createContext<I18n>(null as any);
const { Consumer, Provider } = I18nContext;
export const InjectProvider = Provider;
/**
* 使使
* @param Component
* @param options
*/
function injectI18n(Component, options?: InjectOptions): any {
const {
isUsingForwardRef = false, // 默认不使用
} = options || {};
// 定义一个名为 WrappedI18n 的函数组件,接收传入组件的 props 和 forwardedRef返回传入组件并注入 i18n
const WrappedI18n = props => (
<Consumer>
{context => {
isVariantI18n(context);
const i18nProps = {
intl: context,
formatMessage: context.formatMessage,
formatDate: context.DateTimeFormat,
formatNumber: context.NumberFormat,
};
return <Component {...props} {...i18nProps} ref={isUsingForwardRef ? props.forwardedRef : null} />;
}}
</Consumer>
);
WrappedI18n.WrappedComponent = Component;
// 通过copyStatics方法复制组件中的静态属性
return copyStaticProps(
isUsingForwardRef ?
forwardRef((props, ref) => <WrappedI18n {...props} forwardedRef={ref} />) :
WrappedI18n,
Component
);
}
export default injectI18n;

View File

@ -1,20 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import {I18nCache, I18nProviderProps} from '../types/interfaces';
import I18n, {createI18nInstance} from './I18n';
/**
* createI18n hook函数i8n实例
*/
export const createI18n = (config: I18nProviderProps, cache?: I18nCache): I18n => {
const { locale, defaultLocale, messages } = config;
return createI18nInstance({
locale: locale || defaultLocale || 'en',
messages: messages,
useMemorize: !!cache,
});
};
export default createI18n;

View File

@ -1,25 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Inula, { useContext } from 'inulajs';
import utils from '../../utils/utils';
import { I18nContext } from '../components/InjectI18n';
import I18n from "../I18n";
/**
* useI18n hookHorizon组件一起使用
* 使useI18n钩子函数可以更方便地在函数组件中进行国际化操作
*/
function useI18n() {
const i18nContext = useContext<I18n>(I18nContext);
utils.isVariantI18n(i18nContext);
const i18n = i18nContext;
return {
i18n: i18n,
formatMessage: i18n.formatMessage.bind(i18n),
formatNumber: i18n.formatNumber.bind(i18n),
formatDate: i18n.formatDate.bind(i18n),
};
}
export default useI18n;

View File

@ -1,101 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { UNICODE_REG } from '../constants';
import { CompiledMessage, Locale, LocaleConfig, Locales } from '../types/types';
import generateFormatters from './generateFormatters';
import { FormatOptions } from '../types/interfaces';
/**
*
*/
class Translation {
private readonly compiledMessage: CompiledMessage;
private readonly locale: Locale;
private readonly locales: Locales;
private readonly localeConfig: Record<string, any>;
private readonly useMemorize?: boolean;
constructor(compiledMessage, locale, locales, localeConfig, memorize?) {
this.compiledMessage = compiledMessage;
this.locale = locale;
this.locales = locales;
this.localeConfig = localeConfig;
this.useMemorize = memorize ?? true;
}
/**
* @param values
* @param formatOptions
*/
translate(values: object, formatOptions: FormatOptions = {}): string {
const createTextFormatter = (
locale: Locale,
locales: Locales,
values: object,
formatOptions: FormatOptions,
localeConfig: LocaleConfig,
useMemorize?: boolean
) => {
const textFormatter = (name: string, type: string, format: any) => {
const formatters = generateFormatters(locale, locales, localeConfig, formatOptions);
const value = values[name];
const formatter = formatters[type](value, format, useMemorize);
let message;
if (typeof formatter === 'function') {
message = formatter(textFormatter); // 递归调用
} else {
message = formatter; // 获得变量值 formatted: "Fred"
}
return Array.isArray(message) ? message.join('') : message;
};
return textFormatter;
};
let textFormatter = createTextFormatter(
this.locale,
this.locales,
values,
formatOptions,
this.localeConfig,
this.useMemorize
);
// 通过递归方法formatCore进行格式化处理
const result = this.formatMessage(this.compiledMessage, textFormatter);
return result; // 返回要格式化的结果
}
formatMessage(compiledMessage: CompiledMessage, textFormatter: Function) {
if (!Array.isArray(compiledMessage)) {
return compiledMessage;
}
return compiledMessage.map(token => {
if (typeof token === 'string') {
return token;
}
const [name, type, format] = token;
let replaceValueFormat = format;
// 如果 format 是对象,函数将递归地对它的每个值调用 formatMessage 后保存,否则直接保存
if (format && typeof format !== 'string') {
replaceValueFormat = Object.keys(replaceValueFormat).reduce((text, key) => {
text[key] = this.formatMessage(format[key], textFormatter);
return text;
}, {});
}
//调用 getContent 函数来获取给定 name、type 和 interpolateFormat 的值
const value = textFormatter(name, type, replaceValueFormat);
return value ?? '';
}).join('');
};
}
export default Translation;

View File

@ -1,20 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { I18nCache } from '../../types/interfaces';
/**
*
*/
function creatI18nCache(): I18nCache {
return {
dateTimeFormat: {},
numberFormat: {},
plurals: {},
messages: {},
select: {},
octothorpe: {},
};
}
export default creatI18nCache;

View File

@ -1,56 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import creatI18nCache from '../cache/cache';
import utils from '../../utils/utils';
import { DatePool, Locales } from '../../types/types';
/**
*
*/
class DateTimeFormatter {
private readonly locales: Locales;
private readonly formatOptions: Intl.DateTimeFormatOptions;
// 是否进行存储
private readonly useMemorize: boolean;
// 创建一个缓存对象用于存储DateTimeFormat的对象
private cache = creatI18nCache().dateTimeFormat;
constructor(locales: Locales, formatOptions?: Intl.DateTimeFormatOptions, useMemorize?: boolean) {
this.locales = locales;
this.formatOptions = formatOptions ?? {};
this.useMemorize = useMemorize ?? true;
}
dateTimeFormat(value: DatePool, formatOptions?: Intl.DateTimeFormatOptions): string {
const options = formatOptions ?? this.formatOptions;
const formatter = new Intl.DateTimeFormat(this.locales, options);
// 将传输的字符串转变为日期对象
if (typeof value === 'string') {
value = new Date(value);
}
// 如果启用了记忆化且已经有对应的数字格式化器缓存,则直接返回缓存中的格式化结果。否则创建新的格式化数据,并进行缓存
if (this.useMemorize) {
// 造缓存的keykey包含区域设置和日期时间格式选项
const cacheKey = utils.generateKey<Intl.DateTimeFormatOptions>(this.locales, options);
if (this.cache[cacheKey]) {
return this.cache[cacheKey].format(value);
}
// 查询缓存中的key 若无key则创建新key
this.cache[cacheKey] = formatter;
return formatter.format(value);
}
// 返回格式化后的时间
return formatter.format(value);
}
}
export default DateTimeFormatter;

View File

@ -1,44 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import creatI18nCache from '../cache/cache';
import { Locales } from '../../types/types';
import utils from '../../utils/utils';
/**
*
*/
class NumberFormatter {
private readonly locales: Locales;
private readonly formatOption?: Intl.NumberFormatOptions;
private readonly useMemorize?: boolean;
private cache = creatI18nCache().numberFormat; // 创建一个缓存对象,用于缓存已经创建的数字格式化器
constructor(locales: Locales, formatOption?: Intl.NumberFormatOptions, useMemorize?: boolean) {
this.locales = locales;
this.formatOption = formatOption ?? {};
this.useMemorize = useMemorize ?? true;
}
numberFormat(value: number, formatOption?: Intl.NumberFormatOptions): string {
const options = formatOption ?? this.formatOption;
const formatter = new Intl.NumberFormat(this.locales, options);
// 如果启用了记忆化且已经有对应的数字格式化器缓存,则直接返回缓存中的格式化结果。否则创建新的格式化数据,并进行缓存
if (this.useMemorize) {
// 造缓存的keykey包含区域设置数字格式选项
const cacheKey = utils.generateKey<Intl.NumberFormatOptions>(this.locales, options);
if (this.cache[cacheKey]) {
return this.cache[cacheKey].format(value);
}
this.cache[cacheKey] = formatter;
return formatter.format(value);
}
return formatter.format(value);
}
}
export default NumberFormatter;

View File

@ -1,53 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import utils from '../../utils/utils';
import NumberFormatter from './NumberFormatter';
import { Locale, Locales } from '../../types/types';
/**
*
*/
class PluralFormatter {
private readonly locale: Locale;
private readonly locales: Locales;
private readonly value: number;
private readonly message: any;
private readonly useMemorize: boolean;
private octothorpe: Record<string, any> = {};
constructor(locale, locales, value, message, useMemorize?) {
this.locale = locale;
this.locales = locales;
this.value = value;
this.message = message;
this.useMemorize = useMemorize ?? true;
}
// 将 message中的“#”替换为指定数字value并返回新的字符串或者字符串数组
replaceSymbol(ctx: any) {
const msg = typeof this.message === 'function' ? this.message(ctx) : this.message;
const messages = Array.isArray(msg) ? msg : [msg];
const numberFormatter = new NumberFormatter(this.locales);
const valueStr = numberFormatter.numberFormat(this.value);
if (this.useMemorize) {
// 创建key用于唯一标识
const cacheKey = utils.generateKey<Intl.NumberFormatOptions>(this.locale, this.message);
// 如果key存在则使用缓存中的替代
if (this.octothorpe[cacheKey]) {
return messages.map(msg => (typeof msg === 'string' ? msg.replace('#', this.octothorpe[cacheKey]) : msg));
}
// 如果不存在,则进行缓存
this.octothorpe[cacheKey] = valueStr;
}
return messages.map(msg => (typeof msg === 'string' ? msg.replace('#', valueStr) : msg));
}
}
export default PluralFormatter;

View File

@ -1,39 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import utils from '../../utils/utils';
import { Locale } from '../../types/types';
/**
*
* eg : 输入选择语句 female {She} other {They}} 'female''other''She''They'
* select{ value: 'female' } she
*/
class SelectFormatter {
private readonly locale: Locale;
private selectCache = {};
constructor(locale) {
this.locale = locale;
}
getRule(value, rules, useMemorize?) {
if (useMemorize) {
// 创建key用于唯一标识
const cacheKey = utils.generateKey<Intl.NumberFormatOptions>(this.locale, rules);
// 如果key存在则使用缓存中的替代
if (this.selectCache[cacheKey]) {
return this.selectCache[cacheKey][value] || this.selectCache[cacheKey].other;
}
// 如果不存在,则进行缓存
this.selectCache[cacheKey] = rules;
}
return rules[value] || rules.other;
}
}
export default SelectFormatter;

View File

@ -1,84 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import DateTimeFormatter from './fomatters/DateTimeFormatter';
import NumberFormatter from './fomatters/NumberFormatter';
import {DatePool, Locale, Locales, SelectPool} from '../types/types';
import PluralFormatter from './fomatters/PluralFormatter';
import SelectFormatter from './fomatters/SelectFormatter';
import {FormatOptions, IntlMessageFormat} from "../types/interfaces";
/**
*
*/
const generateFormatters = (
locale: Locale | Locales,
locales: Locales,
localeConfig: Record<string, any> = { plurals: undefined },
formatOptions: FormatOptions = {} // 自定义格式对象
): IntlMessageFormat => {
locale = locales || locale;
const { plurals } = localeConfig;
/**
*
* @param formatOption
*/
const getStyleOption = formatOption => {
if (typeof formatOption === 'string') {
return formatOptions[formatOption] || { option: formatOption };
} else {
return formatOption;
}
};
return {
// 复数规则
plural: (value: number, { offset = 0, ...rules }, useMemorize?) => {
const pluralFormatter = new PluralFormatter(
locale,
locales,
value - offset,
rules[value] || rules[(plurals as any)?.(value - offset)] || rules.other,
useMemorize
);
return pluralFormatter.replaceSymbol.bind(pluralFormatter);
},
selectordinal: (value: number, { offset = 0, ...rules }, useMemorize?) => {
const message = rules[value] || rules[(plurals as any)?.(value - offset, true)] || rules.other;
const pluralFormatter = new PluralFormatter(locale, locales, value - offset, message, useMemorize);
return pluralFormatter.replaceSymbol.bind(pluralFormatter);
},
// 选择规则,如果规则对象中包含与该值相对应的属性,则返回该属性的值;否则,返回 "other" 属性的值。
select: (value: SelectPool, formatRules, useMemorize?) => {
const selectFormatter = new SelectFormatter(locale);
return selectFormatter.getRule(value, formatRules, useMemorize);
},
// 用于将数字格式化为字符串,接受一个数字和一个格式化规则。它会根据规则返回格式化后的字符串。
numberFormat: (value : number, formatOption, useMemorize) => {
return new NumberFormatter(locales, getStyleOption(formatOption), useMemorize).numberFormat(value);
},
// 用于将日期格式化为字符串,接受一个日期对象和一个格式化规则。它会根据规则返回格式化后的字符串。
/**
* eg: { year: 'numeric', month: 'long', day: 'numeric' } DateTimeFormatter如何将日期对象转换为字符串的参数
* \year: 'numeric' 2023
* month: 'long' January
* day: 'numeric' 1
* @param value
* @param formatOption { year: 'numeric', month: 'long', day: 'numeric' }
* @param useMemorize
*/
dateTimeFormat: (value: DatePool, formatOption, useMemorize) => {
return new DateTimeFormatter(locales, getStyleOption(formatOption), useMemorize).dateTimeFormat(value, formatOption);
},
// 用于处理未定义的值,接受一个值并直接返回它。
undefined: value => value,
};
};
export default generateFormatters;

View File

@ -1,53 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import utils from '../utils/utils';
import Translation from './Translation';
import I18n from '../core/I18n';
import { MessageDescriptor, MessageOptions } from '../types/interfaces';
import { CompiledMessage } from '../types/types';
export function getFormatMessage(
i18n: I18n,
id: MessageDescriptor | string,
values: Object | undefined = {},
options: MessageOptions = {}
) {
let { message, context, formatOptions, useMemorize } = options;
const memorize = useMemorize ?? i18n.useMemorize;
if (typeof id !== 'string') {
values = values || id.defaultValues;
message = id.message || id.defaultMessage;
context = id.context;
id = id.id;
}
// 对messages进行判空处理
const isMissingMessage = !context && !i18n.messages[id];
const isMissingContextMessage = context && !i18n.messages[context][id];
const messageUnavailable = isMissingContextMessage || isMissingMessage;
// 对错误消息进行处理
const messageError = i18n.error;
if (messageError && messageUnavailable) {
if (typeof messageError === 'function') {
return messageError(i18n.locale, id, context);
} else {
return messageError;
}
}
let compliedMessage: CompiledMessage;
if (context) {
compliedMessage = i18n.messages[context][id] || message || id;
} else {
compliedMessage = i18n.messages[id] || message || id;
}
// 对解析的messages进行parse解析并输出解析后的Token
compliedMessage = typeof compliedMessage === 'string' ? utils.compile(compliedMessage) : compliedMessage;
const translation = new Translation(compliedMessage, i18n.locale, i18n.locales, i18n.localeConfig, memorize);
return translation.translate(values, formatOptions);
}

View File

@ -1,189 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
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));
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 regexp;
private fast;
private queuedGroup;
private value;
constructor(states, state) {
this.startState = state;
this.states = states;
this.buffer = '';
this.stack = [];
this.reset();
}
public reset(data?, info?) {
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() : [];
return this;
}
private setState(state) {
if (!state || this.state === state) {
return;
}
this.state = state;
const info = this.states[state];
this.groups = info.groups;
this.error = info.error;
this.regexp = info.regexp;
this.fast = info.fast;
}
private popState() {
this.setState(this.stack.pop());
}
private pushState(state) {
this.stack.push(this.state);
this.setState(state);
}
private getGroup(match) {
const groupCount = this.groups.length;
for (let i = 0; i < groupCount; i++) {
if (match[i + 1] !== undefined) {
return this.groups[i];
}
}
throw new Error('No token type found matching text!');
}
private tokenToString() {
return this.value;
}
// 迭代获取下一个 token
public next() {
const index = this.index;
if (this.queuedGroup) {
const token = this.getToken(this.queuedGroup, this.queuedText, index);
this.queuedGroup = null;
this.queuedText = '';
return token;
}
const buffer = this.buffer;
if (index === buffer.length) {
return;
}
const fastGroup = this.fast[buffer.charCodeAt(index)];
if (fastGroup) {
return this.getToken(fastGroup, buffer.charAt(index), index);
}
// 如果没有快速匹配,那么使用预先编译的正则表达式进行匹配操作
const regexp = this.regexp;
regexp.lastIndex = index;
const match = getMatch(regexp, buffer);
const error = this.error;
if (match == null) {
return this.getToken(error, buffer.slice(index, buffer.length), index);
}
const group = this.getGroup(match);
const text = match[0];
if (error.fallback && match.index !== index) {
this.queuedGroup = group;
this.queuedText = text;
return this.getToken(error, buffer.slice(index, match.index), index);
}
return this.getToken(group, text, index);
}
private getToken(group, text, offset) {
let lineNum = 0;
let last = 1; // 最后一个换行符的索引位置
if (group.lineBreaks) {
const matchNL = /\n/g;
if (text === '\n') {
lineNum = 1;
} else {
while (matchNL.exec(text)) {
lineNum++;
last = matchNL.lastIndex;
}
}
}
const token = {
type: (typeof group.type === 'function' && group.type(text)) || group.defaultType,
value: typeof group.value === 'function' ? group.value(text) : text,
text: text,
toString: this.tokenToString,
offset: offset, // 标记在输入 buffer 中的偏移量
lineBreaks: lineNum,
line: this.line, // token 所在的行号
col: this.col, // token 所在的列号
};
const size = text.length;
this.index += size;
this.line += lineNum;
if (lineNum !== 0) {
this.col = size - last + 1;
} else {
this.col += size;
}
if (group.shouldThrow) {
throw new Error('Invalid Syntax!');
}
if (group.pop) {
this.popState();
} else if (group.push) {
this.pushState(group.push);
} else if (group.next) {
this.setState(group.next);
}
return token;
}
// 增加迭代器
[Symbol.iterator]() {
return {
next: (): IteratorResult<T> => {
const token = this.next();
return { value: token, done: !token } as IteratorResult<T>;
}
}
}
}
export default Lexer;

View File

@ -1,63 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
const body: Record<string, any> = {
doubleapos: { match: "''", value: () => "'" },
quoted: {
lineBreaks: true,
match: /'[{}#](?:[^]*?[^'])?'(?!')/u,
value: src => src.slice(1, -1).replace(/''/g, "'"),
},
argument: {
lineBreaks: true,
match: /\{\s*[^\p{Pat_Syn}\p{Pat_WS}]+\s*/u,
push: 'arg',
value: src => src.substring(1).trim(),
},
octothorpe: '#',
end: { match: '}', pop: 1 },
content: { lineBreaks: true, match: /[^][^{}#']*/u },
};
const arg: Record<string, any> = {
select: {
lineBreaks: true,
match: /,\s*(?:plural|select|selectordinal)\s*,\s*/u,
next: 'select',
value: src => src.split(',')[1].trim(),
},
'func-args': {
lineBreaks: true,
match: /,\s*[^\p{Pat_Syn}\p{Pat_WS}]+\s*,/u,
next: 'body',
value: src => src.split(',')[1].trim(),
},
'func-simple': {
lineBreaks: true,
match: /,\s*[^\p{Pat_Syn}\p{Pat_WS}]+\s*/u,
value: src => src.substring(1).trim(),
},
end: { match: '}', pop: 1 },
};
const select: Record<string, any> = {
offset: {
lineBreaks: true,
match: /\s*offset\s*:\s*\d+\s*/u,
value: src => src.split(':')[1].trim(),
},
case: {
lineBreaks: true,
match: /\s*(?:=\d+|[^\p{Pat_Syn}\p{Pat_WS}]+)\s*\{/u,
push: 'body',
value: src => src.substring(0, src.indexOf('{')).trim(),
},
end: { match: /\s*\}/u, pop: 1 },
};
export const mappingRule: Record<string, any> = {
body,
arg,
select
};

View File

@ -1,230 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Lexer from './Lexer';
import { mappingRule } from './mappingRule';
import ruleUtils from '../utils/parseRuleUtils';
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 groups: Record<string, any>[] = [];
const parts: string[] = [];
// 检查是否存在 fallback 规则,若存在则禁用快速匹配
enableFast = isExistsFallback(rules, enableFast);
for (let i = 0; i < rules.length; i++) {
const options = rules[i];
if (options.include) {
throw new Error('Inheritance is not allowed in stateless lexers!');
}
errorRule = isOptionsErrorOrFallback(options, errorRule);
const match = options.match.slice();
if (enableFast) {
// 如果快速匹配允许,则将单字符的规则存入 fast 对象
processFast(match, fast, options);
}
// 检查规则中是否存在不适当的状态切换选项
if (options.pop || options.push || options.next) {
checkStateOptions(hasStates, options);
}
// 只有具有 .match 的规则才会被包含在正则表达式中
if (match.length === 0) {
continue;
}
enableFast = false;
groups.push(options);
// 检查是否所有规则都使用了 unicode 标志,或者都未使用
unicodeFlag = checkUnicode(match, unicodeFlag, options);
const pat = ruleUtils.getRegUnion(match.map(ruleUtils.getReg));
const regexp = new RegExp(pat);
if (regexp.test('')) {
throw new Error('The regex matched the empty string!');
}
const groupCount = ruleUtils.getRegGroups(pat);
if (groupCount > 0) {
throw new Error('The regular expression uses capture groups, use (?: … ) instead!');
}
// 检测规则是否匹配换行符
if (!options.lineBreaks && regexp.test('\n')) {
throw new Error('The matching rule must contain lineBreaks.');
}
parts.push(ruleUtils.getRegCapture(pat));
}
// 如果没有 fallback 规则,则使用 sticky 标志,只在当前索引位置寻找匹配项,如果不支持 sticky 标志,则使用无法被否定的空模式来模拟
const fallbackRule = errorRule && errorRule.fallback;
let flags = ruleUtils.checkSticky() && !fallbackRule ? 'ym' : 'gm';
const suffix = ruleUtils.checkSticky() || fallbackRule ? '' : '|';
if (unicodeFlag === true) {
flags += 'u';
}
const combined = new RegExp(ruleUtils.getRegUnion(parts) + suffix, flags);
return {
regexp: combined,
groups: groups,
fast: fast,
error: errorRule || defaultErrorRule,
};
}
export function checkStateGroup(group: Record<string, any>, name: string, map: Record<string, any>) {
const state = group && (group.push || group.next);
if (state && !map[state]) {
throw new Error('The state is missing.');
}
if (group && group.pop && +group.pop !== 1) {
throw new Error('The value of pop must be 1.');
}
}
// 将国际化解析规则注入分词器中
function parseMappingRule(mappingRule: Record<string, any>, start?: string): Lexer<RawToken> {
const keys = Object.getOwnPropertyNames(mappingRule);
if (!start) {
start = keys[0];
}
// 将每个状态的规则解析为规则数组,并存储在 ruleMap 对象中
const ruleMap = keys.reduce((map, key) => {
map[key] = ruleUtils.getRules(mappingRule[key]);
return map;
}, {});
// 处理规则中的 include 声明,将被包含的规则添加到相应的状态中
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const rules = ruleMap[key];
const included = {};
for (let j = 0; j < rules.length; j++) {
const rule = rules[j];
if (!rule.include) {
continue;
}
const splice = [j, 1];
if (rule.include !== key && !included[rule.include]) {
included[rule.include] = true;
const newRules = ruleMap[rule.include];
if (!newRules) {
throw new Error('Cannot contain a state that does not exist!');
}
newRules.forEach(newRule => {
if (!rules.includes(newRule)) {
splice.push(newRule);
}
});
}
rules.splice.apply(rules, splice);
j--;
}
}
const map = {};
// 将规则映射为词法分析器数据结构,并存储在 map 对象中
keys.forEach(key => {
map[key] = parseRules(ruleMap[key], true);
});
// 检查状态组中的规则是否正确引用了其他状态
keys.forEach(name => {
const state = map[name];
const groups = state.groups;
groups.forEach(group => {
checkStateGroup(group, name, map);
});
const fastKeys = Object.getOwnPropertyNames(state.fast);
fastKeys.forEach(fastKey => {
checkStateGroup(state.fast[fastKey], name, map);
});
});
return new Lexer(map, start);
}
function processFast(match, fast: {}, options) {
while (match.length && typeof match[0] === 'string' && match[0].length === 1) {
const word = match.shift();
fast[word.charCodeAt(0)] = options;
}
}
function handleErrorRule(options, errorRule: Record<string, any>) {
if (!options.fallback === !errorRule.fallback) {
throw new Error('errorRule can only set one!');
} else {
throw new Error('fallback and error cannot be set at the same time!');
}
}
function checkUnicode(match, unicodeFlag, options) {
for (let j = 0; j < match.length; j++) {
const obj = match[j];
if (!ruleUtils.checkRegExp(obj)) {
continue;
}
if (unicodeFlag === null) {
unicodeFlag = obj.unicode;
} else if (unicodeFlag !== obj.unicode && options.fallback === false) {
throw new Error('If the /u flag is used, all!');
}
}
return unicodeFlag;
}
function checkStateOptions(hasStates: boolean, options) {
if (!hasStates) {
throw new Error('State toggle options are not allowed in stateless tokenizers!');
}
if (options.fallback) {
throw new Error('State toggle options are not allowed on fallback tokens!');
}
}
function isExistsFallback(rules: Record<string, any>, enableFast: boolean) {
for (let i = 0; i < rules.length; i++) {
if (rules[i].fallback) {
enableFast = false;
}
}
return enableFast;
}
function isOptionsErrorOrFallback(options, errorRule: Record<string, any> | null) {
if (options.error || options.fallback) {
// 只能设置一个 errorRule
if (errorRule) {
handleErrorRule(options, errorRule);
}
errorRule = options;
}
return errorRule;
}
export const lexer = parseMappingRule(mappingRule);
export default parseMappingRule;

View File

@ -1,197 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
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";
const getContext = (lt: Record<string, any>): TokenContext => ({
offset: lt.offset,
line: lt.line,
col: lt.col,
text: lt.text,
lineNum: lt.lineBreaks,
});
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);
}
isSelectKeyValid(token: RawToken, type: Select['type'], value: string) {
if (value[0] === '=') {
if (type === 'select') {
throw new Error(`The key value of the select type is invalid.`);
}
} else if (type !== 'select') {
const values = type === 'plural' ? this.cardinalKeys : this.ordinalKeys;
if (values.length > 0 && !values.includes(value)) {
throw new Error(`${type} type key value is invalid.`);
}
}
}
processSelect({ value: arg }: any, isPlural: boolean, context: TokenContext, type: Select['type']): Select {
const select: Select = { type, arg, cases: [], ctx: context };
if (type === 'plural' || type === 'selectordinal') {
isPlural = true;
}
for (const token of this.lexer) {
switch (token.type) {
case 'offset': {
if (type === 'select') {
throw new Error('The complex offset of the select type is incorrect.');
}
if (select.cases.length > 0) {
throw new Error('The complex offset must be set before cases.');
}
select.offset = Number(token.value);
context.text += token.text;
context.lineNum += token.lineBreaks;
break;
}
case 'case': {
this.isSelectKeyValid(token, type, token.value);
select.cases.push({
key: token.value.replace(/=/g, ''),
tokens: this.parse(isPlural),
ctx: getContext(token),
});
break;
}
case 'end': {
return select;
}
default: {
throw new Error(`Unrecognized analyzer token: ${token.type}`);
}
}
}
throw new Error('The message end position is invalid.');
}
parseToken(token: RawToken, isPlural: boolean): PlainArg | FunctionArg | Select {
const context = getContext(token);
const nextToken = this.lexer.next();
if (!nextToken) {
throw new Error('The message end position is invalid.');
}
context.text += nextToken.text;
context.lineNum += nextToken.lineBreaks;
switch (nextToken.type) {
case 'end': {
return { type: 'argument', arg: token.value, ctx: context };
}
case 'func-simple': {
const end = this.lexer.next();
if (!end) {
throw new Error('The message end position is invalid.');
}
if (end.type !== 'end') {
throw new Error(`Unrecognized analyzer token: ${end.type}`);
}
context.text += end.text;
if (checkSelectType(nextToken.value.toLowerCase())) {
throw new Error(`Invalid parameter type: ${nextToken.value}`);
}
return {
type: 'function',
arg: token.value,
key: nextToken.value,
ctx: context,
};
}
case 'func-args': {
if (checkSelectType(nextToken.value.toLowerCase())) {
throw new Error(`Invalid parameter type: ${nextToken.value}`);
}
let param = this.parse(isPlural);
return {
type: 'function',
arg: token.value,
key: nextToken.value,
param,
ctx: context,
};
}
case 'select':
if (checkSelectType(nextToken.value)) {
return this.processSelect(token, isPlural, context, nextToken.value);
} else {
throw new Error(`Invalid select type: ${nextToken.value}`);
}
default:
throw new Error(`Unrecognized analyzer token: ${nextToken.type}`);
}
}
// 在根级别解析时,遇到结束符号即结束解析并返回结果;而在非根级别解析时,遇到结束符号会被视为不合法的结束位置,抛出错误
parse(isPlural: boolean, isRoot?: boolean): Array<Content | PlainArg | FunctionArg | Select> {
const tokens: any[] = [];
let content: string | Content | null = null;
for (const token of this.lexer) {
if (token.type === 'argument') {
if (content) {
content = null;
}
tokens.push(this.parseToken(token, isPlural));
} else if (token.type === 'octothorpe' && isPlural) {
if (content) {
content = null;
}
tokens.push({ type: 'octothorpe' });
} else if (token.type === 'end' && !isRoot) {
return tokens;
} else if (token.type === 'doubleapos') {
tokens.push(token.value);
} else if (token.type === 'quoted') {
tokens.push(token.value)
} else if (token.type === 'content') {
tokens.push(token.value);
} else {
let value = token.value;
if (!isPlural && token.type === 'quoted' && value[0] === '#') {
if (value.includes('{')) {
throw new Error(`Invalid template: ${value}`);
}
value = token.text;
}
if (content) {
content = value;
} else {
content = value;
tokens.push(content);
}
}
}
if (isRoot) {
return tokens;
}
throw new Error('The message end position is invalid.');
}
}
export default function parse(message: string): Array<Content | PlainArg | FunctionArg | Select> {
const parser = new Parser(message);
return parser.parse(false, true);
}

View File

@ -1,192 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import {
AllLocaleConfig,
AllMessages,
Locale,
Locales,
Error,
PluralCategory,
DatePool,
SelectPool,
RawToken,
} from './types';
import I18n from '../core/I18n';
import Lexer from '../parser/Lexer';
import injectI18n from "../core/components/InjectI18n";
// FormattedMessage的参数定义
export interface FormattedMessageProps extends MessageDescriptor {
values?: object;
tagName?: string;
children?(nodes: any[]): any;
}
// 信息描述对象有id、信息、内容
export interface MessageDescriptor extends MessageOptions {
id: string;
defaultMessage?: string;
defaultValues?: Record<string, unknown>;
}
export interface MessageOptions {
comment?: string;
message?: string;
context?: string;
formatOptions?: FormatOptions;
useMemorize?: boolean;
}
// I18n类的缓存定义
export interface I18nCache {
dateTimeFormat: Record<string, Intl.DateTimeFormat>;
numberFormat: Record<string, Intl.NumberFormat>;
plurals: Record<string, Intl.PluralRules>;
messages: Record<string, IntlMessageFormat>;
select: Record<string, object>;
octothorpe: Record<string, any>;
}
// I18n类的传参
export interface I18nProps {
locale?: Locale;
locales?: Locales;
messages?: AllMessages;
localeConfig?: AllLocaleConfig;
useMemorize?: boolean;
error?: Error;
}
// 消息格式化选项类型
export interface FormatOptions {
dateTimeFormat?: Intl.DateTimeFormatOptions;
numberFormat?: Intl.NumberFormatOptions;
plurals?: Intl.PluralRulesOptions;
}
export interface InjectOptions {
isUsingForwardRef?: boolean;
ensureContext?: boolean;
}
export interface I18nProviderProps {
i18n?: I18n;
locale?: Locale;
messages?: AllMessages;
defaultLocale?: string;
RenderOnLocaleChange?: boolean;
children?: any;
uesMemorize?: boolean;
}
export interface IntlMessageFormat extends I18nProviderProps, MessageOptions {
plural: (
value: number,
{ offset, ...rules }: { [x: string]: any; offset?: number },
useMemorize?: boolean
) => (ctx: any) => any[];
selectordinal: (
value: number,
{ offset, ...rules }: { [x: string]: any; offset?: number },
useMemorize?: boolean
) => (ctx: any) => any[];
select: (value: SelectPool, formatRules: any, useMemorize?: boolean) => any;
numberFormat: (value: number, formatOption: any, useMemorize: boolean) => string;
dateTimeFormat: (value: DatePool, formatOption: any, useMemorize: boolean) => string;
undefined: (value: any) => any;
}
//错误信息的事件
export interface MissingMessageEvent {
locale: Locale;
id: string;
context?: string;
}
export interface LexerInterface<T> {
reset: (data?: string, info?: Record<string, any>) => Lexer<T>;
next: () => RawToken | undefined;
[Symbol.iterator](): Iterator<T>;
}
export interface TokenContext {
// token 索引值的偏移量
offset: number;
// token 开始计算的初始行号
line: number;
// token 开始计算的初始列号
col: number;
// 原始输入
text: string;
// 换行数
lineNum: number;
}
export interface Content {
type: 'content';
value: string;
ctx: TokenContext;
}
// 需要解析参数定义
export interface PlainArg {
type: 'argument';
arg: string;
ctx: TokenContext;
}
export interface Octothorpe {
type: 'octothorpe';
ctx: TokenContext;
}
export interface FunctionArg {
type: 'function';
arg: string;
key: string;
param?: Array<Content | PlainArg | FunctionArg | Select | Octothorpe>;
ctx: TokenContext;
}
export interface SelectCase {
key: string;
tokens: Array<Content | PlainArg | FunctionArg | Select | Octothorpe>;
ctx: TokenContext;
}
// 选择模式
export interface Select {
type: 'plural' | 'select' | 'selectordinal';
arg: string;
cases: SelectCase[];
offset?: number;
ctx: TokenContext;
}
export interface InjectedIntl {
// 日期格式化
formatDate(value: DatePool, options?: Intl.DateTimeFormatOptions): string;
// 时间格式化
formatTime(value: DatePool, options?: Intl.DateTimeFormatOptions): string;
// 数字格式化
formatNumber(value: number, options?: Intl.NumberFormatOptions): string;
// 信息格式化
formatMessage(
messageDescriptor: MessageDescriptor,
values?: object,
options?: MessageOptions,
useMemorize?: boolean
): string;
}

View File

@ -1,51 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { Content, MissingMessageEvent, Octothorpe, PlainArg, Select, FunctionArg } from './interfaces';
export type Error = string | ((message, id, context) => string);
export type Locale = string;
export type Locales = Locale | Locale[];
export type LocaleConfig = { plurals?: Function };
export type AllLocaleConfig = Record<Locale, LocaleConfig>;
type CompiledMessagePart = string | Array<string | Array<string | (string | undefined)> | Record<string, unknown>>;
export type CompiledMessage = string | CompiledMessagePart[];
export type Messages = Record<string, string> | Record<string, CompiledMessage>;
export type AllMessages = Record<string, string> | Record<Locale, Messages>;
export type EventCallback = (...args: any[]) => any;
// 资源事件
export type Events = {
change: () => void;
missing: (event: MissingMessageEvent) => void;
};
// 默认复数规则
export type PluralCategory = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';
export type Token = Content | PlainArg | FunctionArg | Select | Octothorpe;
export type DatePool = Date | string;
export type SelectPool = string | object;
export type RawToken = {
type: string;
value: string;
text: string;
toString: () => string;
offset: number;
lineBreaks: number;
line: number;
col: number;
};

View File

@ -1,77 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { isMemo, ForwardRef } from 'inulajs';
import {
HORIZON_FORWARD_REF_STATICS,
HORIZON_MEMO_STATICS,
HORIZON_STATICS,
NATIVE_STATICS,
} from '../constants';
const staticsMap = new Map();
staticsMap.set(ForwardRef, HORIZON_FORWARD_REF_STATICS);
// 确定给定的组件是否为Memo组件并返回相应的静态属性
function getStatics(component) {
if (isMemo(component)) {
return HORIZON_MEMO_STATICS;
}
if (staticsMap.has(component['vtype'])) {
return staticsMap.get(component['vtype']) || HORIZON_STATICS;
}
}
/**
*
* @param sourceComponent
* @param key
*/
function isDescriptorValid<U>(sourceComponent: U, key: string | symbol) {
const descriptor = Object.getOwnPropertyDescriptor(sourceComponent, key);
return descriptor && (!descriptor.get || descriptor.get.prototype);
}
// 将一个对象的非react静态属性复制到另一个对象上并返回马目标对象
function copyStaticProps<T, U>(targetComponent: T, sourceComponent: U): T {
if (typeof sourceComponent === 'string') {
return targetComponent;
}
// 递归拷贝静态属性
const inheritedComponent = Object.getPrototypeOf(sourceComponent);
if (inheritedComponent && inheritedComponent !== Object.prototype) {
copyStaticProps(targetComponent, inheritedComponent);
}
// 获取源组件的属性列表
const keys: (string | symbol)[] = [
//获取指定对象自身的所有属性的名称(包括不可枚举属性)
...Object.getOwnPropertyNames(sourceComponent),
//获取指定对象自身的所有 Symbol 类型的属性的名称(包括不可枚举属性)
...Object.getOwnPropertySymbols(sourceComponent),
];
// 获取目标组件和源组件的静态属性
const targetStatics = getStatics(targetComponent);
const sourceStatics = getStatics(sourceComponent);
keys.forEach(key => {
if (
!NATIVE_STATICS[key] &&
!(targetStatics && targetStatics[key]) &&
!(sourceStatics && sourceStatics[key]) &&
isDescriptorValid(sourceComponent, key)
) {
try {
// 在一个已有的targetComponent对象上增加sourceComponent的属性
Object.defineProperty(targetComponent, key, Object.getOwnPropertyDescriptor(sourceComponent, key)!);
} catch (e) {}
}
});
return targetComponent;
};
export default copyStaticProps;

View File

@ -1,69 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { EventCallback } from "../types/types";
/**
* 使
*/
class EventDispatcher<E extends Record<string, EventCallback>> {
// 声明_events用于存储事件和对应的监听器
private _events: Map<keyof E, Set<EventCallback>>;
constructor() {
this._events = new Map();
}
/**
* on
* @param event
* @param listener
*/
on(event: keyof E, listener: E[keyof E]): () => void {
if (!this._events.has(event)) {
this._events.set(event, new Set());
}
const listeners = this._events.get(event)!;
listeners.add(listener);
return () => {
this.removeListener(event, listener);
};
}
/**
* removeListener
* @param event
* @param listener
*/
removeListener(event: keyof E, listener: E[keyof E]): void {
if (!this._events.has(event)) {
return;
}
const listeners = this._events.get(event)!;
listeners.delete(listener);
if (listeners.size === 0) {
this._events.delete(event);
}
}
/**
* emit
* @param event
* @param args
*/
emit(event: keyof E, ...args: Parameters<E[keyof E]>): void {
if (!this._events.has(event)) {
return;
}
// 获取该事件对应的监听器集合,并按照注册顺序执行每个监听器
const listeners = this._events.get(event)!;
for (const listener of listeners) {
listener.apply(this, args);
}
}
}
export default EventDispatcher;

View File

@ -1,42 +0,0 @@
/**
* parse后的Token数组针对不同的匀速类型进行处理
*/
enum TokenType {
octothorpe = 'OCTOTHORPE',
argument = 'ARGUMENT',
function = 'FUNCTION',
}
const processToken = token => {
if (typeof token === 'string') {
return token;
} else if (TokenType[token.type] === 'OCTOTHORPE') { // token为符号
return '#';
} else if (TokenType[token.type] === 'ARGUMENT') { // token为变量
return [token.arg];
} else if (TokenType[token.type] === 'FUNCTION') { // token为函数方法
const _param = token.param && token.param.tokens[0];
const param = typeof _param === 'string' ? _param.trim() : _param;
return [token.arg, token.key, param].filter(Boolean);
}
const offset = token.offset ? parseInt(token.offset) : undefined;
const tempFormatProps = {};
token.cases.forEach(item => {
tempFormatProps[item.key] = getTokenAST(item.tokens);
});
const mergedProps = Object.assign({}, { offset }, tempFormatProps);
return [token.arg, token.type, mergedProps];
};
function getTokenAST(tokens) {
if (!Array.isArray(tokens)) {
return tokens.join('');
}
return tokens.map(token => processToken(token));
}
export default getTokenAST;

View File

@ -1,207 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
function getType(input: any): string {
const str: string = Object.prototype.toString.call(input);
return str.slice(8, -1).toLowerCase();
}
const createTypeChecker = (type: string) => {
return (input: any) => {
return getType(input) === type.toLowerCase();
};
};
const checkObject = (input: any) => input !== null && typeof input === 'object';
const checkRegExp = createTypeChecker('RegExp');
const checkSticky = () => typeof new RegExp('')?.sticky === 'boolean';
// 转义正则表达式中的特殊字符
function transferReg(s: string): string {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
// 计算正则表达式中捕获组的数量
function getRegGroups(s: string): number {
const re = new RegExp('|' + s);
return re.exec('')?.length! - 1;
}
// 创建一个捕获组的正则表达式模式
function getRegCapture(s: string): string {
return '(' + s + ')';
}
// 将正则表达式合并为一个联合的正则表达式模式
function getRegUnion(regexps: string[]): string {
if (!regexps.length) {
return '(?!)';
}
const source = regexps.map(s => '(?:' + s + ')').join('|');
return '(?:' + source + ')';
}
function getReg(input: string | Record<string, any>): string {
if (typeof input === 'string') {
return '(?:' + transferReg(input) + ')';
} else if (checkRegExp(input) || checkObject(input)) {
if (input.ignoreCase) {
throw new Error('/i 标志禁止使用');
}
if (input.global) {
throw new Error('/g 标志禁止使用');
}
if (input.sticky) {
throw new Error('/y 标志禁止使用');
}
if (input.multiline) {
throw new Error('/m 标志禁止使用');
}
return input.source;
} else {
throw new Error(`${input}不符合规范!`);
}
}
function getRulesByObject(object: Record<string, any>) {
const keys = Object.getOwnPropertyNames(object);
// 存储最终的规则数组
const result: any[] = [];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const thing = object[key];
// 将属性值转换为规则数组
const rules = [].concat(thing);
// 如果属性名为 'include',表示需要包含其他规则
if (key === 'include') {
for (let j = 0; j < rules.length; j++) {
result.push({ include: rules[j] });
}
continue;
}
// 用于保存当前规则的匹配模式
let match = [];
rules.forEach(function (rule) {
if (checkObject(rule)) {
// 如果规则是一个对象,表示具有选项设置,添加该规则到结果数组中,并重置匹配模式数组
if (match.length) result.push(getRuleOptions(key, match));
result.push(getRuleOptions(key, rule));
match = [];
} else {
match.push(rule);
}
});
// 如果匹配模式数组中还有剩余的匹配模式,创建规则对象并添加到结果数组中
if (match.length) result.push(getRuleOptions(key, match));
}
return result;
}
function getRulesByArray(array: any[]) {
const result: any[] = [];
for (let i = 0; i < array.length; i++) {
const obj = array[i];
// 如果元素具有 'include' 属性,表示需要包含其他规则
if (obj.include) {
const include = [].concat(obj.include);
for (let j = 0; j < include.length; j++) {
result.push({ include: include[j] });
}
continue;
}
if (!obj.type) {
throw new Error('Rule 没有 type 属性');
}
result.push(getRuleOptions(obj.type, obj));
}
return result;
}
function getRuleOptions(type, obj) {
// 如果 obj 不是一个对象,则将其转换为包含 'match' 属性的对象
if (!checkObject(obj)) {
obj = { match: obj };
}
// 如果 obj 包含 'include' 属性,则抛出错误,因为匹配规则不能包含状态
if (obj.include) {
throw new Error('匹配规则不能包含状态!');
}
// 创建默认的选项对象,初始化各个选项属性
const options: Record<string, any> = {
defaultType: type,
lineBreaks: !!obj.error || !!obj.fallback,
pop: false,
next: null,
push: null,
error: false,
fallback: false,
value: null,
type: null,
shouldThrow: false,
};
Object.assign(options, obj);
if (typeof options.type === 'string' && type !== options.type) {
throw new Error('type 属性不能为字符串!');
}
const match = options.match;
if (Array.isArray(match)) {
options.match = match;
} else if (match) {
options.match = [match];
} else {
options.match = [];
}
options.match.sort((a, b) => {
// 根据规则的类型进行排序,确保正则表达式排在最前面,长度较长的规则排在前面
if (checkRegExp(a) && checkRegExp(b)) {
return 0;
} else if (checkRegExp(b)) {
return -1;
} else if (checkRegExp(a)) {
return +1;
} else {
return b.length - a.length;
}
});
return options;
}
function getRules(spec) {
return Array.isArray(spec) ? getRulesByArray(spec) : getRulesByObject(spec);
}
const ruleUtils = {
checkObject,
checkRegExp,
transferReg,
checkSticky,
getRegGroups,
getRegCapture,
getRegUnion,
getReg,
getRulesByObject,
getRulesByArray,
getRuleOptions,
getRules,
};
export default ruleUtils;

View File

@ -1,37 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { CompiledMessage } from "../types/types";
import parse from "../parser/parser";
import getTokenAST from "./getTokenAST";
import I18n from "../core/I18n";
export function isVariantI18n(i18n?: I18n) {
if (!i18n) {
throw new Error(`I18n object is not found!`);
}
}
function generateKey<T>(locales?: string | string[], options: T = {} as T) {
const localeKey = Array.isArray(locales) ? locales.sort().join('-') : locales;
return `${localeKey}:${JSON.stringify(options)}`;
}
function compile(message: string): CompiledMessage {
try {
return getTokenAST(parse(message));
} catch (e) {
console.error(`Message cannot be parse due to syntax errors: ${message}`);
return message;
}
}
const utils = {
isVariantI18n,
generateKey,
compile,
}
export default utils;

View File

@ -1,125 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import I18n from '../../src/core/I18n';
describe('I18n', () => {
it('load catalog and merge with existing', () => {
const i18n = new I18n({});
const messages = {
Hello: 'Hello',
};
i18n.loadMessage('en', messages);
i18n.changeLanguage('en');
expect(i18n.messages).toEqual(messages);
i18n.loadMessage('fr', { Hello: 'Salut' });
expect(i18n.messages).toEqual(messages);
});
it('should load multiple language ', function () {
const enMessages = {
Hello: 'Hello',
};
const frMessage = {
Hello: 'Salut',
};
const intl = new I18n({});
intl.loadMessage({
en: enMessages,
fr: frMessage,
});
intl.changeLanguage('en');
expect(intl.messages).toEqual(enMessages);
intl.changeLanguage('fr');
expect(intl.messages).toEqual(frMessage);
});
it('should switch active locale', () => {
const messages = {
Hello: 'Salut',
};
const i18n = new I18n({
locale: 'en',
messages: {
fr: messages,
en: {},
},
});
expect(i18n.locale).toEqual('en');
expect(i18n.messages).toEqual({});
i18n.changeLanguage('fr');
expect(i18n.locale).toEqual('fr');
expect(i18n.messages).toEqual(messages);
});
it('should switch active locale', () => {
const messages = {
Hello: 'Salut',
};
const i18n = new I18n({
locale: 'en',
messages: {
en: messages,
fr:{}
},
});
i18n.changeLanguage('en');
expect(i18n.locale).toEqual('en');
expect(i18n.messages).toEqual(messages);
i18n.changeLanguage('fr');
expect(i18n.locale).toEqual('fr');
expect(i18n.messages).toEqual({});
});
it('._ allow escaping syntax characters', () => {
const messages = {
"My ''name'' is '{name}'": "Mi ''nombre'' es '{name}'",
};
const i18n = new I18n({
locale: 'es',
messages: { es: messages },
});
expect(i18n.formatMessage("My ''name'' is '{name}'")).toEqual("Mi 'nombre' es {name}");
});
it('._ should format message from catalog', function () {
const messages = {
Hello: 'Salut',
id: "Je m'appelle {name}",
};
const i18n = new I18n({
locale: 'fr',
messages: { fr: messages },
});
expect(i18n.locale).toEqual('fr');
expect(i18n.formatMessage('Hello')).toEqual('Salut');
expect(i18n.formatMessage('id', { name: 'Fred' })).toEqual("Je m'appelle Fred");
});
it('should return the formatted date and time', () => {
const i18n = new I18n({
locale: 'fr',
});
const formattedDateTime = i18n.formatDate('2023-06-06T07:53:54.465Z', {
dateStyle: 'full',
timeStyle: 'short',
});
expect(typeof formattedDateTime).toBe('string');
expect(formattedDateTime).toEqual('mardi 6 juin 2023 à 15:53');
});
it('should return the formatted number', () => {
const i18n = new I18n({
locale: 'en',
});
const formattedNumber = i18n.formatNumber(123456.789, { style: 'currency', currency: 'USD' });
expect(typeof formattedNumber).toBe('string');
expect(formattedNumber).toEqual('$123,456.79');
});
});

View File

@ -1,52 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import * as React from 'react';
import I18nProvider from '../../../src/core/components/I18nProvider';
import { FormattedMessage } from '../../../index';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import {createI18nInstance} from "../../../src/core/I18n";
const dummyContext = React.createContext('');
const { Provider: DummyProvider, Consumer: DummyConsumer } = dummyContext;
describe('<FormattedMessage>', () => {
const enMessage = {
hello: '你好',
id: "Je m'appelle {name}",
};
const locale = 'en';
const i18n = createI18nInstance({
locale: locale,
messages: enMessage,
});
it('should format context', function () {
const { getByTestId } = render(
<I18nProvider key={locale} locale={locale} messages={enMessage}>
<span data-testid="id">
<FormattedMessage data-testid="id" id={enMessage.hello} />
</span>
</I18nProvider>
);
setTimeout(() => {
expect(getByTestId('id')).toHaveTextContent(i18n.formatMessage('hello', '', {}));
}, 1000);
});
it('should format context', function () {
const props = {
id: enMessage.id,
values: { name: 'fred' },
};
const { getByTestId } = render(
<I18nProvider key={locale} locale={'en'} messages={enMessage}>
<span data-testid="id">
<FormattedMessage data-testid="id" id={props.id} values={props.values} />
</span>
</I18nProvider>
);
expect(getByTestId('id')).toHaveTextContent(i18n.formatMessage('id', { name: 'fred' }, {}));
});
});

View File

@ -1,90 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import * as React from 'react';
import { act, render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { IntlProvider } from '../../../index';
import {createI18nInstance} from "../../../src/core/I18n";
describe('I18nProvider', () => {
afterEach(() => {
jest.resetAllMocks();
});
const locale = 'en';
const i18n = createI18nInstance({
locale: locale,
});
it('should re-render on locale changes', () => {
const CurrentLocale = () => {
return <span>{i18n.locale}</span>;
};
const { container } = render(
<IntlProvider key={locale} locale={locale} messages={{}}>
<CurrentLocale />
</IntlProvider>
);
// First render — no output, because locale isn't activated
expect(container.textContent).toEqual('en');
// act函数值当组件需要被重新渲染的时候进行调度
act(() => {
i18n.loadMessage('en', {});
});
expect(container.textContent).toEqual('en');
act(() => {
i18n.loadMessage('cs', {});
i18n.changeLanguage('cs');
});
// After loading and activating locale, it's finally rendered.
setTimeout(() => {
expect(container.textContent).toEqual('cs');
}, 1000);
});
it('should subscribe for locale changes', () => {
const i18n = createI18nInstance();
i18n.on = jest.fn(() => jest.fn());
expect(i18n.on).not.toBeCalled();
render(
<IntlProvider
key={locale}
locale={locale}
messages={{}}
>
<p />
</IntlProvider>
);
setTimeout(() => {
expect(i18n.on).toBeCalledWith('change', expect.anything());
}, 1000);
});
it('should subscribe for locale changes when param i18n', () => {
const i18n = createI18nInstance();
i18n.on = jest.fn(() => jest.fn());
expect(i18n.on).not.toBeCalled();
render(
<IntlProvider i18n={i18n}>
<p />
</IntlProvider>
);
setTimeout(() => {
expect(i18n.on).toBeCalledWith('change', expect.anything());
}, 1000);
});
it('should render children', () => {
const child = <div data-testid="child" />;
const { getByTestId } = render(
<IntlProvider key={locale} locale={locale} messages={{}}>
{child}
</IntlProvider>
);
expect(getByTestId('child')).toBeTruthy(); // toBeTruthy()匹配任何if语句为真
});
});

View File

@ -1,47 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import * as React from 'react';
import { injectIntl, IntlProvider } from '../../../index';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
const mountWithProvider = (el: JSX.Element) => render(<IntlProvider locale="en">{el}</IntlProvider>);
describe('InjectIntl', () => {
let Wrapped;
beforeEach(() => {
Wrapped = ({ i18n }: { i18n }) => <div data-testid="test">{JSON.stringify(i18n)}</div>;
Wrapped.someStatic = {
type: true,
};
});
it('allows introspection access to the wrapped component', () => {
expect((injectIntl(Wrapped) as any).WrappedComponent).toBe(Wrapped);
});
it(' should copy statics', () => {
expect((injectIntl(Wrapped) as any).someStatic.type).toBe(true);
});
it('throws when InjectI18n is missing from ancestry', () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
const Injected = injectIntl(Wrapped);
// @ts-ignore
expect(() => render(<Injected />)).toThrow("Cannot read properties of null (reading 'i18n')");
});
it('should contain all props in WrappedComponent when use InjectI18n', () => {
const Injected = injectIntl(Wrapped) as any;
const props = {
foo: 'bar',
};
const { getByTestId } = mountWithProvider(<Injected {...props} />);
expect(getByTestId('test')).toHaveTextContent('{"_events":{},"locale":"en","locales":["en"],"allMessages":{},"_localeData":{}}');
});
});

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import createI18n from '../../src/core/createI18n';
describe('createI18n', () => {
it('createIntl', function () {
const i18n = createI18n({
locale: 'en',
messages: {
foo: 'bar',
},
});
expect(
i18n.formatMessage({
id: 'foo',
})
).toBe('bar');
});
it('should not warn when defaultRichTextElements is not used', function () {
const onWarn = jest.fn();
createI18n({
locale: 'en',
messages: {
foo: 'bar',
},
onWarn,
});
expect(onWarn).not.toHaveBeenCalled();
});
});

View File

@ -1,67 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import * as React from 'react'
import {render} from '@testing-library/react'
import {IntlProvider, useIntl} from "../../../index";
const FunctionComponent = ({spy}: { spy?: Function }) => {
const {i18n} = useIntl()
spy!(i18n.locale)
return null
}
const FC = () => {
const i18n = useIntl()
return i18n.formatNumber(10000, {style: 'currency', currency: 'USD'}) as any
}
describe('useIntl() hooks', () => {
it('throws when <IntlProvider> is missing from ancestry', () => {
// So it doesn't spam the console
jest.spyOn(console, 'error').mockImplementation(() => {
})
expect(() => render(<FunctionComponent/>)).toThrow(
'I18n object is not found!'
)
})
it('hooks onto the intl context', () => {
const spy = jest.fn()
render(
<IntlProvider locale="en">
<FunctionComponent spy={spy}/>
</IntlProvider>
)
expect(spy).toHaveBeenCalledWith('en')
})
it('should work when switching locale on provider', () => {
const {rerender, getByTestId} = render(
<IntlProvider locale="en">
<span data-testid="comp">
<FC/>
</span>
</IntlProvider>
)
expect(getByTestId('comp')).toMatchSnapshot()
rerender(
<IntlProvider locale="es">
<span data-testid="comp">
<FC/>
</span>
</IntlProvider>
)
expect(getByTestId('comp')).toMatchSnapshot()
rerender(
<IntlProvider locale="en">
<span data-testid="comp">
<FC/>
</span>
</IntlProvider>
)
expect(getByTestId('comp')).toMatchSnapshot()
})
})

View File

@ -1,116 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Translation from '../../src/format/Translation';
describe('Translation', () => {
let translation;
beforeEach(() => {
// 在每个测试之前创建一个新的 Translation 实例
const compiledMessage = ['Hello, ', ['name', 'text', null], '!'];
const locale = 'en';
const locales = {};
const localeConfig = {};
const useMemorize = true;
translation = new Translation(compiledMessage, locale, locales, localeConfig, useMemorize);
});
describe('formatMessage', () => {
it('should return the message if it is not an array', () => {
const result = translation.formatMessage('test message', jest.fn());
expect(result).toBe('test message');
});
it('should concatenate string tokens in the message array', () => {
const result = translation.formatMessage(['Hello, ', 'World!'], jest.fn());
expect(result).toBe('Hello, World!');
});
it('should handle token arrays and use ctx to get the value', () => {
const ctx = jest.fn().mockReturnValue('Hello');
const result = translation.formatMessage([['name', 'type', 'format']], ctx);
expect(result).toBe('Hello');
expect(ctx).toHaveBeenCalledWith('name', 'type', 'format');
});
it('should skip null values returned by ctx', () => {
const ctx = jest.fn().mockReturnValue(null);
const result = translation.formatMessage([['name', 'type', 'format'], 'World!'], ctx);
expect(result).toBe('World!');
});
it('should handle nested formats in the token array', () => {
const ctx = jest.fn((name, type, format) => format.value);
const formatObject = {
value: 'Hello',
};
const result = translation.formatMessage([['name', 'type', formatObject], ', World!'], ctx);
expect(result).toBe('Hello, World!');
});
it('should return a string when compiledMessage is a string', () => {
const compiledMessage = 'Hello, world!';
const textFormatter = jest.fn();
const result = translation.formatMessage(compiledMessage, textFormatter);
expect(result).toBe('Hello, world!');
expect(textFormatter).not.toHaveBeenCalled();
});
it('should format a message with placeholders', () => {
const compiledMessage = ['Hello, ', ['name', 'text', null], '!'];
// @ts-ignore
const textFormatter = jest.fn((name, type, format) => {
if (name === 'name') {
return 'John';
}
return "";
});
const result = translation.formatMessage(compiledMessage, textFormatter);
expect(result).toBe('Hello, John!');
expect(textFormatter).toHaveBeenCalledWith('name', 'text', null);
expect(textFormatter).toHaveBeenCalledTimes(1);
});
it('should format a message with formatted placeholders', () => {
const compiledMessage = ['Hello, ', ['name', 'text', { text: 'Lowercase' }], '!'];
// @ts-ignore
const textFormatter = jest.fn((name, type, format) => {
if (name === 'name') {
return 'John';
}
return "";
});
const result = translation.formatMessage(compiledMessage, textFormatter);
expect(result).toBe('Hello, John!');
expect(textFormatter).toHaveBeenCalledWith('name', 'text', { text: 'Lowercase' });
expect(textFormatter).toHaveBeenCalledTimes(1);
});
it('should recursively format a message with nested placeholders', () => {
const compiledMessage = [
'Hello, ',
['name', 'text', null],
'! Your age is ',
['age', 'number', { style: 'decimal' }],
'.',
];
// @ts-ignore
const textFormatter = jest.fn((name, type, format) => {
if (name === 'name') {
return 'John';
} else if (name === 'age') {
return '30';
}
return "";
});
const result = translation.formatMessage(compiledMessage, textFormatter);
expect(result).toBe('Hello, John! Your age is 30.');
expect(textFormatter).toHaveBeenCalledWith('name', 'text', null);
expect(textFormatter).toHaveBeenCalledWith('age', 'number', { style: 'decimal' });
expect(textFormatter).toHaveBeenCalledTimes(2);
});
});
});

View File

@ -1,19 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import creatI18nCache from '../../../src/format/cache/cache';
describe('creatI18nCache', () => {
it('should create an empty IntlCache object', () => {
const intlCache = creatI18nCache();
expect(intlCache).toEqual({
dateTimeFormat: {},
numberFormat: {},
messages: {},
plurals: {},
select: {},
octothorpe: {},
});
});
});

View File

@ -1,57 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import Translation from '../../src/format/Translation';
import utils from '../../src/utils/utils';
describe('compile', function () {
const englishPlurals = {
plurals(value, ordinal) {
if (ordinal) {
return (
{
'1': 'one',
'2': 'two',
'3': 'few',
}[value] || 'other'
);
} else {
return value === 1 ? 'one' : 'other';
}
},
};
const prepare = (message, locale?, locales?) => {
const translation = new Translation(utils.compile(message), locale || 'en', locales, englishPlurals);
return translation.translate.bind(translation);
};
it('should compile message with variable', function () {
const cache = utils.compile('Hey {name}!');
expect(new Translation(cache, 'en', [], {}).translate({ name: 'Joe' })).toEqual('Hey Joe!');
});
it('should compile select', function () {
const translate = prepare('{value, select, female {She} other {They}}');
expect(translate({ value: 'female' })).toEqual('She');
expect(translate({ value: 'n/a' })).toEqual('They');
});
it('should compile plurals', function () {
const translate = prepare('{value, plural, one {{value} Book} other {# Books}}');
expect(translate({ value: 1 })).toEqual('1 Book');
expect(translate({ value: 2 })).toEqual('2 Books');
const translatePlurals = prepare('{value, plural, offset:1 =0 {No Books} one {# Book} other {# Books}}');
expect(translatePlurals({ value: 0 })).toEqual('No Books');
expect(translatePlurals({ value: 2 })).toEqual('1 Book');
expect(translatePlurals({ value: 3 })).toEqual('2 Books');
});
it('should compile selectordinal', function () {
const translateSelectordinal = prepare('{value, selectordinal, one {#st Book} two {#nd Book}}');
expect(translateSelectordinal({ value: 1 })).toEqual('1st Book');
expect(translateSelectordinal({ value: 2 })).toEqual('2nd Book');
});
});

View File

@ -1,89 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { DateTimeFormatter } from '../../../index';
describe('DateTimeFormatter', () => {
const date = new Date('2023-04-03T12:34:56Z');
it('date formatter is memoized', async () => {
const firstRunt0 = performance.now();
const dateTimeFormatter1 = new DateTimeFormatter('es', {});
dateTimeFormatter1.dateTimeFormat(new Date());
const firstRunt1 = performance.now();
const firstRunResult = firstRunt1 - firstRunt0;
const seconddRunt0 = performance.now();
const dateTimeFormatter2 = new DateTimeFormatter('es', {}, false);
dateTimeFormatter2.dateTimeFormat(new Date());
const seconddRunt1 = performance.now();
const secondRunResult = seconddRunt1 - seconddRunt0;
expect(secondRunResult).toBeLessThan(firstRunResult);
});
it('should format a date using default options', () => {
const formatted = new DateTimeFormatter('en').dateTimeFormat(date);
expect(formatted).toEqual('4/3/2023');
});
it('should format a date using custom options', () => {
const formatted = new DateTimeFormatter('en-US', { weekday: 'long' }).dateTimeFormat(date);
expect(formatted).toEqual('Monday');
});
it('should parse a date string and format it', () => {
const formatted = new DateTimeFormatter('en-US').dateTimeFormat(date.toISOString());
expect(formatted).toEqual('4/3/2023');
});
it('should memoize formatter instances by options and locales', () => {
const spy = jest.spyOn(Intl, 'DateTimeFormat');
const formatter1 = new DateTimeFormatter('en-US', { month: 'short' });
const formatter2 = new DateTimeFormatter('en-US', { month: 'short' });
const formatter3 = new DateTimeFormatter('en-GB', { month: 'short' });
formatter1.dateTimeFormat(date);
formatter2.dateTimeFormat(date);
formatter3.dateTimeFormat(date);
expect(spy).toHaveBeenCalledWith('en-US', { month: 'short' });
expect(spy).toHaveBeenCalledWith('en-GB', { month: 'short' });
});
it('should not memoize formatter instances when memoize is false', () => {
const spy = jest.spyOn(Intl, 'DateTimeFormat');
const formatter1 = new DateTimeFormatter('en-US', { month: 'short' }, false);
const formatter2 = new DateTimeFormatter('en-US', { month: 'short' }, false);
formatter1.dateTimeFormat(date);
formatter2.dateTimeFormat(date);
expect(spy).toHaveBeenCalledTimes(5);
expect(spy).toHaveBeenCalledWith('en-US', { month: 'short' });
});
it('should format a Date object correctly', () => {
const formatter = new DateTimeFormatter('en-US');
const date = new Date(2023, 0, 1);
const formatted = formatter.dateTimeFormat(date);
expect(formatted).toEqual('1/1/2023');
});
it('should format a string representation of date correctly', () => {
const formatter = new DateTimeFormatter('en-US');
const dateString = '2023-01-01';
const formatted = formatter.dateTimeFormat(dateString);
expect(formatted).toEqual('1/1/2023');
});
it('should format using specified format options', () => {
const formatter = new DateTimeFormatter('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
const date = new Date(2023, 0, 1);
const formatted = formatter.dateTimeFormat(date);
expect(formatted).toEqual('January 1, 2023');
});
it('should format using memorized formatter when useMemorize is true', () => {
const formatter = new DateTimeFormatter('en-US',{"year":'numeric'}, true);
const date = new Date(2023, 0, 1);
const formatted1 = formatter.dateTimeFormat(date);
const formatted2 = formatter.dateTimeFormat(date);
expect(formatted1).toEqual(formatted2);
});
});

View File

@ -1,88 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { NumberFormatter } from '../../../index';
describe('NumberFormatter', () => {
it('number formatter is memoized', async () => {
const firstRunt0 = performance.now();
const numberFormatter1 = new NumberFormatter('es', {});
numberFormatter1.numberFormat(10000);
const firstRunt1 = performance.now();
const firstRunResult = firstRunt1 - firstRunt0;
const seconddRunt0 = performance.now();
const numberFormatter = new NumberFormatter('es', {}, false);
numberFormatter.numberFormat(10000);
const secondRunt1 = performance.now();
const secondRunResult = secondRunt1 - seconddRunt0;
expect(secondRunResult).toBeLessThan(firstRunResult);
});
it('formats a number with default options', () => {
const format = new NumberFormatter('en-US');
expect(format.numberFormat(1000)).toBe('1,000');
});
it('formats a number with custom options', () => {
const format = new NumberFormatter('en-US', { style: 'currency', currency: 'USD' });
expect(format.numberFormat(1000)).toBe('$1,000.00');
});
it('does not memoize the formatter with different options', () => {
const format1 = new NumberFormatter('en-US', { style: 'currency', currency: 'USD' });
const format2 = new NumberFormatter('en-US', { style: 'currency', currency: 'EUR' });
expect(format1).not.toBe(format2);
});
it('formats negative numbers', () => {
const format = new NumberFormatter('en-US', { style: 'currency', currency: 'USD' });
expect(format.numberFormat(-1000)).toBe('-$1,000.00');
});
it('formats numbers with different locales', () => {
const format1 = new NumberFormatter('en-US', { style: 'currency', currency: 'USD' });
const format2 = new NumberFormatter('fr-FR', { style: 'currency', currency: 'EUR' });
expect(format1.numberFormat(1000)).toBe('$1,000.00');
});
it('should format a positive number correctly', () => {
const formatter = new NumberFormatter('en-US');
const number = 12345.6789;
const formatted = formatter.numberFormat(number);
expect(formatted).toEqual('12,345.679');
});
it('should format a negative number correctly', () => {
const formatter = new NumberFormatter('en-US');
const number = -12345.6789;
const formatted = formatter.numberFormat(number);
expect(formatted).toEqual('-12,345.679');
});
it('should format using specified format options', () => {
const formatOptions = { style: 'currency', currency: 'EUR' };
const formatter = new NumberFormatter('en-US', formatOptions);
const number = 12345.6789;
const formatted = formatter.numberFormat(number);
expect(formatted).toEqual('€12,345.68');
});
it('should format using memorized formatter when useMemorize is true', () => {
const formatter = new NumberFormatter('en-US', undefined, true);
const number = 12345.6789;
const formatted1 = formatter.numberFormat(number);
const formatted2 = formatter.numberFormat(number);
expect(formatted1).toEqual(formatted2);
});
it('should create a new formatter when useMemorize is false', () => {
const formatter = new NumberFormatter('en-US', undefined, false);
const number = 12345.6789;
const formatted1 = formatter.numberFormat(number);
const formatted2 = formatter.numberFormat(number);
expect(formatted1).toEqual(formatted2);
});
});

View File

@ -1,38 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import {getFormatMessage} from "../../src/format/getFormatMessage";
import I18n from "../../src/core/I18n";
describe('getFormatMessage', () => {
// Mocked i18nInstance object
const i18nInstance = new I18n({
messages: {
en: {
greeting: 'Hello, {name}!',
},
},
locale: 'en',
error: "missingMessage"
});
it('should return the correct translation for an existing message ID', () => {
const id = 'greeting';
const values = {name: 'John'};
const expectedResult = 'Hello, John!';
const result = getFormatMessage(i18nInstance, id, values);
expect(result).toEqual(expectedResult);
});
it('should return the default message when the translation is missing', () => {
const id = 'missingMessage';
const expectedResult = 'missingMessage';
const result = getFormatMessage(i18nInstance, id);
expect(result).toEqual(expectedResult);
});
});

View File

@ -1,38 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { defineMessage, defineMessages } from '../index';
describe('index', () => {
describe('defineMessages Test', () => {
it('return the input message object', () => {
const messages = {
greeting: {
id: 'greeting',
defaultMessage: 'Hello',
},
farewell: {
id: 'farewell',
defaultMessage: 'Goodbye',
},
};
const result = defineMessages(messages);
expect(result).toEqual(messages);
});
});
describe('defineMessage Test', () => {
it('return the input message object', () => {
const message = {
id: 'greeting',
defaultMessage: 'Hello',
};
const result = defineMessage(message);
expect(result).toEqual(message);
});
});
});

View File

@ -1,18 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { checkSelectType } from '../../src/parser/parser';
describe('checkSelectType function', () => {
it('should return true for valid select types', () => {
expect(checkSelectType('plural')).toBe(true);
expect(checkSelectType('select')).toBe(true);
expect(checkSelectType('selectordinal')).toBe(true);
});
it('should return false for invalid select types', () => {
expect(checkSelectType('invalid')).toBe(false);
expect(checkSelectType('other')).toBe(false);
expect(checkSelectType('')).toBe(false);
});
});

View File

@ -1,36 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import { checkStateGroup } from '../../src/parser/parseMappingRule';
describe('checkStateGroup function', () => {
it('should throw an error if state is missing', () => {
const group = { push: 'missingState' };
const name = 'currentName';
const map = { existingState: {} };
expect(() => {
checkStateGroup(group, name, map);
}).toThrowError('The state is missing.');
});
it('should throw an error if pop is not 1', () => {
const group = { pop: '2' };
const name = 'currentName';
const map = { existingState: {} };
expect(() => {
checkStateGroup(group, name, map);
}).toThrowError('The value of pop must be 1.');
});
it('should not throw an error if state and pop are valid', () => {
const group = { push: 'existingState', pop: '1' };
const name = 'currentName';
const map = { existingState: {} };
expect(() => {
checkStateGroup(group, name, map);
}).not.toThrow();
});
});

View File

@ -1,67 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import copyStaticProps from '../../src/utils/copyStaticProps';
describe('hoistNonReactStatics', () => {
test('should hoist static properties from sourceComponent to targetComponent', () => {
class SourceComponent {
static staticProp = 'sourceProp';
}
class TargetComponent {}
copyStaticProps(TargetComponent, SourceComponent);
expect((TargetComponent as any).staticProp).toBe('sourceProp');
});
test('should hoist static properties from inherited components', () => {
class SourceComponent {
static staticProp = 'sourceProp';
}
class InheritedComponent extends SourceComponent {}
class TargetComponent {}
copyStaticProps(TargetComponent, InheritedComponent);
expect((TargetComponent as any).staticProp).toBe('sourceProp');
});
test('should not hoist properties if descriptor is not valid', () => {
class SourceComponent {
get staticProp() {
return 'sourceProp';
}
}
class TargetComponent {}
copyStaticProps(TargetComponent, SourceComponent);
expect((TargetComponent as any).staticProp).toBeUndefined();
});
test('should not hoist properties if descriptor is not valid', () => {
class SourceComponent {
static get staticProp() {
return 'sourceProp';
}
}
class TargetComponent {}
copyStaticProps(TargetComponent, SourceComponent);
expect((TargetComponent as any).staticProp).toBe('sourceProp');
});
test('copyStaticProps should not copy static properties that already exist in target or source component', () => {
const targetComponent = { staticProp: 'target' };
const sourceComponent = { staticProp: 'source' };
copyStaticProps(targetComponent, sourceComponent);
expect(targetComponent.staticProp).toBe('source'); // The value should remain the same
});
});

View File

@ -1,49 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import EventEmitter from '../../src/utils/eventListener';
describe('eventEmitter', () => {
it('should call registered event listeners on emit', async () => {
const firstListener = jest.fn();
const secondListener = jest.fn(() => 'return value is ignored');
const emitter = new EventEmitter();
emitter.on('test', firstListener);
emitter.on('test', secondListener);
emitter.emit('test', 42);
expect(firstListener).toBeCalledWith(42);
expect(secondListener).toBeCalledWith(42);
});
it('should allow unsubscribing from events', () => {
const listener = jest.fn();
const emitter = new EventEmitter();
const unsubscribe = emitter.on('test', listener);
emitter.emit('test', 42);
expect(listener).toBeCalledWith(42);
listener.mockReset();
unsubscribe();
emitter.emit('test', 42);
expect(listener).not.toBeCalled();
});
it("should do nothing when even doesn't exist", () => {
const unknown = jest.fn();
const emitter = new EventEmitter();
// this should not throw
emitter.emit('test', 42);
// this should not throw
emitter.removeListener('test', unknown);
emitter.on('test', jest.fn());
// this should not throw
emitter.removeListener('test', unknown);
});
});

View File

@ -1,36 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import utils from '../../src/utils/utils';
describe('generateKey', () => {
it('should generate a key for a single locale without options', () => {
const result = utils.generateKey('en');
expect(result).toBe('en:{}');
});
it('should generate a key for multiple locales without options', () => {
const result = utils.generateKey(['en', 'fr']);
expect(result).toBe('en-fr:{}');
});
it('should sort multiple locales before generating the key', () => {
const result = utils.generateKey(['fr', 'en']);
expect(result).toBe('en-fr:{}');
});
it('should generate a key with options for a single locale', () => {
const result = utils.generateKey('en', { foo: 'bar' });
expect(result).toBe('en:{"foo":"bar"}');
});
it('should generate a key with options for multiple locales', () => {
const result = utils.generateKey(['en', 'fr'], { foo: 'bar' });
expect(result).toBe('en-fr:{"foo":"bar"}');
});
it('should sort multiple locales and consider options before generating the key', () => {
const result = utils.generateKey(['fr', 'en'], { foo: 'bar' });
expect(result).toBe('en-fr:{"foo":"bar"}');
});
});

View File

@ -1,162 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import getTokenAST from '../../src/utils/getTokenAST';
import * as assert from 'assert';
describe('getTokenAST', () => {
it('should return an array containing a string', () => {
const result = getTokenAST(['Hello', 'world']);
expect(result).toEqual(['Hello', 'world']);
});
it('should handle octothorpe tokens', () => {
const result = getTokenAST([{ type: 'octothorpe' }, 'Hello']);
expect(result).toContain('#');
});
it('should handle argument tokens', () => {
const result = getTokenAST([{ type: 'argument', arg: 'foo' }]);
expect(result).toEqual([['foo']]);
});
it('should handle function tokens with param', () => {
const result = getTokenAST([{ type: 'function', arg: 'foo', key: 'bar', param: { tokens: ['baz'] } }]);
expect(result).toEqual([['foo', 'bar', 'baz']]);
});
it('should handle function tokens without param', () => {
const result = getTokenAST([{ type: 'function', arg: 'foo', key: 'bar' }]);
expect(result).toEqual([['foo', 'bar']]);
});
it('should handle other tokens with offset', () => {
const result = getTokenAST([{ type: 'other', arg: 'foo', offset: '1', cases: [{ key: 'one', tokens: ['bar'] }] }]);
expect(result).toEqual([['foo', 'other', { offset: 1, one: ['bar'] }]]);
});
it('should handle other tokens without offset', () => {
const result = getTokenAST([{ type: 'other', arg: 'foo', cases: [{ key: 'one', tokens: ['bar'] }] }]);
expect(result).toEqual([['foo', 'other', { one: ['bar'] }]]);
});
it('returns [arg, key, param] if token type is "function"', () => {
const tokens = [
{
type: 'function',
arg: 'arg1',
key: 'key1',
param: { tokens: ['param1'] },
},
];
const result = getTokenAST(tokens);
expect(result).toEqual([['arg1', 'key1', 'param1']]);
});
it('If the input parameter is not an array, an error should be thrown.', () => {
const input = 'invalid input';
assert.throws(() => getTokenAST(input), Error);
});
it('应该返回包含字符串的数组', () => {
const tokens = [
'Hello',
{ type: 'octothorpe' },
{ type: 'argument', arg: 'name' },
{
type: 'function',
arg: 'formatDate',
key: 'date',
param: {
tokens: ['YYYY-MM-DD'],
},
},
];
const expected = ['Hello', '#', ['name'], ['formatDate', 'date', 'YYYY-MM-DD']];
const result = getTokenAST(tokens);
expect(result).toStrictEqual(expected);
});
it('对于复杂的 tokens 数组,应该返回嵌套的格式化数组', () => {
const expected = [
'Hello',
'#',
['name'],
['formatDate', 'date', 'YYYY-MM-DD'],
[
'formatNumber',
'number',
{
cases: [
{
key: 'uppercase',
tokens: [
'TRUE',
{
plural: {
cases: {
'=0': 'zero',
'=1': 'one',
other: 'other',
},
},
},
],
},
{
key: 'lowercase',
tokens: [
'lowercase',
'none',
{
cases: {
'=0': 'zero',
'=1': 'one',
other: 'other',
},
},
],
},
],
offset: 1,
},
],
];
const tokens = [
'Hello',
{ type: 'octothorpe' },
{ type: 'argument', arg: 'name' },
{
type: 'function',
arg: 'formatDate',
key: 'date',
param: {
tokens: ['YYYY-MM-DD'],
},
},
{
type: 'function',
arg: 'formatNumber',
key: 'number',
param: {
tokens: [
{
offset: 1,
cases: [
{
key: 'uppercase',
tokens: ['TRUE', { plural: { cases: { '=0': 'zero', '=1': 'one', other: 'other' } } }],
},
{
key: 'lowercase',
tokens: ['lowercase', 'none', { cases: { '=0': 'zero', '=1': 'one', other: 'other' } }],
},
],
},
],
},
},
];
const result = getTokenAST(tokens);
expect(result).toStrictEqual(expected);
});
});

View File

@ -1,269 +0,0 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
*/
import ruleUtils from '../../src/utils/parseRuleUtils';
describe('ruleUtils test', () => {
describe('getRegGroups function', () => {
it('should return the correct number of capturing groups in the regular expression pattern', () => {
const pattern1 = 'abc(def)ghi(jkl)mno';
const pattern2 = 'abc(def)ghi';
const pattern3 = 'abc';
const pattern4 = '';
const result1 = ruleUtils.getRegGroups(pattern1);
const result2 = ruleUtils.getRegGroups(pattern2);
const result3 = ruleUtils.getRegGroups(pattern3);
const result4 = ruleUtils.getRegGroups(pattern4);
expect(result1).toBe(2);
expect(result2).toBe(1);
expect(result3).toBe(0);
expect(result4).toBe(0);
});
});
describe('getReg', () => {
it('should return the correct regular expression when input is a string', () => {
const input1 = 'abc';
const input2 = '^\\d+$';
const input3 = '[a-zA-Z]';
const result1 = ruleUtils.getReg(input1);
const result2 = ruleUtils.getReg(input2);
const result3 = ruleUtils.getReg(input3);
expect(result1).toBe('(?:abc)');
expect(result2).toBe('(?:\\^\\\\d\\+\\$)');
expect(result3).toBe('(?:\\[a\\-zA\\-Z\\])');
});
it('should throw an error when input is an invalid regular expression object', () => {
const input = {
source: 'abc',
ignoreCase: true,
global: false,
sticky: false,
multiline: true,
};
expect(() => {
ruleUtils.getReg(input);
}).toThrowError('标志禁止使用');
});
it('should throw an error when input regular expression object has forbidden flags', () => {
const input1 = {
source: 'abc',
ignoreCase: true,
global: false,
sticky: false,
multiline: false,
};
const input2 = {
source: 'abc',
ignoreCase: false,
global: true,
sticky: false,
multiline: false,
};
const input3 = {
source: 'abc',
ignoreCase: false,
global: false,
sticky: true,
multiline: false,
};
const input4 = {
source: 'abc',
ignoreCase: false,
global: false,
sticky: false,
multiline: true,
};
expect(() => {
ruleUtils.getReg(input1);
}).toThrowError('/i 标志禁止使用');
expect(() => {
ruleUtils.getReg(input2);
}).toThrowError('/g 标志禁止使用');
expect(() => {
ruleUtils.getReg(input3);
}).toThrowError('/y 标志禁止使用');
expect(() => {
ruleUtils.getReg(input4);
}).toThrowError('/m 标志禁止使用');
});
});
describe('getRegUnion function', () => {
it('should return the correct regular expression union of input patterns', () => {
const patterns1 = ['abc', 'def', 'ghi'];
const patterns2 = ['\\d+', '[a-z]+', '[A-Z]+'];
const patterns3 = [];
const result1 = ruleUtils.getRegUnion(patterns1);
const result2 = ruleUtils.getRegUnion(patterns2);
const result3 = ruleUtils.getRegUnion(patterns3);
expect(result1).toBe('(?:(?:abc)|(?:def)|(?:ghi))');
expect(result2).toBe('(?:(?:\\d+)|(?:[a-z]+)|(?:[A-Z]+))');
expect(result3).toBe('(?!)');
});
});
describe('getRuleOptions function', () => {
it('should return the correct options object with default type', () => {
const type = 'content';
const obj = { match: 'abc' };
const result = ruleUtils.getRuleOptions(type, obj);
expect(result).toEqual({
defaultType: type,
lineBreaks: false,
pop: false,
next: null,
push: null,
error: false,
fallback: false,
value: null,
type: null,
shouldThrow: false,
match: ['abc'],
});
});
it('should throw an error if type property is a string', () => {
const type = 'content';
const obj = { match: 'abc', type: 'invalid' };
expect(() => {
ruleUtils.getRuleOptions(type, obj);
}).toThrowError('type 属性不能为字符串!');
});
it('should throw an error if match property includes include property', () => {
const type = 'content';
const obj = { match: 'abc', include: 'state' };
expect(() => {
ruleUtils.getRuleOptions(type, obj);
}).toThrowError('匹配规则不能包含状态!');
});
it('should sort the match property correctly', () => {
const type = 'content';
const obj = { match: ['abc', /def/, 'ghi', /[0-9]+/, /^xyz$/] };
const result = ruleUtils.getRuleOptions(type, obj);
expect(result.match).toEqual(['abc', 'ghi', /def/, /[0-9]+/, /^xyz$/]);
});
});
describe('getRulesByArray', () => {
it('should return an empty array if the input array is empty', () => {
const array = [];
const result = ruleUtils.getRulesByArray(array);
expect(result).toEqual([]);
});
it('should throw an error if a rule object does not have a type property', () => {
const array = [{ match: 'abc' }, { type: 'content', match: 'def' }];
expect(() => {
ruleUtils.getRulesByArray(array);
}).toThrowError('Rule 没有 type 属性');
});
it('should handle multiple include properties', () => {
const array = [
{ include: [{ type: 'other1', match: 'abc' }] },
{
include: [
{ type: 'other2', match: 'def' },
{ type: 'other3', match: 'ghi' },
],
},
];
const result = ruleUtils.getRulesByArray(array);
expect(result).toEqual([
{ include: { type: 'other1', match: 'abc' } },
{ include: { type: 'other2', match: 'def' } },
{ include: { type: 'other3', match: 'ghi' } },
]);
});
});
describe('getRulesByObject', () => {
it('should handle empty object correctly', () => {
const object = {};
const result = ruleUtils.getRulesByObject(object);
expect(result).toEqual([]);
});
it('should handle include property correctly', () => {
const object = {
include: ['rule1', 'rule2'],
};
const result = ruleUtils.getRulesByObject(object);
expect(result).toEqual([{ include: 'rule1' }, { include: 'rule2' }]);
});
it('should handle nested objects correctly', () => {
const object = {
rule1: {
match: 'abc',
next: 'rule2',
fallback: true,
},
rule2: [/def/, { match: 'ghi', push: 'rule3' }],
rule3: {
match: 'jkl',
error: true,
},
};
const result = ruleUtils.getRulesByObject(object);
const item1 = ruleUtils.getRuleOptions('rule1', { match: 'abc', next: 'rule2', fallback: true });
const item2 = ruleUtils.getRuleOptions('rule2', [/def/]);
delete item2[0];
const item3 = ruleUtils.getRuleOptions('rule2', { match: 'ghi', push: 'rule3' });
const item4 = ruleUtils.getRuleOptions('rule3', { match: 'jkl', error: true });
expect(result).toEqual([item1, item2, item3, item4]);
});
});
describe('transferReg function', () => {
it('should escape special characters in the input string', () => {
const input = '[-/\\^$*+?.()|[\\]{}]';
const expected = '\\[\\-\\/\\\\\\^\\$\\*\\+\\?\\.\\(\\)\\|\\[\\\\\\]\\{\\}\\]';
const result = ruleUtils.transferReg(input);
expect(result).toBe(expected);
});
it('should return the same string if no special characters are present', () => {
const input = 'abcdefg';
const result = ruleUtils.transferReg(input);
expect(result).toBe(input);
});
});
});

View File

@ -1,71 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "./build",
"incremental": false,
"sourceMap": true,
"allowJs": true,
// allowJs=true => tsc compile js as module, no type check
"checkJs": false,
// Disable ts error checking in js
"strict": true,
// js-ts mixed setting
"noImplicitReturns": true,
"noUnusedLocals": false,
// jsts.
"noUnusedParameters": false,
"noImplicitAny": false,
"noImplicitThis": true,
// "strictNullChecks": true,
"module": "es2015",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"target": "es5",
"jsx": "preserve",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": true,
"alwaysStrict": true,
"esModuleInterop": true,
"declaration": true,
"experimentalDecorators": true,
"downlevelIteration": true,
// 使@types/node
"lib": [
"dom",
"esnext",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020"
],
"strictNullChecks": true,
"paths": {
"react": [
"node_modules/react"
],
"react-dom": [
"node_modules/react-dom"
]
}
},
"include": [
"./src/**/*",
"./src/format/**/*.ts",
"./example/**/*"
],
"exclude": [
"node_modules",
"lib",
"**/*.spec.ts",
"dev"
],
"types": [
"node",
"jest",
"@testing-library/jest-dom"
]
}

View File

@ -1,52 +0,0 @@
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const isDevelopment = process.env.NODE_ENV === 'development';
const entryPath = './example/index.tsx';
module.exports = {
entry: resolve(__dirname, entryPath),
output: {
path: resolve(__dirname, './build'),
filename: 'main.js',
},
module: {
rules: [
{
test: /\.([t|j]s)x?$/i,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env',
[
"@babel/preset-react",
{
"runtime": "automatic", // 新增
"importSource": "inulajs" // 新增
}
],
'@babel/preset-typescript'],
},
},
},
],
},
mode: isDevelopment ? 'development' : 'production',
plugins: [
new HtmlWebpackPlugin({
template: resolve(__dirname, './example/index.html'),
}),
],
resolve: {
extensions: ['.tsx', '.jsx', '.ts', '.js', '.json'],
},
devServer: {
https: false,
host: 'localhost',
port: '8080',
open: true,
hot: true,
headers: {
connection: 'keep-alive',
},
},
};

View File

@ -1,59 +0,0 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
root: true,
plugins: ['jest', 'no-for-of-loops', 'no-function-declare-after-return', 'react', '@typescript-eslint'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 8,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
modules: true,
experimentalObjectRestSpread: true,
},
},
env: {
browser: true,
jest: true,
node: true,
es6: true,
},
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-empty-function': 'off',
semi: ['warn', 'always'],
quotes: ['warn', 'single'],
'accessor-pairs': 'off',
'brace-style': ['error', '1tbs'],
'func-style': ['warn', 'declaration', { allowArrowFunctions: true }],
'max-lines-per-function': 'off',
'object-curly-newline': 'off',
// 尾随逗号
'comma-dangle': ['error', 'only-multiline'],
'prefer-const': 'off',
'no-constant-condition': 'off',
'no-for-of-loops/no-for-of-loops': 'error',
'no-function-declare-after-return/no-function-declare-after-return': 'error',
},
globals: {
isDev: true,
isTest: true,
},
overrides: [
{
files: ['scripts/__tests__/**/*.js'],
globals: {
container: true,
},
},
],
};

View File

@ -1,2 +0,0 @@
/node_modules/
/dist/

View File

@ -1 +0,0 @@
node_modules

View File

@ -1,17 +0,0 @@
'use strict';
module.exports = {
printWidth: 120, // 一行120字符数如果超过会进行换行
tabWidth: 2, // tab等2个空格
useTabs: false, // 用空格缩进行
semi: true, // 行尾使用分号
singleQuote: true, // 字符串使用单引号
quoteProps: 'as-needed', // 仅在需要时在对象属性添加引号
jsxSingleQuote: false, // 在JSX中使用双引号
trailingComma: 'es5', // 使用尾逗号(对象、数组等)
bracketSpacing: true, // 对象的括号间增加空格
bracketSameLine: false, // 将多行JSX元素的>放在最后一行的末尾
arrowParens: 'avoid', // 在唯一的arrow函数参数周围省略括号
vueIndentScriptAndStyle: false, // 不缩进Vue文件中的<script>和<style>标记内的代码
endOfLine: 'lf', // 仅限换行(\n
};

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +0,0 @@
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
"browsers" : ["> 1%", "last 2 versions", "not ie <= 8"],
"node": "current"
},
useBuiltIns: "usage",
corejs: 3,
}
],
[
'@babel/preset-typescript',
]
]
};

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1691465395748" clover="3.2.0">
<project timestamp="1691465395748" name="All files">
<metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0" elements="0" coveredelements="0" complexity="0" loc="0" ncloc="0" packages="0" files="0" classes="0"/>
</project>
</coverage>

View File

@ -1,224 +0,0 @@
body, html {
margin:0; padding: 0;
height: 100%;
}
body {
font-family: Helvetica Neue, Helvetica, Arial;
font-size: 14px;
color:#333;
}
.small { font-size: 12px; }
*, *:after, *:before {
-webkit-box-sizing:border-box;
-moz-box-sizing:border-box;
box-sizing:border-box;
}
h1 { font-size: 20px; margin: 0;}
h2 { font-size: 14px; }
pre {
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
margin: 0;
padding: 0;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
}
a { color:#0074D9; text-decoration:none; }
a:hover { text-decoration:underline; }
.strong { font-weight: bold; }
.space-top1 { padding: 10px 0 0 0; }
.pad2y { padding: 20px 0; }
.pad1y { padding: 10px 0; }
.pad2x { padding: 0 20px; }
.pad2 { padding: 20px; }
.pad1 { padding: 10px; }
.space-left2 { padding-left:55px; }
.space-right2 { padding-right:20px; }
.center { text-align:center; }
.clearfix { display:block; }
.clearfix:after {
content:'';
display:block;
height:0;
clear:both;
visibility:hidden;
}
.fl { float: left; }
@media only screen and (max-width:640px) {
.col3 { width:100%; max-width:100%; }
.hide-mobile { display:none!important; }
}
.quiet {
color: #7f7f7f;
color: rgba(0,0,0,0.5);
}
.quiet a { opacity: 0.7; }
.fraction {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 10px;
color: #555;
background: #E8E8E8;
padding: 4px 5px;
border-radius: 3px;
vertical-align: middle;
}
div.path a:link, div.path a:visited { color: #333; }
table.coverage {
border-collapse: collapse;
margin: 10px 0 0 0;
padding: 0;
}
table.coverage td {
margin: 0;
padding: 0;
vertical-align: top;
}
table.coverage td.line-count {
text-align: right;
padding: 0 5px 0 20px;
}
table.coverage td.line-coverage {
text-align: right;
padding-right: 10px;
min-width:20px;
}
table.coverage td span.cline-any {
display: inline-block;
padding: 0 5px;
width: 100%;
}
.missing-if-branch {
display: inline-block;
margin-right: 5px;
border-radius: 3px;
position: relative;
padding: 0 4px;
background: #333;
color: yellow;
}
.skip-if-branch {
display: none;
margin-right: 10px;
position: relative;
padding: 0 4px;
background: #ccc;
color: white;
}
.missing-if-branch .typ, .skip-if-branch .typ {
color: inherit !important;
}
.coverage-summary {
border-collapse: collapse;
width: 100%;
}
.coverage-summary tr { border-bottom: 1px solid #bbb; }
.keyline-all { border: 1px solid #ddd; }
.coverage-summary td, .coverage-summary th { padding: 10px; }
.coverage-summary tbody { border: 1px solid #bbb; }
.coverage-summary td { border-right: 1px solid #bbb; }
.coverage-summary td:last-child { border-right: none; }
.coverage-summary th {
text-align: left;
font-weight: normal;
white-space: nowrap;
}
.coverage-summary th.file { border-right: none !important; }
.coverage-summary th.pct { }
.coverage-summary th.pic,
.coverage-summary th.abs,
.coverage-summary td.pct,
.coverage-summary td.abs { text-align: right; }
.coverage-summary td.file { white-space: nowrap; }
.coverage-summary td.pic { min-width: 120px !important; }
.coverage-summary tfoot td { }
.coverage-summary .sorter {
height: 10px;
width: 7px;
display: inline-block;
margin-left: 0.5em;
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
}
.coverage-summary .sorted .sorter {
background-position: 0 -20px;
}
.coverage-summary .sorted-desc .sorter {
background-position: 0 -10px;
}
.status-line { height: 10px; }
/* yellow */
.cbranch-no { background: yellow !important; color: #111; }
/* dark red */
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
.low .chart { border:1px solid #C21F39 }
.highlighted,
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
background: #C21F39 !important;
}
/* medium red */
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
/* light red */
.low, .cline-no { background:#FCE1E5 }
/* light green */
.high, .cline-yes { background:rgb(230,245,208) }
/* medium green */
.cstat-yes { background:rgb(161,215,106) }
/* dark green */
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
.high .chart { border:1px solid rgb(77,146,33) }
/* dark yellow (gold) */
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
.medium .chart { border:1px solid #f9cd0b; }
/* light yellow */
.medium { background: #fff4c2; }
.cstat-skip { background: #ddd; color: #111; }
.fstat-skip { background: #ddd; color: #111 !important; }
.cbranch-skip { background: #ddd !important; color: #111; }
span.cline-neutral { background: #eaeaea; }
.coverage-summary td.empty {
opacity: .5;
padding-top: 4px;
padding-bottom: 4px;
line-height: 1;
color: #888;
}
.cover-fill, .cover-empty {
display:inline-block;
height: 12px;
}
.chart {
line-height: 0;
}
.cover-empty {
background: white;
}
.cover-full {
border-right: none !important;
}
pre.prettyprint {
border: none !important;
padding: 0 !important;
margin: 0 !important;
}
.com { color: #999 !important; }
.ignore-none { color: #999; font-weight: normal; }
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -48px;
}
.footer, .push {
height: 48px;
}

View File

@ -1,87 +0,0 @@
/* eslint-disable */
var jumpToCode = (function init() {
// Classes of code we would like to highlight in the file view
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
// Elements to highlight in the file listing view
var fileListingElements = ['td.pct.low'];
// We don't want to select elements that are direct descendants of another match
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
// Selecter that finds elements on the page to which we can jump
var selector =
fileListingElements.join(', ') +
', ' +
notSelector +
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
// The NodeList of matching elements
var missingCoverageElements = document.querySelectorAll(selector);
var currentIndex;
function toggleClass(index) {
missingCoverageElements
.item(currentIndex)
.classList.remove('highlighted');
missingCoverageElements.item(index).classList.add('highlighted');
}
function makeCurrent(index) {
toggleClass(index);
currentIndex = index;
missingCoverageElements.item(index).scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center'
});
}
function goToPrevious() {
var nextIndex = 0;
if (typeof currentIndex !== 'number' || currentIndex === 0) {
nextIndex = missingCoverageElements.length - 1;
} else if (missingCoverageElements.length > 1) {
nextIndex = currentIndex - 1;
}
makeCurrent(nextIndex);
}
function goToNext() {
var nextIndex = 0;
if (
typeof currentIndex === 'number' &&
currentIndex < missingCoverageElements.length - 1
) {
nextIndex = currentIndex + 1;
}
makeCurrent(nextIndex);
}
return function jump(event) {
if (
document.getElementById('fileSearch') === document.activeElement &&
document.activeElement != null
) {
// if we're currently focused on the search input, we don't want to navigate
return;
}
switch (event.which) {
case 78: // n
case 74: // j
goToNext();
break;
case 66: // b
case 75: // k
case 80: // p
goToPrevious();
break;
}
};
})();
window.addEventListener('keydown', jumpToCode);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

View File

@ -1,101 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for All files</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1>All files</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">Unknown% </span>
<span class="quiet">Statements</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">Unknown% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">Unknown% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">Unknown% </span>
<span class="quiet">Lines</span>
<span class='fraction'>0/0</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input oninput="onInput()" type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line medium'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2023-08-08T03:29:55.735Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@ -1 +0,0 @@
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

View File

@ -1,196 +0,0 @@
/* eslint-disable */
var addSorting = (function() {
'use strict';
var cols,
currentSort = {
index: 0,
desc: false
};
// returns the summary table element
function getTable() {
return document.querySelector('.coverage-summary');
}
// returns the thead element of the summary table
function getTableHeader() {
return getTable().querySelector('thead tr');
}
// returns the tbody element of the summary table
function getTableBody() {
return getTable().querySelector('tbody');
}
// returns the th element for nth column
function getNthColumn(n) {
return getTableHeader().querySelectorAll('th')[n];
}
function onFilterInput() {
const searchValue = document.getElementById('fileSearch').value;
const rows = document.getElementsByTagName('tbody')[0].children;
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
if (
row.textContent
.toLowerCase()
.includes(searchValue.toLowerCase())
) {
row.style.display = '';
} else {
row.style.display = 'none';
}
}
}
// loads the search box
function addSearchBox() {
var template = document.getElementById('filterTemplate');
var templateClone = template.content.cloneNode(true);
templateClone.getElementById('fileSearch').oninput = onFilterInput;
template.parentElement.appendChild(templateClone);
}
// loads all columns
function loadColumns() {
var colNodes = getTableHeader().querySelectorAll('th'),
colNode,
cols = [],
col,
i;
for (i = 0; i < colNodes.length; i += 1) {
colNode = colNodes[i];
col = {
key: colNode.getAttribute('data-col'),
sortable: !colNode.getAttribute('data-nosort'),
type: colNode.getAttribute('data-type') || 'string'
};
cols.push(col);
if (col.sortable) {
col.defaultDescSort = col.type === 'number';
colNode.innerHTML =
colNode.innerHTML + '<span class="sorter"></span>';
}
}
return cols;
}
// attaches a data attribute to every tr element with an object
// of data values keyed by column name
function loadRowData(tableRow) {
var tableCols = tableRow.querySelectorAll('td'),
colNode,
col,
data = {},
i,
val;
for (i = 0; i < tableCols.length; i += 1) {
colNode = tableCols[i];
col = cols[i];
val = colNode.getAttribute('data-value');
if (col.type === 'number') {
val = Number(val);
}
data[col.key] = val;
}
return data;
}
// loads all row data
function loadData() {
var rows = getTableBody().querySelectorAll('tr'),
i;
for (i = 0; i < rows.length; i += 1) {
rows[i].data = loadRowData(rows[i]);
}
}
// sorts the table using the data for the ith column
function sortByIndex(index, desc) {
var key = cols[index].key,
sorter = function(a, b) {
a = a.data[key];
b = b.data[key];
return a < b ? -1 : a > b ? 1 : 0;
},
finalSorter = sorter,
tableBody = document.querySelector('.coverage-summary tbody'),
rowNodes = tableBody.querySelectorAll('tr'),
rows = [],
i;
if (desc) {
finalSorter = function(a, b) {
return -1 * sorter(a, b);
};
}
for (i = 0; i < rowNodes.length; i += 1) {
rows.push(rowNodes[i]);
tableBody.removeChild(rowNodes[i]);
}
rows.sort(finalSorter);
for (i = 0; i < rows.length; i += 1) {
tableBody.appendChild(rows[i]);
}
}
// removes sort indicators for current column being sorted
function removeSortIndicators() {
var col = getNthColumn(currentSort.index),
cls = col.className;
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
col.className = cls;
}
// adds sort indicators for current column being sorted
function addSortIndicators() {
getNthColumn(currentSort.index).className += currentSort.desc
? ' sorted-desc'
: ' sorted';
}
// adds event listeners for all sorter widgets
function enableUI() {
var i,
el,
ithSorter = function ithSorter(i) {
var col = cols[i];
return function() {
var desc = col.defaultDescSort;
if (currentSort.index === i) {
desc = !currentSort.desc;
}
sortByIndex(i, desc);
removeSortIndicators();
currentSort.index = i;
currentSort.desc = desc;
addSortIndicators();
};
};
for (i = 0; i < cols.length; i += 1) {
if (cols[i].sortable) {
// add the click event handler on the th so users
// dont have to click on those tiny arrows
el = getNthColumn(i).querySelector('.sorter').parentElement;
if (el.addEventListener) {
el.addEventListener('click', ithSorter(i));
} else {
el.attachEvent('onclick', ithSorter(i));
}
}
}
}
// adds sorting functionality to the UI
return function() {
if (!getTable()) {
return;
}
cols = loadColumns();
loadData();
addSearchBox();
addSortIndicators();
enableUI();
};
})();
window.addEventListener('load', addSorting);

View File

@ -1,58 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Horizon Request Cancel Request Test</title>
<link rel="stylesheet" type="text/css" href="cancelStyles.css">
</head>
<body>
<header>Horizon Request Cancel Request Test</header>
<div class="container">
<div class="button-group">
<button id="sendRequestButton">Send Request</button>
<button id="cancelRequestButton">Cancel Request</button>
</div>
<div class="message" id="message">等待发送请求...</div>
</div>
<script src="../../dist/bundle.js"></script>
<script>
const sendRequestButton = document.getElementById('sendRequestButton');
const cancelRequestButton = document.getElementById('cancelRequestButton');
const message = document.getElementById('message');
let cancelTokenSource;
sendRequestButton.addEventListener('click', function() {
message.innerHTML = '';
cancelTokenSource = inulaRequest.CancelToken.source();
inulaRequest.get('http://localhost:3001/data', {
cancelToken: cancelTokenSource.token
}).then(function(response) {
message.innerHTML = '请求成功: ' + JSON.stringify(response.data, null, 2);
}).catch(function(error) {
message.innerHTML = JSON.stringify(error, null, 2);
});
});
cancelRequestButton.addEventListener('click', function () {
const CancelToken = inulaRequest.CancelToken;
const source = CancelToken.source();
inulaRequest.get('http://localhost:3001/data', {
cancelToken: source.token
}).then(function(response) {
console.log(response.data);
}).catch(function(error) {
if (inulaRequest.isCancel(error)) {
message.innerHTML = '请求已被取消:' + error.message;
} else {
message.innerHTML = '请求出错:' + error.message;
}
});
source.cancel('请求被用户取消。');
});
</script>
</body>
</html>

View File

@ -1,58 +0,0 @@
body {
font-family: Arial, sans-serif;
background-color: #f8f8f8;
padding: 0;
margin: 0;
}
header {
display: flex;
justify-content: center;
align-items: center;
height: 80px;
background-color: #007bff;
color: #fff;
font-size: 24px;
font-weight: bold;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.button-group {
display: flex;
justify-content: center;
align-items: center;
margin-top: 30px;
}
button {
font-family: Arial, sans-serif;
font-size: 16px;
font-weight: bold;
color: #fff;
background-color: #007bff;
border: none;
border-radius: 4px;
padding: 10px 20px;
cursor: pointer;
transition: background-color 0.3s ease-in-out;
margin: 5px;
}
button:hover {
background-color: #0062cc;
}
.message {
text-align: center;
font-size: 18px;
margin-top: 30px;
padding: 20px;
border-radius: 4px;
background-color: #fff;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}

View File

@ -1,87 +0,0 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 2rem;
background-color: #f0f2f5;
}
header {
display: flex;
justify-content: center;
align-items: center;
height: 80px;
background-color: #007bff;
color: #fff;
font-size: 24px;
font-weight: bold;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h2 {
color: #333;
border-bottom: 1px solid #ccc;
padding-bottom: 0.5rem;
text-align: center;
margin-bottom: 1.5rem;
}
h3 {
text-align: center;
font-weight: 600;
margin-bottom: 0.5rem;
}
.button {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
}
button {
background-color: #007bff;
border: none;
border-radius: 0.25rem;
color: white;
cursor: pointer;
font-size: 1rem;
padding: 0.5rem 1rem;
text-align: center;
text-decoration: none;
display: inline-block;
margin: 0.25rem 1rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease-in-out;
}
button:hover {
background-color: #0056b3;
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.1);
}
pre {
background-color: #f8f8f8;
border: 1px solid #ccc;
padding: 10px;
white-space: pre-wrap;
word-wrap: break-word;
text-align: center;
font-size: large;
border-radius: 4px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
width: 50vh;
display: inline-block;
}
.response-container {
margin-top: 1rem;
background-color: #fff;
padding: 1.5rem;
border-radius: 4px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-bottom: 2rem;
}
.pre-container {
text-align: center;
}

View File

@ -1,104 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Inula Request Interceptor Test</title>
<link rel="stylesheet" type="text/css" href="interceptorStyles.css">
</head>
<body>
<header>Inula Request interceptor Test</header>
<h2>使用拦截器:</h2>
<div class="response-container">
<h3>响应状态码:</h3>
<div class="pre-container">
<pre id="responseStatusWithInterceptor">等待发送请求...</pre>
</div>
<h3>请求拦截反馈:</h3>
<div class="pre-container">
<pre id="requestInterceptorFeedback">等待发送请求...</pre>
</div>
<h3>响应拦截反馈:</h3>
<div class="pre-container">
<pre id="responseInterceptorFeedback">等待发送请求...</pre>
</div>
<h3>响应数据:</h3>
<div class="pre-container">
<pre id="responseDataWithInterceptor">等待发送请求...</pre>
</div>
<div class="button">
<button id="sendRequestWithInterceptor">发送请求</button>
</div>
</div>
<h2>不使用拦截器:</h2>
<div class="response-container">
<h3>响应状态码:</h3>
<div class="pre-container">
<pre id="responseStatusWithoutInterceptor">等待发送请求...</pre>
</div>
<h3>拦截反馈:</h3>
<div class="pre-container">
<pre id="noInterceptorFeedback">等待发送请求...</pre>
</div>
<h3>响应数据:</h3>
<div class="pre-container">
<pre id="responseDataWithoutInterceptor">等待发送请求...</pre>
</div>
<div class="button">
<button id="sendRequestWithoutInterceptor">发送请求</button>
</div>
</div>
<script src="../../dist/bundle.js"></script>
<script>
// 创建使用拦截器的 IR 实例
const irInstance = inulaRequest.create();
// 添加请求拦截器
irInstance.interceptors.request.use(function(config) {
// 为请求添加自定义请求头
config.headers['IR-Custom-Header'] = 'CustomHeaderValue';
document.getElementById('requestInterceptorFeedback').textContent = '请求已拦截并添加请求头IR-Custom-Header';
return config;
}, function(error) {
return Promise.reject(error);
});
// 添加响应拦截器
irInstance.interceptors.response.use(function(response) {
// 更新响应状态码
response.status = 404;
document.getElementById('responseStatusWithInterceptor').textContent = String(response.status);
document.getElementById('responseInterceptorFeedback').textContent = '响应已被拦截状态响应码被强制修改成404'
return response;
}, function(error) {
return Promise.reject(error);
});
// 使用拦截器的请求
document.getElementById('sendRequestWithInterceptor').addEventListener('click', function () {
irInstance.get('http://localhost:3001/')
.then(function (response) {
document.getElementById('responseDataWithInterceptor').textContent = JSON.stringify(response.data, null, 2);
}).catch(function (error) {
document.getElementById('responseDataWithInterceptor').textContent = 'Request failed:' + error;
})
});
// 不使用拦截器的请求
document.getElementById('sendRequestWithoutInterceptor').addEventListener('click', function () {
inulaRequest.get('http://localhost:3001/')
.then(function (response) {
document.getElementById('responseStatusWithoutInterceptor').textContent = response.status;
document.getElementById('responseDataWithoutInterceptor').textContent = JSON.stringify(response.data, null, 2);
document.getElementById('noInterceptorFeedback').textContent = '请求未被拦截';
}).catch(function (error) {
document.getElementById('responseDataWithoutInterceptor').textContent = 'Request failed:' + error;
})
});
</script>
</body>
</html>

View File

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>downloadTest</title>
</head>
<body>
<h3>Hello Horizon Request Master!</h3>
</body>
</html>

View File

@ -1,86 +0,0 @@
body {
font-family: Arial, sans-serif;
background-color: #f8f8f8;
margin: 0;
padding: 0;
}
header {
display: flex;
justify-content: center;
align-items: center;
height: 80px;
background-color: #007bff;
color: #fff;
font-size: 24px;
font-weight: bold;
}
h1 {
text-align: center;
margin-top: 50px;
color: #2c3e50;
font-size: 36px;
}
.container {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin-bottom: 50px;
}
.card {
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 5px #aaa;
width: 320px;
padding: 20px;
margin: 20px;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 0 15px #aaa;
transform: translateY(-5px);
}
.card h2 {
margin-bottom: 10px;
font-size: 24px;
color: #2c3e50;
}
.card pre {
background-color: #f0f0f0;
padding: 10px;
font-size: 14px;
border-radius: 5px;
white-space: pre-wrap;
word-wrap: break-word;
}
.button {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
}
button {
font-family: Arial, sans-serif;
font-size: 16px;
font-weight: bold;
color: #fff;
background-color: #007bff;
border: none;
border-radius: 4px;
padding: 10px 20px;
cursor: pointer;
transition: background-color 0.3s ease-in-out;
}
button:hover {
background-color: #0056b3;
}

View File

@ -1,214 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Inula Request API Test</title>
<link rel="stylesheet" type="text/css" href="requestStyles.css">
</head>
<body>
<header>Inula Request API Test</header>
<div class="container">
<div class="card">
<h2>Request</h2>
<pre id="request-result">等待发送请求...</pre>
</div>
</div>
<div class="container">
<div class="card">
<h2>GET Request</h2>
<pre id="get-result">等待发送请求...</pre>
</div>
<div class="card">
<h2>POST Request</h2>
<pre id="post-result">等待发送请求...</pre>
</div>
<div class="card">
<h2>PUT Request</h2>
<pre id="put-result">等待发送请求...</pre>
</div>
<div class="card">
<h2>DELETE Request</h2>
<pre id="delete-result">等待发送请求...</pre>
</div>
</div>
<div class="container">
<div class="card">
<h2>HEAD Request</h2>
<pre id="head-result">等待发送请求...</pre>
</div>
<div class="card">
<h2>OPTIONS Request</h2>
<pre id="options-result">等待发送请求...</pre>
</div>
<div class="card">
<h2>PATCH Request</h2>
<pre id="patch-result">等待发送请求...</pre>
</div>
</div>
<div class="container">
<div class="card" style="height: 250px">
<h2>UPLOAD Request</h2>
<input type="file" id="fileInput">
<pre id="upload-progress">UploadProgress: 0%</pre>
<pre id="upload-result">等待发送请求...</pre>
</div>
<div class="card" style="height: 250px">
<h2>DOWNLOAD Request</h2>
<div style="height: 23px"></div>
<pre id="download-progress">DownloadProgress: 0%</pre>
<pre id="download-result">等待发送请求...</pre>
</div>
</div>
<div class="button">
<button id="queryButton">点击发送请求</button>
</div>
<br>
<div class="button">
<button id="resetButton">点击重置</button>
</div>
<script src="../../dist/bundle.js"></script>
<script>
const requestResult = document.getElementById('request-result');
const getResult = document.getElementById('get-result');
const postResult = document.getElementById('post-result');
const putResult = document.getElementById('put-result');
const deleteResult = document.getElementById('delete-result');
const headResult = document.getElementById('head-result');
const optionsResult = document.getElementById('options-result');
const patchResult = document.getElementById('patch-result');
const fileInput = document.getElementById('fileInput');
const uploadProgress = document.getElementById('upload-progress');
const uploadResult = document.getElementById('upload-result');
const downloadProgress = document.getElementById('download-progress');
const downloadResult = document.getElementById('download-result');
const queryButton = document.getElementById('queryButton');
const resetButton = document.getElementById('resetButton');
queryButton.addEventListener('click', function () {
const irInstance = inulaRequest.create();
irInstance.request('http://localhost:3001/', {method: 'GET', data: {}})
.then(function (response) {
requestResult.innerHTML = JSON.stringify(response.data, null, 2);
})
.catch(function (error) {
requestResult.innerHTML = JSON.stringify(error, null, 2);
})
inulaRequest.default.get('http://localhost:3001/')
.then(function (response) {
getResult.innerHTML = JSON.stringify(response.data, null, 2);
})
.catch(function (error) {
getResult.innerHTML = JSON.stringify(error, null, 2);
});
inulaRequest('http://localhost:3001/', {method:'POST', name: 'Alice'})
.then(function (response) {
postResult.innerHTML = JSON.stringify(response.data, null, 2);
})
.catch(function (error) {
postResult.innerHTML = JSON.stringify(error, null, 2);
});
inulaRequest.put('http://localhost:3001/users', {id: 1, name: 'Bob'})
.then(function (response) {
putResult.innerHTML = JSON.stringify(response.data, null, 2);
})
.catch(function (error) {
putResult.innerHTML = JSON.stringify(error, null, 2);
});
inulaRequest.delete('http://localhost:3001/users', {params: {id: 1}})
.then(function (response) {
deleteResult.innerHTML = JSON.stringify(response.data, null, 2);
})
.catch(function (error) {
deleteResult.innerHTML = JSON.stringify(error, null, 2);
});
inulaRequest.head('http://localhost:3001/')
.then(function (response) {
headResult.innerHTML = 'Header: ' + JSON.stringify(response.headers['x-powered-by'], null, 2); // IE 浏览器不支持 HEAD 方式访问响应头
})
.catch(function (error) {
headResult.innerHTML = JSON.stringify(error, null, 2);
});
inulaRequest.options('http://localhost:3001/', {
headers: {
'Access-Control-Request-Method': 'POST'
}
})
.then(function (response) {
optionsResult.innerHTML = 'status: ' + JSON.stringify(response.status, null, 2);
})
.catch(function (error) {
optionsResult.innerHTML = JSON.stringify(error, null, 2);
});
inulaRequest.patch('http://localhost:3001/', {name: 'IR'})
.then(function (response) {
patchResult.innerHTML = JSON.stringify(response.data, null, 2);
})
.catch(function (error) {
patchResult.innerHTML = JSON.stringify(error, null, 2);
});
inulaRequest.get('http://localhost:3001/download', {
responseType: 'text',
onDownloadProgress: function (progressEvent) {
const loaded = progressEvent.loaded;
const total = progressEvent.total;
const progressPercentage = Math.round((loaded / total) * 100);
downloadProgress.innerHTML = 'Download progress: ' + progressPercentage + '%';
},
}).then(function (response) {
downloadResult.innerHTML = JSON.stringify(response.data, null, 2);
}).catch(function (error) {
downloadResult.innerHTML = JSON.stringify(error, null, 2);
})
});
resetButton.addEventListener('click', function () {
requestResult.innerHTML = '等待发送请求...';
getResult.innerHTML = '等待发送请求...';
postResult.innerHTML = '等待发送请求...';
putResult.innerHTML = '等待发送请求...';
deleteResult.innerHTML = '等待发送请求...';
headResult.innerHTML = '等待发送请求...';
optionsResult.innerHTML = '等待发送请求...';
patchResult.innerHTML = '等待发送请求...';
fileInput.value = '';
uploadProgress.innerHTML = 'UploadProgress: 0%';
uploadResult.innerHTML = '等待发送请求...';
downloadProgress.innerHTML = 'DownloadProgress: 0%';
downloadResult.innerHTML = '等待发送请求...';
});
function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
inulaRequest.post('http://localhost:3001/', formData, {
onUploadProgress: function (progressEvent) {
const loaded = progressEvent.loaded;
const total = progressEvent.total;
const progressPercentage = Math.round((loaded / total) * 100);
uploadProgress.innerHTML = 'Upload progress: ' + progressPercentage + '%';
},
})
.then(function (response) {
uploadResult.innerHTML = JSON.stringify(response.data, null, 2);
})
.catch(function (error) {
uploadResult.innerHTML = JSON.stringify(error, null, 2);
});
}
fileInput.addEventListener('change', function (event) {
const file = event.target.files[0];
uploadFile(file);
});
</script>
</body>
</html>

View File

@ -1,93 +0,0 @@
import express from "express";
import * as fs from "fs";
import bodyParser from "body-parser";
import cors from "cors";
const app = express();
const port = 3001;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 自定义 CORS 配置
const corsOptions = {
origin: '*',
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
allowedHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'X-Requested-With,content-type', 'IR-Custom-Header'],
exposedHeaders: ['X-Powered-By'],
optionsSuccessStatus: 200, // 设置 OPTIONS 请求成功时的状态码为 200
credentials: true
};
app.use(cors(corsOptions));
// 处理 GET 请求
app.get('/', (req, res) => {
res.send('Hello Inula Request!');
})
app.get('/data', (req, res) => {
const data = {
message: 'Hello Inula Request!',
};
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(data));
});
app.get('/download', (req, res) => {
const filePath = 'D:\\code\\MRcode\\inula-Request\\examples\\request\\downloadTest.html';
const fileName = 'downloadTest.html';
const fileSize = fs.statSync(filePath).size;
if (fs.existsSync(filePath)) {
res.setHeader('Content-Disposition', `attachment; filename=${fileName}`);
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Length', fileSize);
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
} else {
res.status(404).send('File not found');
}
});
// 处理 POST 请求
app.post('/', (req, res) => {
res.send('Got a POST request!');
});
// 处理 PUT 请求
app.put('/users', (req, res) => {
res.send('Got a PUT request at /users');
});
// 处理 DELETE 请求
app.delete('/users', (req, res) => {
res.send('Got a DELETE request at /users');
});
// 处理 HEAD 请求
app.head('/', (req, res) => {
res.setHeader('x-powered-by', 'Express');
res.json({ 'x-powered-by': 'Express' });
res.sendStatus(200);
});
// 处理 OPTIONS 请求
app.options('/', (req, res) => {
res.sendStatus(200);
});
// 处理 PATCH 请求
app.patch('/', (req, res) => {
const name = req.body.name;
const message = `Hello, ${name}! Your name has been updated.`;
res.setHeader('Content-Type', 'text/plain');
res.send(message);
});
// 启动服务器
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});

View File

@ -1,36 +0,0 @@
import Inula, { useState } from 'inulajs';
import { useIR } from '../../index';
const App = () => {
const [message, setMessage] = useState('等待发送请求...');
const [isSent, setSend] = useState(false);
const options = {
pollingInterval: 3000,
enablePollingOptimization: true,
limitation: {minInterval: 500, maxInterval: 4000}
};
const {data} = useIR('http://localhost:3001/', null, options);
const handleClick = () => {
setSend(true);
}
return (
<>
<header>useIR Test</header>
<div className="container">
<div className="card">
<h2 style={{whiteSpace: "pre-wrap"}}>{options ? `实时数据流已激活\n更新间隔${options?.pollingInterval} ms`
: '实时数据流未激活'}</h2>
<pre>{isSent ? data : message}</pre>
</div>
</div>
<div className="button">
<button onClick={handleClick}>点击发送请求</button>
</div>
</>
);
}
export default App;

View File

@ -1,98 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>useIR Test</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f8f8;
margin: 0;
padding: 0;
}
header {
display: flex;
justify-content: center;
align-items: center;
height: 80px;
background-color: #007bff;
color: #fff;
font-size: 24px;
font-weight: bold;
}
h1 {
text-align: center;
margin-top: 50px;
color: #2c3e50;
font-size: 36px;
}
.container {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin-bottom: 50px;
}
.card {
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 5px #aaa;
width: 320px;
padding: 20px;
margin: 20px;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 0 15px #aaa;
transform: translateY(-5px);
}
.card h2 {
margin-bottom: 10px;
font-size: 24px;
color: #2c3e50;
}
.card pre {
background-color: #f0f0f0;
padding: 10px;
font-size: 14px;
border-radius: 5px;
white-space: pre-wrap;
word-wrap: break-word;
}
.button {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
}
button {
font-family: Arial, sans-serif;
font-size: 16px;
font-weight: bold;
color: #fff;
background-color: #007bff;
border: none;
border-radius: 4px;
padding: 10px 20px;
cursor: pointer;
transition: background-color 0.3s ease-in-out;
}
button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@ -1,9 +0,0 @@
import Inula from 'inulajs';
import App from "./App ";
Inula.render(
<Inula.Fragment>
<App/>
</Inula.Fragment>,
document.querySelector('#root')
)

View File

@ -1,57 +0,0 @@
import inulaRequest from './src/inulaRequest';
import useIR from './src/core/useIR/useIR';
const {
create,
request,
get,
post,
put,
['delete']: propToDelete,
head,
options,
InulaRequest,
IrError,
CanceledError,
isCancel,
CancelToken,
all,
Cancel,
isIrError,
spread,
IrHeaders,
// 兼容axios
Axios,
AxiosError,
AxiosHeaders,
isAxiosError,
} = inulaRequest;
export {
create,
request,
get,
post,
put,
propToDelete as delete,
head,
options,
InulaRequest,
IrError,
CanceledError,
isCancel,
CancelToken,
all,
Cancel,
isIrError,
spread,
IrHeaders,
useIR,
// 兼容axios
Axios,
AxiosError,
AxiosHeaders,
isAxiosError,
};
export default inulaRequest;

View File

@ -1,25 +0,0 @@
module.exports = {
clearMocks: true,
collectCoverage: true,
coverageDirectory: "coverage",
coverageProvider: "v8",
moduleFileExtensions: [
"js",
"mjs",
"cjs",
"jsx",
"ts",
"tsx",
"json",
"node"
],
testEnvironment: "jest-environment-jsdom",
testMatch: ["**/tests/**/*.test.[jt]s?(x)"],
testPathIgnorePatterns: [
"\\\\node_modules\\\\"
],
transform: {
"^.+\\.(js|jsx)$": "babel-jest",
"^.+\\.(ts|tsx)$": "babel-jest"
},
};

View File

@ -1,11 +0,0 @@
{
"restartable": "rs",
"ignore": [
".git",
"node_modules/**/node_modules"
],
"env": {
"NODE_ENV": "development"
},
"ext": "js,json"
}

View File

@ -1,69 +0,0 @@
{
"name": "inula-request",
"version": "1.0.20",
"description": "Horizon-request brings you a convenient request experience!",
"main": "index.ts",
"scripts": {
"test": "jest --config jest.config.cjs",
"buildExample": "rollup -c rollup.config.example.js --bundleConfigAsCjs",
"build": "rollup -c rollup.config.js --bundleConfigAsCjs",
"useIRExample": "webpack serve --config webpack.useHR.config.js --mode development",
"server": "nodemon .\\examples\\server\\serverTest.mjs"
},
"files": [
"dist",
"README.md"
],
"repository": {
"type": "git",
"url": "ssh://git@szv-open.codehub.huawei.com:2222/innersource/fenghuang/horizon/horizon-ecosystem.git"
},
"keywords": [
"inula-request"
],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.21.8",
"@babel/preset-env": "^7.21.5",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.21.4",
"@rollup/plugin-commonjs": "^19.0.0",
"@types/jest": "^29.2.5",
"@types/react": "^17.0.34",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.1",
"babel-jest": "^20.0.3",
"babel-loader": "^9.1.0",
"body-parser": "^1.20.2",
"core-js": "3.32.1",
"cors": "^2.8.5",
"eslint": "^8.31.0",
"express": "^4.18.2",
"html-webpack-plugin": "^5.5.3",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.4.1",
"jsdom": "^22.0.0",
"nodemon": "^2.0.22",
"prettier": "^2.6.2",
"rollup": "^3.20.2",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.34.1",
"ts-jest": "^29.0.4",
"ts-loader": "^9.4.2",
"ts-node": "^10.9.1",
"tslib": "^2.5.0",
"typescript": "^4.9.4",
"webpack": "^5.81.0",
"webpack-cli": "^5.0.2",
"webpack-dev-server": "^4.13.3"
},
"dependencies": {
"inulajs": "0.0.11"
},
"exclude": [
"node_modules"
]
}

Some files were not shown because too many files have changed in this diff Show More