I recently came across a useful library called https://github.com/ivanhofer/typesafe-i18n
This library has the capability to generate strongly typed translation data and functions, as illustrated below. (the examples provided are simplified for clarity)
export type MyTranslations = {
Hello: (arg: { field: unknown}) => string
Bye: (arg: { field: unknown, date: unknown}) => string
Foo: (arg: { field: unknown}) => unknown
Bar: (arg: { max: unknown}) => unknown,
test: string // this is just here to show that not every property of MyTranslations needs to be a function
}
const translations: MyTranslations = {
Hello: (arg: { field: unknown}) => 'hello',
Bye: (arg: { field: unknown, date: unknown}) => 'bye',
Foo: (arg: { field: unknown}) => 'foo',
Bar: (arg: { max: unknown}) => 'bar',
test: '' // this is just here to show that not every property of MyTranslations needs to be a function
}
In my code, I have a function that dynamically translates messages without prior knowledge of what needs to be translated.
By leveraging TypeScript typing information, it can infer potential translations using keyof
.
Here's a snippet of the code in progress.
I've invested significant time in this, and I'm not certain if it's achievable or practical, but I'm eager to find out :)
// preparation
interface MyParams {
[index: string]: boolean | number | string | undefined
field?: keyof MyTranslations
}
interface Result {
transKey: keyof MyTranslations,
params?: MyParams
}
const results: Result[] = [
{
transKey: 'Hello',
params: {
field: 'Bye'
}
},
{
transKey: 'Bar',
params: {
max: 'test'
}
}
]
type PickByType<T, V> = {
[P in keyof T as T[P] extends V | undefined ? P : never]: T[P]
}
The translation function
function translate(results: Result[]) {
results.forEach((result: Result) => {
type A = PickByType<MyTranslations, Function>
type C = keyof A
if(result.params) {
type T = typeof result.params
type Req = Required<T>
const req = result.params as Req
const func = translations[result.transKey]
type F = typeof func
const f = translations as A
f[result.transKey as C](req)
}
})
}
translate(results)
The issue arises at f[result.transKey as C](req)
Error
Argument of type 'Required<MyParams>' is not assignable to parameter of type '{ field: unknown; } & { field: unknown; date: unknown; } & { field: unknown; } & { max: unknown; }'.
Property 'date' is missing in type 'Required<MyParams>' but required in type '{ field: unknown; date: unknown; }'
This constraint makes sense. TypeScript expects an intersection type.
Therefore, I thought of a possible solution where I could create this type (holding all the required parameters field, max, and date
) and utilize this information to construct a new object with corresponding properties, as depicted in pseudo code below
type D = getAllParametersFromTypeAsIntersectionType() // <- this is easy
const newParams = createNewParamsAsTypeD(result.params)
Any suggestions or ideas?