@digitaldefiance/i18n-lib 4.0.4 → 4.0.5

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.
Files changed (2) hide show
  1. package/README.md +279 -0
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -31,6 +31,7 @@ Part of [Express Suite](https://github.com/Digital-Defiance/express-suite)
31
31
  - **Fluent Builder**: I18nBuilder for clean, chainable engine configuration
32
32
  - **Core System Strings**: Pre-built translations for common UI elements and errors
33
33
  - **Type Safety**: Full TypeScript support with generic types
34
+ - **Branded Enums**: Runtime-identifiable string keys with collision detection and component routing
34
35
  - **Error Handling**: Comprehensive error classes with translation support and ICU formatting
35
36
  - **93.22% Test Coverage**: 1,738 tests covering all features
36
37
  - **Security Hardened**: See [SECURITY.md](SECURITY.md) for details
@@ -671,6 +672,21 @@ LanguageCodes.UK // 'uk'
671
672
  - `switchToAdmin()` - Switch to admin context
672
673
  - `switchToUser()` - Switch to user context
673
674
  - `validate()` - Validate all components
675
+ - `registerBrandedComponent(registration)` - Register component with branded string keys
676
+ - `getCollisionReport()` - Get map of key collisions across components
677
+
678
+ ### Branded Enum Functions
679
+
680
+ - `createI18nStringKeys(componentId, keys)` - Create a branded enum for i18n keys
681
+ - `createI18nStringKeysFromEnum(componentId, enum)` - Convert legacy enum to branded enum
682
+ - `mergeI18nStringKeys(newId, ...enums)` - Merge multiple branded enums
683
+ - `findStringKeySources(key)` - Find components containing a key
684
+ - `resolveStringKeyComponent(key)` - Resolve key to single component
685
+ - `getStringKeysByComponentId(id)` - Get enum by component ID
686
+ - `getRegisteredI18nComponents()` - List all registered components
687
+ - `getStringKeyValues(enum)` - Get all values from enum
688
+ - `isValidStringKey(value, enum)` - Type guard for key validation
689
+ - `checkStringKeyCollisions(...enums)` - Check enums for collisions
674
690
 
675
691
  ### Core Functions
676
692
 
@@ -738,6 +754,176 @@ const registration: ComponentRegistration<MyStringKeys, MyLanguages> = {
738
754
  };
739
755
  ```
740
756
 
757
+ ## Branded Enums
758
+
759
+ Branded enums enable runtime identification of string keys and collision detection between components. Unlike traditional TypeScript enums (erased at compile time), branded enums embed metadata for runtime component routing.
760
+
761
+ ### Why Branded Enums?
762
+
763
+ - **Runtime Identification**: Determine which component a string key belongs to
764
+ - **Collision Detection**: Detect key collisions between components automatically
765
+ - **Component Routing**: Route translations to the correct handler when keys overlap
766
+ - **Zero Overhead**: Values remain raw strings with embedded metadata
767
+
768
+ ### Creating Branded Enums
769
+
770
+ ```typescript
771
+ import { createI18nStringKeys, BrandedStringKeyValue } from '@digitaldefiance/i18n-lib';
772
+
773
+ // Create a branded enum for i18n keys
774
+ export const UserKeys = createI18nStringKeys('user-component', {
775
+ Login: 'user.login',
776
+ Logout: 'user.logout',
777
+ Profile: 'user.profile',
778
+ } as const);
779
+
780
+ // Export the value type for type annotations
781
+ export type UserKeyValue = BrandedStringKeyValue<typeof UserKeys>;
782
+ ```
783
+
784
+ ### Converting from Legacy Enums
785
+
786
+ ```typescript
787
+ import { createI18nStringKeysFromEnum } from '@digitaldefiance/i18n-lib';
788
+
789
+ // Legacy enum
790
+ enum LegacyUserKeys {
791
+ Login = 'user.login',
792
+ Logout = 'user.logout',
793
+ }
794
+
795
+ // Convert to branded enum
796
+ const BrandedUserKeys = createI18nStringKeysFromEnum('user-component', LegacyUserKeys);
797
+ ```
798
+
799
+ ### Registering Branded Components
800
+
801
+ ```typescript
802
+ // Use registerBrandedComponent instead of registerComponent
803
+ engine.registerBrandedComponent({
804
+ component: {
805
+ id: 'user-component',
806
+ name: 'User Component',
807
+ brandedStringKeys: UserKeys,
808
+ },
809
+ strings: {
810
+ [LanguageCodes.EN_US]: {
811
+ [UserKeys.Login]: 'Log In',
812
+ [UserKeys.Logout]: 'Log Out',
813
+ [UserKeys.Profile]: 'My Profile',
814
+ },
815
+ },
816
+ });
817
+ ```
818
+
819
+ ### Collision Detection
820
+
821
+ ```typescript
822
+ import { checkStringKeyCollisions } from '@digitaldefiance/i18n-lib';
823
+
824
+ // Check specific enums for collisions
825
+ const result = checkStringKeyCollisions(UserKeys, AdminKeys, CommonKeys);
826
+
827
+ if (result.hasCollisions) {
828
+ console.warn('String key collisions detected:');
829
+ result.collisions.forEach(c => {
830
+ console.warn(` "${c.value}" in: ${c.componentIds.join(', ')}`);
831
+ });
832
+ }
833
+
834
+ // Or use the engine's collision report
835
+ const collisions = engine.getCollisionReport();
836
+ for (const [key, componentIds] of collisions) {
837
+ console.warn(`Key "${key}" found in: ${componentIds.join(', ')}`);
838
+ }
839
+ ```
840
+
841
+ ### Finding Key Sources
842
+
843
+ ```typescript
844
+ import { findStringKeySources, resolveStringKeyComponent } from '@digitaldefiance/i18n-lib';
845
+
846
+ // Find all components that have a specific key
847
+ const sources = findStringKeySources('user.login');
848
+ // Returns: ['i18n:user-component']
849
+
850
+ // Resolve to a single component (null if ambiguous)
851
+ const componentId = resolveStringKeyComponent('user.login');
852
+ // Returns: 'user-component'
853
+ ```
854
+
855
+ ### Type Guards
856
+
857
+ ```typescript
858
+ import { isValidStringKey } from '@digitaldefiance/i18n-lib';
859
+
860
+ function handleKey(key: string) {
861
+ if (isValidStringKey(key, UserKeys)) {
862
+ // key is now typed as UserKeyValue
863
+ return translateUserKey(key);
864
+ }
865
+ if (isValidStringKey(key, AdminKeys)) {
866
+ // key is now typed as AdminKeyValue
867
+ return translateAdminKey(key);
868
+ }
869
+ return key; // Unknown key
870
+ }
871
+ ```
872
+
873
+ ### Merging Enums
874
+
875
+ ```typescript
876
+ import { mergeI18nStringKeys, getStringKeyValues } from '@digitaldefiance/i18n-lib';
877
+
878
+ // Create a combined key set for the entire app
879
+ const AllKeys = mergeI18nStringKeys('all-keys',
880
+ CoreStringKeys,
881
+ UserKeys,
882
+ AdminKeys,
883
+ );
884
+
885
+ // Get all values from an enum
886
+ const allValues = getStringKeyValues(AllKeys);
887
+ ```
888
+
889
+ ### Best Practices
890
+
891
+ 1. **Use Namespaced Key Values**: Prevent collisions with prefixed values
892
+ ```typescript
893
+ // ✅ Good - namespaced values
894
+ const Keys = createI18nStringKeys('user', {
895
+ Welcome: 'user.welcome',
896
+ } as const);
897
+
898
+ // ❌ Bad - generic values may collide
899
+ const Keys = createI18nStringKeys('user', {
900
+ Welcome: 'welcome',
901
+ } as const);
902
+ ```
903
+
904
+ 2. **Use Consistent Component IDs**: Match IDs across enum creation and registration
905
+
906
+ 3. **Always Use `as const`**: Preserve literal types
907
+ ```typescript
908
+ // ✅ Correct - literal types preserved
909
+ const Keys = createI18nStringKeys('id', { A: 'a' } as const);
910
+
911
+ // ❌ Wrong - types widened to string
912
+ const Keys = createI18nStringKeys('id', { A: 'a' });
913
+ ```
914
+
915
+ 4. **Check for Collisions During Development**:
916
+ ```typescript
917
+ if (process.env.NODE_ENV === 'development') {
918
+ const collisions = engine.getCollisionReport();
919
+ if (collisions.size > 0) {
920
+ console.warn('⚠️ String key collisions detected!');
921
+ }
922
+ }
923
+ ```
924
+
925
+ For complete migration guide, see [BRANDED_ENUM_MIGRATION.md](docs/BRANDED_ENUM_MIGRATION.md).
926
+
741
927
  ## Browser Support
742
928
 
743
929
  - Chrome/Edge: Latest 2 versions
@@ -767,6 +953,99 @@ Contributions welcome! Please:
767
953
 
768
954
  ## ChangeLog
769
955
 
956
+ ### Version 4.0.4
957
+
958
+ **Branded Enums for Runtime String Key Identification**
959
+
960
+ This release introduces branded enums - a powerful feature enabling runtime identification of i18n string keys, collision detection, and intelligent component routing.
961
+
962
+ **Why Branded Enums?**
963
+
964
+ Traditional TypeScript enums are erased at compile time, making it impossible to:
965
+ - Determine which component a string key belongs to at runtime
966
+ - Detect key collisions between components
967
+ - Route translations to the correct handler when keys overlap
968
+
969
+ Branded enums solve these problems by embedding metadata that enables runtime identification while maintaining zero overhead (values remain raw strings).
970
+
971
+ **New Features:**
972
+
973
+ - **`createI18nStringKeys()`**: Factory function to create branded enums with component metadata
974
+ ```typescript
975
+ const UserKeys = createI18nStringKeys('user-component', {
976
+ Login: 'user.login',
977
+ Logout: 'user.logout',
978
+ } as const);
979
+ ```
980
+
981
+ - **`createI18nStringKeysFromEnum()`**: Convert existing TypeScript enums to branded enums for gradual migration
982
+
983
+ - **`mergeI18nStringKeys()`**: Combine multiple branded enums into a single namespace
984
+
985
+ - **Collision Detection**: Automatically detect when multiple components use the same string key
986
+ ```typescript
987
+ const result = checkStringKeyCollisions(UserKeys, AdminKeys);
988
+ if (result.hasCollisions) {
989
+ // Handle collisions
990
+ }
991
+ ```
992
+
993
+ - **Key Source Resolution**: Find which component(s) a key belongs to
994
+ ```typescript
995
+ const sources = findStringKeySources('user.login'); // ['i18n:user-component']
996
+ const componentId = resolveStringKeyComponent('user.login'); // 'user-component'
997
+ ```
998
+
999
+ - **Type Guards**: Runtime validation with TypeScript type narrowing
1000
+ ```typescript
1001
+ if (isValidStringKey(key, UserKeys)) {
1002
+ // key is now typed as UserKeyValue
1003
+ }
1004
+ ```
1005
+
1006
+ - **Engine Integration**: New `registerBrandedComponent()` method and `getCollisionReport()` for engine-level collision tracking
1007
+
1008
+ **New Types:**
1009
+
1010
+ - `BrandedStringKeys<T>` - Type alias for branded enum objects
1011
+ - `BrandedStringKeyValue<E>` - Extract value union from branded enum
1012
+ - `StringKeyCollisionResult` - Result type for collision detection
1013
+
1014
+ **API Additions:**
1015
+
1016
+ | Function | Description |
1017
+ |----------|-------------|
1018
+ | `createI18nStringKeys(componentId, keys)` | Create a branded enum for i18n keys |
1019
+ | `createI18nStringKeysFromEnum(componentId, enum)` | Convert legacy enum to branded enum |
1020
+ | `mergeI18nStringKeys(newId, ...enums)` | Merge multiple branded enums |
1021
+ | `findStringKeySources(key)` | Find components containing a key |
1022
+ | `resolveStringKeyComponent(key)` | Resolve key to single component |
1023
+ | `getStringKeysByComponentId(id)` | Get enum by component ID |
1024
+ | `getRegisteredI18nComponents()` | List all registered components |
1025
+ | `getStringKeyValues(enum)` | Get all values from enum |
1026
+ | `isValidStringKey(value, enum)` | Type guard for key validation |
1027
+ | `checkStringKeyCollisions(...enums)` | Check enums for collisions |
1028
+
1029
+ **Documentation:**
1030
+
1031
+ - **[BRANDED_ENUM_MIGRATION.md](docs/BRANDED_ENUM_MIGRATION.md)** - Complete migration guide with examples
1032
+ - Added Branded Enums section to README with usage examples
1033
+ - API reference updated with all new functions
1034
+
1035
+ **Breaking Changes:**
1036
+
1037
+ None - This release is fully backward compatible. Traditional enums continue to work with existing APIs. Branded enums are opt-in for new code or gradual migration.
1038
+
1039
+ **Migration:**
1040
+
1041
+ No migration required for existing code. To adopt branded enums:
1042
+
1043
+ 1. Convert enums using `createI18nStringKeys()` or `createI18nStringKeysFromEnum()`
1044
+ 2. Use `registerBrandedComponent()` instead of `registerComponent()`
1045
+ 3. Optionally enable collision detection during development
1046
+
1047
+ See [BRANDED_ENUM_MIGRATION.md](docs/BRANDED_ENUM_MIGRATION.md) for detailed migration steps.
1048
+
770
1049
  ### Version 3.7.5
771
1050
 
772
1051
  **Type Safety Improvements Release**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitaldefiance/i18n-lib",
3
- "version": "4.0.4",
3
+ "version": "4.0.5",
4
4
  "description": "i18n library with enum translation support",
5
5
  "homepage": "https://github.com/Digital-Defiance/i18n-lib",
6
6
  "repository": {