Text Properties in EK9

The EK9 text construct holds the text your application outputs, in one or more spoken languages.

A whole construct to hold some text 'Strings' may seem excessive. You could put the text in the code, or pull it from properties files, a CMS or a database, and all of those can work. EK9 provides a built-in alternative, with compiler and type support.

Benefits of the Text construct

Example

The CheckTextAccess program and the Person record drive the example. There are two text blocks: one for "en_GB" (British English) and one for "de" (Deutsch).

When declarations for several languages share a name, every one of them must have the same methods, with the same parameter signatures; the compiler checks it (E07150). In effect, the shared name is a 'supertype', and each language's declaration must implement all of its methods.

Why EK9 does it this way. This comes from experience with 'property files'. It is easy to add or change a property for one language and forget the others, and that is usually found only at runtime. So EK9 mandates the same methods (and parameter signatures) on every declaration with the same name, and the compiler finds what is missing.

#!ek9
defines module site.textProperties.welcome

  defines program

    CheckTextAccess
      stdout <- Stdout()

      lang <- "en_GB"
      me <- Person("Steve", "Limb")
      englishWelcome <- WelcomePageText(lang)
      stdout.println(englishWelcome.mainWelcome())
      stdout.println(englishWelcome.namedWelcome(me))

      validator <- LanguageValidator(lang)
      stdout.println(validator.valueTooShort("Some Long Input"))

      deutschWelcome <- WelcomePageText("de")
      stdout.println(deutschWelcome.mainWelcome())
      stdout.println(deutschWelcome.namedWelcome(me))

      nonSuchWelcome <- WelcomePageText("none")
      stdout.println(nonSuchWelcome.mainWelcome())
      stdout.println(nonSuchWelcome.namedWelcome(me))

  defines record
    Person
      firstName String: String()
      lastName String: String()

      Person()
        ->
          firstName String
          lastName String
        require firstName? and lastName?

        this.firstName :=: firstName
        this.lastName :=: lastName

      operator $ as pure
        <- rtn String: `${firstName} ${lastName}`

      default operator ?

  defines text for "en_GB"

    WelcomePageText

      namedWelcome()
        -> person Person
        `Welcome ${person.firstName}`

      mainWelcome()
        "The main difference in processing html if you want to use hardwired content like this.
You must put it all in quotes.
As you can see above\nwith welcomeMessage, EK9 will look for variable names,
but anything in double quotes check tab[\t] is a literal.
In general you would probably use the 'text' mechanisms described in the section before this."

      endWelcome()
        `End Message.`

    LanguageValidator
      valueTooShort()
        -> input as String
        //Escape the $ with \$ as it is used for interpolation
        `The value:
\$ '${input}'
you entered is too short.
This can be multi-lined`

      valueTooLong
        -> input as String
        `The value ${input} you entered is too long`

      unknownLanguage
        ->
          input as String
          constraint as String
        `The value ${input} is not one of ${constraint}`

  defines text for "de"

    WelcomePageText
      namedWelcome()
        -> person Person
        `Willkommen ${person.firstName}`

      mainWelcome()
        "Der Hauptunterschied bei der Verarbeitung von HTML, wenn Sie fest verdrahteten Inhalt wie diesen verwenden möchten.
Sie müssen alles in Anführungszeichen setzen.
Wie Sie oben mit welcomeMessage sehen können, sucht EK9 nach Variablennamen.
aber alles in doppelten Anführungszeichen ist ein Literal.
Im Allgemeinen würden Sie wahrscheinlich die im vorherigen Abschnitt beschriebenen 'Text'-Mechanismen verwenden."

      endWelcome()
        "End Message."

    LanguageValidator
      valueTooShort()
        -> input as String
        `Der eingegebene Wert ${input} ist zu kurz`

      valueTooLong
        -> input as String
        `Der von Ihnen eingegebene Wert ${input} ist zu lang`

      unknownLanguage
        ->
          input as String
          constraint as String
        `Der Wert ${input} gehört nicht zu ${constraint}`

The example mixes fixed text, interpolated text, special characters and escape sequences. It prints:

The main difference in processing html if you want to use hardwired content like this.
You must put it all in quotes.
As you can see above
with welcomeMessage, EK9 will look for variable names,
but anything in double quotes check tab[	] is a literal.
In general you would probably use the 'text' mechanisms described in the section before this.
Welcome Steve
The value:
$ 'Some Long Input'
you entered is too short.
This can be multi-lined
Der Hauptunterschied bei der Verarbeitung von HTML, wenn Sie fest verdrahteten Inhalt wie diesen verwenden möchten.
Sie müssen alles in Anführungszeichen setzen.
Wie Sie oben mit welcomeMessage sehen können, sucht EK9 nach Variablennamen.
aber alles in doppelten Anführungszeichen ist ein Literal.
Im Allgemeinen würden Sie wahrscheinlich die im vorherigen Abschnitt beschriebenen 'Text'-Mechanismen verwenden.
Willkommen Steve

Summary

The main program CheckTextAccess is simple and clean, not cluttered with formatted text. Choosing the set of language resources is a constructor call. If the requested language does not exist, you get back an unset value, not an exception, so you can test it with ? and fall back to a default language.

This comes back to SOLID, and in particular a separation of concerns.

The methods can take any number of parameters of any type, and use them in interpolated Strings to build any String you need.

One of the most important benefits is that you can unit test all of an application's output text, quickly and in isolation. With the text in separate constructs, testing can focus on the formatting of the output, which is not a trivial task (for HTML emails, for example).

The compiler still checks that every method exists for every language, even when they are spread over several files.

Next Steps

The next construct sections are about higher level architecture and software design. They take composition further with components and dependency injection, and end with web services, the largest and final architectural construct in EK9.