Forgetting to include property annotations for objects

I am attempting to create a localized strings object:

const strings = {
  hello: {
    en: 'hello',
    pl: 'hej'
  },
  bye: {
    en: 'bye',
    pl: 'pa'
  }
}

While this setup works and Intellisense shows available options like hello and bye, it does not enforce strict typing. Some properties may be missing the en or pl keys, or even have additional keys that should not exist. To make the object more specific, I used an enum and typed the object:

enum Language {
  en = 'en',
  pl = 'pl'
}

type Strings = { [k: string]: { [lang in Language]: string } }

const strings: Strings = {
  // ...same as before
}

Now, my strings properties are properly typed. However, there is a trade-off as now the properties lose their explicit typings due to [k: string]. This means that accessing strings.dontExist will not throw an error.

So, my question is how can I have inferred properties (like in the initial example) while also having strongly typed properties explicitly set by me ({ [lang in Language]: string })?

[using typescript 3.6.2]

Answer №1

In my opinion, it would be more efficient to rearrange the structure by placing the en and pl at the top level for easier typing. To achieve this, you can first create the object and then determine the type of keys in a new object. Here is an example:

enum Language {
  en = 'en',
  pl = 'pl'
}

type Translation =  { [lang in Language]: string } 

const strings  = {
  hello: {
    en: 'hello',
    pl: 'hej'
  },
  bye: {
    en: 'bye',
    pl: 'pa'

  }
}

type TranslationGroup =  keyof typeof strings;

const typed: Record<TranslationGroup, Translation> = strings

typed.foo //error

Answer №2

If I were to approach this problem, I would consider making the Strings type generic in the keys K, as shown below:

type Strings<K extends string> = { [P in K]: { [L in Language]: string } };

To simplify the process of inferring K from an object literal without manual input, a small helper function can be utilized:

const asStrings = <K extends string>(s: Strings<K>) => s;

The implementation example is demonstrated here:

const strings = asStrings({
  morning: {
    en: "morning",
    es: "mañana"
  },
  night: {
    en: "night",
    es: "noche"
  }
}); // Strings<"morning" | "night">

In case a language field is excluded, error messages will prompt. I hope this explanation is beneficial!

Code Link

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

The value stored in Ionic Storage will only be visible on the HTML page after a refresh is performed

After updating my Ionic Storage values, they are not showing up on the HTML page until I reload it. I have researched similar issues faced by others, but the solutions I found either do not work or are no longer applicable due to changes in the Ionic versi ...

Is there a method to restrict the scope of identical components appearing multiple times on a single page in Angular 7?

I have a scenario where I need to place multiple instances of the same component on a page, but when one of them receives input and refreshes, all other components also refresh. Is there a way to prevent this from happening? <tr *ngFor="let map of imgs ...

Exploring the Depths of JSON Arrays within Typescript

I am faced with a challenge in extracting the value of the "id" from the following array of JSON data. The issue lies in the fact that the value is enclosed within double square brackets "[[" which are causing complications in retrieving the desired result ...

Using Angular Material to dynamically hide columns in a table that is being created on the fly

Is there a way to hide columns in my table based on the ID value sent to the page upon opening it? While researching, I found a method for tables with dynamically generated columns in this post: https://github.com/angular/components/issues/9940. However, t ...

The Spring Boot and Angular Fusion Application

Currently, I am in the process of developing a Spring Boot Application with Angular as the frontend and Spring-Boot for the backend. My goal is to create a deployable war. After researching various resources online, my current understanding is that Spring ...

Compiling a TypeScript project to generate the correct index.js file is currently not successfully completed

Looking to set up the TypeScript project here. The steps are: git clone https://github.com/simpleledger/BitcoinFilesJS.git test/ cd test npm i tsc # or npx tsc The resulting index.js is shown below: "use strict"; var __createBinding = (this & ...

Change object values to capital letters

Upon retrieving myObject with JSON.stringify, I am now looking to convert all the values (excluding keys) to uppercase. In TypeScript, what would be the best way to accomplish this? myObj = { "name":"John", "age":30, "cars": [ { "name":"Ford", ...

angular primeng table has a checkbox to select all checkboxes

Is there a way to check all checkboxes in a table when the checkbox in the table header is clicked? I am currently utilizing angular 12 and primeNG table for this functionality. <p-table styleClass="text-sm" [value]="value" [loading] ...

Is it possible to retrieve a randomized set of the top 10% results from an Angular Material data source?

Sorry for the roughness of the code. It's a work in progress. While I believe it's almost working, I'm struggling to complete it. My goal is to display 10% of the results from an HTTP POST request in a material table. Any tips would be appre ...

How can we leverage Shared and Core modules alongside Feature modules in Angular development?

When developing my Angular application, I have adopted a specific architecture approach and included a shared.module.ts file in the shared folder. While leveraging lazy-loading in my app, I find myself puzzled about the necessary imports, declarations, and ...

Cordova's FileReader takes precedence over TypeScript's FileReader functionality

I encountered an issue when adding the cordova-plugin-file-transfer plugin, where I received the error message: reader.addEventListener is not a function. This problem arises due to Cordova FileReader class overriding typescript FileReader. How can this ...

Angular2 encounters an error when attempting to build, displaying the message: "Unable to proceed operation, directory creation not allowed, mkdir 'dir

Despite my extensive search, I have not been able to find a solution to my problem. When I tried to build my Angular2 & Spring Boot project on another computer, I encountered an error while running ng build. The error message states: Error: EPERM: opera ...

What is the best method for extracting the initial data from an array?

Having an issue in the node (html file) where I need to extract only the first data instead of all the data. I attempted the following: <% if(tblData) {%> <% tblData.forEach(function(res,row) {%> <tr> <td> ...

strategies for chaining together multiple observables with varying data types and operations

Hey everyone! I'm facing a situation where I have a form with multiple select types, and the options for these inputs are coming from an API. I then take the data emitted from the HTTP request observable and feed it into my FormGroup, and everything i ...

Using a functional wrapper component to reset the modal field in Reactstrap upon closing and reopening

In the main component that displays a list of to-do tasks, we have the ability to add or edit existing tasks. To facilitate this functionality, a separate wrapper was created. import React, { useEffect, useState } from 'react'; import { Label ...

A generic function in Typescript that can accept an object with the first argument as a specified key

I am looking to create a function that will take a string as its first argument and will only accept a second argument of type object if it contains the first argument as a key with a boolean value: const checkFlag = (str:string, obj) => obj[str] Alth ...

Repeated pathway encountered upon refreshing

I'm currently dealing with a problem related to duplicated paths. To illustrate the issue for testing purposes, I created a TestingComponent. Here is the code snippet: const routes: Routes = [ { path: '', redirectTo: 'testing ...

Angular6 - accessing elements that are not visible on the page

Currently, I am facing a situation where I have a component embedded in my HTML template that needs to remain hidden until a certain condition is met by checking a box. The tricky part is that the condition for setting "showControl" to true relies on calli ...

The inability to utilize custom images as icons with the "icon" property is a limitation for Android and IOS development in NativeScript

Having some trouble with the "icon" property in the Navigation button tag to use a custom image as an icon. On Android, it's working fine, but on iOS the image appears oversize. I've tried using scss and setting height in the tags themselves wit ...

Is it possible to generate type definitions for inner classes?

I'm currently exploring the usage of TypeScript within a Salesforce project involving RemoteObject One challenge I'm facing is creating typings for the external JavaScript object syntax. For example, in JavaScript: var ct = new RemoteObjectMod ...