Text formatting
Learn how text and data can be formatted, combined, and translated using Desk functions and classes.
Overview
Text formatting is a core concept in Desk, that’s tightly integrated with many other parts of the framework.
Why? — Once an application grows in complexity, it becomes difficult to add features such as translation (including pluralization), internationalization (e.g. number formatting), and structured logging in a consistent way. Using the framework’s core features from the start can help to avoid these issues.
Implementation — Rather than passing around ‘raw’ strings, different functions expect or return values that are either a string or an object that represents a formatted string — including the original format and data. These values are then converted using JavaScript’s native String()
method at the last moment, applying internationalization and formatting lazily — e.g. when rendering a UI label to the screen.
User-facing text is therefore typically represented using the StringConvertible type: either a string, or any object that has a toString()
method, such as the ‘lazily evaluated’ result of the strf() method described below.
- type StringConvertibleString type or object that has a toString method.
Creating a formatted string
To create an object that represents a formatted and translatable string, with or without placeholders (see below), you can use the strf() function. This function returns a LazyString object, with its own toString()
method.
- function strf(format, …values)Returns a (lazily) formatted string incorporating the provided values.
- class LazyStringAn object that encapsulates a string, evaluated only when needed.
let lazyString = strf("Hello, world");
console.log(String(lazyString)); // "Hello, world"
The toString()
method of the lazily evaluated string returned by strf(...)
—
- Uses internationalization features to translate the string itself, if possible
- Uses LazyString.format() to fill in any placeholders with formatted values, if any
- Uses LazyString.cache() to retain the result (typically, until the current I18nProvider is changed)
Using placeholders
The string that’s passed to strf()
can contain placeholders for data that should be filled in when the string is evaluated. Placeholders are loosely based on the syntax of the printf()
function in C, starting with a %
character.
Data can be provided directly to the strf()
function, as arguments, or as an object with named properties. Alternatively, you can call the LazyString.format() method on the result of strf()
to fill in placeholder data at a later time.
let msg1 = strf("Hello %s", user.fullName);
let msg2 = strf("You have %i new message#{/s}", nMessages);
let msg3 = strf(
"Hello %[name], you have %[count] %[count:plural|message|messages]",
{
name: user.fullName,
count: user.messages.length,
},
);
Note: Combine as much as possible of your user-facing text into a single
strf()
call, where possible. This makes it easier for translators to reformat the text, since languages may have different word orders, pluralization rules, or even punctuation characters. Avoid combining formatted strings or data using the+
operator, as this will make it more difficult to translate the resulting string.
For more information on available placeholders and formatting options, refer to the documentation for the format() method.
- format(…args)Replaces placeholders in the string with string-formatted values.
Some useful placeholders include:
%s
— A string value%i
— An integer value%f
— A floating-point number%.2f
— A floating-point number with 2 decimal places%04i
— An integer value, padded with leading zeros to 4 digits%[name]
— A named placeholder, wherename
is a property of the object passed toformat()
%[name:i]
— A named placeholder with an integer value%[name:local|date]
— A named placeholder with a value that should be formatted as a date, using the current i18n provider (see internationalization)
Creating a formatted string binding
The same features that make strf()
useful for creating internationalized and formatted strings can be used to create a binding that represents a lazily formatted string. Using a string-formatted binding, you can automatically update text properties based on the value of multiple other properties, combined into a single string.
To create a string-formatted binding, use the bound.strf()
method. This method returns a StringFormatBinding object.
- function strf(format, …bindings) staticCreates a new string-formatted binding, with nested property bindings.
- class StringFormatBindingA class that represents a string-formatted binding with nested property bindings.
// using binding source paths:
bound.strf("Today is %s", "dayOfTheWeek");
// using an object argument and property syntax:
bound.strf("%[user] is %[age] years old", {
user: bound("user.name", strf("Unknown user")),
age: bound.number("user.age").else(99),
});
The syntax of the format string is the same as for strf()
, and the placeholders are filled in using the current values of the individual bindings.
Note: The resulting string is only initialized when all individual bindings are bound. For example, a string-formatted binding that depends on bindings
user.name
anddayOfTheWeek
only takes a (string) value when bothuser
anddayOfTheWeek
are found on any attached parent. Bindings may be bound to different containing objects, and the resulting string will be updated whenever any of the individual bound values change.
Displaying formatted text
You can assign the result of strf()
directly to a property any UI component that displays text, such as a label, button, or text field (for its placeholder text).
Additionally, plain text within JSX code is automatically passed to strf()
(making it localizable).
// using static methods
const view = ui.column(
{ padding: 16 },
ui.label(strf("Enter your name:")),
ui.textField({ placeholder: strf("Your name") }),
);
// using JSX
export default (
<column padding={16}>
<label>Enter your name:</label>
<textfield placeholder={strf("Your name")} />
</column>
);
Using string-formatted bindings
You can assign the result of bound.strf()
directly to a property of a UI component — wherever a binding is valid, and the property is typed as StringConvertible. The result of bound.strf()
is a StringFormatBinding, not a LazyString.
const view = ui.cell(ui.label(bound.strf("Hello, %s!", "name")));
JSX text bindings — In addition, you can use %[...]
placeholders in JSX text. Text content will be scanned for these placeholders automatically, inserting string-formatted bindings at runtime.
export default (
<column>
<label>Hello, %[name]</label>
<label>New messages: %[messages.count]</label>
</column>
);
For more information on bindings and JSX syntax, refer to the following articles:
- BindingsLearn how to use bindings, to communicate property changes between attached objects.
- ViewsThis article is not yet available.
Formatting dialog message text
Message dialogs are a common way to display formatted text to the user. To make it easier to define all text in your application away from business logic, Desk provides a way to group this text together in a single object.
Use the MessageDialogOptions class to store text and options for an alert or confirmation dialog, including placeholders for data that should be filled in when displaying the dialog. Instances of this class can be passed to the app.showAlertDialogAsync() and app.showConfirmDialogAsync() methods, and can be formatted with data using the format() method.
- class MessageDialogOptionsA representation of the contents of an alert or confirm dialog.
// messages.ts
export default {
CONFIRM_DELETE_ITEMS: new MessageDialogOptions(
[strf("Delete %i item#{/s}?"), strf("This action cannot be undone.")],
strf("Yes, delete"),
strf("Cancel"),
),
// ...
};
// elsewhere:
let nItems = toBeDeleted.length;
let confirmed = app.showConfirmDialogAsync(
messages.CONFIRM_DELETE_ITEMS.format(nItems),
);
For more information, refer to the following article:
- Message dialogsThis article is not yet available.
Formatting log messages
Log messages can also be formatted using the strf()
function. When the result is passed to a LogWriter method, the log message is formatted and any data that was passed to strf()
can be stored in a structured format (if supported by the log output sink).
// Write a formatted log message
app.log.verbose(strf("Logged in as %[name]", userData));
For more information about logging and error messages, refer to the following article:
- Errors and loggingUnderstand how errors are handled, and how you can use built-in logging features to help observe and debug your application.