invoke a specified function at runtime

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?

TS Playground

Answer №1

If you want to manipulate result.params in a different way, it's best not to consider it as an intersection type. In reality, it doesn't encompass all properties but only those required by translations[result.transKey] for a specific result.transKey. TypeScript assumes it should be an intersection due to the lack of clarity regarding the relationship between result.transKey and result.params. The current Result type fails to define such a connection; for instance, specifying

{ transKey: 'Hello', params: { max: 'Bye' } }
would still be valid despite being incorrect for Hello. Even if union types were employed for each transKey, challenges like "correlated unions" arise within the forEach() callback.

The absence of direct support for correlated unions is addressed in microsoft/TypeScript#30581. The suggested solution involves leveraging generics in a specific manner as outlined in microsoft/TypeScript#47109.

The concept revolves around creating a "base" object type that captures the key-value association of interest. Subsequent operations should rely on this type along with generic indexes into it and mapped types over it.

The foundational object type looks like this:

interface TransArg {
    Hello: { field: unknown; };
    Bye: { field: unknown; date: unknown; };
    Foo: { field: unknown; };
    Bar: { max: unknown; };
}

This can actually be derived from MyTranslations through:

type TransKey = {
  [K in keyof MyTranslations]: MyTranslations[K] extends (arg: any) => any ? K : never
}[keyof MyTranslations]

type TransArg = { [K in TransKey]: Parameters<MyTranslations[K]>[0] }

The focus shifts towards rewriting the type of translations in terms of TransArg:

const _translations: { [K in TransKey]: (arg: TransArg[K]) => void } =
  translations;

While seemingly straightforward, this verification ensures that translations adheres to the specified mapped type. By substituting _translations for translations, the compiler gains better insight into its behavior across arbitrary keys K.

A more precise definition of Result emerges as a distributive object type:

type Result<K extends TransKey = TransKey> =
  { [P in K]: { transKey: P, params?: TransArg[P] } }[K]

Result<K> aligns with the desired type for a particular K, while Result<TransKey> compiles the complete union of Result<K> for all K in TransKey. This default argument strategy clarifies Result usage and allows for:

const results: Result[] = [
  {
    transKey: 'Hello',
    params: {
      field: 'Bye'
    }
  },
  {
    transKey: 'Bar',
    params: {
      max: 'test'
    }
  }
]

Any attempt to mix up the params will trigger an error, ensuring data integrity.

Lastly, results.forEach() call necessitates a generic callback function:

function translate(results: Result[]) {
  results.forEach(<K extends TransKey>(result: Result<K>) => {
    if (result.params) _translations[result.transKey](result.params);
  })
}

Within the callback, interactions between _translations[result.transKey] and result.params epitomize their corresponding types, enabling seamless compilation.

Explore the code example in TypeScript Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips on transferring key values when inputText changes in ReactJs using TypeScript

I have implemented a switch case for comparing object keys with strings in the following code snippet: import { TextField, Button } from "@material-ui/core"; import React, { Component, ReactNode } from "react"; import classes from "./Contact.module.scss" ...

Strategies for Dealing with 'No Search Results' in Your Search Functionality

I am currently facing an issue with displaying "No Results Found" when a user utilizes my search feature. The current problem is that "No Results Found" appears immediately on the screen and then disappears while a search query is being processed, only to ...

Having trouble setting State in React with Typescript?

I have encountered an issue with merging strings in an array. Despite successfully joining two strings and logging the result using console.log('Dates: ' + mergedActions), the merged string doesn't seem to be set in this.state.MergedAllActio ...

Issue with Angular 8: discrepancy between the value utilized in component.html and the value stored in component.ts (Azure application service)

Encountering a peculiar behavior in one of my Angular applications. In the component.html file, I aim to display "UAT" and style the Angular mat elements with a vibrant orange color when in UAT mode, while displaying them in blue without any mention of UAT ...

Angular 4 Operator for adding elements to the front of an array and returning the updated array

