| name | fluent-syntax-messages |
| description | Use when writing FTL messages, placeables, or multiline translations. Prevents whitespace and indentation errors caused by the dedentation algorithm. Covers simple and multiline messages, variable references, message references, comment system, and special character handling. Keywords: FTL messages, placeables, multiline, dedentation, variables, message references, comments, write FTL message, use variables in translations, multiline translation, FTL syntax.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Fluent Syntax 1.0. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
fluent-syntax-messages
Quick Reference
Message Anatomy
| Component | Syntax | Example |
|---|
| Simple message | key = Value | hello = Hello, world! |
| Multiline message | Indented continuation lines | See below |
| Block text | Value starts on next line | key =\n Block text here |
| Placeable | { expression } | { $user }, { other-msg } |
| Variable reference | { $name } | { $email-count } |
| Message reference | { identifier } | { menu-save } |
Identifier Rules
| Rule | Detail |
|---|
| First character | MUST be a letter [a-zA-Z] |
| Subsequent characters | Letters, digits, underscores, hyphens [a-zA-Z0-9_-] |
| Invalid characters | Dots, spaces, or any other special characters |
| EBNF | Identifier ::= [a-zA-Z] [a-zA-Z0-9_-]* |
Comment Levels
| Syntax | Name | Scope | Binding |
|---|
# | Comment | Message-level | Bound to next message if directly above (no blank line) |
## | Group Comment | Section-level | ALWAYS standalone; divides file into groups |
### | Resource Comment | File-level | ALWAYS standalone; describes entire file |
Critical Warnings
NEVER use tabs for indentation. Tabs (U+0009) are NOT whitespace in FTL -- they are treated as regular text characters. ALWAYS use spaces (U+0020) for indentation. This is the number one cause of broken FTL output from AI-generated code.
NEVER omit the space after # in comments. #comment is invalid FTL. It MUST be # comment. An empty comment line (# alone) IS valid.
NEVER start a continuation line with [, *, or . -- these characters are reserved for variant keys, default variant markers, and attribute accessors. Use a quoted text placeable to escape them: {"["}.
NEVER place literal { or } in text -- they MUST be escaped using quoted text placeables: {"{"} or {"}"}.
Simple Messages
The basic unit of translation is a message: an identifier, an equals sign, and a value (called a pattern).
hello = Hello, world!
app-title = My Application
logout = Log out
The text value begins at the first non-blank character after =. Leading spaces between = and the value are stripped.
Multiline Messages
Text spans multiple lines when each continuation line is indented by at least one space.
single = Text can be written in a single line.
multi = Text can also span multiple lines
as long as each new line is indented
by at least one space.
block =
Sometimes it's more readable to format
multiline text as a "block", starting
on a new line. All lines must be indented
by at least one space.
Dedentation Algorithm
In multiline patterns, common indent is stripped from all indented lines:
- The first line (on the same line as
=) is EXCLUDED from dedentation calculations.
- The common indent of all remaining indented lines is calculated.
- That common indent is removed from every indented line.
example =
This has 4 spaces of indent in source.
This line retains 4 extra visible spaces.
Result: "This has 4 spaces of indent in source.\n This line retains 4 extra visible spaces."
Blank Line Handling
| Position | Behavior |
|---|
Leading blank lines (between = and first content) | Ignored |
| Trailing blank lines (after last content) | Ignored |
| Interior blank lines (content before AND after) | Preserved |
leading-spaces = This value starts with "This" (leading spaces stripped).
leading-lines =
This value starts with "This".
The blank lines under the identifier are ignored.
interior-blank =
First paragraph.
The blank line above IS preserved in output.
Placeables and Variables
Placeables use curly braces { } to incorporate dynamic content into text.
Variable References
Variables are external data provided by the application at runtime, using $name syntax:
welcome = Welcome, { $user }!
unread-emails = { $user } has { $email-count } unread emails.
Numbers and dates passed as variables are AUTOMATICALLY formatted according to the user's locale. With $duration = 12345.678:
- American English:
12,345.678
- South African English:
12 345,678
Message References
One message can embed another by referencing its identifier (without $):
menu-save = Save
help-menu-save = Click { menu-save } to save the file.
This keeps translations consistent and simplifies maintenance.
Comments
Three-Level System
### Localization for Server-side strings of Firefox Screenshots
## Global phrases shared across pages
# This is shown as the page title
my-shots = My Shots
home-link = Home
## Creating page
# Note: { $title } is a placeholder for the title of the web page
# $title (String) - The title of the web page being captured
creating-page-title = Creating { $title }
Binding Rules
- A
# comment placed directly above a message (no blank line) is bound to that message. Localization tools display them together.
- A blank line between a
# comment and a message makes the comment standalone.
## and ### comments are ALWAYS standalone and NEVER bind to messages.
- A space MUST follow
#, ##, or ### before comment text.
Special Characters
Restricted Characters at Line Start
The first character of a continuation line (after indentation) CANNOT be [, *, or .. These are parsed as variant keys, default variant markers, or attribute accessors. ALWAYS escape them:
# WRONG -- "[" parsed as variant key start
bracket-wrong =
First line
[This breaks]
# CORRECT
bracket-correct =
First line
{"["}This works]
Curly Braces in Text
Literal { and } MUST be escaped:
opening-brace = This has a curly brace: {"{"}.
closing-brace = This has a closing brace: {"}"}.
Escape Sequences (Quoted Text Only)
Escape sequences work ONLY inside quoted text (string literals), NOT in regular text:
| Sequence | Meaning |
|---|
\" | Literal double quote |
\\ | Literal backslash |
\uHHHH | Unicode U+0000 to U+FFFF |
\UHHHHHH | Any Unicode code point (6 hex digits) |
privacy-label = Privacy{"\u00A0"}Policy
In regular (unquoted) text, the backslash \ has no special meaning.
Preserving Leading Whitespace
Use a quoted text placeable to force leading spaces in output:
indented = {" "}This message starts with 4 spaces.
Without the placeable, the dedentation algorithm strips leading spaces.
Decision Tree: Message Format
Need to write a message?
├─ Single line of text → key = Value text here
├─ Multiple lines of text
│ ├─ First line on same line as = → key = First line
│ │ continuation here
│ └─ Block format (cleaner) → key =
│ All lines indented
│ by at least one space
├─ Need dynamic content → Use placeables: { $variable }
├─ Need to reference another message → { other-message }
├─ Need literal { or } → Use {"{"} or {"}"}
└─ Need [ * . at line start → Use {"["} or {"*"} or {"."}
EBNF Grammar (Key Productions)
Message ::= Identifier blank_inline? "=" blank_inline?
((Pattern Attribute*) | (Attribute+))
Pattern ::= PatternElement+
PatternElement ::= inline_text | block_text | inline_placeable | block_placeable
Identifier ::= [a-zA-Z] [a-zA-Z0-9_-]*
VariableReference ::= "$" Identifier
MessageReference ::= Identifier AttributeAccessor?
inline_placeable ::= "{" blank? (SelectExpression | InlineExpression) blank? "}"
CommentLine ::= ("###" | "##" | "#") ("\u0020" comment_char*)? line_end
blank_inline ::= "\u0020"+
A message MUST have either a value (Pattern), attributes, or both. Identifiers MUST start with a letter and may contain [a-zA-Z0-9_-].
Reference Links
- references/methods.md -- EBNF grammar excerpts for Message, Pattern, Identifier, VariableReference, MessageReference, CommentLine
- references/examples.md -- Complete examples: simple messages, multiline, block text, placeables, variables, message references, comments, special characters
- references/anti-patterns.md -- Common mistakes: whitespace errors, tab usage, missing comment space, special characters at line start
Official Sources