Understand how Smart Strings format dynamic text using named placeholders, sources, and formatters.
Smart Strings are a Unity port of the SmartFormat library, provided by the Unity.SmartStrings module. They are an alternative to String.Format for generating dynamic text.
You can create data-driven templates that support pluralization, gender conjugation, lists, and conditional logic. Named placeholders give translators more context, and make variables more readable and less error-prone than numeric indices.
Format a Smart String with Smart.Format:
// "The name is Lara."
Smart.Format("The name is {Name}.", new Character { Name = "Lara" });
A Smart String is literal text interspersed with placeholders. A placeholder is wrapped in braces ({ and }) and contains up to four parts, in order: a selector, a formatter name, formatter options, and a format.
flowchart TD
full["{slider.value:choose(1|2|3):one|two|three}"]
full --> p1["slider.value"]
full --> p2["choose"]
full --> p3["(1|2|3)"]
full --> p4["one|two|three"]
p1 --> d1["Selectors"]
p2 --> d2["Formatter name"]
p3 --> d3["Formatter options"]
p4 --> d4["Format"]
classDef code fill:#F5F5F5,stroke:#BDBDBD,color:#111;
classDef orange fill:none,stroke:none,color:#F37021;
classDef green fill:none,stroke:none,color:#67BC6B;
classDef pink fill:none,stroke:none,color:#EB417A;
classDef purple fill:none,stroke:none,color:#765BA7;
class full code;
class p1,d1 orange;
class p2,d2 green;
class p3,d3 pink;
class p4,d4 purple;
linkStyle default stroke:#2196F3,stroke-width:1.5px;
For example, in the Smart String {player.score:choose(1|2|3):one|two|three}, the parts can be split into the following:
| Part | Example | Description |
|---|---|---|
| Selectors | player.score |
Extracts the value to format. A source evaluates the selectors. |
| Formatter name | choose |
The formatter to apply. Optional: omit it to apply an implicit formatter. |
| Formatter options | (1\|2\|3) |
Options passed to the formatter, inside parentheses. |
| Format | one\|two\|three |
The output template the formatter uses. |
To escape a literal brace, use a backslash: \{ and \}.
Note: By default, Smart Strings use { and } as the placeholder characters and a backslash to escape them (\{ and \}). The parser also interprets character escapes such as \n and \t in the format string. Refer to Smart Strings settings reference for information on the different settings and how to enable them.
To format a placeholder, Smart Strings evaluate the sources to extract a value and then apply a formatter to convert that value to text.
flowchart TD
parse["Parse the format string"]
subgraph item["For each format item"]
literal{"Literal text?"}
resolve["Resolve the value<br/>using the Sources"]
resolved{"Value resolved?"}
format["Format the value<br/>using the Formatters"]
formatted{"Value formatted?"}
writeText["Write text to output"]
writeVal["Write result to output"]
errSel["Error: could not<br/>evaluate the selector"]
errFmt["Error: no suitable<br/>Formatter found"]
literal -->|Yes| writeText
literal -->|No| resolve
resolve --> resolved
resolved -->|No| errSel
resolved -->|Yes| format
format --> formatted
formatted -->|No| errFmt
formatted -->|Yes| writeVal
end
parse --> literal
item --> returnStr["Return the output string"]
classDef proc fill:#F5F5F5,stroke:#2196F3,color:#111;
classDef err fill:#FCE5EC,stroke:#EB417A,color:#111;
classDef ok fill:#E9F4E9,stroke:#67BC6B,color:#111;
class parse,literal,resolve,resolved,format,formatted,returnStr proc;
class errSel,errFmt err;
class writeText,writeVal ok;
style item fill:#DAEDFD,stroke:#2196F3;
linkStyle default stroke:#2196F3,stroke-width:1.5px;
Note: By default, a selector that no source can evaluate, or a value that no formatter can handle, raises a formatting error. Change the parser and formatter error actions in the Smart Strings settings to instead ignore the error, output it in the result, or leave the placeholder unchanged. Refer to Smart Strings settings reference for information on the different settings and how to enable them.
Selectors extract the object to format, usually from an argument. Sources evaluate the selectors. Each source is tried in order until one can handle the selector. For example, the Properties source reads a member by name from the current object.
Selector syntax is similar to C# interpolated strings:
- Use dot notation to drill into values, for example {1.gameObject.name}. When no index is given, the argument at index 0 is used.
- Use the null-conditional operator ?. for safe access ({gameObject?.transform?.parent}). If a selector before the null-conditional operator ?. is null, the placeholder evaluates to null instead of raising an error.
- Access list elements by index with brackets, for example {items[0]}.
Because sources are tried in order, the order can change the result. Refer to Smart Strings settings for information on how to configure the order in which sources are tried.
Avoid named placeholders that conflict across sources. Refer to the source pages for the available sources, such as Default, Properties, Dictionary, and String.
Pass multiple arguments and select them by index:
var person = new Character { Name = "John" };
var location = new Place { City = "Washington" };
// "First: John, second: Washington"
Smart.Format("First: {0.Name}, second: {1.City}", person, location);
Nest placeholders to avoid repeating a selector. The following are equivalent:
{User.Name} ({User.Age}) {User.Address.City}
{User:{Name} ({Age})} {User.Address:{City}}
You can still reach outer scopes from within a nested scope. An empty placeholder {} refers to the current value in scope.
Formatters convert the value returned by the selectors into text. You can format date and time, lists, plurals, or conditional logic.
Each formatter has a single name. Apply a formatter explicitly by name, or omit the name to let any formatter that supports automatic detection apply implicitly:
| Use | Example |
|---|---|
| Explicit | I have {0:plural:1 apple\|{} apples} |
| Implicit | I have {0:1 apple\|{} apples} |
Note: Each formatter has one name (for example, plural) and a CanAutoDetect flag that controls implicit use.
Implicit formatters are tried in order, so prefer an explicit name to avoid conflicts. Some formatters take options in parentheses, for example the 1, 2, and 3 in {0:choose(1|2|3):one|two|three}.
Refer to Smart Strings settings for information on how to configure the order in which formatters are tried.
Refer to the formatter pages for the available formatters, such as Choose, Conditional, Plural, List, and Sub String. To add your own, inherit from FormatterBase. Refer to Create a custom formatter for more information.
Add an alignment after the selectors, separated by a comma, to pad the result to a fixed width, like String.Format alignment. A positive value right-aligns the value and a negative value left-aligns it:
Smart.Format("[{Name,10}]", new Character { Name = "Joe" }); // "[ Joe]"
Smart.Format("[{Name,-10}]", new Character { Name = "Joe" }); // "[Joe ]"
Alignment works with every formatter, not only the default formatter.