I am searching for a solution in TypeScript that adds an element to the beginning of an array and returns the updated array. I am working with Angular and Redux, trying to write a reducer function that requires this specific functionality. Using unshift ...

The .ts source file is mysteriously missing from the development tool's view after being deployed

When I work locally, I can see the .ts source files, but once I deploy them, they are not visible in any environment. Despite setting my sourcemap to true and configuring browserTargets for serve, it still doesn't work. Can someone help with this issu ...

TypeScript's TypeGuard wandering aimlessly within the enumerator

I'm puzzled by the fact that filter.formatter (in the penultimate line) is showing as undefined even though I have already confirmed its existence: type Filter = { formatter?: { index: number, func: (value: string) => void ...

We were unable to locate a declaration file for the module known as 'firebase-tools'

As I delve into writing my inaugural cloud function for Firebase, I find myself in need of the firebase-tools module. To bring it on board, I updated my dependencies by editing the package.json file and executing the command npm install. Next, I attempted ...

What is the method for executing a custom command within the scope of a tree view item?

I am trying to implement a custom "ping" function in my VS Code Extension that will send the details of a selected treeview object. Despite looking at various examples, I have been unable to properly build and register the command. Although I can invoke th ...

Unable to install Typescript using npm

I recently started a tutorial on Typescript and wanted to install it globally using npm. npm i typescript -g However, I encountered an issue where the installation gets stuck on the first line and displays the following message: (⠂⠂⠂⠂⠂⠂⠂⠂ ...

What causes TS2322 to only appear in specific situations for me?

I have been trying to create HTML documentation for my TypeScript project using Typedoc. Within one of the many files, there is a snippet of code: public doSomething(val: number | undefined | null | string): string | undefined | null { if (val === null ...

Best practices for implementing dual ngFor directives within a single tr element?

Click here to view the page The image attached shows the view I want to iterate through two ngFor loops inside the tr tag. When using a div tag inside the tr, it's looping the button next to the tag instead of where I want it in the file table header ...

Why is my index.tsx file not properly exporting? (React + Typescript)

I've developed a basic Context Provider that I'd like to package and distribute via npm. To package my code, I utilized the create-react-library tool. In my project, I've set up an index.tsx file that should serve as the entry point for im ...

Determine the data type of an index within an array declaration

Imagine we have the following array literal: const list = ['foo', 'bar', 'baz'] as const; We are attempting to create a type that represents potential indices of this array. This is what we tried: const list = ['foo&ap ...

Angular-4: Exploring Component Reference on Click Event

One of my challenges involves dynamically adding different components when the user clicks, allowing them to add and remove any component. However, I am struggling to figure out how to reference the component where the user clicked in Angular-4. here are s ...

Tips for sending asynchronous data to Google-Charts-Angular

I am currently working on generating a chart using data obtained from an API call. To achieve this, I am utilizing the google-charts-angular package within my HTML: <google-chart [title]="title" [type]="type" [data]="data" ...

Automate the process of triggering the "Organize Imports" command on a VSCode / Typescript project through code

Is there a way to automatically run VSCode's 'Organize Imports' quickfix on every file in a project, similar to how tslint can be run programatically over the entire project? tslint --project tsconfig.json --config tslint.json --fix I want ...

Simulated FileList for Angular 5 App Unit Testing

Imitation FileList In my pursuit of writing a unit test (Angular5), I have encountered the need for a FileList. Despite researching extensively, I have been unable to uncover any clues or solutions. I am starting to question whether this is even feasible ...

Typescript is struggling to locate a module that was specified in the "paths" configuration

Within my React project, I have set up a module alias in the webpack config. Now, I am looking to transition to Typescript. // I have tried to simplify the setup as much as possible Here is my tsconfig.json located in the root directory: { "compilerOp ...

Tips for creating a redirect to a specific page after clicking a link in an email using Angular

I've been working on implementing a feature in Angular where users can click on a link provided in an email and then get redirected to the respective page after authentication. I've tried a few different approaches, but none of them seem to be wo ...