feat(core): type the locale layer (#DS-3897) - #1899
Open
lskramarov wants to merge 6 commits into
Open
Conversation
KbqLocaleService was untyped at its core: `locales: any`, an implicit `any` on `current` and on the data parameter of `addLocale`, and `getParams(name): any`. With `noImplicitAny` off and `no-explicit-any` disabled nothing pushed back, so custom locale data registered through KBQ_LOCALE_DATA or addLocale() was never checked, and 21 components each re-invented their own typing on top of that `any`. - `KbqLocaleData` describes every section; the five locale files carry one `satisfies` each instead of scattered per-section ones. - `getParams` resolves the section type for a literal name and still returns `any` for a dynamically built string, so no existing call site changes. - Partial locale data is completed at registration from the shipped locale of the same id, which lets `getParams` promise a complete section and removes the class of crashes on a missing one. - Signals `localeId()`, `data()`, `items()` and `params()` join `changes`. Reading them from a template is what makes a runtime `setLocale()` reach OnPush children. - One shared `kbqInjectLocaleConfiguration` replaces five hand-rolled copies, the four `InjectionToken<unknown>` and four `configuration: any` members are typed, and the configuration providers accept a partial. Fixed while typing: `addLocale` never updated `current`, five unsubscribed `changes.subscribe`, a missing optional chain in KbqRoundDecimalPipe, and a crash in the multiple-file-upload example when tk-TM is selected. Adds a localization guide, which the docs did not have, and `check-public-api-any` - a ratchet on the amount of hand-written `any` in the golden files, 553 across 53 packages at the baseline. Nothing breaks: every renamed type keeps a deprecated alias, and every widened signature still accepts what it accepted before.
Every component that renders localized strings now reads
`kbqInjectLocaleConfiguration(section, token)`, which returns a signal. The read
registers on the view that performs it, so a runtime `setLocale()` reaches OnPush
children that a `markForCheck()` in the parent could never mark. Twelve components
lose their `localeService.changes` subscription, `updateLocaleParams`,
`initDefaultParams` and `externalConfiguration` along the way.
The two roles the tokens used to share are now separate. `KBQ_<X>_CONFIGURATION`
carries the defaults and applies only when the application provides no
`KBQ_LOCALE_SERVICE`, while `kbq<X>LocaleConfigurationProvider(patch)` registers an
override that is deep-merged on top of the active locale: the keys it passes stay
pinned across `setLocale()`, the rest keep following it. Overrides travel through one
`multi` token, because overriding two sections in the same `providers` array is the
normal case and a single-value token would let the second provider drop the first.
`select` is the one section three packages render, so its token lives in core rather
than in `@koobiq/components/select`, which neither tree nor tree-select depends on.
Three defects fixed on the way:
- a partial `KBQ_LOCALE_DATA` registered only the ids it patched while `items` kept
offering the full shipped list, so activating any other locale left `data()`
undefined and crashed `KbqDataSizePipe`;
- the configuration providers merged with a shallow spread, so overriding one key of
a nested section blanked every sibling key in it;
- an explicit `[hiddenItemsText]` binding was wiped by the next `setLocale()`.
BREAKING CHANGE: `{ provide: KBQ_<X>_CONFIGURATION, useValue }` no longer outranks the
locale service. Replace it with `kbq<X>LocaleConfigurationProvider(...)`; the
`ng update` migration `locale-configuration-providers` rewrites it. The same change
removes `externalConfiguration` from those components and makes `configuration`
read-only.
|
Visit the preview URL for this PR (updated for commit 841ebf1): https://koobiq-next--prs-1899-rmoelgf5.web.app (expires Thu, 20 Aug 2026 11:03:02 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: c9e37e518febda70d0317d07e8ceb35ac43c534c |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR types and unifies the locale configuration layer across Koobiq components, introducing signal-based locale configuration injection with deep-partial overrides, and shipping tooling + schematics support to help consumers migrate safely.
Changes:
- Introduces typed locale contracts (
Kbq*LocaleConfiguration), partial locale input support, and signal-first locale APIs inKbqLocaleService. - Migrates multiple components to read locale configuration via
kbqInjectLocaleConfiguration(...)+ deep-partial override providers, ensuring runtimesetLocale()updates reachOnPushchildren. - Adds an
any/unknownpublic API “ratchet” tool and a schematic to migrate provider usages of defaults-only configuration tokens.
Reviewed changes
Copilot reviewed 87 out of 87 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tools/public_api_guard/components/tree.api.md | API snapshot update (tree) |
| tools/public_api_guard/components/tree-select.api.md | API snapshot update (tree-select) |
| tools/public_api_guard/components/timezone.api.md | API snapshot update (timezone) |
| tools/public_api_guard/components/timepicker.api.md | API snapshot update (timepicker) |
| tools/public_api_guard/components/time-range.api.md | API snapshot update (time-range) |
| tools/public_api_guard/components/select.api.md | API snapshot update (select) |
| tools/public_api_guard/components/search-expandable.api.md | API snapshot update (search-expandable) |
| tools/public_api_guard/components/notification-center.api.md | API snapshot update (notification-center) |
| tools/public_api_guard/components/navbar.api.md | API snapshot update (navbar) |
| tools/public_api_guard/components/input.api.md | API snapshot update (input) |
| tools/public_api_guard/components/filter-bar.api.md | API snapshot update (filter-bar) |
| tools/public_api_guard/components/file-upload.api.md | API snapshot update (file-upload) |
| tools/public_api_guard/components/datepicker.api.md | API snapshot update (datepicker) |
| tools/public_api_guard/components/code-block.api.md | API snapshot update (code-block) |
| tools/public_api_guard/components/clamped-text.api.md | API snapshot update (clamped-text) |
| tools/public_api_guard/components/app-switcher.api.md | API snapshot update (app-switcher) |
| tools/public_api_guard/components/actions-panel.api.md | API snapshot update (actions-panel) |
| tools/check-public-api-any/tsconfig.json | TS config for new tool |
| tools/check-public-api-any/index.ts | New any/unknown ratchet implementation |
| tools/check-public-api-any/baseline.json | Baseline counts per package |
| packages/schematics/src/migrations/locale-configuration-providers/schema.ts | New migration schema type |
| packages/schematics/src/migrations/locale-configuration-providers/schema.json | New migration schema JSON |
| packages/schematics/src/migrations/locale-configuration-providers/README.md | Migration documentation |
| packages/schematics/src/migrations/locale-configuration-providers/index.ts | Migration rule implementation |
| packages/schematics/src/migrations/locale-configuration-providers/data.ts | Token/helper mapping + warnings |
| packages/schematics/src/migrations.json | Registers new migration |
| packages/schematics/src/collection.json | Exposes migration via schematics collection |
| packages/docs-examples/components/file-upload/en-US.ts | Docs locale data typing updates |
| packages/docs-examples/components/file-upload/es-LA.ts | Docs locale data typing updates |
| packages/docs-examples/components/file-upload/pt-BR.ts | Docs locale data typing updates |
| packages/docs-examples/components/file-upload/ru-RU.ts | Docs locale data typing updates |
| packages/docs-examples/components/file-upload/tk-TM.ts | Adds tk-TM docs example locale data |
| packages/docs-examples/components/file-upload/file-upload-multiple-custom-text-overview/file-upload-multiple-custom-text-overview-example.ts | Example updated to typed config + fallback |
| packages/docs-examples/components/file-upload/file-upload-custom-text-via-input/file-upload-custom-text-via-input-example.ts | Example updated to new locale config types |
| packages/components/tree/tree-selection.component.ts | Tree selection reads typed select locale config |
| packages/components/tree-select/tree-select.component.ts | Tree-select reads select locale config + accessor input |
| packages/components/timezone/timezone-select.component.ts | Timezone select locale config token/provider + effect-based placeholder |
| packages/components/timepicker/timepicker.directive.ts | Timepicker locale config token/provider + reactive placeholder updates |
| packages/components/time-range/time-range.ts | Time-range locale config via shared helper + deep override provider |
| packages/components/time-range/time-range.spec.ts | Adds/updates tests for locale override behavior |
| packages/components/select/select.component.ts | Select reads shared select locale config; accessor input + selectAll getter |
| packages/components/search-expandable/search-expandable.ts | Search-expandable config token typed + signal-based configuration |
| packages/components/notification-center/notification-center.ts | Notification center typed config + signal-backed getter |
| packages/components/notification-center/notification-center.spec.ts | Tests runtime locale update propagation to items |
| packages/components/navbar/vertical-navbar.component.ts | Navbar typed config token/provider + signal-backed getter |
| packages/components/navbar/navbar-toggle.component.ts | Toggle updates tooltip via effect on locale config |
| packages/components/navbar/navbar.component.spec.ts | Tests locale changes and override behavior for toggle tooltip |
| packages/components/input/input-number.ts | Number input reads typed locale config via helper + effect reformatting |
| packages/components/filter-bar/filters.spec.ts | Updates tests to drive locale changes via service |
| packages/components/filter-bar/filter-bar.types.ts | Filter-bar config token factory + deep override provider + readonly host |
| packages/components/filter-bar/filter-bar.ts | Filter-bar configuration now signal-backed via shared helper |
| packages/components/filter-bar/filter-bar.spec.ts | Updates precedence tests for override-vs-locale behavior |
| packages/components/datepicker/datepicker-input.directive.ts | Datepicker config token typed + effect-driven reformatting on locale change |
| packages/components/core/utils/utils.ts | Adds KbqDeepPartial + kbqDeepMerge |
| packages/components/core/locales/types.ts | Adds typed locale model + normalized Kbq*LocaleConfiguration names |
| packages/components/core/locales/a11y.ts | Moves a11y config to shared helper + deep override provider |
| packages/components/core/locales/a11y.spec.ts | Extends tests for overrides + fallback behavior |
| packages/components/core/locales/configuration.ts | Adds overrides token + shared kbqInjectLocaleConfiguration helper |
| packages/components/core/locales/select.ts | Adds shared select locale config token/provider in core |
| packages/components/core/locales/index.ts | Exports new locale utilities |
| packages/components/core/locales/locale-service.ts | Refactors locale service to typed signals + partial locale completion |
| packages/components/core/locales/locale-service.spec.ts | Expanded test coverage for typed locale service behavior |
| packages/components/core/locales/formatters.ts | Types formatter locale data with satisfies |
| packages/components/core/locales/en-US.ts | Locale data typed to KbqLocaleStringsData |
| packages/components/core/locales/es-LA.ts | Locale data typed to KbqLocaleStringsData |
| packages/components/core/locales/pt-BR.ts | Locale data typed to KbqLocaleStringsData |
| packages/components/core/locales/ru-RU.ts | Locale data typed to KbqLocaleStringsData |
| packages/components/core/locales/tk-TM.ts | Locale data typed to KbqLocaleStringsData |
| packages/components/core/formatters/number/formatter.ts | Pipes unsubscribe via takeUntilDestroyed; guard unknown locale ids |
| packages/components/core/formatters/filesize/formatter.ts | Adds fallback size units config when missing |
| packages/components/code-block/code-block.ts | Code block locale config via shared helper + deep override provider |
| packages/components/clamped-text/constants.ts | Clamped-text locale config via shared helper + renamed inject fn |
| packages/components/clamped-text/clamped-text.ts | Uses new clamped-text inject function |
| packages/components/clamped-text/clamped-list.ts | Uses new clamped-text inject function |
| packages/components/app-switcher/app-switcher.ts | App-switcher typed config + shared helper + deep override provider |
| packages/components/actions-panel/actions-panel-container.ts | Actions panel locale config via shared helper + deep override provider |
| packages/angular-moment-adapter/adapter/moment-date-adapter.ts | Unsubscribe locale changes via takeUntilDestroyed |
| packages/angular-luxon-adapter/adapter/date-adapter.ts | Unsubscribe locale changes via takeUntilDestroyed |
| package.json | Adds scripts for check-public-api-any + approve-public-api-any |
| docs/guides/migration.ru.md | Documents locale layer typing migration |
| docs/guides/migration.en.md | Documents locale layer typing migration |
| docs/guides/localization.ru.md | New localization guide (RU) |
| docs/guides/localization.en.md | New localization guide (EN) |
| apps/docs/src/app/structure.ts | Adds localization guide to docs nav |
| .github/workflows/api.yml | Runs check-public-api-any in CI |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
lskramarov
marked this pull request as draft
August 14, 2026 15:34
`setLocale` registers an id the registry does not know, completing it from the shipped locale of that id or from the default one, but the constructor activated `KBQ_LOCALE_ID` by reading `locales[id]` directly. `KBQ_LOCALE_ID` accepts any string, so an application naming a locale it registers later through `addLocale` started with `data()` undefined until the first `setLocale` call, while `data` is typed to promise a complete locale. Both paths now go through one private `register`, which is where that invariant lives.
The baseline was recorded before this branch was rebased onto main, so it predates `alwaysShowActionbar` on `kbq-code-block` and the split of the scrollbar package. Both are `main`'s, and neither is something this branch can narrow: 553 across 53 packages becomes 555 across 54, with no package this branch touches moving.
Every `kbq<X>LocaleConfigurationProvider` contributes to one shared `multi` token, and Angular resolves a `multi` token from the nearest injector that has any entry for it, without merging the levels above. Scoping one section to a component — the pattern the localization guide recommends — therefore hid every section an ancestor had overridden from that whole subtree: a root `kbqA11yLocaleConfigurationProvider` stopped applying inside a component that registered a `select` override of its own, silently falling back to the active locale. Each provider now also re-contributes the batch it inherited, resolved with `skipSelf`, and the consumer flattens and de-dupes by reference, which keeps ancestor-before-descendant precedence when several providers sit at the same level. Follow-ups from the same review: - `KbqDataSizePipe` read `locales[locale].sizeUnits` unguarded, so a locale id that was never registered threw instead of falling back — the guard the number pipes already carry; - the `locale-configuration-providers` migration matched the first import clause of the module whichever kind it was, so a file carrying a separate `import type` clause got the helper spliced into it and stopped compiling (TS1361); - `KbqSelect` kept a `localeService` field nothing reads, behind a comment claiming `KbqTimezoneSelect` resolves its section through it; - section 16 of the migration guide both claimed and denied that `ng update` rewrites the configuration providers; - `core/locales/select.ts` had no tests at all, and the navbar override test asserted the same pinned string before and after `setLocale()`, so it could not tell a reactive configuration from one frozen at construction.
`copy-meta-to-dist.js` copied each migration's `README.md` as a required file, so the first migration without one — `scrollbar-deprecated-path` — threw and aborted the loop. `utils/` stayed empty while every built migration requires it at runtime, `tag-slots` and `v20-upgrade` never reached the package, and `new-icons-pack` lost its data files. The top-level handler only logged the failure, so the build exited 0 and CI stayed green while `ng update` could not have loaded a single migration. `README.md` is now optional with a warning, the way `data.js` already was, and a copy failure sets a non-zero exit code.
lskramarov
marked this pull request as ready for review
August 17, 2026 13:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.