Template Literal Types

type EmailLocaleIDs =
    | "welcome_email"
    | "email_heading";
type FooterLocaleIDs =
    | "footer_title"
    | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type AllLocaleIDs =
    | "welcome_email_id" 
    | "email_heading_id" 
    | "footer_title_id" 
    | "footer_sendoff_id"
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type Lang =
    | "en" 
    | "ja" 
    | "pt";
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;
type LocaleMessageIDs =
    | "en_welcome_email_id" 
    | "en_email_heading_id" 
    | "en_footer_title_id" 
    | "en_footer_sendoff_id" 
    | "ja_welcome_email_id" 
    | "ja_email_heading_id" 
    | "ja_footer_title_id" 
    | "ja_footer_sendoff_id" 
    | "pt_welcome_email_id" 
    | "pt_email_heading_id" 
    | "pt_footer_title_id" 
    | "pt_footer_sendoff_id"

type PropEventSource<Type> =
{
    on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;
};
 
/// Create a "watched object" with an `on` method
/// so that you can watch for changes to properties.
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;

const person = makeWatchedObject ({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26
});
 
person.on("firstNameChanged", () => {});
 
// Prevent easy human error (using the key instead of the event name)
//person.on("firstName", () => {});
//Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
 
// It's typo-resistant
//person.on("frstNameChanged", () => {});
//Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.

type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting>
type ShoutyGreeting = "HELLO, WORLD"
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<"my_app">
type MainID = "ID-MY_APP"