Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .storybook/docs-source/ApiGuide.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,40 @@ const WeekEnum = Enum(enumInit, {
});
```

## ⚙️ autoLocalize

`{ nameTemplate?: string | Function, itemTemplate?: Record<string, string | Function> }`

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)`
Expand Down
34 changes: 34 additions & 0 deletions .storybook/docs-source/ApiGuide.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,40 @@ const WeekEnum = Enum(enumInit, {
});
```

## ⚙️ autoLocalize

`{ nameTemplate?: string | Function, itemTemplate?: Record<string, string | Function> }`

自动为枚举名称、枚举项标签和枚举项元数据字段生成本地化 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)`
Expand Down
43 changes: 43 additions & 0 deletions README-FULL.md
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,49 @@ const WeekEnum = Enum(enumInit, {
});
```

### ⚙️ autoLocalize

`{ nameTemplate?: string | Function, itemTemplate?: Record<string, string | Function> }`

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)`
Expand Down
43 changes: 43 additions & 0 deletions README-FULL.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,49 @@ const WeekEnum = Enum(enumInit, {
});
```

### ⚙️ autoLocalize

`{ nameTemplate?: string | Function, itemTemplate?: Record<string, string | Function> }`

自动为枚举名称、枚举项标签和枚举项元数据字段生成本地化 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)`
Expand Down
163 changes: 163 additions & 0 deletions src/auto-localize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import type { EnumValue } from '../lib';

Check failure on line 1 in src/auto-localize.ts

View workflow job for this annotation

GitHub Actions / test / install-and-build

Cannot find module '../lib' or its corresponding type declarations.

Check failure on line 1 in src/auto-localize.ts

View workflow job for this annotation

GitHub Actions / test / run-e2e

Cannot find module '../lib' or its corresponding type declarations.
import type { EnumInitOptions } from './enum';
import type { EnumItemInterface } from './enum-item';
import { internalConfig } from './global-config';
import type { EnumInit, EnumKey, ValueTypeFromSingleInit } from './types';

export type AutoLocalizeContext<
T extends EnumInit<K, V>,
K extends EnumKey<T> = EnumKey<T>,
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
Options extends EnumInitOptions<T, K, V> = EnumInitOptions<T, K, V>,
> =
| {
type: 'name';
options?: Options;
}
| {
type: 'item';
item: EnumItemInterface<T, K, V, Options>;
options?: Options;
};

export type AutoLocalizeTemplate<
T extends EnumInit<K, V>,
K extends EnumKey<T> = EnumKey<T>,
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
Options extends EnumInitOptions<T, K, V> = EnumInitOptions<T, K, V>,
> = string | ((context: AutoLocalizeContext<T, K, V, Options>) => string | undefined);

export interface AutoLocalizeConfig<
T extends EnumInit<K, V>,
K extends EnumKey<T> = EnumKey<T>,
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
Options extends EnumInitOptions<T, K, V> = EnumInitOptions<T, K, V>,
> {
nameTemplate?: AutoLocalizeTemplate<T, K, V, Options>;
itemTemplate?: Record<Exclude<keyof T[keyof T], 'key' | 'value' | 'label'>, AutoLocalizeTemplate<T, K, V, Options>>;
}

export type AutoLocalizeOption<
T extends EnumInit<K, V>,
K extends EnumKey<T> = EnumKey<T>,
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
Options extends EnumInitOptions<T, K, V> = EnumInitOptions<T, K, V>,
> = AutoLocalizeConfig<T, K, V, Options> | ((context: AutoLocalizeContext<T, K, V, Options>) => string | undefined);

export type LiteralStringKeys<T> = string extends keyof T ? never : Extract<keyof T, string>;

export type AutoLocalizeItemTemplateFields<Options> = Options extends { autoLocalize?: infer AutoLocalize }
? AutoLocalize extends (...args: never[]) => unknown
? never
: AutoLocalize extends { itemTemplate?: infer ItemTemplate }
? Exclude<LiteralStringKeys<NonNullable<ItemTemplate>>, 'label'>
: never
: never;

export function mergeAutoLocalizeConfig<
T extends EnumInit<K, V>,
K extends EnumKey<T> = EnumKey<T>,
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
Options extends EnumInitOptions<T, K, V> = EnumInitOptions<T, K, V>,
>(local?: AutoLocalizeOption<T, K, V, Options>): AutoLocalizeConfig<T, K, V, Options> | undefined {
const global = internalConfig.autoLocalize as AutoLocalizeOption<T, K, V, Options> | undefined;
const normalizedGlobal = normalizeAutoLocalizeConfig(global);
const normalizedLocal = normalizeAutoLocalizeConfig(local);
if (!normalizedGlobal) {
return normalizedLocal;
}
if (!normalizedLocal) {
return normalizedGlobal;
}
return {
nameTemplate: normalizedLocal.nameTemplate ?? normalizedGlobal.nameTemplate,
itemTemplate: {

Check failure on line 74 in src/auto-localize.ts

View workflow job for this annotation

GitHub Actions / test / install-and-build

Type '{}' is not assignable to type 'Record<Exclude<keyof T[keyof T], "value" | "label" | "key">, AutoLocalizeTemplate<T, K, V, Options>>'.

Check failure on line 74 in src/auto-localize.ts

View workflow job for this annotation

GitHub Actions / test / run-e2e

Type '{}' is not assignable to type 'Record<Exclude<keyof T[keyof T], "value" | "label" | "key">, AutoLocalizeTemplate<T, K, V, Options>>'.
...(normalizedGlobal.itemTemplate ?? {}),
...(normalizedLocal.itemTemplate ?? {}),
},
};
}

