2

I'm looking for the best solution to separate the usage of strings as identifiers for translated messages and literal text values.

So i might have an object field called title, which in this case refers to the title of a form and it will have the value SIGNUP_FORM_TITLE or MAIL_FORM_TITLE so it will refer to a localized string and will be passed to a function like format(id) which returns the actual localized text for the current locale.

But then i might also have an object field name which refers to a person's name and is not localized, so it might have the value John Doe.

How can i make sure i get a type error when i pass name to format(id), and vice versa when i pass title to an API that expects a plain (text) string.

I can't (of course) just say type LocalizationIdentifierType = string as this is just an alias.

Maybe i could make a subclass of string class LocalizationIdentifier extends String but this would still allow LocalizationIdentifier to be passed to a string based API (or at least i think thats the case).

Has anyone a better idea to sort of nominally type a string? Or maybe i'm completely on the wrong track? ("Auf dem Holzweg" as we say in Germany)


There is a related question and a proposal to add similar functionality to typescript. Choosing typescript over flowtype is, of course, not an option.

Moritz Mahringer
  • 1,240
  • 16
  • 28

2 Answers2

3

I think what you may be looking for are Flow's opaque type aliases

You could make an opaque type alias: opaque type LocalizationIdentifierType = string

This would behave as a string inside the file, but as a nominal type outside of the file.

You mention that you want something similar to LocalizationIdentifierType extends String -- you can achieve this by adding a subtyping constraint onto your opaque type alias:

opaque type LocalizationIdentifierType: string = string

This means that a LocalizationIdentifierType is a string, but not all strings are a LocalizationIdentifierType

Jordan Brown
  • 638
  • 1
  • 5
  • 14
1

Literal types almost work. You can specify the signature of format as

function format(id: "SIGNUP_FORM_TITLE" | "MAIL_FORM_TITLE" | ...): string {
   ...
}

Unfortunately, this falls apart for your other case (localized text wrongly passed to the plain text API). An object literal type seem like the JavaScript path of least resistance:

type LocalizedString = {
  +field: "SIGNUP_FORM_TITLE" | "MAIL_FORM_TITLE"
};
type PlainText = string;

const signupFormTitle: LocalizedString = { field: "SIGNUP_FORM_TITLE" };
const mailFormTitle: LocalizedString = { field: "MAIL_FORM_TITLE" };

If you need to introduce variations on LocalizedString, an additional tag field in the objects works nicely, e.g.:

type LocalizedString = {
  +variant: "variant1",
  +field: "SIGNUP_FORM_TITLE" | "MAIL_FORM_TITLE"
};
popham
  • 582
  • 4
  • 11
  • Thanks for the workaround unfortunately this didn't quite match what i was thinking about. I'm really glad, that opaque types made it now ☺️ – Moritz Mahringer Aug 07 '17 at 18:08