feat: typed translation keys via augmentable registry and DeepKeys#1634
Open
CodeAndWeb wants to merge 1 commit into
Open
feat: typed translation keys via augmentable registry and DeepKeys#1634CodeAndWeb wants to merge 1 commit into
CodeAndWeb wants to merge 1 commit into
Conversation
Augment NgxTranslateConfig once to switch the whole library to a typed key-space across both imperative and template APIs: - NgxTranslateConfig registry + TranslationKey (i18next CustomTypeOptions pattern); defaults to string so existing apps are unaffected. - DeepKeys<T> derives the dotted leaf-path union from a translation shape; arrays and primitives are leaf keys, only plain objects recurse. - TranslateService<Key>, TranslatePipe<Key>, the [translate] directive, the *translateBlock t() helper, and the standalone translate() function are all keyed to the registry default; per-instance <Key> override is retained where it is reachable. - injectTranslateService() helper for the imperative path, since a bare inject(TranslateService) widens the key to any. Tests: type-level type-tests (tsc) plus end-to-end template type-tests (ngtsc strictTemplates) covering the pipe, directive and *translateBlock, plus a big-dictionary stress check. CI now runs test-types and test-templates.
Member
Author
|
While this works for a scenario with only one main set of translations, it practically breaks with the modularity we added with ngx-translate 18. One could provide a union over all JSON files - this would enable auto-complete but the checks per module are not there. I am currently not really sure if this is a solution that should be supported or not... It adds certain complexity - and is only a 30% solution and feels too incomplete to really add it to the library. |
|
@CodeAndWeb hey, seems like this PR is a bit less surgical than the one I offered, but I guess it's all the same. |
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.
Typed translation keys — augmentable registry +
DeepKeysOpt-in compile-time validation and autocomplete for translation keys across the whole public surface — both the imperative API and the template APIs. Fully backward-compatible: with no augmentation, every key type stays
stringand existing apps compile unchanged.Supersedes the per-instance-generic approach in #1620 (keeps that as an escape hatch) by making the registry the default key source, so plain
inject(TranslateService),{{ key | translate }},[translate], and*translateBlockare all keyed without per-call-site ceremony.How you opt in
That one augmentation retypes the entire library to the app's key-space — code and templates.
What's added
NgxTranslateConfig— augmentable registry interface (i18nextCustomTypeOptionspattern). Ships empty, soTranslationKeyfalls back tostring.TranslationKey— resolves to the augmentedkeysunion, orstringwhen unaugmented.DeepKeys<T>— derives the dotted leaf-path union from a translation shape ({ a: "A", b: { c: "C" } }→"a" | "b.c"). Arrays and primitives are leaf keys; only plain objects recurse.injectTranslateService<Key>()— keyed inject helper (a bareinject(TranslateService)of a generic class widens the key toany).TranslateService<Key>,TranslatePipe<Key>, the[translate]directive, the*translateBlockt()helper, and the standalonetranslate()function.Key-space coverage
<Key>overrideTranslateServiceinject<TranslateService<K>>(…)translate()fntranslate<K>(…)TranslatePipe[translate]directive*translateBlockt()Known limitation
The design assumes a single global key-space. Per-subtree / per-child-service typed key-spaces are not supported: templates can't pass type arguments, and the parent/root chain carries
Keycosmetically (TS method bivariance), so a mixed-key-space hierarchy isn't soundly typed. Per-instance<Key>overrides remain a code-only escape hatch.Tests
type-tests/,pnpm run test-types): registry augmentation retypes the default service/pipe/directive/block/translate(); positive +@ts-expect-errornegative coverage; a big-dictionary stress fixture (10-level deep, 100-leaf wide) guarding thetscrecursion / union-size ceiling.template-tests/,pnpm run test-templates): realngtscstrictTemplatescompiles proving in-union keys compile and out-of-union keys are rejected on all three template surfaces (| translate,[translate],*translateBlock).translate.type-safety.spec.ts): per-instance generic on service + pipe, backward-compat default-stringpath,DeepKeysleaf derivation incl. arrays.test-typesandtest-templatesin thebuildjob.Compatibility
No runtime behavior change — all changes are type-level (erased at compile time).
ng test: 432 (ngx-translate) + 28 (http-loader) pass;build-all,lint,test-types,test-templatesall green.