fix(frontend-plugin-api): fix FlattenedMessages type depth for TypeScript 6

Restructure the FlattenedMessages conditional type to check
`TMessages[TKey] extends string` directly instead of using an
intermediate `infer TValue` pattern, which caused excessive type
instantiation depth in TypeScript 6.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
This commit is contained in:
Fredrik Adelöw
2026-03-24 22:30:56 +01:00
parent bdd21b9f9c
commit ddc5247f67
2 changed files with 15 additions and 10 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': patch
---
Fixed `FlattenedMessages` type to avoid excessive type instantiation depth in TypeScript 6 when using `createTranslationRef` with the `translations` option.
@@ -51,16 +51,16 @@ type FlattenedMessages<TMessages extends AnyNestedMessages> =
// final step of flipping this unions around to an intersection by inferring the function parameter.
{
[TKey in keyof TMessages]: (
_: TMessages[TKey] extends infer TValue // "local variable" for the value
? TValue extends AnyNestedMessages
? FlattenedMessages<TValue> extends infer TNested // Recurse into nested messages, "local variable" for the result
? {
[TNestedKey in keyof TNested as `${TKey & string}.${TNestedKey &
string}`]: TNested[TNestedKey];
}
: never
: { [_ in TKey]: TValue } // Primitive object values are passed through with the same key
: never,
_: TMessages[TKey] extends string
? { [_ in TKey]: TMessages[TKey] } // String values are leaf nodes, passed through with the same key
: TMessages[TKey] extends AnyNestedMessages
? FlattenedMessages<TMessages[TKey]> extends infer TNested // Recurse into nested messages, "local variable" for the result
? {
[TNestedKey in keyof TNested as `${TKey & string}.${TNestedKey &
string}`]: TNested[TNestedKey];
}
: never
: { [_ in TKey]: TMessages[TKey] }, // Other primitive values are passed through with the same key,
) => void;
// The `[keyof TMessages]` extracts the object values union from our flattened structure, still wrapped up in function parameters.
// The `extends (_: infer TIntersection) => void` flips the union to an intersection, at which point we have the correct type.