Initial commit of akmon project

This commit is contained in:
2026-01-20 08:04:15 +08:00
commit 77a2bab985
1309 changed files with 343305 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
// @ts-nocheck
import { warn, isObject } from './util'
type Token = {
type : 'text' | 'named' | 'list' | 'unknown',
value : string
}
const RE_TOKEN_LIST_VALUE = /^(?:\d)+/
const RE_TOKEN_NAMED_VALUE = /^(?:\w)+/
/**
* 解析格式化字符串并生成一个包含标记Token的数组。
* 这些标记可以是文本、列表或命名值。
*
* @param {string} format - 需要解析的格式化字符串。
* @returns {Array<Token>} 返回一个包含解析后的标记的数组。
*/
export function parse(format : string) : Array<Token> {
const tokens : Array<Token> = []
let position : number = 0
let text : string = ''
while (position < format.length) {
let char : string = format.charAt(position++)
if (char == '{') {
if (text.length > 0) {
const token : Token = { type: 'text', value: text }
tokens.push(token)
}
text = ''
let sub : string = ''
char = format.charAt(position++)
while (char != '}') {
sub += char
char = format.charAt(position++)
}
const isClosed = char == '}'
const type = RE_TOKEN_LIST_VALUE.test(sub)
? 'list'
: isClosed && RE_TOKEN_NAMED_VALUE.test(sub)
? 'named'
: 'unknown'
const token : Token = { type, value: sub }
tokens.push(token)
} else if (char == '%') {
// when found rails i18n syntax, skip text capture
if (format.charAt(position) != '{') {
text += char
}
} else {
text += char
}
}
if (text.length > 0) {
const token : Token = { type: 'text', value: text }
tokens.push(token)
}
return tokens
}
/**
* 根据给定的标记数组和值对象或数组,编译出相应的值数组。
*
* @param {Array<Token>} tokens - 标记数组,包含文本、列表和命名值。
* @param {Object | Array<any>} values - 值对象或数组,用于替换标记中的占位符。
* @returns {Array<any>} 返回编译后的值数组。
*/
function compile(tokens : Array<Token>, values : UTSJSONObject) : Array<any>
function compile(tokens : Array<Token>, values : Array<any>) : Array<any>
function compile(tokens : Array<Token>, values : any) : Array<any> {
const compiled : Array<any> = []
let index : number = 0;
const mode : string = Array.isArray(values)
? 'list'
: isObject(values)
? 'named'
: 'unknown'
if (mode == 'unknown') {
return compiled
}
while (index < tokens.length) {
const token : Token = tokens[index]
switch (token.type) {
case 'text':
compiled.push(token.value)
break
case 'list':
const index = parseInt(token.value, 10)
if(mode == 'list') {
const value = (values as any[])[index]
compiled.push(value)
} else {
if (process.env.NODE_ENV !== 'production') {
warn('list did not receive a valid values array')
}
}
break
case 'named':
if (mode == 'named') {
const value = (values as UTSJSONObject)[token.value] ?? ''
compiled.push(`${value}`)
} else {
if (process.env.NODE_ENV !== 'production') {
warn(`Type of token '${token.type}' and format of value '${mode}' don't match!`)
}
}
break
case 'unknown':
if(token.value.startsWith("'") && token.value.endsWith("'")) {
compiled.push(token.value.slice(1, -1))
} else if (process.env.NODE_ENV !== 'production') {
warn(`Detect 'unknown' type of token!`)
}
break
}
index++
}
return compiled
}
export {compile}
export default class BaseFormatter {
private _caches : Map<string, Token[]>
constructor() {
this._caches = new Map<string, Token[]>()
}
interpolate(message : string, values : any | null) : any[] {
if (values == null) {
return [message]
}
let tokens : Array<Token> | null = this._caches.get(message)
if (tokens == null) {
tokens = parse(message)
this._caches.set(message, tokens)
}
return compile(tokens, values)
}
}