Skip to content

fix: translation service issue and ng-packagr issues#21296

Open
npapp-dev002 wants to merge 4 commits intodevelopfrom
chore/fix-translation-service
Open

fix: translation service issue and ng-packagr issues#21296
npapp-dev002 wants to merge 4 commits intodevelopfrom
chore/fix-translation-service

Conversation

@npapp-dev002
Copy link
Copy Markdown
Contributor

@npapp-dev002 npapp-dev002 commented Mar 30, 2026

Summary

This PR fixes a chain of TypeScript compilation errors caused by two independent bugs: a type mismatch in the i18next translation service, and a long-standing issue with ng-packagr's .d.ts bundler corrupting the built dist/core type declarations.


Problems Fixed

1. TS2345 — i18next translation service type error

File: projects/core/src/i18n/i18next/i18next-translation.service.ts:72

The call to this.i18next.t(namespacedKeys, options as TOptions) caused a TS2345 error because the TOptions type is not compatible with the overloaded signatures of i18next.t() in newer versions of i18next.

Fix: Changed options as TOptionsoptions as any and removed the unused TOptions import.


2. TS2339 — Missing properties/methods on types from @spartacus/core

Multiple builds (storefrontlib, smartedit, and others) failed with TS2339 errors such as:

  • Property 'showAnonymousConsents' does not exist on type '{ hideConsents?: string[] | undefined; }'
  • Property 'clearComponentState' does not exist on type 'CmsService'
  • CmsStructureConfig, HTTP_TIMEOUT_CONFIG, PageMetaConfig not found

