user13103906
user13103906

Reputation:

How do I dynamically index a record with known types in TypeScript?

I am trying to dynamically index a record with a known type using a string which can possibly be typed too, below is all the relevant code including the error which shows up inside my reduce (acc[val]), I am having troubles understanding the error so I do not know how to fix it

interface CommandsPingDescription {
  type: string;
  props: never;
}
interface LocalizationMap {
  "commands.ping.description": CommandsPingDescription;
}
import base from "../locale/base.json";
import en from "../locale/en.json";

type Language = keyof typeof languages;

const languages: Record<"en", typeof base> = { en };

export const setLanguage = <T extends Language>(lang: T) => {
  return <
    K extends keyof LocalizationMap,
    V extends LocalizationMap[K]["props"]
  >(
    key: K,
    ...placeholders: V[]
  ) => {
    let translation = key
      .split(".")
      .reduce((acc, val) => acc[val], languages[lang]);
(parameter) acc: Record<"en", {
    commands: {
        ping: {
            description: string;
        };
    };
}>[T]
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ commands: { ping: { description: string; }; }; }'.
  No index signature with a parameter of type 'string' was found on type '{ commands: { ping: { description: string; }; }; }'.

Playground link: https://www.typescriptlang.org/play/#code/MYewdgzgLgBARgQwgUxgXhgbwFAzzUAW0ITABMIAuLXfOgBwEswBzanOz-M5CYAJ0b0ojcNQBE42lwC+0mHLnZQkWMjDoadIiXJUtXGE1bt5hmDz6DhosBPEBuM-jl1F2bFACe9VABlSFgBXBBZUDABrZC8QADMYb184mAAbQJCwiCdlcGhU9NDeagAlZFB+MgAecXVxABoEn2RkxBQAPk1MGHUFbOQAD3oQflgVPJQoANYM8JhKgBVu-qh1Chgp4MK2gAo0kxh5gEp0Do58fmQoIP4NSuc8AGkllb0YKJj4vxBgBBTGAC8ECJwABZBD0Or3GAANWeqwg62+vwBQNsYPoAG0HgBdDHiej8ED0CDibHyHZQ97UB6Q8wAOgZ9DSwGQAAsQCkePx9NCMWS6Mc0KcoSlLgl+KQIGlgRpItEoXQ6RAmYwoNtxHTxIcFfg6RcyEEWdttghgMAGgA3X6Cjqm4AYq0pbENPabTIY13Yw5ONzYJTMFb8WKm1AAYRAxFIFAACswWAARXgCIQygyJZDUaCCVg+oyE4nUMDIC3IfhOOQB0vBlmIn5-QEy9EGcQ6KMQOnGFh0yzJmzgcTUcORvSx1iJqwp2zloA

Upvotes: 1

Views: 655

Answers (1)

joshwilsonvu
joshwilsonvu

Reputation: 2679

It seems like the meat of your code is here:

const key = "commands.ping.description";
const en = {
    commands: {
        ping: {
            description: "";
        }
    }
}
let translation = key
    .split(".")
    .reduce((acc, val) => acc[val], en);

and what you want TypeScript to do is to figure out the type of acc[val] where val is a substring of key. That's not going to happen; it's well beyond the limitations of string literal types, and for good reason.

What I recommend you do is loosen your types to something like this:

const key = "commands.ping.description";
interface RecursiveRecord<T> {
    [key: string]: T | RecursiveRecord<T>;
}
const en: RecursiveRecord<string> = {
    commands: {
        ping: {
            description: "hello";
        }
    }
}

// go through the properties from key.split(".") 
// and assign the string at the end to translation
let record = en;
let translation: string = "";
for (const substr of key.split(".")) {
    const oneLevelDeeper = record[substr];
    if (typeof oneLevelDeeper === "string") {
        translation = oneLevelDeeper
        break;
    } else {
        record = oneLevelDeeper;
    }
}
console.log(translation); // "hello"

Upvotes: 2

Related Questions