Preview:ts ... // @ts-expect-error The key does not exist. translations.doesnotexist; ...
#How can my function type automatically infer a type corresponding to one of its parameters?
1 messages · Page 1 of 1 (latest)
I've used a declare statement on purpose because I'm trying to overwrite translate's type which comes from the import
i think i see what you're trying to do, but it seems a bit odd to expect the caller to check their own code via a type parameter of your function like that, since the type parameter isn't actually necessary for translate's signature. is there a specific reason it needs to be done this way?
my first instinct would be for call sites to just store id in a variable and reuse it across all of the translation objects, then it's guaranteed to be consistent
I'm trying to enforce the ID syntax for translation locales as the developer declares them
In order to prevent typos and locales that are not declared in the right place
While also allowing for auto-completion when you import translations
well you're accepting any string, so it doesn't really prevent typos
It should be enforced with the first generic Prefix, at least that was the plan
even if it were possible to partially-infer type parameter lists like you wanted (it isn't, BTW. TS doesn't have this feature), what's to stop someone from writing translate<string>(...)?
or not writing anything at all there and letting it be inferred as whatever
do you know what all the possible prefixes are at compile time? if so you could be stricter here
Then the id would have to fit in ${string}.${string} I suppose, which is fine although I don't think there's a valid reason to declare any locale like that
The end goal is not to be fully error-proof, but rather export a const from domain/subdomain/component/translations.ts and ensure all locales contained use the right prefix domain.subdomain.component.*
so is the answer to this no? if so then no matter what you're relying on the caller to check their own value
in case it wasn't clear, i was suggesting something like this here:
Preview:```ts
...
const id = "domain.subdomain.open"
const translations = translate([
{
id,
key: "open",
},
{
id,
key: "close",
},
])
...```
which is just as safe (but IMO easier to review) than what you were going for (since like i said translate<string>(...) would have been totally valid)
there might be a way to write a validator type that accepts only string literals, but it's going to be weird/complicated and not bulletproof in any case. but if you think you really need that let me know and i'll play around
actually, here's another idea: change the signature of translate to accept id once as a separate parameter from the keys. then it's literally impossible to have more than one value 😄
While I read your playground, I was retrieving the first version I had, which doesn't enforce much https://www.typescriptlang.org/play/?#code/JYOwLgpgTgZghgYwgAgCIRqYZgHsQA8A0hAJ7IQAekIAJgM7L1hSgDmyAvEy+wHzIA3gChkyYLQBcyEqQDco5AGsyAfmmyFAX2HDaEBABs4UFDACuIBDnzIWcEPWORiZCtQh1GzViDZ8ACkV9TBBsPEdpdFDw-FdSPgBtAF0AGmEASmkAJQNcKFp41J5ffwVhBHxmOygHJzgbRy4auucIAMTFETExCWkAclpcAFs4UAA6enMAIyHRidwAB09+9J7lMgGllbXkLV3unr7kQZGxkEmZufPxo1x6CFXFMRVSAbuHp7F94WSM8vsjmcEXoCgA9GDkAA9VS6QH1Rr0W6Ge4QcGQ9aY6Gw4QQ5AAATA9AAtFRltZSVAoPlkAAVAAWKFeyCGEEYIFwYHcwGY42E8OBVXGrPoHMglB5YHRWMxMOEQA
Which doesn't enforce the prefix but does provide autocompletion based on the input array
With my first playground, I was trying to 1. add the prefix rule 2. remove the id from the autocomplation (see the infered type for the translations const)
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
@merry idol Here's a shortened URL of your playground link! You can remove the full link from your message.
Preview:```ts
interface Definition<Key extends string = string> {
id: Key
key?: Key
}
declare function translate<Key extends string>(
definitions: Definition<Key>[]
): Record<Key, string>
const translations = translate([
{
id: "domain.subdomain.open",
key: "open"
...```
You can choose specific lines to embed by selecting them before copying the link.
did you see this suggestion? here's how it might look:
Preview:```ts
...
declare function translate<Key extends string>(
id: ${string}.${string},
definitions: readonly Key[]
): Record<Key, string>
const translations = translate("domain.subdomain", [
"open",
"close",
])
...```
Yeah I was coming to it, I can't change translate implementation unfortunately
ah, too bad
Basically yes that works, except I'd like the prefix declared with the type and not from a value
Because ${string}.${string} is too broad and only checks that there are 2 segments essentially, whereas I'd like to set that prefix for each translate call
What I'm not getting is how can TypeScript infer Key here,
declare function translate<Key extends string>(
definitions: Definition<`${string}.${string}`, Key>[],
): Record<Key, string>;
But couldn't infer it when I tried pulling ${string}.${string} from the translate generic with my first proposal
you were trying to explicitly specify a single type parameter for a function that requires two type parameters
that is simply not a thing you can do in TS
either all type parameters must have explicit concrete values (default values count) or all are inferred. there's no way to say "infer type parameter 2 but not type parameter 1"
you can kinda fake it by splitting your function up, like instead of <A, B>() => Z do <A>() => <B>() => Z, but i don't know that it really gets you anything since like i said it's still up to callers to check their own ids, which they can do on their own without your function needing a fancy signature
That might be the part I was missing
I think I can loosen restrictions for the IDs then
As long the output is not a dumb Record<string, string> that's fine I think
The issue now becomes that translate in its implementation has key as optional and when omitted, the output uses id as key in the dictionary of locales
maybe you can make key required in your stricter type signature?
That'll make key required in the whole code base right?
The declare statement is in my current file where I can be stricter with the declarations but the rest of the code base shouldn't have type errors (yet)
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
@merry idol Here's a shortened URL of your playground link! You can remove the full link from your message.
Preview:```ts
interface Definition<Key extends string = string> {
id: Key
key?: Key
}
declare function translate<Key extends string>(
definitions: Definition<Key>[]
): Record<Key, string>
const translations = translate([
{
id: "domain.subdomain.open",
key: "open"
...```
You can choose specific lines to embed by selecting them before copying the link.
The issue is that translations if you hover over it has both open and domain.subdomain.open as valid indexes but shouldn't have the latter
The declare statement is in my current file where I can be stricter with the declarations but the rest of the code base shouldn't have type errors (yet)
i meant just makingkeyrequired locally, in thedeclarestatement
i was thinking about this approach some more and threw this together: https://tsplay.dev/wg1L9m. that still doesn't go out of its way to prevent translate<string>()(...) and whatnot (but i could add that if you really need it). i still suspect it's not really worth it though, and since you said you have to use the existing translate signature then that extra call is probably not going to work anyway
let me know if there's some problem with this. maybe i don't understand the key issue
rather than changing the implementation or overriding its type signature, could you just create your own wrapper function that is stricter?
Right, but it will affect all translate imports
I've used it that way in my code
import { translate } from 'some/module/translate';
declare module 'some/module/translate' {
function translate...
}
export const translations = translate(...);
ah, i must have been confused about what you meant by "The declare statement is in my current file where I can be stricter with the declarations"
are declare module blocks not module-scoped in module files? you're saying this setup affects the type signature of translate even in other files that don't contain their own declare module block? if so, TIL
That looks like what I wanted to achieve, but I struggled with the function keyword
Couldn't find a way to declare the Prefix generic like you did with const, but with my function. Is that a thing?
If not, I'm not sure how to write that, when I'm importing translate already
if you're trying to just override the type without changing the runtime value, you can't. the first call to translate() would fail, or at least return a value that's not a function so the second call would fail
Right
did you see this message? is there a reason you can't write a wrapper around the upstream translate function? if you can then you can do whatever you want there without any compromises
I'm not sure why I haven't tried that already tbh
Sounds like the easiest, although more code to maintain
Good call
probably better than maintaining tricky types IMO