Text Properties in EK9
The EK9 text construct holds the text your application outputs, in one or more spoken languages.
- A small application in a single spoken language may not need it.
- An application with a lot of text, or text in several languages, probably will.
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
- Separation of large amounts of text from code
- Encapsulation of text content in specific areas
- Explicit support for multiple spoken languages
- Cross checking of text constructs with same name
- Built in support for String Interpolation using parameters
- Built in support for multi-line text blocks - retaining formatting
- Follows the EK9 philosophy of separating definition from use
- Facilitates language translation by enabling all text to be collocated
- Provides a 'known' and expected place for output text and content to be located
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).
- Each text block holds text declarations, which are a little like classes. WelcomePageText and LanguageValidator are declared for both languages; a declaration can also be for one language only.
- A declaration has what look like methods (the () is optional), which can take parameters. Each consists of a single String.
- The String is either a plain String " ... " or an interpolated String ` ... ${...} ...`. It can run over several lines, and keeps its formatting.
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}`
Download textProperties_welcome.ek9
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).
- A lot of text: store it in separate source files, all in the same module.
- Many spoken languages: store each language's text declarations in its own file.
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.