export function normalizeAutoLocalizeConfig<
T extends EnumInit<K, V>,
K extends EnumKey<T> = EnumKey<T>,
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
Options extends EnumInitOptions<T, K, V> = EnumInitOptions<T, K, V>,
>(config?: AutoLocalizeOption<T, K, V, Options>): AutoLocalizeConfig<T, K, V, Options> | undefined {
if (!config) {
return undefined;
}
if (typeof config === 'function') {
return { itemTemplate: { label: config } };

Check failure on line 91 in src/auto-localize.ts

View workflow job for this annotation

GitHub Actions / test / install-and-build

Object literal may only specify known properties, and 'label' does not exist in type 'Record<Exclude<keyof T[keyof T], "value" | "label" | "key">, AutoLocalizeTemplate<T, K, V, Options>>'.

Check failure on line 91 in src/auto-localize.ts

View workflow job for this annotation

GitHub Actions / test / run-e2e

Object literal may only specify known properties, and 'label' does not exist in type 'Record<Exclude<keyof T[keyof T], "value" | "label" | "key">, AutoLocalizeTemplate<T, K, V, Options>>'.
}
return config;
}

export function resolveAutoLocalizeTemplate<
T extends EnumInit<K, V>,
K extends EnumKey<T> = EnumKey<T>,
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
Options extends EnumInitOptions<T, K, V> = EnumInitOptions<T, K, V>,
>(
template: AutoLocalizeTemplate<T, K, V, Options> | undefined,
context: AutoLocalizeContext<T, K, V, Options>,
): string | undefined {
if (!template) {
return undefined;
}
if (typeof template === 'function') {
return template(context);
}
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<
T extends EnumInit<K, V>,
K extends EnumKey<T> = EnumKey<T>,
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
Options extends EnumInitOptions<T, K, V> = EnumInitOptions<T, K, V>,
>(options?: { autoLocalize?: AutoLocalizeOption<T, K, V, Options> } | unknown) {
const resolvedOptions = options as { autoLocalize?: AutoLocalizeOption<T, K, V, Options> } | undefined;
const config = mergeAutoLocalizeConfig(resolvedOptions?.autoLocalize);
return Object.keys(config?.itemTemplate ?? {});
}

export function isAutoLocalizeMetaField<
T extends EnumInit<K, V>,
K extends EnumKey<T> = EnumKey<T>,
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
Options extends EnumInitOptions<T, K, V> = EnumInitOptions<T, K, V>,
>(
field: string,
options?:
| {
autoLocalizeMeta?: boolean | readonly (string | number | symbol)[];
autoLocalize?: AutoLocalizeOption<T, K, V, Options>;
}
| unknown,
) {
const resolvedOptions = options as
| {
autoLocalizeMeta?: boolean | readonly (string | number | symbol)[];
autoLocalize?: AutoLocalizeOption<T, K, V, Options>;
}
| 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 ?? {});
Comment on lines +161 to +162
}
12 changes: 10 additions & 2 deletions src/enum-collection.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -123,11 +124,18 @@
if (typeof opts?.name === 'function') {
return opts.name(undefined!);
}
const autoLocalize = mergeAutoLocalizeConfig(opts?.autoLocalize);

Check failure on line 127 in src/enum-collection.ts

View workflow job for this annotation

GitHub Actions / test / install-and-build

Argument of type 'AutoLocalizeOption<T[K], K, V, EnumInitOptions<T[K], K, V, any>> | undefined' is not assignable to parameter of type 'AutoLocalizeOption<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<keyof T[K], symbol>, V, EnumInitOptions<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<...>, V, any>> | undefined'.

Check failure on line 127 in src/enum-collection.ts

View workflow job for this annotation

GitHub Actions / test / run-e2e

Argument of type 'AutoLocalizeOption<T[K], K, V, EnumInitOptions<T[K], K, V, any>> | undefined' is not assignable to parameter of type 'AutoLocalizeOption<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<keyof T[K], symbol>, V, EnumInitOptions<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<...>, V, any>> | undefined'.
const localeKey = autoLocalize?.nameTemplate
? resolveAutoLocalizeTemplate(autoLocalize.nameTemplate, {

Check failure on line 129 in src/enum-collection.ts

View workflow job for this annotation

GitHub Actions / test / install-and-build

Argument of type 'AutoLocalizeTemplate<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<keyof T[K], symbol>, V, EnumInitOptions<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<...>, V, any>>' is not assignable to parameter of type 'AutoLocalizeTemplate<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<Exclude<keyof T[K], symbol>, symbol>, V, EnumInitOptions<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<...>, V, any>> | undefined'.

Check failure on line 129 in src/enum-collection.ts

View workflow job for this annotation

GitHub Actions / test / run-e2e

Argument of type 'AutoLocalizeTemplate<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<keyof T[K], symbol>, V, EnumInitOptions<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<...>, V, any>>' is not assignable to parameter of type 'AutoLocalizeTemplate<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<Exclude<keyof T[K], symbol>, symbol>, V, EnumInitOptions<EnumInit<Exclude<keyof T[K], symbol>, V>, Exclude<...>, V, any>> | undefined'.
type: 'name',
options: opts,
})
: 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<KV extends V | K | NonNullable<PrimitiveOf<V>> | NonNullable<PrimitiveOf<K>> | undefined>(keyOrValue: KV) {
Expand Down
Loading
Loading