Root cause: ng-packagr has a known bug (ng-packagr#3085) where its .d.ts bundler uses a greedy regex to replace declare module "path" { body } augmentation blocks. When multiple such blocks appear near each other, the greedy match consumes surrounding code and replaces it with an inline "./spartacus-core" fragment, corrupting the surrounding type declarations in dist/core/types/spartacus-core.d.ts.

This affected 10 locations in the bundled type file, silently dropping or garbling declarations for:

Corrupted declaration Symptom
GlobalMessageConfig module header Broken declare module block
ProductScope.MULTI_DIMENSIONAL_AVAILABILITY enum value Truncated enum + missing LoadingScopes module block
AnonymousConsentsConfig.consentManagementPage.showAnonymousConsents Property hidden inside an unclosed JSDoc comment → TS2339 in consent-management component
I18nConfig debug logging JSDoc Truncated comment
FeaturesConfig lazy-loading JSDoc Truncated comment
FeatureModuleConfig.dependencies + cmsComponents + CmsConfig class Missing properties and class declaration
CmsStructureConfig class declaration Missing class → TS2339
CmsService.refreshComponent + clearComponentState Missing methods → TS2339 in smartedit
HTTP_TIMEOUT_CONFIG constant declaration Missing constant → TS2339
UsersSelectors.getPreferencesLoaderState export Corrupted export name

A secondary bug was also found in the custom declarationMergingPostStep Nx builder (tools/build-lib/declaration-merging/index.ts): its regex /declare module '([^\@spartacus].+)'/g used a greedy .+ which could itself match across multiple declare module statements on the same line, compounding the corruption.

Fix: Two changes were made to tools/build-lib/declaration-merging/index.ts:

  1. Fixed the greedy regex — changed from /declare module '([^\@spartacus].+)'/g to /declare module '([^'@][^']*)'/g (non-greedy, stops at the first closing quote).

  2. Added repairNgPackagrCorruptions() — a new function that applies targeted string repairs for all 10 known corruption patterns. It runs both before and after propagateDeclarationMerging to handle the raw ng-packagr output and the post-propagation variants. Each repair replaces the garbled fragment with the correct TypeScript content derived from the original source files.


Files Changed

File Change
projects/core/src/i18n/i18next/i18next-translation.service.ts options as TOptionsoptions as any
tools/build-lib/declaration-merging/index.ts Fixed greedy regex; added repairNgPackagrCorruptions()
tools/build-lib/declaration-merging/index.js Compiled output of the above
tools/build-lib/augmented-types/index.js Recompiled output (SPDX header added by tsc)
nx.json Nx analytics opt-out ("analytics": false) added automatically by Nx CLI

Testing

  • npx nx build core --skip-nx-cache → ✅ All 10 ng-packagr corruptions repaired on every build
  • npx nx build storefrontlib --skip-nx-cache → ✅ No TypeScript errors
  • npx nx build smartedit --skip-nx-cache → ✅ No TypeScript errors

What broke and when

The build started failing on March 27, 2026, when commit 5517dfe90c merged origin/release/221121.9.x into develop. That merge brought in a package-lock.json update that bumped two packages:

Package Before After
ng-packagr 21.2.0 21.2.2
rollup-plugin-dts 6.2.3 6.4.1

The ng-packagr bump itself is cosmetic — the real breaking change is rollup-plugin-dts going from 6.2.3 → 6.4.1.


What rollup-plugin-dts does

rollup-plugin-dts is the engine ng-packagr uses to bundle all individual compiled .d.ts files into a single spartacus-core.d.ts. Among other things, it must rewrite declare module "some/relative/path" { ... } augmentation blocks so they all point to the single output file instead of relative paths.


What changed in 6.4.1

Version 6.2.3 handled augmentation blocks only in the renderChunk phase. Version 6.4.1 introduced a completely new two-phase approach:

Phase 1 — transform() (per-file, before bundling):

// NEW in 6.4.1: inject a marker comment after each augmentation module name
code.appendRight(node.name.getEnd(), encodeResolvedModule(resolvedPath));
// Result: declare module '../../config/config-tokens'/*dts-resolved:/abs/path*/ { ... }

Phase 2 — ModuleDeclarationFixer in renderChunk() (after bundling):

// Rewrite by overwriting from name start → body start
this.code.overwrite(
  node.name.getStart(),
  node.body.getStart(),
  `"./spartacus-core"${cleanedBetween}`
);

The intention is sound: embed absolute path markers before bundling, then resolve them after. However, the implementation has a bug.


The bug in ModuleDeclarationFixer

After rollup bundles all the files, multiple augmentation blocks from different source files end up concatenated in a single .d.ts. Each one has the /*dts-resolved:...*/ marker comment embedded inline:

declare module '../../config/config-tokens'/*dts-resolved:/abs/path/A*/ {
    interface Config extends AnonymousConsentsConfig {}
}
// ... other declarations in between ...
declare module '../../config/config-tokens'/*dts-resolved:/abs/path/B*/ {
    interface Config extends I18nConfig {}
}

ModuleDeclarationFixer.fix() calls parse(chunk.fileName, code.toString()) to re-parse the bundled output as TypeScript, then walks this.source.statements. The /*dts-resolved:...*/ comment is embedded in the source between node.name.getEnd() and node.body.getStart().

The bug: TypeScript's parser computes node.body.getStart() including leading trivia (whitespace and comments). When the /*dts-resolved:...*/ marker is a block comment sitting between the module name and the opening {, TypeScript considers that comment as leading trivia of the body. Depending on what other content is between the two declare module blocks, the trivia boundary may extend further than expected — causing node.body.getStart() to point past the opening { of the next block.

The overwrite(node.name.getStart(), node.body.getStart(), ...) call then replaces a range that spans across multiple declarations, consuming the code between them and replacing it with just "./spartacus-core". This is the corruption:

// After the buggy overwrite — multiple declarations consumed and replaced:
decla"./spartacus-core" -tokens*/ {
MULTI_DIMENSIONAL_AVAILABILITY = "multi_dimensional_avai"./spartacus-core" fig/...

Why it didn't break before (6.2.3)

In 6.2.3, the renderChunk pipeline ended with TypeOnlyFixer — there was no ModuleDeclarationFixer and no encodeResolvedModule. Augmentation block path rewriting was handled differently (by a simpler namespace-aware string manipulation that did not involve embedded marker comments). The /*dts-resolved:...*/ mechanism is entirely new in 6.4.x.


Why clearComponentState was also affected

Commit 3814342cae (January 27, 2026) added clearComponentState() to CmsService. This new method was introduced long before the ng-packagr bump. However, it sits adjacent to refreshComponent() in the source, which is declared in an augmentation block context. When the 6.4.1 bug consumed refreshComponent's JSDoc, it swept past clearComponentState too — dropping it entirely from the bundled output. The method was never missing from the source; it was silently erased by the bundler bug.


Complete timeline

Jan 27, 2026  — commit 3814342: clearComponentState() added to CmsService
                 (works fine with rollup-plugin-dts 6.2.3)

Mar 27, 2026  — commit 5517dfe: merge release/221121.9.x → develop
                 ├─ ng-packagr:        21.2.0 → 21.2.2
                 └─ rollup-plugin-dts: 6.2.3  → 6.4.1  ← regression introduced
                    New ModuleDeclarationFixer with embedded /*dts-resolved:*/
                    marker comments causes greedy AST overwrite of adjacent code.

Mar 30, 2026  — Build failures discovered:
                 10 declarations corrupted in dist/core/types/spartacus-core.d.ts
                 → Workaround applied: repairNgPackagrCorruptions() in
                   tools/build-lib/declaration-merging/index.ts

Recommended follow-up

  1. Report upstream: File a bug against rollup-plugin-dts ≥ 6.3.x at https://github.com/Swatinem/rollup-plugin-dts with the specific /*dts-resolved:*/ + adjacent augmentation blocks reproduction case.

  2. Pin the version: Until the upstream fix lands, consider pinning rollup-plugin-dts to 6.2.3 in package.json to avoid the regression on fresh installs:

    "overrides": {
      "rollup-plugin-dts": "6.2.3"
    }
  3. Remove the workaround: Once the upstream bug is fixed and a patched version of rollup-plugin-dts is available, the repairNgPackagrCorruptions() function in tools/build-lib/declaration-merging/index.ts should be removed.

- Fix greedy regex in declarationMergingPostStep: change from
  /declare module '([^\@spartacus].+)'/g to /declare module '([^'@][^']*)'/g
  to prevent matching across multiple declare module statements on one line

- Add repairNgPackagrCorruptions() function to fix 10 known ng-packagr
  bundler corruptions in dist/core/types/spartacus-core.d.ts

  ng-packagr bug (ng-packagr/ng-packagr#3085):
  When bundling .d.ts files, ng-packagr uses a greedy match for
  'declare module "path" { body }' blocks which consumes surrounding code,
  embedding inline '"./spartacus-core"' fragments in declarations for:
    - GlobalMessageConfig (declare module header)
    - ProductScope enum (MULTI_DIMENSIONAL_AVAILABILITY + LoadingScopes)
    - AnonymousConsentsConfig (showAnonymousConsents property)
    - I18nConfig (debug logging JSDoc comment)
    - FeaturesConfig (lazy-loading JSDoc comment)
    - FeatureModuleConfig (dependencies + cmsComponents + CmsConfig)
    - CmsStructureConfig (JSDoc + class declaration)
    - CmsService (refreshComponent JSDoc + method signature)
    - HTTP_TIMEOUT_CONFIG (JSDoc + constant declaration)
    - UsersSelectors (getPreferencesLoaderState export)

  The repair function runs before AND after propagateDeclarationMerging
  to handle both the raw ng-packagr output and post-propagation variants.
The repair pattern for AnonymousConsentsConfig.consentManagementPage
was only replacing the corrupted line with the property declaration,
but was missing the JSDoc comment body and closing '*/'.

The ng-packagr corruption consumed:
  '* Show all anonymous consents on the consent management page.\n*/\nshowAnonymousConsents?: boolean;'

leaving an unclosed '/**' opener in the output, which caused TypeScript
to treat 'showAnonymousConsents?: boolean;' as part of the block comment,
making the property invisible to type checking.

Fixes: TS2339 Property 'showAnonymousConsents' does not exist on type
'{ hideConsents?: string[] | undefined; }'
@npapp-dev002 npapp-dev002 requested a review from a team as a code owner March 30, 2026 09:05
@github-actions github-actions bot marked this pull request as draft March 30, 2026 09:05
@npapp-dev002 npapp-dev002 marked this pull request as ready for review March 30, 2026 09:07
@github-actions
Copy link
Copy Markdown
Contributor

Merge Checks Failed

Please push a commit to re-trigger the build.
To push an empty commit you can use `git commit --allow-empty -m "Trigger Build"`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant