@digitaldefiance/i18n-lib 4.3.2 → 4.5.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 +422 -15
- package/package.json +2 -2
- package/src/core/i18n-engine.d.ts +29 -3
- package/src/core/i18n-engine.d.ts.map +1 -1
- package/src/core/i18n-engine.js +69 -9
- package/src/core/i18n-engine.js.map +1 -1
- package/src/core/string-key-enum-registry.d.ts +76 -1
- package/src/core/string-key-enum-registry.d.ts.map +1 -1
- package/src/core/string-key-enum-registry.js +147 -18
- package/src/core/string-key-enum-registry.js.map +1 -1
- package/src/create-i18n-setup.d.ts +11 -0
- package/src/create-i18n-setup.d.ts.map +1 -0
- package/src/create-i18n-setup.js +89 -0
- package/src/create-i18n-setup.js.map +1 -0
- package/src/index.d.ts +1 -0
- package/src/index.d.ts.map +1 -1
- package/src/index.js +4 -1
- package/src/index.js.map +1 -1
- package/src/interfaces/i18n-component-package.interface.d.ts +14 -0
- package/src/interfaces/i18n-component-package.interface.d.ts.map +1 -0
- package/src/interfaces/i18n-component-package.interface.js +3 -0
- package/src/interfaces/i18n-component-package.interface.js.map +1 -0
- package/src/interfaces/i18n-engine.interface.d.ts +3 -2
- package/src/interfaces/i18n-engine.interface.d.ts.map +1 -1
- package/src/interfaces/i18n-setup-config.interface.d.ts +25 -0
- package/src/interfaces/i18n-setup-config.interface.d.ts.map +1 -0
- package/src/interfaces/i18n-setup-config.interface.js +3 -0
- package/src/interfaces/i18n-setup-config.interface.js.map +1 -0
- package/src/interfaces/i18n-setup-result.interface.d.ts +31 -0
- package/src/interfaces/i18n-setup-result.interface.d.ts.map +1 -0
- package/src/interfaces/i18n-setup-result.interface.js +3 -0
- package/src/interfaces/i18n-setup-result.interface.js.map +1 -0
- package/src/interfaces/index.d.ts +3 -0
- package/src/interfaces/index.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -1315,6 +1315,406 @@ function translateAnyValue<T extends string | number>(
|
|
|
1315
1315
|
}
|
|
1316
1316
|
```
|
|
1317
1317
|
|
|
1318
|
+
## Monorepo i18n-setup Guide
|
|
1319
|
+
|
|
1320
|
+
When building an application that consumes multiple Express Suite packages (e.g., `suite-core-lib`, `ecies-lib`), you need a single `i18n-setup.ts` file that initializes the engine, registers all components and their branded string key enums, sets up the global context, and exports translation helpers.
|
|
1321
|
+
|
|
1322
|
+
### Recommended: Factory Approach (`createI18nSetup`)
|
|
1323
|
+
|
|
1324
|
+
The `createI18nSetup()` factory replaces ~200 lines of manual boilerplate with a single function call. It handles engine creation, core component registration, library component registration, branded enum registration, and context initialization automatically.
|
|
1325
|
+
|
|
1326
|
+
```typescript
|
|
1327
|
+
// i18n-setup.ts — Application-level i18n initialization (factory approach)
|
|
1328
|
+
|
|
1329
|
+
import {
|
|
1330
|
+
createI18nSetup,
|
|
1331
|
+
createI18nStringKeys,
|
|
1332
|
+
LanguageCodes,
|
|
1333
|
+
} from '@digitaldefiance/i18n-lib';
|
|
1334
|
+
import { createSuiteCoreComponentPackage } from '@digitaldefiance/suite-core-lib';
|
|
1335
|
+
import { createEciesComponentPackage } from '@digitaldefiance/ecies-lib';
|
|
1336
|
+
|
|
1337
|
+
// 1. Define your application component
|
|
1338
|
+
export const AppComponentId = 'MyApp';
|
|
1339
|
+
|
|
1340
|
+
export const AppStringKey = createI18nStringKeys(AppComponentId, {
|
|
1341
|
+
SiteTitle: 'siteTitle',
|
|
1342
|
+
SiteDescription: 'siteDescription',
|
|
1343
|
+
WelcomeMessage: 'welcomeMessage',
|
|
1344
|
+
} as const);
|
|
1345
|
+
|
|
1346
|
+
const appStrings = {
|
|
1347
|
+
[LanguageCodes.EN_US]: {
|
|
1348
|
+
siteTitle: 'My Application',
|
|
1349
|
+
siteDescription: 'An Express Suite application',
|
|
1350
|
+
welcomeMessage: 'Welcome, {name}!',
|
|
1351
|
+
},
|
|
1352
|
+
[LanguageCodes.FR]: {
|
|
1353
|
+
siteTitle: 'Mon Application',
|
|
1354
|
+
siteDescription: 'Une application Express Suite',
|
|
1355
|
+
welcomeMessage: 'Bienvenue, {name} !',
|
|
1356
|
+
},
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1359
|
+
// 2. Create the i18n setup — one call does everything
|
|
1360
|
+
const i18n = createI18nSetup({
|
|
1361
|
+
componentId: AppComponentId,
|
|
1362
|
+
stringKeyEnum: AppStringKey,
|
|
1363
|
+
strings: appStrings,
|
|
1364
|
+
aliases: ['AppStringKey'],
|
|
1365
|
+
libraryComponents: [
|
|
1366
|
+
createSuiteCoreComponentPackage(),
|
|
1367
|
+
createEciesComponentPackage(),
|
|
1368
|
+
],
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
// 3. Export the public API
|
|
1372
|
+
export const { engine: i18nEngine, translate, safeTranslate } = i18n;
|
|
1373
|
+
export const i18nContext = i18n.context;
|
|
1374
|
+
```
|
|
1375
|
+
|
|
1376
|
+
The factory:
|
|
1377
|
+
- Creates or reuses an `I18nEngine` instance (idempotent via `instanceKey`, defaults to `'default'`)
|
|
1378
|
+
- Registers the Core i18n component automatically
|
|
1379
|
+
- Registers each library component's `ComponentConfig` and branded `stringKeyEnum` from the `I18nComponentPackage`
|
|
1380
|
+
- Registers your application component and its branded enum
|
|
1381
|
+
- Initializes `GlobalActiveContext` with the specified `defaultLanguage` (defaults to `'en-US'`)
|
|
1382
|
+
- Returns an `I18nSetupResult` with `engine`, `translate`, `safeTranslate`, `context`, `setLanguage`, `setAdminLanguage`, `setContext`, `getLanguage`, `getAdminLanguage`, and `reset`
|
|
1383
|
+
|
|
1384
|
+
Calling `createI18nSetup()` multiple times with the same `instanceKey` reuses the existing engine — safe for monorepos where a subset library and a superset API both call the factory.
|
|
1385
|
+
|
|
1386
|
+
### I18nComponentPackage Interface
|
|
1387
|
+
|
|
1388
|
+
Library authors bundle a `ComponentConfig` with its branded string key enum in a single `I18nComponentPackage` object. This lets the factory auto-register both the component and its enum in one step.
|
|
1389
|
+
|
|
1390
|
+
```typescript
|
|
1391
|
+
import type { AnyBrandedEnum } from '@digitaldefiance/branded-enum';
|
|
1392
|
+
import type { ComponentConfig } from '@digitaldefiance/i18n-lib';
|
|
1393
|
+
|
|
1394
|
+
interface I18nComponentPackage {
|
|
1395
|
+
readonly config: ComponentConfig;
|
|
1396
|
+
readonly stringKeyEnum?: AnyBrandedEnum;
|
|
1397
|
+
}
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
Each library exports a `createXxxComponentPackage()` function:
|
|
1401
|
+
|
|
1402
|
+
```typescript
|
|
1403
|
+
// In suite-core-lib
|
|
1404
|
+
import { createSuiteCoreComponentPackage } from '@digitaldefiance/suite-core-lib';
|
|
1405
|
+
const pkg = createSuiteCoreComponentPackage();
|
|
1406
|
+
// pkg.config → SuiteCore ComponentConfig
|
|
1407
|
+
// pkg.stringKeyEnum → SuiteCoreStringKey branded enum
|
|
1408
|
+
|
|
1409
|
+
// In ecies-lib
|
|
1410
|
+
import { createEciesComponentPackage } from '@digitaldefiance/ecies-lib';
|
|
1411
|
+
const pkg = createEciesComponentPackage();
|
|
1412
|
+
|
|
1413
|
+
// In node-ecies-lib
|
|
1414
|
+
import { createNodeEciesComponentPackage } from '@digitaldefiance/node-ecies-lib';
|
|
1415
|
+
const pkg = createNodeEciesComponentPackage();
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
The existing `createSuiteCoreComponentConfig()` and `createEciesComponentConfig()` functions remain available for consumers that prefer the manual approach.
|
|
1419
|
+
|
|
1420
|
+
### Browser-Safe Fallback
|
|
1421
|
+
|
|
1422
|
+
In browser environments, bundlers like Vite and webpack may create separate copies of `@digitaldefiance/branded-enum`, causing `isBrandedEnum()` to fail due to Symbol/WeakSet identity mismatch. This breaks `registerStringKeyEnum()` and `translateStringKey()`.
|
|
1423
|
+
|
|
1424
|
+
The engine now includes a transparent fallback: when the `StringKeyEnumRegistry` fails to resolve a component ID for a known string key value, the engine scans all registered components' string keys to find the matching component. The result is cached in a `ValueComponentLookupCache` for subsequent lookups, and the cache is invalidated whenever a new component is registered.
|
|
1425
|
+
|
|
1426
|
+
This means consumers do not need manual workarounds (like `safeRegisterStringKeyEnum` or `_componentLookup` maps) for bundler Symbol mismatch issues. Both `translateStringKey` and `safeTranslateStringKey` use the fallback automatically.
|
|
1427
|
+
|
|
1428
|
+
### Advanced: Manual Setup
|
|
1429
|
+
|
|
1430
|
+
For advanced use cases where you need full control over engine creation, validation options, or custom registration order, you can use the manual approach:
|
|
1431
|
+
|
|
1432
|
+
<details>
|
|
1433
|
+
<summary>Click to expand manual i18n-setup.ts example</summary>
|
|
1434
|
+
|
|
1435
|
+
```typescript
|
|
1436
|
+
// i18n-setup.ts — Manual approach (advanced)
|
|
1437
|
+
|
|
1438
|
+
import {
|
|
1439
|
+
I18nBuilder,
|
|
1440
|
+
I18nEngine,
|
|
1441
|
+
LanguageCodes,
|
|
1442
|
+
GlobalActiveContext,
|
|
1443
|
+
getCoreLanguageDefinitions,
|
|
1444
|
+
createCoreComponentRegistration,
|
|
1445
|
+
createI18nStringKeys,
|
|
1446
|
+
type CoreLanguageCode,
|
|
1447
|
+
type IActiveContext,
|
|
1448
|
+
type ComponentConfig,
|
|
1449
|
+
type LanguageContextSpace,
|
|
1450
|
+
} from '@digitaldefiance/i18n-lib';
|
|
1451
|
+
import type { BrandedEnumValue } from '@digitaldefiance/branded-enum';
|
|
1452
|
+
import {
|
|
1453
|
+
createSuiteCoreComponentConfig,
|
|
1454
|
+
SuiteCoreStringKey,
|
|
1455
|
+
} from '@digitaldefiance/suite-core-lib';
|
|
1456
|
+
import {
|
|
1457
|
+
createEciesComponentConfig,
|
|
1458
|
+
EciesStringKey,
|
|
1459
|
+
} from '@digitaldefiance/ecies-lib';
|
|
1460
|
+
|
|
1461
|
+
export const AppComponentId = 'MyApp';
|
|
1462
|
+
|
|
1463
|
+
export const AppStringKey = createI18nStringKeys(AppComponentId, {
|
|
1464
|
+
SiteTitle: 'siteTitle',
|
|
1465
|
+
SiteDescription: 'siteDescription',
|
|
1466
|
+
WelcomeMessage: 'welcomeMessage',
|
|
1467
|
+
} as const);
|
|
1468
|
+
|
|
1469
|
+
export type AppStringKeyValue = BrandedEnumValue<typeof AppStringKey>;
|
|
1470
|
+
|
|
1471
|
+
const appStrings: Record<string, Record<string, string>> = {
|
|
1472
|
+
[LanguageCodes.EN_US]: {
|
|
1473
|
+
siteTitle: 'My Application',
|
|
1474
|
+
siteDescription: 'An Express Suite application',
|
|
1475
|
+
welcomeMessage: 'Welcome, {name}!',
|
|
1476
|
+
},
|
|
1477
|
+
[LanguageCodes.FR]: {
|
|
1478
|
+
siteTitle: 'Mon Application',
|
|
1479
|
+
siteDescription: 'Une application Express Suite',
|
|
1480
|
+
welcomeMessage: 'Bienvenue, {name} !',
|
|
1481
|
+
},
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
function createAppComponentConfig(): ComponentConfig {
|
|
1485
|
+
return { id: AppComponentId, strings: appStrings, aliases: ['AppStringKey'] };
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// Create or reuse engine
|
|
1489
|
+
let i18nEngine: I18nEngine;
|
|
1490
|
+
if (I18nEngine.hasInstance('default')) {
|
|
1491
|
+
i18nEngine = I18nEngine.getInstance('default');
|
|
1492
|
+
} else {
|
|
1493
|
+
i18nEngine = I18nBuilder.create()
|
|
1494
|
+
.withLanguages(getCoreLanguageDefinitions())
|
|
1495
|
+
.withDefaultLanguage(LanguageCodes.EN_US)
|
|
1496
|
+
.withFallbackLanguage(LanguageCodes.EN_US)
|
|
1497
|
+
.withInstanceKey('default')
|
|
1498
|
+
.build();
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// Register components
|
|
1502
|
+
const coreReg = createCoreComponentRegistration();
|
|
1503
|
+
i18nEngine.registerIfNotExists({
|
|
1504
|
+
id: coreReg.component.id,
|
|
1505
|
+
strings: coreReg.strings as Record<string, Record<string, string>>,
|
|
1506
|
+
});
|
|
1507
|
+
i18nEngine.registerIfNotExists(createSuiteCoreComponentConfig());
|
|
1508
|
+
i18nEngine.registerIfNotExists(createEciesComponentConfig());
|
|
1509
|
+
i18nEngine.registerIfNotExists(createAppComponentConfig());
|
|
1510
|
+
|
|
1511
|
+
// Register branded enums
|
|
1512
|
+
if (!i18nEngine.hasStringKeyEnum(SuiteCoreStringKey)) {
|
|
1513
|
+
i18nEngine.registerStringKeyEnum(SuiteCoreStringKey);
|
|
1514
|
+
}
|
|
1515
|
+
if (!i18nEngine.hasStringKeyEnum(EciesStringKey)) {
|
|
1516
|
+
i18nEngine.registerStringKeyEnum(EciesStringKey);
|
|
1517
|
+
}
|
|
1518
|
+
if (!i18nEngine.hasStringKeyEnum(AppStringKey)) {
|
|
1519
|
+
i18nEngine.registerStringKeyEnum(AppStringKey);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Initialize context
|
|
1523
|
+
const globalContext = GlobalActiveContext.getInstance<
|
|
1524
|
+
CoreLanguageCode,
|
|
1525
|
+
IActiveContext<CoreLanguageCode>
|
|
1526
|
+
>();
|
|
1527
|
+
globalContext.createContext(LanguageCodes.EN_US, LanguageCodes.EN_US, AppComponentId);
|
|
1528
|
+
|
|
1529
|
+
// Export helpers
|
|
1530
|
+
export { i18nEngine };
|
|
1531
|
+
|
|
1532
|
+
export const translate = (
|
|
1533
|
+
name: AppStringKeyValue,
|
|
1534
|
+
variables?: Record<string, string | number>,
|
|
1535
|
+
language?: CoreLanguageCode,
|
|
1536
|
+
context?: LanguageContextSpace,
|
|
1537
|
+
): string => {
|
|
1538
|
+
const activeContext =
|
|
1539
|
+
context ?? globalContext.getContext(AppComponentId).currentContext;
|
|
1540
|
+
const lang =
|
|
1541
|
+
language ??
|
|
1542
|
+
(activeContext === 'admin'
|
|
1543
|
+
? globalContext.getContext(AppComponentId).adminLanguage
|
|
1544
|
+
: globalContext.getContext(AppComponentId).language);
|
|
1545
|
+
return i18nEngine.translateStringKey(name, variables, lang);
|
|
1546
|
+
};
|
|
1547
|
+
|
|
1548
|
+
export const safeTranslate = (
|
|
1549
|
+
name: AppStringKeyValue,
|
|
1550
|
+
variables?: Record<string, string | number>,
|
|
1551
|
+
language?: CoreLanguageCode,
|
|
1552
|
+
context?: LanguageContextSpace,
|
|
1553
|
+
): string => {
|
|
1554
|
+
const activeContext =
|
|
1555
|
+
context ?? globalContext.getContext(AppComponentId).currentContext;
|
|
1556
|
+
const lang =
|
|
1557
|
+
language ??
|
|
1558
|
+
(activeContext === 'admin'
|
|
1559
|
+
? globalContext.getContext(AppComponentId).adminLanguage
|
|
1560
|
+
: globalContext.getContext(AppComponentId).language);
|
|
1561
|
+
return i18nEngine.safeTranslateStringKey(name, variables, lang);
|
|
1562
|
+
};
|
|
1563
|
+
```
|
|
1564
|
+
|
|
1565
|
+
</details>
|
|
1566
|
+
|
|
1567
|
+
Key points for the manual approach:
|
|
1568
|
+
|
|
1569
|
+
- **Idempotent engine creation**: `I18nEngine.hasInstance('default')` checks for an existing engine before building a new one.
|
|
1570
|
+
- **Core component first**: Always register the Core component before other components — it provides error message translations used internally.
|
|
1571
|
+
- **`registerIfNotExists`**: All component registrations use the idempotent variant so multiple packages can safely register without conflicts.
|
|
1572
|
+
- **`hasStringKeyEnum` guard**: Prevents duplicate enum registration when multiple setup files run in the same process.
|
|
1573
|
+
- **Context-aware helpers**: The `translate` and `safeTranslate` functions resolve the active language from `GlobalActiveContext`, respecting user vs. admin context.
|
|
1574
|
+
|
|
1575
|
+
### Migration Guide
|
|
1576
|
+
|
|
1577
|
+
Converting an existing manual `i18n-setup.ts` to the factory approach is straightforward. There are no breaking changes — the factory produces the same engine state as the manual approach.
|
|
1578
|
+
|
|
1579
|
+
#### Before (manual)
|
|
1580
|
+
|
|
1581
|
+
```typescript
|
|
1582
|
+
import { I18nBuilder, I18nEngine, LanguageCodes, GlobalActiveContext, ... } from '@digitaldefiance/i18n-lib';
|
|
1583
|
+
import { createSuiteCoreComponentConfig, SuiteCoreStringKey } from '@digitaldefiance/suite-core-lib';
|
|
1584
|
+
import { createEciesComponentConfig, EciesStringKey } from '@digitaldefiance/ecies-lib';
|
|
1585
|
+
|
|
1586
|
+
// ~200 lines: engine creation, core registration, library registration,
|
|
1587
|
+
// enum registration, context initialization, translate helpers...
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
#### After (factory)
|
|
1591
|
+
|
|
1592
|
+
```typescript
|
|
1593
|
+
import { createI18nSetup, createI18nStringKeys, LanguageCodes } from '@digitaldefiance/i18n-lib';
|
|
1594
|
+
import { createSuiteCoreComponentPackage } from '@digitaldefiance/suite-core-lib';
|
|
1595
|
+
import { createEciesComponentPackage } from '@digitaldefiance/ecies-lib';
|
|
1596
|
+
|
|
1597
|
+
const i18n = createI18nSetup({
|
|
1598
|
+
componentId: AppComponentId,
|
|
1599
|
+
stringKeyEnum: AppStringKey,
|
|
1600
|
+
strings: appStrings,
|
|
1601
|
+
libraryComponents: [
|
|
1602
|
+
createSuiteCoreComponentPackage(),
|
|
1603
|
+
createEciesComponentPackage(),
|
|
1604
|
+
],
|
|
1605
|
+
});
|
|
1606
|
+
|
|
1607
|
+
export const { engine: i18nEngine, translate, safeTranslate } = i18n;
|
|
1608
|
+
```
|
|
1609
|
+
|
|
1610
|
+
#### Migration steps
|
|
1611
|
+
|
|
1612
|
+
1. Replace `createXxxComponentConfig` imports with `createXxxComponentPackage` imports
|
|
1613
|
+
2. Replace manual engine creation (`I18nBuilder` / `I18nEngine.registerIfNotExists`) with `createI18nSetup()`
|
|
1614
|
+
3. Move library component registrations into the `libraryComponents` array
|
|
1615
|
+
4. Remove manual `registerStringKeyEnum` calls — the factory handles them
|
|
1616
|
+
5. Remove manual `GlobalActiveContext` initialization — the factory handles it
|
|
1617
|
+
6. Destructure the returned `I18nSetupResult` to get `engine`, `translate`, `safeTranslate`, etc.
|
|
1618
|
+
|
|
1619
|
+
#### Notes
|
|
1620
|
+
|
|
1621
|
+
- Existing `createXxxComponentConfig()` functions remain available for consumers that prefer the manual approach
|
|
1622
|
+
- The factory uses the same `registerIfNotExists` pattern internally, so it is safe to mix factory and manual consumers sharing the same engine instance
|
|
1623
|
+
- There are no breaking changes or behavioral differences between the manual and factory approaches
|
|
1624
|
+
|
|
1625
|
+
### createI18nStringKeys vs createI18nStringKeysFromEnum
|
|
1626
|
+
|
|
1627
|
+
Both functions produce identical `BrandedStringKeys` output. Choose based on your starting point.
|
|
1628
|
+
|
|
1629
|
+
#### createI18nStringKeys — preferred for new code
|
|
1630
|
+
|
|
1631
|
+
Creates a branded enum directly from an `as const` object literal:
|
|
1632
|
+
|
|
1633
|
+
```typescript
|
|
1634
|
+
import { createI18nStringKeys } from '@digitaldefiance/i18n-lib';
|
|
1635
|
+
|
|
1636
|
+
export const AppStringKey = createI18nStringKeys('my-app', {
|
|
1637
|
+
Welcome: 'welcome',
|
|
1638
|
+
Goodbye: 'goodbye',
|
|
1639
|
+
} as const);
|
|
1640
|
+
```
|
|
1641
|
+
|
|
1642
|
+
Use this when writing a new component from scratch. The `as const` assertion preserves literal types so each key is fully type-safe.
|
|
1643
|
+
|
|
1644
|
+
#### createI18nStringKeysFromEnum — useful for migration
|
|
1645
|
+
|
|
1646
|
+
Wraps an existing TypeScript enum into a branded enum:
|
|
1647
|
+
|
|
1648
|
+
```typescript
|
|
1649
|
+
import { createI18nStringKeysFromEnum } from '@digitaldefiance/i18n-lib';
|
|
1650
|
+
|
|
1651
|
+
// Existing traditional enum
|
|
1652
|
+
enum LegacyStringKeys {
|
|
1653
|
+
Welcome = 'welcome',
|
|
1654
|
+
Goodbye = 'goodbye',
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
export const AppStringKey = createI18nStringKeysFromEnum(
|
|
1658
|
+
'my-app',
|
|
1659
|
+
LegacyStringKeys,
|
|
1660
|
+
);
|
|
1661
|
+
```
|
|
1662
|
+
|
|
1663
|
+
Use this when migrating code that already has a traditional `enum`. Internally, `createI18nStringKeysFromEnum` filters out TypeScript's reverse numeric mappings and then delegates to `createI18nStringKeys`.
|
|
1664
|
+
|
|
1665
|
+
#### Comparison
|
|
1666
|
+
|
|
1667
|
+
| Aspect | `createI18nStringKeys` | `createI18nStringKeysFromEnum` |
|
|
1668
|
+
|---|---|---|
|
|
1669
|
+
| Input | Object literal with `as const` | Existing TypeScript enum |
|
|
1670
|
+
| Use case | New code, fresh components | Migrating existing enum-based code |
|
|
1671
|
+
| Output | `BrandedStringKeys<T>` | `BrandedStringKeys<T>` |
|
|
1672
|
+
| Internal behavior | Calls `createBrandedEnum` directly | Filters reverse numeric mappings, then delegates to `createI18nStringKeys` |
|
|
1673
|
+
|
|
1674
|
+
### Troubleshooting: Branded Enum Module Identity in Monorepos
|
|
1675
|
+
|
|
1676
|
+
#### Symptom
|
|
1677
|
+
|
|
1678
|
+
`registerStringKeyEnum()` throws or `hasStringKeyEnum()` returns `false` for a branded enum that was created with `createI18nStringKeys` or `createI18nStringKeysFromEnum`.
|
|
1679
|
+
|
|
1680
|
+
#### Root Cause
|
|
1681
|
+
|
|
1682
|
+
`isBrandedEnum()` returns `false` when the `@digitaldefiance/branded-enum` global registry holds a different module instance. This happens when multiple copies of the package are installed — each copy has its own `WeakSet` / `Symbol` registry, so enums created by one copy are not recognized by another.
|
|
1683
|
+
|
|
1684
|
+
#### Diagnosis
|
|
1685
|
+
|
|
1686
|
+
Check for duplicate installations:
|
|
1687
|
+
|
|
1688
|
+
```bash
|
|
1689
|
+
# npm
|
|
1690
|
+
npm ls @digitaldefiance/branded-enum
|
|
1691
|
+
|
|
1692
|
+
# yarn
|
|
1693
|
+
yarn why @digitaldefiance/branded-enum
|
|
1694
|
+
```
|
|
1695
|
+
|
|
1696
|
+
If you see more than one resolved version (or multiple paths), the registry is split.
|
|
1697
|
+
|
|
1698
|
+
#### Solutions
|
|
1699
|
+
|
|
1700
|
+
1. **Single version via resolutions/overrides** — pin a single version in your root `package.json`:
|
|
1701
|
+
|
|
1702
|
+
```jsonc
|
|
1703
|
+
// npm (package.json)
|
|
1704
|
+
"overrides": {
|
|
1705
|
+
"@digitaldefiance/branded-enum": "<version>"
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// yarn (package.json)
|
|
1709
|
+
"resolutions": {
|
|
1710
|
+
"@digitaldefiance/branded-enum": "<version>"
|
|
1711
|
+
}
|
|
1712
|
+
```
|
|
1713
|
+
|
|
1714
|
+
2. **Bundler deduplication** — if you use webpack, Rollup, or esbuild, ensure the `@digitaldefiance/branded-enum` module is resolved to a single path. For webpack, the `resolve.alias` or `resolve.dedupe` options can help.
|
|
1715
|
+
|
|
1716
|
+
3. **Consistent package resolution** — in Nx or other monorepo tools, verify that all projects resolve the same physical copy. Running `nx graph` can help visualize dependency relationships.
|
|
1717
|
+
|
|
1318
1718
|
## Browser Support
|
|
1319
1719
|
|
|
1320
1720
|
- Chrome/Edge: Latest 2 versions (minimum: Chrome 90, Edge 90)
|
|
@@ -1500,6 +1900,28 @@ Contributions welcome! Please:
|
|
|
1500
1900
|
|
|
1501
1901
|
## ChangeLog
|
|
1502
1902
|
|
|
1903
|
+
### Version 4.4.0
|
|
1904
|
+
|
|
1905
|
+
**Factory-Based i18n Setup & Browser-Safe Fallback**
|
|
1906
|
+
|
|
1907
|
+
This release introduces `createI18nSetup()`, a factory function that replaces ~200 lines of boilerplate per consumer with a single function call. It also adds a browser-safe fallback for `translateStringKey` when bundler-duplicated packages break Symbol-based identity checks.
|
|
1908
|
+
|
|
1909
|
+
**New Features:**
|
|
1910
|
+
|
|
1911
|
+
- **`createI18nSetup()`**: Factory function that handles engine creation, core/library/app component registration, branded enum registration, and `GlobalActiveContext` initialization in one call
|
|
1912
|
+
- **`I18nComponentPackage`** interface: Bundles a `ComponentConfig` with its branded string key enum so the factory can auto-register both
|
|
1913
|
+
- **`I18nSetupConfig`** / **`I18nSetupResult`** interfaces: Typed config input and result output for the factory
|
|
1914
|
+
- **`createSuiteCoreComponentPackage()`**: New function in `suite-core-lib` returning an `I18nComponentPackage`
|
|
1915
|
+
- **`createEciesComponentPackage()`**: New function in `ecies-lib` returning an `I18nComponentPackage`
|
|
1916
|
+
- **`createNodeEciesComponentPackage()`**: New function in `node-ecies-lib` returning an `I18nComponentPackage`
|
|
1917
|
+
- **Browser-safe fallback**: `translateStringKey` and `safeTranslateStringKey` now fall back to scanning registered components when the `StringKeyEnumRegistry` fails (e.g., due to bundler Symbol mismatch), with a lazily-built `ValueComponentLookupCache` that invalidates on new component registration
|
|
1918
|
+
- **Updated starter template**: `express-suite-starter` scaffolding now uses `createI18nSetup()` for minimal boilerplate
|
|
1919
|
+
|
|
1920
|
+
**Backward Compatibility:**
|
|
1921
|
+
|
|
1922
|
+
- All existing APIs (`createSuiteCoreComponentConfig`, `createEciesComponentConfig`, `I18nBuilder`, manual registration) remain unchanged
|
|
1923
|
+
- The factory uses the same `registerIfNotExists` pattern internally, so factory and manual consumers can safely share the same engine instance
|
|
1924
|
+
|
|
1503
1925
|
### Version 4.3.0
|
|
1504
1926
|
|
|
1505
1927
|
**String Key Enum Registration for Direct Translation**
|
|
@@ -2623,21 +3045,6 @@ const myEngine = PluginI18nEngine.createInstance<MyLanguageCodes>('custom', lang
|
|
|
2623
3045
|
|
|
2624
3046
|
- Wed Sep 24 2025 15:20:07 GMT-0700 (Pacific Daylight Time)
|
|
2625
3047
|
- Initial release of the TypeScript internationalization library with enum translation, template processing, context management, and currency formatting.
|
|
2626
|
-
PluginI18nEngine.resetAll();
|
|
2627
|
-
});
|
|
2628
|
-
|
|
2629
|
-
afterEach(() => {
|
|
2630
|
-
PluginI18nEngine.resetAll();
|
|
2631
|
-
});
|
|
2632
|
-
|
|
2633
|
-
it('should translate', () => {
|
|
2634
|
-
const engine = PluginI18nEngine.createInstance('test', languages);
|
|
2635
|
-
engine.registerComponent(registration);
|
|
2636
|
-
expect(engine.translate('app', 'hello')).toBe('Hello');
|
|
2637
|
-
});
|
|
2638
|
-
});
|
|
2639
|
-
|
|
2640
|
-
```
|
|
2641
3048
|
|
|
2642
3049
|
## TypeScript Support
|
|
2643
3050
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@digitaldefiance/i18n-lib",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "i18n library with enum translation support",
|
|
5
5
|
"homepage": "https://github.com/Digital-Defiance/i18n-lib",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"packageManager": "yarn@4.10.3",
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@digitaldefiance/branded-enum": "0.0.
|
|
45
|
+
"@digitaldefiance/branded-enum": "0.0.7",
|
|
46
46
|
"currency-codes": "^2.2.0",
|
|
47
47
|
"lru-cache": "^5.1.1",
|
|
48
48
|
"moment": "^2.30.1",
|
|
@@ -19,6 +19,7 @@ export declare class I18nEngine implements II18nEngine {
|
|
|
19
19
|
private readonly config;
|
|
20
20
|
private readonly aliasToComponent;
|
|
21
21
|
private readonly componentKeyLookup;
|
|
22
|
+
private valueComponentLookupCache;
|
|
22
23
|
/**
|
|
23
24
|
* Constructs an I18nEngine instance, registering languages, setting defaults,
|
|
24
25
|
* and optionally registering and setting this instance as default.
|
|
@@ -53,6 +54,25 @@ export declare class I18nEngine implements II18nEngine {
|
|
|
53
54
|
* @param config - Component configuration object.
|
|
54
55
|
*/
|
|
55
56
|
private registerComponentMetadata;
|
|
57
|
+
/**
|
|
58
|
+
* Invalidates the value-to-component lookup cache.
|
|
59
|
+
* Called after component registration so new keys are discoverable.
|
|
60
|
+
*/
|
|
61
|
+
private invalidateValueComponentLookupCache;
|
|
62
|
+
/**
|
|
63
|
+
* Builds a cache mapping string key values to component IDs
|
|
64
|
+
* by scanning all registered components.
|
|
65
|
+
*/
|
|
66
|
+
private buildValueComponentLookupCache;
|
|
67
|
+
/**
|
|
68
|
+
* Resolves a string key value to its component ID, falling back to
|
|
69
|
+
* scanning registered components when the branded enum registry fails
|
|
70
|
+
* (e.g., due to bundler Symbol mismatch in browser environments).
|
|
71
|
+
* @param stringKeyValue - The string key value to resolve.
|
|
72
|
+
* @returns The component ID that owns this string key.
|
|
73
|
+
* @throws {I18nError} If the key is not found in any registered component.
|
|
74
|
+
*/
|
|
75
|
+
private resolveComponentIdWithFallback;
|
|
56
76
|
/**
|
|
57
77
|
* Internal: Normalizes legacy keys into snake_case lowercased.
|
|
58
78
|
* @param rawKey - The raw key string to normalize.
|
|
@@ -331,8 +351,9 @@ export declare class I18nEngine implements II18nEngine {
|
|
|
331
351
|
* will cause an error to be thrown.
|
|
332
352
|
*
|
|
333
353
|
* @param stringKeyEnum - Branded enum created by createI18nStringKeys
|
|
334
|
-
* @
|
|
335
|
-
* @
|
|
354
|
+
* @param componentId - Optional explicit component ID (escape hatch for cross-module scenarios)
|
|
355
|
+
* @returns The extracted or provided component ID
|
|
356
|
+
* @throws {I18nError} If not a valid branded enum and no fallback succeeds (INVALID_STRING_KEY_ENUM)
|
|
336
357
|
*
|
|
337
358
|
* @example Basic registration
|
|
338
359
|
* ```typescript
|
|
@@ -345,6 +366,11 @@ export declare class I18nEngine implements II18nEngine {
|
|
|
345
366
|
* console.log(componentId); // 'user'
|
|
346
367
|
* ```
|
|
347
368
|
*
|
|
369
|
+
* @example Explicit componentId escape hatch
|
|
370
|
+
* ```typescript
|
|
371
|
+
* engine.registerStringKeyEnum(plainObj, 'user'); // 'user'
|
|
372
|
+
* ```
|
|
373
|
+
*
|
|
348
374
|
* @example Idempotent registration
|
|
349
375
|
* ```typescript
|
|
350
376
|
* engine.registerStringKeyEnum(UserKeys); // 'user'
|
|
@@ -354,7 +380,7 @@ export declare class I18nEngine implements II18nEngine {
|
|
|
354
380
|
* @see {@link translateStringKey} - Translate registered string key values
|
|
355
381
|
* @see {@link hasStringKeyEnum} - Check if an enum is registered
|
|
356
382
|
*/
|
|
357
|
-
registerStringKeyEnum(stringKeyEnum: AnyBrandedEnum): string;
|
|
383
|
+
registerStringKeyEnum(stringKeyEnum: AnyBrandedEnum, componentId?: string): string;
|
|
358
384
|
/**
|
|
359
385
|
* Translates a branded string key value directly.
|
|
360
386
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n-engine.d.ts","sourceRoot":"","sources":["../../../../../packages/digitaldefiance-i18n-lib/src/core/i18n-engine.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EACjB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EACL,eAAe,EACf,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAkBvB;;;GAGG;AACH,qBAAa,UAAW,YAAW,WAAW;IAC5C,OAAO,CAAC,MAAM,CAAC,SAAS,CAAiC;IACzD,OAAO,CAAC,MAAM,CAAC,UAAU,CAAuB;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAa;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAwB;IAE9D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAwB;IAC9D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAC9D,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA0C;
|
|
1
|
+
{"version":3,"file":"i18n-engine.d.ts","sourceRoot":"","sources":["../../../../../packages/digitaldefiance-i18n-lib/src/core/i18n-engine.ts"],"names":[],"mappings":"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"}
|