import {isPlainObject, merge, snakeCase} from 'lodash-es' export const DEFAULT_LOCALE = 'zh_cn' type LocaleTextDisposer = (...args: any[]) => string export type LocaleText = string | LocaleTextDisposer /** * 字典通用结构 * * localeDict.level_0.level_1.level_2.level_... */ export interface LocaleDict { [key: string]: LocaleText | LocaleDict } /** * 词库 * { * confirm_label: 'OK', * total: (total: number) => (total > 1 ? 'items' : 'item'), * per_page: '$1 Per Page' * } */ export interface LocalePack { [key: string]: LocaleText } /** * 组件库字典标准结构,只有 3 层 as `localeDict.locale.component.word` */ export interface StandardLocaleDict extends LocaleDict { [locale: string]: { [componentName: string]: LocalePack } } /** * 组件字典结构,内部使用,只有 2 层 as `componentDict.locale.word` */ export interface ComponentLocaleDict extends LocaleDict { [locale: string]: LocalePack } /** * 组件国际化信息对象 */ export interface ComponentLocale { componentName: string defaultLocaleDict: T } // function isLocaleText(value: any): value is LocaleText { // const typeOfValue = typeof value // return typeOfValue === 'string' || typeOfValue === 'function' // } /** * 标准化词库 * - all keys should be snake_case * - support flat shape like { 'locale_a.component_b.word_c': 'text' } */ export function normalizeKeyToSnakeCaseDeep(obj: T) { if (!isPlainObject(obj)) { return obj } const ret = {} Object.keys(obj).forEach(key => { let keyToSnake = key let valueToNormalize = obj[key] as LocaleDict // {'locale.component.word': 'text'} => { locale: { component: { word: 'text'} } } const idxDot = key.indexOf('.') const subKey = key.substring(idxDot + 1) if (subKey && subKey !== key) { keyToSnake = key.substring(0, idxDot) valueToNormalize = {[subKey]: obj[key]} } merge(ret, { [snakeCase(keyToSnake)]: normalizeKeyToSnakeCaseDeep(valueToNormalize), }) }) return ret as T } /** * 常见标准的 component locale 对象 * all keys should be snake_case */ export function createComponentLocale( componentName: string, defaultLocaleDict: T ): ComponentLocale { return { componentName: snakeCase(componentName), defaultLocaleDict: normalizeKeyToSnakeCaseDeep(defaultLocaleDict), } } export type LocaleTextReplacement = string | number | null | void export function disposeLocaleText(text: string, ...args: LocaleTextReplacement[]) { if (!text) { return '' } if (args.length) { const m = text.match(/\$\{(\d+)\}/g) if (m && m.length) { const minIndex = parseInt(m.sort()[0].substr(2), 10) if (minIndex === 1) { args.unshift('') } } return text.replace(/\$\{(\d+)\}/g, (match: string, p) => { const index = parseInt(p, 10) const replacement = args[index] // 数字直接转成 string if (typeof replacement === 'number') { return `${replacement}` } // null undefined 为空串 return replacement || '' }) } return text }