From 0c8a602bab4fcb9bf9ea21793387fbd9cf56b86b Mon Sep 17 00:00:00 2001 From: shijistar Date: Thu, 2 Jul 2026 01:47:35 +0800 Subject: [PATCH 1/2] feat: add autoLocalize runtime config --- .storybook/docs-source/ApiGuide.en-US.md | 34 +++++++ .storybook/docs-source/ApiGuide.zh-CN.md | 34 +++++++ README-FULL.md | 43 ++++++++ README-FULL.zh-CN.md | 43 ++++++++ src/auto-localize.ts | 123 +++++++++++++++++++++++ src/enum-collection.ts | 13 ++- src/enum-item.ts | 88 ++++++++++------ src/enum-items.ts | 64 +++++++----- src/enum.ts | 17 ++-- src/global-config.ts | 3 + test/auto-localize.test.ts | 67 ++++++++++++ test/test-suites/interface.ts | 27 +++++ test/test-suites/localization.ts | 99 ++++++++++++++++++ 13 files changed, 589 insertions(+), 66 deletions(-) create mode 100644 src/auto-localize.ts create mode 100644 test/auto-localize.test.ts diff --git a/.storybook/docs-source/ApiGuide.en-US.md b/.storybook/docs-source/ApiGuide.en-US.md index baeab67b..6bba5edd 100644 --- a/.storybook/docs-source/ApiGuide.en-US.md +++ b/.storybook/docs-source/ApiGuide.en-US.md @@ -339,6 +339,40 @@ const WeekEnum = Enum(enumInit, { }); ``` +## ⚙️ autoLocalize + +`{ nameTemplate?: string | Function, itemTemplate?: Record }` + +Automatically generates localization keys for the enum name, item labels, and item meta fields. It is the recommended unified replacement for new localization setups. Legacy `labelPrefix`, `autoLabel`, and `autoLocalizeMeta` continue to work. + +```ts +Enum.config.autoLocalize = { + nameTemplate: 'enum.{name}.name', + itemTemplate: { + label: 'enum.{name}.{item}.label', + description: 'enum.{name}.{item}.description', + }, +}; + +const WeekEnum = Enum( + { Sunday: { value: 0 }, Monday: { value: 1 } }, + { + name: 'week', + autoLocalize: { + itemTemplate: { abbr: 'enum.{name}.{item}.abbr' }, + }, + }, +); + +WeekEnum.named.Sunday.description; // localize('enum.week.Sunday.description') +WeekEnum.named.Sunday.abbr; // localize('enum.week.Sunday.abbr') +WeekEnum.items.meta.description; // string[] +``` + +Templates support `{name}`, `{item}`, and `{field}`. Instance-level item templates merge with global templates field by field and override same-name fields. Template-declared meta fields are generated even when raw enum items do not declare them. For TypeScript inference, prefer literal instance-level template keys. + +> `autoLocalizeMeta` remains the correct legacy option name. `autoLocalizedMeta` and `!abbr` exclusion syntax are not supported. + ## ⚙️ autoLabel `boolean | ((params: { item: EnumItemClass; labelPrefix?: any }) => string)` diff --git a/.storybook/docs-source/ApiGuide.zh-CN.md b/.storybook/docs-source/ApiGuide.zh-CN.md index 995a80ab..53799214 100644 --- a/.storybook/docs-source/ApiGuide.zh-CN.md +++ b/.storybook/docs-source/ApiGuide.zh-CN.md @@ -336,6 +336,40 @@ const WeekEnum = Enum(enumInit, { }); ``` +## ⚙️ autoLocalize + +`{ nameTemplate?: string | Function, itemTemplate?: Record }` + +自动为枚举名称、枚举项标签和枚举项元数据字段生成本地化 key。这是新的统一配置方式。旧的 `labelPrefix`、`autoLabel`、`autoLocalizeMeta` 仍继续兼容。 + +```ts +Enum.config.autoLocalize = { + nameTemplate: 'enum.{name}.name', + itemTemplate: { + label: 'enum.{name}.{item}.label', + description: 'enum.{name}.{item}.description', + }, +}; + +const WeekEnum = Enum( + { Sunday: { value: 0 }, Monday: { value: 1 } }, + { + name: 'week', + autoLocalize: { + itemTemplate: { abbr: 'enum.{name}.{item}.abbr' }, + }, + }, +); + +WeekEnum.named.Sunday.description; // localize('enum.week.Sunday.description') +WeekEnum.named.Sunday.abbr; // localize('enum.week.Sunday.abbr') +WeekEnum.items.meta.description; // string[] +``` + +模板支持 `{name}`、`{item}`、`{field}`。实例级 item templates 会和全局 templates 按字段合并,并覆盖同名字段。模板声明的元数据字段即使没有出现在原始枚举项中,也会自动生成。TypeScript 类型推导建议使用实例级字面量模板字段。 + +> `autoLocalizeMeta` 仍然是正确的旧 API 名称。`autoLocalizedMeta` 和 `!abbr` 排除语法均不支持。 + ## ⚙️ autoLabel `boolean | ((params: { item: EnumItemClass; labelPrefix?: any }) => string)` diff --git a/README-FULL.md b/README-FULL.md index 78115f14..e666eff4 100644 --- a/README-FULL.md +++ b/README-FULL.md @@ -632,6 +632,49 @@ const WeekEnum = Enum(enumInit, { }); ``` +### ⚙️ autoLocalize + +`{ nameTemplate?: string | Function, itemTemplate?: Record }` + +Automatically generates localization keys for the enum name, item labels, and item meta fields. It is the recommended unified replacement for new localization setups. `labelPrefix`, `autoLabel`, and `autoLocalizeMeta` are still supported for backward compatibility. + +Templates can be strings using `{name}`, `{item}`, and `{field}`, or functions that receive `{ field, item, options, resource }`. Instance-level `autoLocalize.itemTemplate` is merged with `Enum.config.autoLocalize.itemTemplate` field by field, and instance fields override global fields with the same name. + +```ts +Enum.config.autoLocalize = { + nameTemplate: 'enum.{name}.name', + itemTemplate: { + label: 'enum.{name}.{item}.label', + description: 'enum.{name}.{item}.description', + }, +}; + +const WeekEnum = Enum( + { + Sunday: { value: 0 }, + Monday: { value: 1 }, + }, + { + name: 'week', + autoLocalize: { + itemTemplate: { + abbr: 'enum.{name}.{item}.abbr', + }, + }, + }, +); + +WeekEnum.name; // localize('enum.week.name') +WeekEnum.named.Sunday.label; // localize('enum.week.Sunday.label') +WeekEnum.named.Sunday.description; // localize('enum.week.Sunday.description') +WeekEnum.named.Sunday.abbr; // localize('enum.week.Sunday.abbr') +WeekEnum.items.meta.description; // string[] +``` + +Meta fields declared by `autoLocalize.itemTemplate`, such as `description` and `abbr`, are generated even when raw enum items do not declare those fields. For TypeScript inference, prefer declaring instance-level templates with literal keys. Global-only template fields are runtime-capable but cannot be inferred precisely by normal TypeScript generics. + +> `autoLocalizeMeta` is still the correct legacy option name. `autoLocalizedMeta` is not a supported API, and there is no `!abbr` exclusion syntax. + ### ⚙️ autoLabel `boolean | ((params: { item: EnumItemClass; labelPrefix?: any }) => string)` diff --git a/README-FULL.zh-CN.md b/README-FULL.zh-CN.md index e397f9b3..acfcce60 100644 --- a/README-FULL.zh-CN.md +++ b/README-FULL.zh-CN.md @@ -627,6 +627,49 @@ const WeekEnum = Enum(enumInit, { }); ``` +### ⚙️ autoLocalize + +`{ nameTemplate?: string | Function, itemTemplate?: Record }` + +自动为枚举名称、枚举项标签和枚举项元数据字段生成本地化 key。这是新的统一配置方式,推荐新项目优先使用。`labelPrefix`、`autoLabel`、`autoLocalizeMeta` 仍会保留,用于兼容旧 API。 + +模板可以是包含 `{name}`、`{item}`、`{field}` 的字符串,也可以是接收 `{ field, item, options, resource }` 的函数。实例级 `autoLocalize.itemTemplate` 会和 `Enum.config.autoLocalize.itemTemplate` 按字段合并;同名字段以实例级配置为准。 + +```ts +Enum.config.autoLocalize = { + nameTemplate: 'enum.{name}.name', + itemTemplate: { + label: 'enum.{name}.{item}.label', + description: 'enum.{name}.{item}.description', + }, +}; + +const WeekEnum = Enum( + { + Sunday: { value: 0 }, + Monday: { value: 1 }, + }, + { + name: 'week', + autoLocalize: { + itemTemplate: { + abbr: 'enum.{name}.{item}.abbr', + }, + }, + }, +); + +WeekEnum.name; // localize('enum.week.name') +WeekEnum.named.Sunday.label; // localize('enum.week.Sunday.label') +WeekEnum.named.Sunday.description; // localize('enum.week.Sunday.description') +WeekEnum.named.Sunday.abbr; // localize('enum.week.Sunday.abbr') +WeekEnum.items.meta.description; // string[] +``` + +由 `autoLocalize.itemTemplate` 声明的元数据字段(例如 `description`、`abbr`),即使没有出现在原始枚举项中,也会自动生成。TypeScript 类型推导方面,建议在实例级模板中使用字面量字段名;仅通过全局配置声明的字段运行时可用,但普通 TypeScript 泛型无法精确推导。 + +> `autoLocalizeMeta` 仍然是正确的旧 API 名称。`autoLocalizedMeta` 不是受支持的 API,也不支持 `!abbr` 这类排除语法。 + ### ⚙️ autoLabel `boolean | ((params: { item: EnumItemClass; labelPrefix?: any }) => string)` diff --git a/src/auto-localize.ts b/src/auto-localize.ts new file mode 100644 index 00000000..581e6d91 --- /dev/null +++ b/src/auto-localize.ts @@ -0,0 +1,123 @@ +import { internalConfig } from './global-config'; + +export interface AutoLocalizeTemplateContext { + field: string; + item?: Item; + options?: Options; + resource?: unknown; +} + +export type AutoLocalizeTemplate = + | string + | ((context: AutoLocalizeTemplateContext) => string | undefined); + +export interface AutoLocalizeConfig { + nameTemplate?: AutoLocalizeTemplate; + itemTemplate?: Record>; +} + +export type AutoLocalizeOption = + | AutoLocalizeConfig + | ((context: AutoLocalizeTemplateContext) => string | undefined); + +export type LiteralStringKeys = string extends keyof T ? never : Extract; + +export type AutoLocalizeItemTemplateFields = Options extends { autoLocalize?: infer AutoLocalize } + ? AutoLocalize extends (...args: never[]) => unknown + ? never + : AutoLocalize extends { itemTemplate?: infer ItemTemplate } + ? Exclude>, 'label'> + : never + : never; + +export type AutoLocalizeMetaRecord = { + readonly [Key in AutoLocalizeItemTemplateFields]: string; +}; + +export function mergeAutoLocalizeConfig( + local?: AutoLocalizeOption, +): AutoLocalizeConfig | undefined { + const global = internalConfig.autoLocalize as AutoLocalizeOption | undefined; + const normalizedGlobal = normalizeAutoLocalizeConfig(global); + const normalizedLocal = normalizeAutoLocalizeConfig(local); + if (!normalizedGlobal) { + return normalizedLocal; + } + if (!normalizedLocal) { + return normalizedGlobal; + } + return { + nameTemplate: normalizedLocal.nameTemplate ?? normalizedGlobal.nameTemplate, + itemTemplate: { + ...(normalizedGlobal.itemTemplate ?? {}), + ...(normalizedLocal.itemTemplate ?? {}), + }, + }; +} + +export function normalizeAutoLocalizeConfig( + config?: AutoLocalizeOption, +): AutoLocalizeConfig | undefined { + if (!config) { + return undefined; + } + if (typeof config === 'function') { + return { itemTemplate: { label: config } }; + } + return config; +} + +export function resolveAutoLocalizeTemplate( + template: AutoLocalizeTemplate | undefined, + context: AutoLocalizeTemplateContext, +): string | undefined { + if (!template) { + return undefined; + } + if (typeof template === 'function') { + return template(context); + } + return template + .split('{name}') + .join(String((context.options as { name?: unknown } | undefined)?.name ?? '')) + .split('{item}') + .join(String((context.item as { key?: unknown } | undefined)?.key ?? '')) + .split('{field}') + .join(context.field); +} + +export function getAutoLocalizeTemplateFields( + options?: { autoLocalize?: AutoLocalizeOption } | unknown, +) { + const resolvedOptions = options as { autoLocalize?: AutoLocalizeOption } | undefined; + const config = mergeAutoLocalizeConfig(resolvedOptions?.autoLocalize); + return Object.keys(config?.itemTemplate ?? {}); +} + +export function isAutoLocalizeMetaField( + field: string, + options?: + | { + autoLocalizeMeta?: boolean | readonly (string | number | symbol)[]; + autoLocalize?: AutoLocalizeOption; + } + | unknown, +) { + const resolvedOptions = options as + | { + autoLocalizeMeta?: boolean | readonly (string | number | symbol)[]; + autoLocalize?: AutoLocalizeOption; + } + | undefined; + if (field === 'label') { + return true; + } + if (resolvedOptions?.autoLocalizeMeta === true) { + return true; + } + if (Array.isArray(resolvedOptions?.autoLocalizeMeta) && resolvedOptions.autoLocalizeMeta.includes(field)) { + return true; + } + const config = mergeAutoLocalizeConfig(resolvedOptions?.autoLocalize); + return field in (config?.itemTemplate ?? {}); +} diff --git a/src/enum-collection.ts b/src/enum-collection.ts index 174ac2b2..102118c5 100644 --- a/src/enum-collection.ts +++ b/src/enum-collection.ts @@ -1,4 +1,5 @@ import type { EnumExtension } from 'enum-plus/extension'; +import { mergeAutoLocalizeConfig, resolveAutoLocalizeTemplate } from './auto-localize'; import type { EnumInitOptions } from './enum'; import type { EnumItemInterface, EnumItemOptions } from './enum-item'; import type { EnumItemFields, InheritableEnumItems, MapResult, ToListConfig, ToMapConfig } from './enum-items'; @@ -123,11 +124,19 @@ export class EnumCollectionClass< if (typeof opts?.name === 'function') { return opts.name(undefined!); } + const autoLocalize = mergeAutoLocalizeConfig(opts?.autoLocalize); + const localeKey = autoLocalize?.nameTemplate + ? resolveAutoLocalizeTemplate(autoLocalize.nameTemplate, { + field: 'name', + options: opts, + resource: opts?.name, + }) + : opts?.name; const localize = opts?.localize ?? localizer.localize; if (typeof localize === 'function') { - return localize(opts?.name); + return localize(localeKey); } - return opts?.name; + return localeKey; } label> | NonNullable> | undefined>(keyOrValue: KV) { diff --git a/src/enum-item.ts b/src/enum-item.ts index efe4292f..a9bbe747 100644 --- a/src/enum-item.ts +++ b/src/enum-item.ts @@ -1,3 +1,11 @@ +import { + type AutoLocalizeMetaRecord, + type AutoLocalizeOption, + getAutoLocalizeTemplateFields, + isAutoLocalizeMetaField, + mergeAutoLocalizeConfig, + resolveAutoLocalizeTemplate, +} from './auto-localize'; import { internalConfig, localizer } from './global-config'; import type { EnumItemInit, @@ -16,11 +24,12 @@ export type EnumItemInterface< V extends EnumValue = ValueTypeFromSingleInit, // eslint-disable-next-line @typescript-eslint/no-explicit-any LP = any, + OP = unknown, > = EnumItemClass & // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style { [key in Exclude]: T[key]; - }; + } & AutoLocalizeMetaRecord; /** * - **EN:** Represents a single item in an enumeration collection. @@ -67,35 +76,35 @@ export class EnumItemClass< _options: { value: options }, }); - // Determines whether a property should be auto localized based on the autoLocalizeMeta option + // Determines whether a property should be auto localized based on autoLocalizeMeta + // and autoLocalize.itemTemplate. Template-declared meta fields are generated even + // when the raw enum item does not explicitly declare the property. const autoLocalizePropMap: PropertyDescriptorMap = {}; - if (typeof raw === 'object') { - const autoLocalizeMeta = options?.autoLocalizeMeta; - Object.keys(raw).forEach((metaKey) => { - if (!['value', 'label'].includes(metaKey)) { - if ( - autoLocalizeMeta === true || - (Array.isArray(autoLocalizeMeta) && autoLocalizeMeta.includes(metaKey as never)) - ) { - const descriptor = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get: function get(this: EnumItemClass): any { - // @ts-expect-error: because _metaKey is dynamically added to the getter function - const { _metaKey } = get; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return this._localizeResource((this.raw as any)[_metaKey]); - }, - enumerable: true, - }; - autoLocalizePropMap[metaKey] = descriptor; + const rawMetaKeys = + raw && typeof raw === 'object' && Object.prototype.toString.call(raw) === '[object Object]' + ? Object.keys(raw).filter((metaKey) => !['value', 'label'].includes(metaKey)) + : []; + const templateMetaKeys = getAutoLocalizeTemplateFields(options).filter((metaKey) => metaKey !== 'label'); + const metaKeys = Array.from(new Set([...rawMetaKeys, ...templateMetaKeys])); + metaKeys.forEach((metaKey) => { + if (isAutoLocalizeMetaField(metaKey, options)) { + const descriptor = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + get: function get(this: EnumItemClass): any { // @ts-expect-error: because _metaKey is dynamically added to the getter function - descriptor.get._metaKey = metaKey; - } else { - this[metaKey as keyof this] = (raw as object)[metaKey as never]; - } - } - }); - } + const { _metaKey } = get; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return this._localizeResource((this.raw as any)?.[_metaKey], _metaKey); + }, + enumerable: true, + }; + autoLocalizePropMap[metaKey] = descriptor; + // @ts-expect-error: because _metaKey is dynamically added to the getter function + descriptor.get._metaKey = metaKey; + } else if (raw && typeof raw === 'object') { + this[metaKey as keyof this] = (raw as object)[metaKey as never]; + } + }); // Define getters to localize i18n key into localized text defines(this, { @@ -226,15 +235,24 @@ export class EnumItemClass< } return content; } - private _localizeResource(resource: EnumItemLabel | undefined) { + private _localizeResource(resource: EnumItemLabel | undefined, field = 'label') { const labelPrefix = this._options?.labelPrefix; const autoLabel = this._options?.autoLabel ?? internalConfig.autoLabel; + const autoLocalize = mergeAutoLocalizeConfig(this._options?.autoLocalize); let localeKey = resource; if (typeof localeKey === 'function') { // eslint-disable-next-line @typescript-eslint/no-explicit-any return localeKey(this as any); } - if (autoLabel && labelPrefix != null) { + const template = autoLocalize?.itemTemplate?.[field]; + if (template) { + localeKey = resolveAutoLocalizeTemplate(template, { + field, + item: this, + options: this._options, + resource, + }) as EnumItemLabel | undefined; + } else if (field === 'label' && autoLabel && labelPrefix != null) { if (typeof autoLabel === 'function') { localeKey = autoLabel({ item: this, @@ -244,7 +262,7 @@ export class EnumItemClass< localeKey = `${labelPrefix as string}${resource}`; } } - return this._localize(localeKey) ?? localeKey; + return this._localize(localeKey as string | undefined) ?? localeKey; } } @@ -297,6 +315,14 @@ export interface EnumItemOptions< */ autoLabel?: boolean | ((options: { item: EnumItemClass; labelPrefix: LP }) => string); + /** + * - **EN:** Automatically generate locale keys for enum name, item label, and item meta fields. + * This is the new unified localization configuration. `labelPrefix` and `autoLabel` are kept + * for backward compatibility. + * - **CN:** 自动生成枚举名称、枚举项标签和枚举项元信息字段的本地化键名。这是新的统一本地化配置。`labelPrefix` 和 `autoLabel` 会继续保留以兼容旧 API。 + */ + autoLocalize?: AutoLocalizeOption, EnumItemOptions>; + /** * - **EN:** Set the array of meta information fields to be automatically localized, similar to the * handling of `label`. diff --git a/src/enum-items.ts b/src/enum-items.ts index e6ba24e1..05343d7a 100644 --- a/src/enum-items.ts +++ b/src/enum-items.ts @@ -1,3 +1,8 @@ +import { + type AutoLocalizeItemTemplateFields, + getAutoLocalizeTemplateFields, + isAutoLocalizeMetaField, +} from './auto-localize'; import { EnumItemClass, type EnumItemInterface, type EnumItemOptions } from './enum-item'; import type { EnumInit, @@ -103,28 +108,29 @@ export class EnumItemsArray< this.push(item); named[key] = item; - // Collect custom meta fields + // Collect custom meta fields, including fields declared by autoLocalize.itemTemplate. const itemRaw = raw[key]; - if (itemRaw && typeof itemRaw === 'object') { - Object.keys(itemRaw).forEach((k) => { - const metaKey = k as Exclude; - if (!['value', 'label'].includes(k)) { - if (meta[metaKey] == null) { - meta[metaKey] = []; - } - const metaValue = item[k as never]; - if (metaValue != null) { - meta[metaKey].push(metaValue); - } - } - }); - } + const rawMetaKeys = + itemRaw && typeof itemRaw === 'object' && Object.prototype.toString.call(itemRaw) === '[object Object]' + ? Object.keys(itemRaw).filter((k) => !['value', 'label'].includes(k)) + : []; + const templateMetaKeys = getAutoLocalizeTemplateFields(options).filter((k) => k !== 'label'); + Array.from(new Set([...rawMetaKeys, ...templateMetaKeys])).forEach((k) => { + const metaKey = k as Exclude; + if (meta[metaKey] == null) { + meta[metaKey] = []; + } + const metaValue = item[k as never]; + if (metaValue != null) { + meta[metaKey].push(metaValue); + } + }); }); const autoLocalizeMeta = options?.autoLocalizeMeta; // Freeze meta arrays Object.keys(meta).forEach((k) => { - const autoLocalize = autoLocalizeMeta && (autoLocalizeMeta === true || autoLocalizeMeta.includes(k as never)); + const autoLocalize = isAutoLocalizeMetaField(k, options); if (autoLocalize) { const descriptor = { get: function get(): unknown[] { @@ -146,7 +152,7 @@ export class EnumItemsArray< freeze(meta[k as keyof typeof meta]); } }); - if (autoLocalizeMeta) { + if (autoLocalizeMeta || getAutoLocalizeTemplateFields(options).some((k) => k !== 'label')) { define(meta, '_items', { value: this }); } @@ -438,7 +444,8 @@ export interface IEnumItems< V extends EnumValue = ValueTypeFromSingleInit, // eslint-disable-next-line @typescript-eslint/no-explicit-any LP = any, -> extends InheritableEnumItems { + OP = unknown, +> extends InheritableEnumItems { /** * - **EN:** A boolean value indicates that this is an enum items array. * - **CN:** 布尔值,表示这是一个枚举项数组 @@ -489,7 +496,8 @@ export interface IEnumItems< T[key], key, ValueTypeFromSingleInit, - LP + LP, + OP >; }; @@ -499,9 +507,10 @@ export interface IEnumItems< * - **CN:** 获取枚举项的全部自定义元字段,返回一个对象,其中key为字段名,value为每个字段的原始值数组 */ readonly meta: T extends object - ? { [K in Exclude]: T[keyof T][K][] } - : // eslint-disable-next-line @typescript-eslint/ban-types - {}; + ? { [K in Exclude]: T[keyof T][K][] } & { + [K in AutoLocalizeItemTemplateFields]: string[]; + } + : { [K in AutoLocalizeItemTemplateFields]: string[] }; } // typeof IS_ENUM_ITEMS | typeof ITEMS | typeof KEYS | typeof VALUES | 'labels' | 'meta' | 'named' @@ -511,6 +520,7 @@ export interface InheritableEnumItems< V extends EnumValue = ValueTypeFromSingleInit, // eslint-disable-next-line @typescript-eslint/no-explicit-any LP = any, + OP = unknown, > { /** * - **EN:** A method that determines if a constructor object recognizes an object as one of the @@ -601,18 +611,20 @@ export interface InheritableEnumItems< ? undefined : NonNullable extends K ? // @ts-expect-error: because the type infer is not clever enough, KV here should be one of K - EnumItemInterface], NonNullable, FindValueByKey>> + EnumItemInterface], NonNullable, FindValueByKey>, LP, OP> : NonNullable extends V ? EnumItemInterface< // @ts-expect-error: because the type infer is not clever enough, KV here should be one of V T[FindEnumKeyByValue>], FindEnumKeyByValue>, - NonNullable + NonNullable, + LP, + OP > : PrimitiveOf extends KV - ? EnumItemInterface | undefined + ? EnumItemInterface | undefined : PrimitiveOf extends KV - ? EnumItemInterface | undefined + ? EnumItemInterface | undefined : undefined); /** diff --git a/src/enum.ts b/src/enum.ts index bc0ef440..15184f77 100644 --- a/src/enum.ts +++ b/src/enum.ts @@ -1,4 +1,5 @@ import type { EnumExtension } from 'enum-plus/extension'; +import type { AutoLocalizeOption } from './auto-localize'; import { EnumCollectionClass, EnumExtensionClass } from './enum-collection'; import type { EnumItemInterface, EnumItemOptions } from './enum-item'; import type { IEnumItems, InheritableEnumItems } from './enum-items'; @@ -148,7 +149,7 @@ export interface EnumInterface { OP extends EnumInitOptions = EnumInitOptions, >( raw: T, - options?: EnumInitOptions, + options?: OP, ): IEnum & NativeEnumMembers; /** @@ -219,6 +220,8 @@ export interface EnumInterface { // eslint-disable-next-line @typescript-eslint/no-explicit-any labelPrefix: any; }) => string); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + autoLocalize?: AutoLocalizeOption; }; /** @@ -330,7 +333,7 @@ export interface IEnum< ? // eslint-disable-next-line @typescript-eslint/no-explicit-any EnumItemInterface[] : T extends { items: unknown } - ? EnumItemInterface[] & IEnumItems + ? EnumItemInterface[] & IEnumItems : never; /** * - **EN:** All items in the enumeration as an array @@ -348,7 +351,7 @@ export interface IEnum< EnumItemInterface[] & IEnumItems : T extends { items: unknown } ? ValueTypeFromSingleInit - : EnumItemInterface[] & IEnumItems; + : EnumItemInterface[] & IEnumItems; /** * - **EN:** Alias for the `keys` array, when any enum key conflicts with `keys`, you can access all * enum keys through this alias @@ -428,7 +431,7 @@ export interface IEnum< ? // eslint-disable-next-line @typescript-eslint/no-explicit-any Record> : T extends { named: unknown } - ? IEnumItems['named'] + ? IEnumItems['named'] : never; /** * - **EN:** Get all names of the enumeration items as an array @@ -442,7 +445,7 @@ export interface IEnum< Record> : T extends { named: unknown } ? ValueTypeFromSingleInit - : IEnumItems['named']; + : IEnumItems['named']; /** * - **EN:** Alias for the `meta` array, when any enum key conflicts with `meta`, you can access all * enum meta information through this alias @@ -452,7 +455,7 @@ export interface IEnum< readonly [META]: IsAny extends true ? Record : T extends { meta: unknown } - ? IEnumItems['meta'] + ? IEnumItems['meta'] : never; /** * - **EN:** Get all meta information of the enumeration items as an array @@ -469,7 +472,7 @@ export interface IEnum< ? Record : T extends { meta: unknown } ? ValueTypeFromSingleInit - : IEnumItems['meta']; + : IEnumItems['meta']; } export type NativeEnumMembers< diff --git a/src/global-config.ts b/src/global-config.ts index 1f06021e..59577f36 100644 --- a/src/global-config.ts +++ b/src/global-config.ts @@ -1,3 +1,4 @@ +import type { AutoLocalizeOption } from './auto-localize'; import type { LocalizeInterface } from './localize-interface'; import { defaultLocalize } from './utils'; @@ -20,6 +21,8 @@ export const internalConfig: { // eslint-disable-next-line @typescript-eslint/no-explicit-any labelPrefix: any; }) => string); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + autoLocalize?: AutoLocalizeOption; } = { autoLabel: true, }; diff --git a/test/auto-localize.test.ts b/test/auto-localize.test.ts new file mode 100644 index 00000000..6c5d81b6 --- /dev/null +++ b/test/auto-localize.test.ts @@ -0,0 +1,67 @@ +import { Enum } from '../src'; +import { + isAutoLocalizeMetaField, + mergeAutoLocalizeConfig, + normalizeAutoLocalizeConfig, + resolveAutoLocalizeTemplate, +} from '../src/auto-localize'; + +describe('autoLocalize helpers', () => { + test('normalizes function shorthand to label itemTemplate', () => { + const template = ({ item }: { item?: { key: string } }) => `weekday.${item?.key}`; + + expect(normalizeAutoLocalizeConfig(template)).toEqual({ + itemTemplate: { + label: template, + }, + }); + }); + + test('resolves empty and function templates', () => { + expect(resolveAutoLocalizeTemplate(undefined, { field: 'label' })).toBe(undefined); + expect( + resolveAutoLocalizeTemplate(({ field, item }) => `${field}.${item?.key}`, { + field: 'abbr', + item: { key: 'Sunday' }, + }), + ).toBe('abbr.Sunday'); + }); + + test('recognizes label as an auto-localized meta field', () => { + expect(isAutoLocalizeMetaField('label')).toBe(true); + }); + + test('merges partial global and local config safely', () => { + Enum.config.autoLocalize = { + itemTemplate: { description: 'global.{item}.description' }, + }; + try { + expect(mergeAutoLocalizeConfig({ nameTemplate: 'local.{name}' })).toEqual({ + nameTemplate: 'local.{name}', + itemTemplate: { + description: 'global.{item}.description', + }, + }); + } finally { + Enum.config.autoLocalize = undefined; + } + + Enum.config.autoLocalize = { + nameTemplate: 'global.{name}', + }; + try { + expect(mergeAutoLocalizeConfig({ itemTemplate: { abbr: 'local.{item}.abbr' } })).toEqual({ + nameTemplate: 'global.{name}', + itemTemplate: { + abbr: 'local.{item}.abbr', + }, + }); + } finally { + Enum.config.autoLocalize = undefined; + } + }); + + test('resolves string templates without optional context values', () => { + expect(resolveAutoLocalizeTemplate('{name}.{item}.{field}', { field: 'label' })).toBe('..label'); + }); +}); diff --git a/test/test-suites/interface.ts b/test/test-suites/interface.ts index 692fdfa3..0a2b99a2 100644 --- a/test/test-suites/interface.ts +++ b/test/test-suites/interface.ts @@ -91,6 +91,33 @@ const testTyping = (engine: TestEngineBase<'jest' | 'playwright'>) => { validateEnum(engine, weekEnum.items, WeekConfig); }, ); + engine.test( + 'autoLocalize item templates should infer instance-level meta fields', + ({ EnumPlus: { Enum }, WeekConfig: { WeekValueOnlyConfig } }) => { + const weekEnum = Enum(WeekValueOnlyConfig, { + name: 'week', + autoLocalize: { + itemTemplate: { + description: 'weekday.{item}.description', + abbr: 'weekday.{item}Abbr', + }, + }, + }); + return { weekEnum }; + }, + ({ weekEnum }) => { + weekEnum.named.Sunday.description satisfies string; + weekEnum.named.Monday.abbr satisfies string; + weekEnum.items.meta.description satisfies string[]; + weekEnum.items.meta.abbr satisfies string[]; + if (Date.now() < 0) { + // @ts-expect-error: because autoLocalize generated meta fields are readonly + weekEnum.named.Sunday.description = 'manual'; + } + // @ts-expect-error: because undeclared autoLocalize fields should not be added to item types + weekEnum.named.Sunday.tooltip; + }, + ); }); }; diff --git a/test/test-suites/localization.ts b/test/test-suites/localization.ts index d23ccb97..8968d19a 100644 --- a/test/test-suites/localization.ts +++ b/test/test-suites/localization.ts @@ -720,6 +720,105 @@ const testLocalization = (engine: TestEngineBase<'jest' | 'playwright'>) => { }, ); + engine.test( + 'autoLocalize can generate label and undeclared meta fields from global templates', + ({ + EnumPlus: { Enum, defaultLocalize }, + WeekConfig: { WeekValueOnlyConfig, setLang, getLocales }, + i18n: { enUS }, + }) => { + setLang('en-US', Enum, getLocales, defaultLocalize); + Enum.config.autoLabel = true; + Enum.config.autoLocalize = { + nameTemplate: 'weekDay.name', + itemTemplate: { + label: 'weekday.{item}', + abbr: 'weekday.{item}Abbr', + }, + }; + const weekEnum = Enum(WeekValueOnlyConfig, { name: 'week' }); + return { Enum, weekEnum, enUS }; + }, + ({ Enum, weekEnum, enUS }) => { + engine.expect(weekEnum.name).toBe(enUS['weekDay.name']); + engine.expect(weekEnum.named.Sunday.label).toBe(enUS['weekday.Sunday']); + engine.expect((weekEnum.named.Sunday as unknown as { abbr: string }).abbr).toBe(enUS['weekday.SundayAbbr']); + engine + .expect((weekEnum.items.meta as { abbr: string[] }).abbr) + .toEqual([ + enUS['weekday.SundayAbbr'], + enUS['weekday.MondayAbbr'], + enUS['weekday.TuesdayAbbr'], + enUS['weekday.WednesdayAbbr'], + enUS['weekday.ThursdayAbbr'], + enUS['weekday.FridayAbbr'], + enUS['weekday.SaturdayAbbr'], + ]); + Enum.config.autoLocalize = undefined; + }, + ); + + engine.test( + 'autoLocalize instance templates override global item templates', + ({ + EnumPlus: { Enum, defaultLocalize }, + WeekConfig: { WeekValueOnlyConfig, setLang, getLocales }, + i18n: { enUS }, + }) => { + setLang('en-US', Enum, getLocales, defaultLocalize); + Enum.config.autoLocalize = { + itemTemplate: { + abbr: 'NOT_EXISTED_KEY', + }, + }; + const weekEnum = Enum(WeekValueOnlyConfig, { + name: 'week', + autoLocalize: { + itemTemplate: { + abbr: 'weekday.{item}Abbr', + }, + }, + }); + return { Enum, weekEnum, enUS }; + }, + ({ Enum, weekEnum, enUS }) => { + engine.expect((weekEnum.named.Sunday as unknown as { abbr: string }).abbr).toBe(enUS['weekday.SundayAbbr']); + engine.expect((weekEnum.items.meta as { abbr: string[] }).abbr[1]).toBe(enUS['weekday.MondayAbbr']); + Enum.config.autoLocalize = undefined; + }, + ); + + engine.test( + 'autoLocalize function shorthand and templates support omitted raw fields and enum name', + ({ EnumPlus: { Enum, defaultLocalize }, WeekConfig: { setLang, getLocales }, i18n: { enUS } }) => { + setLang('en-US', Enum, getLocales, defaultLocalize); + Enum.config.autoLocalize = { nameTemplate: 'weekDay.name' }; + const unnamedEnum = Enum({ Sunday: 0 }); + const unnamedEnumName = unnamedEnum.name; + Enum.config.autoLocalize = ({ item }) => `weekday.${item?.key}`; + const labelEnum = Enum({ Sunday: undefined, Monday: undefined }); + const metaEnum = Enum( + { Sunday: undefined, Monday: undefined }, + { + autoLocalize: { + nameTemplate: 'weekDay.name', + itemTemplate: { + abbr: ({ item }) => `weekday.${item?.key}Abbr`, + }, + }, + }, + ); + return { Enum, unnamedEnumName, labelEnum, metaEnum, enUS }; + }, + ({ Enum, unnamedEnumName, labelEnum, metaEnum, enUS }) => { + engine.expect(unnamedEnumName).toBe(enUS['weekDay.name']); + engine.expect(labelEnum.named.Sunday.label).toBe(enUS['weekday.Sunday']); + engine.expect(metaEnum.name).toBe(enUS['weekDay.name']); + engine.expect((metaEnum.named.Sunday as unknown as { abbr: string }).abbr).toBe(enUS['weekday.SundayAbbr']); + Enum.config.autoLocalize = undefined; + }, + ); + engine.test( 'Enum name should support global localization (English)', ({ From a51536ec57316821f4f268ddf6a6ba4b1f910aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sage=20Starr=20=28=E6=9D=8E=E5=87=A4=E5=AE=9D=29?= Date: Sun, 5 Jul 2026 21:32:06 +0800 Subject: [PATCH 2/2] feat: autoLocalize1 --- src/auto-localize.ts | 126 ++++++++++++++++++++++++------------- src/enum-collection.ts | 3 +- src/enum-item.ts | 11 ++-- src/enum-items.ts | 13 ++-- src/enum.ts | 3 +- src/extension.d.ts | 28 ++++++--- src/global-config.ts | 2 +- test/auto-localize.test.ts | 8 +-- 8 files changed, 119 insertions(+), 75 deletions(-) diff --git a/src/auto-localize.ts b/src/auto-localize.ts index 581e6d91..7cf7d906 100644 --- a/src/auto-localize.ts +++ b/src/auto-localize.ts @@ -1,24 +1,48 @@ +import type { EnumValue } from '../lib'; +import type { EnumInitOptions } from './enum'; +import type { EnumItemInterface } from './enum-item'; import { internalConfig } from './global-config'; +import type { EnumInit, EnumKey, ValueTypeFromSingleInit } from './types'; -export interface AutoLocalizeTemplateContext { - field: string; - item?: Item; - options?: Options; - resource?: unknown; -} +export type AutoLocalizeContext< + T extends EnumInit, + K extends EnumKey = EnumKey, + V extends EnumValue = ValueTypeFromSingleInit, + Options extends EnumInitOptions = EnumInitOptions, +> = + | { + type: 'name'; + options?: Options; + } + | { + type: 'item'; + item: EnumItemInterface; + options?: Options; + }; -export type AutoLocalizeTemplate = - | string - | ((context: AutoLocalizeTemplateContext) => string | undefined); +export type AutoLocalizeTemplate< + T extends EnumInit, + K extends EnumKey = EnumKey, + V extends EnumValue = ValueTypeFromSingleInit, + Options extends EnumInitOptions = EnumInitOptions, +> = string | ((context: AutoLocalizeContext) => string | undefined); -export interface AutoLocalizeConfig { - nameTemplate?: AutoLocalizeTemplate; - itemTemplate?: Record>; +export interface AutoLocalizeConfig< + T extends EnumInit, + K extends EnumKey = EnumKey, + V extends EnumValue = ValueTypeFromSingleInit, + Options extends EnumInitOptions = EnumInitOptions, +> { + nameTemplate?: AutoLocalizeTemplate; + itemTemplate?: Record, AutoLocalizeTemplate>; } -export type AutoLocalizeOption = - | AutoLocalizeConfig - | ((context: AutoLocalizeTemplateContext) => string | undefined); +export type AutoLocalizeOption< + T extends EnumInit, + K extends EnumKey = EnumKey, + V extends EnumValue = ValueTypeFromSingleInit, + Options extends EnumInitOptions = EnumInitOptions, +> = AutoLocalizeConfig | ((context: AutoLocalizeContext) => string | undefined); export type LiteralStringKeys = string extends keyof T ? never : Extract; @@ -30,14 +54,13 @@ export type AutoLocalizeItemTemplateFields = Options extends { autoLoca : never : never; -export type AutoLocalizeMetaRecord = { - readonly [Key in AutoLocalizeItemTemplateFields]: string; -}; - -export function mergeAutoLocalizeConfig( - local?: AutoLocalizeOption, -): AutoLocalizeConfig | undefined { - const global = internalConfig.autoLocalize as AutoLocalizeOption | undefined; +export function mergeAutoLocalizeConfig< + T extends EnumInit, + K extends EnumKey = EnumKey, + V extends EnumValue = ValueTypeFromSingleInit, + Options extends EnumInitOptions = EnumInitOptions, +>(local?: AutoLocalizeOption): AutoLocalizeConfig | undefined { + const global = internalConfig.autoLocalize as AutoLocalizeOption | undefined; const normalizedGlobal = normalizeAutoLocalizeConfig(global); const normalizedLocal = normalizeAutoLocalizeConfig(local); if (!normalizedGlobal) { @@ -55,9 +78,12 @@ export function mergeAutoLocalizeConfig( }; } -export function normalizeAutoLocalizeConfig( - config?: AutoLocalizeOption, -): AutoLocalizeConfig | undefined { +export function normalizeAutoLocalizeConfig< + T extends EnumInit, + K extends EnumKey = EnumKey, + V extends EnumValue = ValueTypeFromSingleInit, + Options extends EnumInitOptions = EnumInitOptions, +>(config?: AutoLocalizeOption): AutoLocalizeConfig | undefined { if (!config) { return undefined; } @@ -67,9 +93,14 @@ export function normalizeAutoLocalizeConfig( return config; } -export function resolveAutoLocalizeTemplate( - template: AutoLocalizeTemplate | undefined, - context: AutoLocalizeTemplateContext, +export function resolveAutoLocalizeTemplate< + T extends EnumInit, + K extends EnumKey = EnumKey, + V extends EnumValue = ValueTypeFromSingleInit, + Options extends EnumInitOptions = EnumInitOptions, +>( + template: AutoLocalizeTemplate | undefined, + context: AutoLocalizeContext, ): string | undefined { if (!template) { return undefined; @@ -77,36 +108,45 @@ export function resolveAutoLocalizeTemplate( if (typeof template === 'function') { return template(context); } - return template - .split('{name}') - .join(String((context.options as { name?: unknown } | undefined)?.name ?? '')) - .split('{item}') - .join(String((context.item as { key?: unknown } | undefined)?.key ?? '')) - .split('{field}') - .join(context.field); + const name = context.options?.name; + if (typeof name === 'string') { + template = template.replace(/{name}/g, name); + } + if (context.type === 'item') { + template = template.replace(/{key}/g, context.item.key as string); + } + return template; } -export function getAutoLocalizeTemplateFields( - options?: { autoLocalize?: AutoLocalizeOption } | unknown, -) { - const resolvedOptions = options as { autoLocalize?: AutoLocalizeOption } | undefined; +export function getAutoLocalizeTemplateFields< + T extends EnumInit, + K extends EnumKey = EnumKey, + V extends EnumValue = ValueTypeFromSingleInit, + Options extends EnumInitOptions = EnumInitOptions, +>(options?: { autoLocalize?: AutoLocalizeOption } | unknown) { + const resolvedOptions = options as { autoLocalize?: AutoLocalizeOption } | undefined; const config = mergeAutoLocalizeConfig(resolvedOptions?.autoLocalize); return Object.keys(config?.itemTemplate ?? {}); } -export function isAutoLocalizeMetaField( +export function isAutoLocalizeMetaField< + T extends EnumInit, + K extends EnumKey = EnumKey, + V extends EnumValue = ValueTypeFromSingleInit, + Options extends EnumInitOptions = EnumInitOptions, +>( field: string, options?: | { autoLocalizeMeta?: boolean | readonly (string | number | symbol)[]; - autoLocalize?: AutoLocalizeOption; + autoLocalize?: AutoLocalizeOption; } | unknown, ) { const resolvedOptions = options as | { autoLocalizeMeta?: boolean | readonly (string | number | symbol)[]; - autoLocalize?: AutoLocalizeOption; + autoLocalize?: AutoLocalizeOption; } | undefined; if (field === 'label') { diff --git a/src/enum-collection.ts b/src/enum-collection.ts index 102118c5..86e0884b 100644 --- a/src/enum-collection.ts +++ b/src/enum-collection.ts @@ -127,9 +127,8 @@ export class EnumCollectionClass< const autoLocalize = mergeAutoLocalizeConfig(opts?.autoLocalize); const localeKey = autoLocalize?.nameTemplate ? resolveAutoLocalizeTemplate(autoLocalize.nameTemplate, { - field: 'name', + type: 'name', options: opts, - resource: opts?.name, }) : opts?.name; const localize = opts?.localize ?? localizer.localize; diff --git a/src/enum-item.ts b/src/enum-item.ts index a9bbe747..a27577f3 100644 --- a/src/enum-item.ts +++ b/src/enum-item.ts @@ -1,11 +1,12 @@ +import type { EnumItemExtension } from 'enum-plus/extension'; import { - type AutoLocalizeMetaRecord, type AutoLocalizeOption, getAutoLocalizeTemplateFields, isAutoLocalizeMetaField, mergeAutoLocalizeConfig, resolveAutoLocalizeTemplate, } from './auto-localize'; +import type { EnumInitOptions } from './enum'; import { internalConfig, localizer } from './global-config'; import type { EnumItemInit, @@ -24,12 +25,11 @@ export type EnumItemInterface< V extends EnumValue = ValueTypeFromSingleInit, // eslint-disable-next-line @typescript-eslint/no-explicit-any LP = any, - OP = unknown, > = EnumItemClass & // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style { [key in Exclude]: T[key]; - } & AutoLocalizeMetaRecord; + } & EnumItemExtension; /** * - **EN:** Represents a single item in an enumeration collection. @@ -247,10 +247,9 @@ export class EnumItemClass< const template = autoLocalize?.itemTemplate?.[field]; if (template) { localeKey = resolveAutoLocalizeTemplate(template, { - field, + type: field, item: this, options: this._options, - resource, }) as EnumItemLabel | undefined; } else if (field === 'label' && autoLabel && labelPrefix != null) { if (typeof autoLabel === 'function') { @@ -321,7 +320,7 @@ export interface EnumItemOptions< * for backward compatibility. * - **CN:** 自动生成枚举名称、枚举项标签和枚举项元信息字段的本地化键名。这是新的统一本地化配置。`labelPrefix` 和 `autoLabel` 会继续保留以兼容旧 API。 */ - autoLocalize?: AutoLocalizeOption, EnumItemOptions>; + autoLocalize?: AutoLocalizeOption>; /** * - **EN:** Set the array of meta information fields to be automatically localized, similar to the diff --git a/src/enum-items.ts b/src/enum-items.ts index 05343d7a..526b7b6d 100644 --- a/src/enum-items.ts +++ b/src/enum-items.ts @@ -1,8 +1,5 @@ -import { - type AutoLocalizeItemTemplateFields, - getAutoLocalizeTemplateFields, - isAutoLocalizeMetaField, -} from './auto-localize'; +import type { AutoLocalizeItemTemplateFields } from './auto-localize'; +import { getAutoLocalizeTemplateFields, isAutoLocalizeMetaField } from './auto-localize'; import { EnumItemClass, type EnumItemInterface, type EnumItemOptions } from './enum-item'; import type { EnumInit, @@ -496,8 +493,7 @@ export interface IEnumItems< T[key], key, ValueTypeFromSingleInit, - LP, - OP + LP >; }; @@ -618,8 +614,7 @@ export interface InheritableEnumItems< T[FindEnumKeyByValue>], FindEnumKeyByValue>, NonNullable, - LP, - OP + LP > : PrimitiveOf extends KV ? EnumItemInterface | undefined diff --git a/src/enum.ts b/src/enum.ts index 15184f77..c87d97b5 100644 --- a/src/enum.ts +++ b/src/enum.ts @@ -220,8 +220,9 @@ export interface EnumInterface { // eslint-disable-next-line @typescript-eslint/no-explicit-any labelPrefix: any; }) => string); + // eslint-disable-next-line @typescript-eslint/no-explicit-any - autoLocalize?: AutoLocalizeOption; + autoLocalize?: AutoLocalizeOption; }; /** diff --git a/src/extension.d.ts b/src/extension.d.ts index 0b9312d3..20d18119 100644 --- a/src/extension.d.ts +++ b/src/extension.d.ts @@ -1,8 +1,7 @@ declare module 'enum-plus/extension' { /** - * **EN:** Global extension of the enumeration, which can be used to add global extension methods - * - * **CN:** 枚举的全局扩展,可以用来添加全局扩展方法 + * - **EN:** Global extension of the enumeration, which can be used to add global extension methods + * - **CN:** 枚举的全局扩展,可以用来添加全局扩展方法 * * @template {extends EnumInit} T - The type of the enumeration * @template {extends EnumKey = EnumKey} K - The key type of the enumeration @@ -11,18 +10,29 @@ declare module 'enum-plus/extension' { */ // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-unused-vars interface EnumExtension {} + /** - * **EN:** Enum global localization extension + * - **EN:** Add global extension field definitions for enumeration items. It can be used to add + * type definitions for fields globally added to `Enum.config.autoLocalize`. + * - **CN:** 为枚举项添加全局扩展字段定义。可以用来为`Enum.config.autoLocalize`全局添加的字段,添加类型生命扩展。 * - * **CN:** 枚举本地化的全局扩展 + * @template {extends EnumInit} T - The type of the enumeration + * @template {extends EnumKey = EnumKey} K - The key type of the enumeration + * @template {extends EnumValue = ValueTypeFromSingleInit} V - The value type of the + * enumeration + */ + // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-unused-vars + interface EnumItemExtension {} + /** + * - **EN:** Enum global localization extension + * - **CN:** 枚举本地化的全局扩展 */ // eslint-disable-next-line @typescript-eslint/no-empty-interface interface EnumLocaleExtends { /** - * **EN:** Key values of the localized text of the enumeration, which can be used to enhance the - * intelligent prompt of the editor - * - * **CN:** 枚举本地化文本的Key值,可以用来增强编辑器的智能提示 + * - **EN:** Key values of the localized text of the enumeration, which can be used to enhance the + * intelligent prompt of the editor + * - **CN:** 枚举本地化文本的Key值,可以用来增强编辑器的智能提示 */ LocaleKeys: // eslint-disable-next-line @typescript-eslint/ban-types | (string & {}) diff --git a/src/global-config.ts b/src/global-config.ts index 59577f36..60ab26c8 100644 --- a/src/global-config.ts +++ b/src/global-config.ts @@ -22,7 +22,7 @@ export const internalConfig: { labelPrefix: any; }) => string); // eslint-disable-next-line @typescript-eslint/no-explicit-any - autoLocalize?: AutoLocalizeOption; + autoLocalize?: AutoLocalizeOption; } = { autoLabel: true, }; diff --git a/test/auto-localize.test.ts b/test/auto-localize.test.ts index 6c5d81b6..54d0102c 100644 --- a/test/auto-localize.test.ts +++ b/test/auto-localize.test.ts @@ -18,10 +18,10 @@ describe('autoLocalize helpers', () => { }); test('resolves empty and function templates', () => { - expect(resolveAutoLocalizeTemplate(undefined, { field: 'label' })).toBe(undefined); + expect(resolveAutoLocalizeTemplate(undefined, { type: 'label' })).toBe(undefined); expect( - resolveAutoLocalizeTemplate(({ field, item }) => `${field}.${item?.key}`, { - field: 'abbr', + resolveAutoLocalizeTemplate(({ type: field, item }) => `${field}.${item?.key}`, { + type: 'abbr', item: { key: 'Sunday' }, }), ).toBe('abbr.Sunday'); @@ -62,6 +62,6 @@ describe('autoLocalize helpers', () => { }); test('resolves string templates without optional context values', () => { - expect(resolveAutoLocalizeTemplate('{name}.{item}.{field}', { field: 'label' })).toBe('..label'); + expect(resolveAutoLocalizeTemplate('{name}.{item}.{field}', { type: 'label' })).toBe('..label'); }); });