@digitaldefiance/i18n-lib 4.1.2 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +170 -0
- package/package.json +1 -1
- package/src/branded-enum-utils.d.ts +226 -0
- package/src/branded-enum-utils.d.ts.map +1 -0
- package/src/branded-enum-utils.js +254 -0
- package/src/branded-enum-utils.js.map +1 -0
- package/src/core/enum-registry.d.ts +219 -8
- package/src/core/enum-registry.d.ts.map +1 -1
- package/src/core/enum-registry.js +231 -9
- package/src/core/enum-registry.js.map +1 -1
- package/src/core/i18n-engine.d.ts +124 -11
- package/src/core/i18n-engine.d.ts.map +1 -1
- package/src/core/i18n-engine.js +121 -11
- package/src/core/i18n-engine.js.map +1 -1
- package/src/enum-registry.d.ts +243 -21
- package/src/enum-registry.d.ts.map +1 -1
- package/src/enum-registry.js +255 -18
- package/src/enum-registry.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.d.ts.map +1 -1
- package/src/index.js +2 -0
- package/src/index.js.map +1 -1
- package/src/plugin-i18n-engine.d.ts +101 -5
- package/src/plugin-i18n-engine.d.ts.map +1 -1
- package/src/plugin-i18n-engine.js +98 -2
- package/src/plugin-i18n-engine.js.map +1 -1
- package/src/types.d.ts +55 -0
- package/src/types.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -924,6 +924,176 @@ const allValues = getStringKeyValues(AllKeys);
|
|
|
924
924
|
|
|
925
925
|
For complete migration guide, see [BRANDED_ENUM_MIGRATION.md](docs/BRANDED_ENUM_MIGRATION.md).
|
|
926
926
|
|
|
927
|
+
### Branded Enum Translation
|
|
928
|
+
|
|
929
|
+
The enum translation system supports branded enums from `@digitaldefiance/branded-enum`, enabling automatic name inference and type-safe enum value translations.
|
|
930
|
+
|
|
931
|
+
#### Why Use Branded Enums for Enum Translation?
|
|
932
|
+
|
|
933
|
+
- **Automatic Name Inference**: No need to provide an explicit enum name - it's extracted from the branded enum's component ID
|
|
934
|
+
- **Type Safety**: Full TypeScript support for enum values and translations
|
|
935
|
+
- **Consistent Naming**: Enum names in error messages match your component IDs
|
|
936
|
+
- **Unified Pattern**: Use the same branded enum pattern for both string keys and enum translations
|
|
937
|
+
|
|
938
|
+
#### Registering Branded Enums for Translation
|
|
939
|
+
|
|
940
|
+
```typescript
|
|
941
|
+
import { createBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
942
|
+
import { I18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
943
|
+
|
|
944
|
+
// Create a branded enum
|
|
945
|
+
const Status = createBrandedEnum('status', {
|
|
946
|
+
Active: 'active',
|
|
947
|
+
Inactive: 'inactive',
|
|
948
|
+
Pending: 'pending',
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
// Create engine
|
|
952
|
+
const engine = new I18nEngine([
|
|
953
|
+
{ id: LanguageCodes.EN_US, name: 'English', code: 'en-US', isDefault: true },
|
|
954
|
+
{ id: LanguageCodes.ES, name: 'Spanish', code: 'es' },
|
|
955
|
+
]);
|
|
956
|
+
|
|
957
|
+
// Register branded enum - name is automatically inferred as 'status'
|
|
958
|
+
engine.registerEnum(Status, {
|
|
959
|
+
[LanguageCodes.EN_US]: {
|
|
960
|
+
active: 'Active',
|
|
961
|
+
inactive: 'Inactive',
|
|
962
|
+
pending: 'Pending',
|
|
963
|
+
},
|
|
964
|
+
[LanguageCodes.ES]: {
|
|
965
|
+
active: 'Activo',
|
|
966
|
+
inactive: 'Inactivo',
|
|
967
|
+
pending: 'Pendiente',
|
|
968
|
+
},
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
// Translate enum values
|
|
972
|
+
engine.translateEnum(Status, Status.Active, LanguageCodes.EN_US); // 'Active'
|
|
973
|
+
engine.translateEnum(Status, Status.Active, LanguageCodes.ES); // 'Activo'
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
#### Comparison: Traditional vs Branded Enum Registration
|
|
977
|
+
|
|
978
|
+
```typescript
|
|
979
|
+
// Traditional enum - requires explicit name
|
|
980
|
+
enum TraditionalStatus {
|
|
981
|
+
Active = 'active',
|
|
982
|
+
Inactive = 'inactive',
|
|
983
|
+
}
|
|
984
|
+
engine.registerEnum(TraditionalStatus, translations, 'Status'); // Name required
|
|
985
|
+
|
|
986
|
+
// Branded enum - name inferred from component ID
|
|
987
|
+
const BrandedStatus = createBrandedEnum('status', {
|
|
988
|
+
Active: 'active',
|
|
989
|
+
Inactive: 'inactive',
|
|
990
|
+
});
|
|
991
|
+
engine.registerEnum(BrandedStatus, translations); // Name 'status' inferred automatically
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
#### Using with PluginI18nEngine
|
|
995
|
+
|
|
996
|
+
The `PluginI18nEngine` also supports branded enum translation with language validation:
|
|
997
|
+
|
|
998
|
+
```typescript
|
|
999
|
+
import { PluginI18nEngine, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1000
|
+
import { createBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
1001
|
+
|
|
1002
|
+
const Priority = createBrandedEnum('priority', {
|
|
1003
|
+
High: 'high',
|
|
1004
|
+
Medium: 'medium',
|
|
1005
|
+
Low: 'low',
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
const engine = PluginI18nEngine.createInstance('myapp', [
|
|
1009
|
+
{ id: LanguageCodes.EN_US, name: 'English', code: 'en-US', isDefault: true },
|
|
1010
|
+
{ id: LanguageCodes.FR, name: 'French', code: 'fr' },
|
|
1011
|
+
]);
|
|
1012
|
+
|
|
1013
|
+
// Register with automatic name inference
|
|
1014
|
+
engine.registerEnum(Priority, {
|
|
1015
|
+
[LanguageCodes.EN_US]: {
|
|
1016
|
+
high: 'High Priority',
|
|
1017
|
+
medium: 'Medium Priority',
|
|
1018
|
+
low: 'Low Priority',
|
|
1019
|
+
},
|
|
1020
|
+
[LanguageCodes.FR]: {
|
|
1021
|
+
high: 'Haute Priorité',
|
|
1022
|
+
medium: 'Priorité Moyenne',
|
|
1023
|
+
low: 'Basse Priorité',
|
|
1024
|
+
},
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
// Translate
|
|
1028
|
+
engine.translateEnum(Priority, Priority.High); // Uses current language
|
|
1029
|
+
engine.translateEnum(Priority, Priority.High, LanguageCodes.FR); // 'Haute Priorité'
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
#### Utility Functions
|
|
1033
|
+
|
|
1034
|
+
The library provides utility functions for working with branded enums:
|
|
1035
|
+
|
|
1036
|
+
```typescript
|
|
1037
|
+
import {
|
|
1038
|
+
isBrandedEnum,
|
|
1039
|
+
getBrandedEnumComponentId,
|
|
1040
|
+
getBrandedEnumId
|
|
1041
|
+
} from '@digitaldefiance/i18n-lib';
|
|
1042
|
+
|
|
1043
|
+
// Check if an enum is branded
|
|
1044
|
+
if (isBrandedEnum(Status)) {
|
|
1045
|
+
// TypeScript knows Status is a branded enum here
|
|
1046
|
+
const componentId = getBrandedEnumComponentId(Status);
|
|
1047
|
+
console.log(componentId); // 'status'
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Get the raw brand ID (includes 'i18n:' prefix for i18n string keys)
|
|
1051
|
+
const rawId = getBrandedEnumId(Status); // 'status'
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
#### Error Messages with Branded Enums
|
|
1055
|
+
|
|
1056
|
+
When translation errors occur, the enum name in error messages is automatically derived from the branded enum's component ID:
|
|
1057
|
+
|
|
1058
|
+
```typescript
|
|
1059
|
+
const Status = createBrandedEnum('user-status', { Active: 'active' });
|
|
1060
|
+
engine.registerEnum(Status, { en: { active: 'Active' } });
|
|
1061
|
+
|
|
1062
|
+
// If translation fails, error message includes the inferred name:
|
|
1063
|
+
// "No translations found for enum: user-status"
|
|
1064
|
+
// "Translation missing for enum value 'unknown' in language 'en'"
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
#### Type Definitions
|
|
1068
|
+
|
|
1069
|
+
New types are available for working with branded enum translations:
|
|
1070
|
+
|
|
1071
|
+
```typescript
|
|
1072
|
+
import type {
|
|
1073
|
+
BrandedEnumTranslation,
|
|
1074
|
+
RegisterableEnum,
|
|
1075
|
+
TranslatableEnumValue,
|
|
1076
|
+
} from '@digitaldefiance/i18n-lib';
|
|
1077
|
+
|
|
1078
|
+
// Type for branded enum translation maps
|
|
1079
|
+
type MyTranslations = BrandedEnumTranslation<typeof Status, 'en' | 'es'>;
|
|
1080
|
+
|
|
1081
|
+
// Union type accepting both traditional and branded enums
|
|
1082
|
+
function registerAnyEnum<T extends string | number>(
|
|
1083
|
+
enumObj: RegisterableEnum<T>,
|
|
1084
|
+
translations: Record<string, Record<string, string>>,
|
|
1085
|
+
): void {
|
|
1086
|
+
// Works with both enum types
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Union type for translatable values
|
|
1090
|
+
function translateAnyValue<T extends string | number>(
|
|
1091
|
+
value: TranslatableEnumValue<T>,
|
|
1092
|
+
): string {
|
|
1093
|
+
// Works with both traditional and branded enum values
|
|
1094
|
+
}
|
|
1095
|
+
```
|
|
1096
|
+
|
|
927
1097
|
## Browser Support
|
|
928
1098
|
|
|
929
1099
|
- Chrome/Edge: Latest 2 versions
|
package/package.json
CHANGED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branded Enum Utilities for i18n enum translation support
|
|
3
|
+
*
|
|
4
|
+
* This module provides utility functions for detecting and extracting
|
|
5
|
+
* information from branded enums created with `@digitaldefiance/branded-enum`.
|
|
6
|
+
* These utilities enable the i18n library to seamlessly support both traditional
|
|
7
|
+
* TypeScript enums and branded enums for enum translations.
|
|
8
|
+
*
|
|
9
|
+
* ## Overview
|
|
10
|
+
*
|
|
11
|
+
* Branded enums carry runtime metadata (a unique component ID) that enables:
|
|
12
|
+
* - Automatic enum name inference during registration
|
|
13
|
+
* - Runtime identification for debugging and logging
|
|
14
|
+
* - Type-safe translation lookups
|
|
15
|
+
*
|
|
16
|
+
* ## Key Functions
|
|
17
|
+
*
|
|
18
|
+
* - {@link isBrandedEnum} - Type guard to detect branded enums
|
|
19
|
+
* - {@link getBrandedEnumComponentId} - Extract component ID (prefix stripped)
|
|
20
|
+
* - {@link getBrandedEnumId} - Get raw brand ID (for debugging)
|
|
21
|
+
*
|
|
22
|
+
* ## Usage with Enum Translation
|
|
23
|
+
*
|
|
24
|
+
* These utilities are used internally by `EnumRegistry` and `EnumTranslationRegistry`
|
|
25
|
+
* to provide automatic name inference and proper translation lookups for branded enums.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { createBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
30
|
+
* import { isBrandedEnum, getBrandedEnumComponentId } from '@digitaldefiance/i18n-lib';
|
|
31
|
+
*
|
|
32
|
+
* const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' });
|
|
33
|
+
*
|
|
34
|
+
* if (isBrandedEnum(Status)) {
|
|
35
|
+
* const componentId = getBrandedEnumComponentId(Status);
|
|
36
|
+
* console.log(componentId); // 'status'
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @module branded-enum-utils
|
|
41
|
+
* @see {@link EnumRegistry} - V2 enum registry with branded enum support
|
|
42
|
+
* @see {@link EnumTranslationRegistry} - Legacy registry with branded enum support
|
|
43
|
+
*/
|
|
44
|
+
import type { AnyBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
45
|
+
/**
|
|
46
|
+
* Type guard to check if an object is a branded enum from `@digitaldefiance/branded-enum`.
|
|
47
|
+
*
|
|
48
|
+
* Branded enums are identified by having a valid enum ID retrievable via `getEnumId()`.
|
|
49
|
+
* This function safely checks for this property without throwing errors, making it
|
|
50
|
+
* suitable for use in conditional logic and type narrowing.
|
|
51
|
+
*
|
|
52
|
+
* ## When to Use
|
|
53
|
+
*
|
|
54
|
+
* Use this function when you need to:
|
|
55
|
+
* - Determine if an enum object is branded or traditional
|
|
56
|
+
* - Conditionally apply branded enum-specific logic
|
|
57
|
+
* - Type-narrow an unknown enum object
|
|
58
|
+
*
|
|
59
|
+
* ## Implementation Details
|
|
60
|
+
*
|
|
61
|
+
* The function checks:
|
|
62
|
+
* 1. The object is not null or undefined
|
|
63
|
+
* 2. The object is of type 'object'
|
|
64
|
+
* 3. The object has a valid enum ID (via `getEnumId()`)
|
|
65
|
+
*
|
|
66
|
+
* @param obj - The object to check (can be any value)
|
|
67
|
+
* @returns `true` if the object is a branded enum, `false` otherwise
|
|
68
|
+
*
|
|
69
|
+
* @example Basic usage
|
|
70
|
+
* ```typescript
|
|
71
|
+
* import { createBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
72
|
+
* import { isBrandedEnum } from '@digitaldefiance/i18n-lib';
|
|
73
|
+
*
|
|
74
|
+
* const BrandedStatus = createBrandedEnum('status', { Active: 'active' });
|
|
75
|
+
* const TraditionalStatus = { Active: 'active' };
|
|
76
|
+
*
|
|
77
|
+
* isBrandedEnum(BrandedStatus); // true
|
|
78
|
+
* isBrandedEnum(TraditionalStatus); // false
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @example Type narrowing
|
|
82
|
+
* ```typescript
|
|
83
|
+
* function processEnum(enumObj: object) {
|
|
84
|
+
* if (isBrandedEnum(enumObj)) {
|
|
85
|
+
* // TypeScript knows enumObj is AnyBrandedEnum here
|
|
86
|
+
* const id = getBrandedEnumComponentId(enumObj);
|
|
87
|
+
* console.log(`Processing branded enum: ${id}`);
|
|
88
|
+
* } else {
|
|
89
|
+
* console.log('Processing traditional enum');
|
|
90
|
+
* }
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @example Edge cases
|
|
95
|
+
* ```typescript
|
|
96
|
+
* isBrandedEnum(null); // false
|
|
97
|
+
* isBrandedEnum(undefined); // false
|
|
98
|
+
* isBrandedEnum('string'); // false
|
|
99
|
+
* isBrandedEnum(123); // false
|
|
100
|
+
* isBrandedEnum({}); // false
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @see {@link getBrandedEnumComponentId} - Extract component ID from branded enum
|
|
104
|
+
* @see {@link getBrandedEnumId} - Get raw brand ID from branded enum
|
|
105
|
+
*/
|
|
106
|
+
export declare function isBrandedEnum(obj: unknown): obj is AnyBrandedEnum;
|
|
107
|
+
/**
|
|
108
|
+
* Extracts the component ID from a branded enum.
|
|
109
|
+
*
|
|
110
|
+
* For branded enums created with `createI18nStringKeys()`, the brand ID
|
|
111
|
+
* is prefixed with `'i18n:'`. This function strips that prefix to return
|
|
112
|
+
* just the component ID, which is suitable for use as an enum name in
|
|
113
|
+
* registration and error messages.
|
|
114
|
+
*
|
|
115
|
+
* ## Prefix Stripping
|
|
116
|
+
*
|
|
117
|
+
* | Input Brand ID | Returned Component ID |
|
|
118
|
+
* |----------------|----------------------|
|
|
119
|
+
* | `'i18n:my-component'` | `'my-component'` |
|
|
120
|
+
* | `'my-component'` | `'my-component'` |
|
|
121
|
+
* | `'status'` | `'status'` |
|
|
122
|
+
*
|
|
123
|
+
* ## When to Use
|
|
124
|
+
*
|
|
125
|
+
* Use this function when you need:
|
|
126
|
+
* - The component ID for automatic enum name inference
|
|
127
|
+
* - A clean identifier without the `i18n:` prefix
|
|
128
|
+
* - The default name for error messages
|
|
129
|
+
*
|
|
130
|
+
* For debugging purposes where you need the full brand ID including prefix,
|
|
131
|
+
* use {@link getBrandedEnumId} instead.
|
|
132
|
+
*
|
|
133
|
+
* @param enumObj - The object to extract the component ID from
|
|
134
|
+
* @returns The component ID without the `'i18n:'` prefix, or `null` if not a branded enum
|
|
135
|
+
*
|
|
136
|
+
* @example Basic usage
|
|
137
|
+
* ```typescript
|
|
138
|
+
* import { createI18nStringKeys } from '@digitaldefiance/i18n-lib';
|
|
139
|
+
*
|
|
140
|
+
* const MyKeys = createI18nStringKeys('my-component', { A: 'a' });
|
|
141
|
+
*
|
|
142
|
+
* getBrandedEnumComponentId(MyKeys); // 'my-component'
|
|
143
|
+
* getBrandedEnumComponentId({ A: 'a' }); // null (not a branded enum)
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @example With createBrandedEnum (no prefix)
|
|
147
|
+
* ```typescript
|
|
148
|
+
* import { createBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
149
|
+
*
|
|
150
|
+
* const Status = createBrandedEnum('status', { Active: 'active' });
|
|
151
|
+
*
|
|
152
|
+
* getBrandedEnumComponentId(Status); // 'status'
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @example Automatic name inference in registration
|
|
156
|
+
* ```typescript
|
|
157
|
+
* const MyKeys = createI18nStringKeys('user-profile', { Title: 'title' });
|
|
158
|
+
*
|
|
159
|
+
* // When registering without explicit name, component ID is used
|
|
160
|
+
* registry.register(MyKeys, translations);
|
|
161
|
+
* // Internally uses: getBrandedEnumComponentId(MyKeys) → 'user-profile'
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @see {@link isBrandedEnum} - Check if an object is a branded enum
|
|
165
|
+
* @see {@link getBrandedEnumId} - Get raw brand ID without prefix stripping
|
|
166
|
+
*/
|
|
167
|
+
export declare function getBrandedEnumComponentId(enumObj: unknown): string | null;
|
|
168
|
+
/**
|
|
169
|
+
* Gets the raw brand/ID from a branded enum without prefix stripping.
|
|
170
|
+
*
|
|
171
|
+
* Unlike {@link getBrandedEnumComponentId}, this function returns the full
|
|
172
|
+
* brand ID including any prefixes (such as `'i18n:'`). This is useful for
|
|
173
|
+
* debugging, logging, and cases where you need the exact brand identifier.
|
|
174
|
+
*
|
|
175
|
+
* ## Comparison with getBrandedEnumComponentId
|
|
176
|
+
*
|
|
177
|
+
* | Function | Input Brand | Output |
|
|
178
|
+
* |----------|-------------|--------|
|
|
179
|
+
* | `getBrandedEnumId` | `'i18n:my-component'` | `'i18n:my-component'` |
|
|
180
|
+
* | `getBrandedEnumComponentId` | `'i18n:my-component'` | `'my-component'` |
|
|
181
|
+
*
|
|
182
|
+
* ## When to Use
|
|
183
|
+
*
|
|
184
|
+
* Use this function when you need:
|
|
185
|
+
* - The exact brand ID for debugging or logging
|
|
186
|
+
* - To distinguish between different branded enum sources
|
|
187
|
+
* - The full identifier including any prefixes
|
|
188
|
+
*
|
|
189
|
+
* For registration and user-facing names, use {@link getBrandedEnumComponentId}
|
|
190
|
+
* which strips the `i18n:` prefix.
|
|
191
|
+
*
|
|
192
|
+
* @param enumObj - The object to get the brand ID from
|
|
193
|
+
* @returns The raw brand ID, or `null` if not a branded enum
|
|
194
|
+
*
|
|
195
|
+
* @example Debugging and logging
|
|
196
|
+
* ```typescript
|
|
197
|
+
* import { createI18nStringKeys } from '@digitaldefiance/i18n-lib';
|
|
198
|
+
*
|
|
199
|
+
* const MyKeys = createI18nStringKeys('my-component', { A: 'a' });
|
|
200
|
+
*
|
|
201
|
+
* // For debugging, get the full brand ID
|
|
202
|
+
* console.log(`Brand ID: ${getBrandedEnumId(MyKeys)}`);
|
|
203
|
+
* // Output: 'Brand ID: i18n:my-component'
|
|
204
|
+
*
|
|
205
|
+
* // For user-facing names, use component ID
|
|
206
|
+
* console.log(`Component: ${getBrandedEnumComponentId(MyKeys)}`);
|
|
207
|
+
* // Output: 'Component: my-component'
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* @example Comparing branded enums
|
|
211
|
+
* ```typescript
|
|
212
|
+
* import { createBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
213
|
+
* import { createI18nStringKeys } from '@digitaldefiance/i18n-lib';
|
|
214
|
+
*
|
|
215
|
+
* const RegularBranded = createBrandedEnum('status', { A: 'a' });
|
|
216
|
+
* const I18nBranded = createI18nStringKeys('status', { A: 'a' });
|
|
217
|
+
*
|
|
218
|
+
* getBrandedEnumId(RegularBranded); // 'status'
|
|
219
|
+
* getBrandedEnumId(I18nBranded); // 'i18n:status'
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @see {@link isBrandedEnum} - Check if an object is a branded enum
|
|
223
|
+
* @see {@link getBrandedEnumComponentId} - Get component ID with prefix stripped
|
|
224
|
+
*/
|
|
225
|
+
export declare function getBrandedEnumId(enumObj: unknown): string | null;
|
|
226
|
+
//# sourceMappingURL=branded-enum-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branded-enum-utils.d.ts","sourceRoot":"","sources":["../../../../packages/digitaldefiance-i18n-lib/src/branded-enum-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAGpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,cAAc,CAOjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAUzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAMhE"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Branded Enum Utilities for i18n enum translation support
|
|
4
|
+
*
|
|
5
|
+
* This module provides utility functions for detecting and extracting
|
|
6
|
+
* information from branded enums created with `@digitaldefiance/branded-enum`.
|
|
7
|
+
* These utilities enable the i18n library to seamlessly support both traditional
|
|
8
|
+
* TypeScript enums and branded enums for enum translations.
|
|
9
|
+
*
|
|
10
|
+
* ## Overview
|
|
11
|
+
*
|
|
12
|
+
* Branded enums carry runtime metadata (a unique component ID) that enables:
|
|
13
|
+
* - Automatic enum name inference during registration
|
|
14
|
+
* - Runtime identification for debugging and logging
|
|
15
|
+
* - Type-safe translation lookups
|
|
16
|
+
*
|
|
17
|
+
* ## Key Functions
|
|
18
|
+
*
|
|
19
|
+
* - {@link isBrandedEnum} - Type guard to detect branded enums
|
|
20
|
+
* - {@link getBrandedEnumComponentId} - Extract component ID (prefix stripped)
|
|
21
|
+
* - {@link getBrandedEnumId} - Get raw brand ID (for debugging)
|
|
22
|
+
*
|
|
23
|
+
* ## Usage with Enum Translation
|
|
24
|
+
*
|
|
25
|
+
* These utilities are used internally by `EnumRegistry` and `EnumTranslationRegistry`
|
|
26
|
+
* to provide automatic name inference and proper translation lookups for branded enums.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { createBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
31
|
+
* import { isBrandedEnum, getBrandedEnumComponentId } from '@digitaldefiance/i18n-lib';
|
|
32
|
+
*
|
|
33
|
+
* const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' });
|
|
34
|
+
*
|
|
35
|
+
* if (isBrandedEnum(Status)) {
|
|
36
|
+
* const componentId = getBrandedEnumComponentId(Status);
|
|
37
|
+
* console.log(componentId); // 'status'
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @module branded-enum-utils
|
|
42
|
+
* @see {@link EnumRegistry} - V2 enum registry with branded enum support
|
|
43
|
+
* @see {@link EnumTranslationRegistry} - Legacy registry with branded enum support
|
|
44
|
+
*/
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.isBrandedEnum = isBrandedEnum;
|
|
47
|
+
exports.getBrandedEnumComponentId = getBrandedEnumComponentId;
|
|
48
|
+
exports.getBrandedEnumId = getBrandedEnumId;
|
|
49
|
+
const branded_enum_1 = require("@digitaldefiance/branded-enum");
|
|
50
|
+
/**
|
|
51
|
+
* Type guard to check if an object is a branded enum from `@digitaldefiance/branded-enum`.
|
|
52
|
+
*
|
|
53
|
+
* Branded enums are identified by having a valid enum ID retrievable via `getEnumId()`.
|
|
54
|
+
* This function safely checks for this property without throwing errors, making it
|
|
55
|
+
* suitable for use in conditional logic and type narrowing.
|
|
56
|
+
*
|
|
57
|
+
* ## When to Use
|
|
58
|
+
*
|
|
59
|
+
* Use this function when you need to:
|
|
60
|
+
* - Determine if an enum object is branded or traditional
|
|
61
|
+
* - Conditionally apply branded enum-specific logic
|
|
62
|
+
* - Type-narrow an unknown enum object
|
|
63
|
+
*
|
|
64
|
+
* ## Implementation Details
|
|
65
|
+
*
|
|
66
|
+
* The function checks:
|
|
67
|
+
* 1. The object is not null or undefined
|
|
68
|
+
* 2. The object is of type 'object'
|
|
69
|
+
* 3. The object has a valid enum ID (via `getEnumId()`)
|
|
70
|
+
*
|
|
71
|
+
* @param obj - The object to check (can be any value)
|
|
72
|
+
* @returns `true` if the object is a branded enum, `false` otherwise
|
|
73
|
+
*
|
|
74
|
+
* @example Basic usage
|
|
75
|
+
* ```typescript
|
|
76
|
+
* import { createBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
77
|
+
* import { isBrandedEnum } from '@digitaldefiance/i18n-lib';
|
|
78
|
+
*
|
|
79
|
+
* const BrandedStatus = createBrandedEnum('status', { Active: 'active' });
|
|
80
|
+
* const TraditionalStatus = { Active: 'active' };
|
|
81
|
+
*
|
|
82
|
+
* isBrandedEnum(BrandedStatus); // true
|
|
83
|
+
* isBrandedEnum(TraditionalStatus); // false
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example Type narrowing
|
|
87
|
+
* ```typescript
|
|
88
|
+
* function processEnum(enumObj: object) {
|
|
89
|
+
* if (isBrandedEnum(enumObj)) {
|
|
90
|
+
* // TypeScript knows enumObj is AnyBrandedEnum here
|
|
91
|
+
* const id = getBrandedEnumComponentId(enumObj);
|
|
92
|
+
* console.log(`Processing branded enum: ${id}`);
|
|
93
|
+
* } else {
|
|
94
|
+
* console.log('Processing traditional enum');
|
|
95
|
+
* }
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @example Edge cases
|
|
100
|
+
* ```typescript
|
|
101
|
+
* isBrandedEnum(null); // false
|
|
102
|
+
* isBrandedEnum(undefined); // false
|
|
103
|
+
* isBrandedEnum('string'); // false
|
|
104
|
+
* isBrandedEnum(123); // false
|
|
105
|
+
* isBrandedEnum({}); // false
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @see {@link getBrandedEnumComponentId} - Extract component ID from branded enum
|
|
109
|
+
* @see {@link getBrandedEnumId} - Get raw brand ID from branded enum
|
|
110
|
+
*/
|
|
111
|
+
function isBrandedEnum(obj) {
|
|
112
|
+
if (obj === null || obj === undefined || typeof obj !== 'object') {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
// Branded enums have an enum ID retrievable via getEnumId
|
|
116
|
+
const enumId = (0, branded_enum_1.getEnumId)(obj);
|
|
117
|
+
return enumId !== undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Extracts the component ID from a branded enum.
|
|
121
|
+
*
|
|
122
|
+
* For branded enums created with `createI18nStringKeys()`, the brand ID
|
|
123
|
+
* is prefixed with `'i18n:'`. This function strips that prefix to return
|
|
124
|
+
* just the component ID, which is suitable for use as an enum name in
|
|
125
|
+
* registration and error messages.
|
|
126
|
+
*
|
|
127
|
+
* ## Prefix Stripping
|
|
128
|
+
*
|
|
129
|
+
* | Input Brand ID | Returned Component ID |
|
|
130
|
+
* |----------------|----------------------|
|
|
131
|
+
* | `'i18n:my-component'` | `'my-component'` |
|
|
132
|
+
* | `'my-component'` | `'my-component'` |
|
|
133
|
+
* | `'status'` | `'status'` |
|
|
134
|
+
*
|
|
135
|
+
* ## When to Use
|
|
136
|
+
*
|
|
137
|
+
* Use this function when you need:
|
|
138
|
+
* - The component ID for automatic enum name inference
|
|
139
|
+
* - A clean identifier without the `i18n:` prefix
|
|
140
|
+
* - The default name for error messages
|
|
141
|
+
*
|
|
142
|
+
* For debugging purposes where you need the full brand ID including prefix,
|
|
143
|
+
* use {@link getBrandedEnumId} instead.
|
|
144
|
+
*
|
|
145
|
+
* @param enumObj - The object to extract the component ID from
|
|
146
|
+
* @returns The component ID without the `'i18n:'` prefix, or `null` if not a branded enum
|
|
147
|
+
*
|
|
148
|
+
* @example Basic usage
|
|
149
|
+
* ```typescript
|
|
150
|
+
* import { createI18nStringKeys } from '@digitaldefiance/i18n-lib';
|
|
151
|
+
*
|
|
152
|
+
* const MyKeys = createI18nStringKeys('my-component', { A: 'a' });
|
|
153
|
+
*
|
|
154
|
+
* getBrandedEnumComponentId(MyKeys); // 'my-component'
|
|
155
|
+
* getBrandedEnumComponentId({ A: 'a' }); // null (not a branded enum)
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* @example With createBrandedEnum (no prefix)
|
|
159
|
+
* ```typescript
|
|
160
|
+
* import { createBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
161
|
+
*
|
|
162
|
+
* const Status = createBrandedEnum('status', { Active: 'active' });
|
|
163
|
+
*
|
|
164
|
+
* getBrandedEnumComponentId(Status); // 'status'
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* @example Automatic name inference in registration
|
|
168
|
+
* ```typescript
|
|
169
|
+
* const MyKeys = createI18nStringKeys('user-profile', { Title: 'title' });
|
|
170
|
+
*
|
|
171
|
+
* // When registering without explicit name, component ID is used
|
|
172
|
+
* registry.register(MyKeys, translations);
|
|
173
|
+
* // Internally uses: getBrandedEnumComponentId(MyKeys) → 'user-profile'
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* @see {@link isBrandedEnum} - Check if an object is a branded enum
|
|
177
|
+
* @see {@link getBrandedEnumId} - Get raw brand ID without prefix stripping
|
|
178
|
+
*/
|
|
179
|
+
function getBrandedEnumComponentId(enumObj) {
|
|
180
|
+
if (!isBrandedEnum(enumObj)) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const brand = (0, branded_enum_1.getEnumId)(enumObj);
|
|
184
|
+
if (brand === undefined) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
// Remove 'i18n:' prefix if present (used by createI18nStringKeys)
|
|
188
|
+
return brand.startsWith('i18n:') ? brand.slice(5) : brand;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Gets the raw brand/ID from a branded enum without prefix stripping.
|
|
192
|
+
*
|
|
193
|
+
* Unlike {@link getBrandedEnumComponentId}, this function returns the full
|
|
194
|
+
* brand ID including any prefixes (such as `'i18n:'`). This is useful for
|
|
195
|
+
* debugging, logging, and cases where you need the exact brand identifier.
|
|
196
|
+
*
|
|
197
|
+
* ## Comparison with getBrandedEnumComponentId
|
|
198
|
+
*
|
|
199
|
+
* | Function | Input Brand | Output |
|
|
200
|
+
* |----------|-------------|--------|
|
|
201
|
+
* | `getBrandedEnumId` | `'i18n:my-component'` | `'i18n:my-component'` |
|
|
202
|
+
* | `getBrandedEnumComponentId` | `'i18n:my-component'` | `'my-component'` |
|
|
203
|
+
*
|
|
204
|
+
* ## When to Use
|
|
205
|
+
*
|
|
206
|
+
* Use this function when you need:
|
|
207
|
+
* - The exact brand ID for debugging or logging
|
|
208
|
+
* - To distinguish between different branded enum sources
|
|
209
|
+
* - The full identifier including any prefixes
|
|
210
|
+
*
|
|
211
|
+
* For registration and user-facing names, use {@link getBrandedEnumComponentId}
|
|
212
|
+
* which strips the `i18n:` prefix.
|
|
213
|
+
*
|
|
214
|
+
* @param enumObj - The object to get the brand ID from
|
|
215
|
+
* @returns The raw brand ID, or `null` if not a branded enum
|
|
216
|
+
*
|
|
217
|
+
* @example Debugging and logging
|
|
218
|
+
* ```typescript
|
|
219
|
+
* import { createI18nStringKeys } from '@digitaldefiance/i18n-lib';
|
|
220
|
+
*
|
|
221
|
+
* const MyKeys = createI18nStringKeys('my-component', { A: 'a' });
|
|
222
|
+
*
|
|
223
|
+
* // For debugging, get the full brand ID
|
|
224
|
+
* console.log(`Brand ID: ${getBrandedEnumId(MyKeys)}`);
|
|
225
|
+
* // Output: 'Brand ID: i18n:my-component'
|
|
226
|
+
*
|
|
227
|
+
* // For user-facing names, use component ID
|
|
228
|
+
* console.log(`Component: ${getBrandedEnumComponentId(MyKeys)}`);
|
|
229
|
+
* // Output: 'Component: my-component'
|
|
230
|
+
* ```
|
|
231
|
+
*
|
|
232
|
+
* @example Comparing branded enums
|
|
233
|
+
* ```typescript
|
|
234
|
+
* import { createBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
235
|
+
* import { createI18nStringKeys } from '@digitaldefiance/i18n-lib';
|
|
236
|
+
*
|
|
237
|
+
* const RegularBranded = createBrandedEnum('status', { A: 'a' });
|
|
238
|
+
* const I18nBranded = createI18nStringKeys('status', { A: 'a' });
|
|
239
|
+
*
|
|
240
|
+
* getBrandedEnumId(RegularBranded); // 'status'
|
|
241
|
+
* getBrandedEnumId(I18nBranded); // 'i18n:status'
|
|
242
|
+
* ```
|
|
243
|
+
*
|
|
244
|
+
* @see {@link isBrandedEnum} - Check if an object is a branded enum
|
|
245
|
+
* @see {@link getBrandedEnumComponentId} - Get component ID with prefix stripped
|
|
246
|
+
*/
|
|
247
|
+
function getBrandedEnumId(enumObj) {
|
|
248
|
+
if (!isBrandedEnum(enumObj)) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const brand = (0, branded_enum_1.getEnumId)(enumObj);
|
|
252
|
+
return brand ?? null;
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=branded-enum-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branded-enum-utils.js","sourceRoot":"","sources":["../../../../packages/digitaldefiance-i18n-lib/src/branded-enum-utils.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;;AAkEH,sCAOC;AA8DD,8DAUC;AA2DD,4CAMC;AA/MD,gEAA0D;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,SAAgB,aAAa,CAAC,GAAY;IACxC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACjE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,0DAA0D;IAC1D,MAAM,MAAM,GAAG,IAAA,wBAAS,EAAC,GAAqB,CAAC,CAAC;IAChD,OAAO,MAAM,KAAK,SAAS,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,SAAgB,yBAAyB,CAAC,OAAgB;IACxD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,IAAA,wBAAS,EAAC,OAAO,CAAC,CAAC;IACjC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,kEAAkE;IAClE,OAAO,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,SAAgB,gBAAgB,CAAC,OAAgB;IAC/C,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,IAAA,wBAAS,EAAC,OAAO,CAAC,CAAC;IACjC,OAAO,KAAK,IAAI,IAAI,CAAC;AACvB,CAAC"}
|