| name | fluent-syntax-terms |
| description | Use when defining or referencing terms (-term-name) in FTL for brand names or shared vocabulary. Prevents accidental runtime API access to terms, which is never allowed. Covers term syntax, parameterized terms with named arguments, term attributes for grammatical metadata, and visibility rules. Keywords: FTL terms, -term-name, parameterized terms, term attributes, brand names, grammatical cases, runtime visibility, reusable translation, shared vocabulary, brand name translation, term syntax.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Fluent Syntax 1.0. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
fluent-syntax-terms
Quick Reference
Term Syntax Overview
| Feature | Syntax | Example |
|---|
| Define a term | -name = Value | -brand-name = Firefox |
| Reference a term | { -name } | about = About { -brand-name }. |
| Parameterized term | { -name(key: "value") } | { -brand-name(case: "locative") } |
| Term attribute | -name = Value + .attr = data | -brand-name = Firefox + .gender = masculine |
| Attribute as selector | { -name.attr -> ... } | { -brand-name.gender -> [masculine] ... } |
Term vs Message Comparison
| Aspect | Message | Term |
|---|
| Identifier prefix | None (starts with letter) | - (hyphen prefix) |
| Runtime access | bundle.getMessage("id") returns it | bundle.getMessage("-id") returns undefined |
| Purpose | User-facing text displayed in UI | Shared vocabulary, brand consistency, grammatical metadata |
| Variable data source | Received from application code | Received from referencing messages via named arguments |
| Value requirement | Can be attribute-only (no value) | MUST have a value (Pattern required) |
| Attribute visibility | Public (accessible via .attributes) | Private (usable ONLY as selectors in FTL) |
| Named argument values | N/A (messages use application variables) | MUST be string or number literals |
Critical Warnings
NEVER try to access terms via bundle.getMessage("-term-name") -- this returns undefined. Terms are resolved automatically when referenced inside messages. There is NO public API to directly format a term.
NEVER use variable references as named argument values in term references. { -term(case: $myCase) } is NOT valid FTL. Named argument values MUST be string literals ("locative") or number literals (1).
ALWAYS give terms a value (Pattern). Unlike messages, a term CANNOT be attribute-only. The grammar requires Term ::= "-" Identifier "=" Pattern Attribute* -- the Pattern is mandatory because terms are designed to be interpolated in placeables.
ALWAYS use the hyphen prefix when defining terms. Without the hyphen, you are defining a regular message, which changes visibility and variable resolution semantics.
NEVER treat term attributes as retrievable data. Term attributes like .gender = feminine are private to the FTL file and can ONLY be used as selectors within FTL. They are NOT accessible via any runtime API.
Term Definition and Usage
Basic Terms
Terms define reusable, internal-only translation fragments. The hyphen prefix (-) distinguishes them from messages:
-brand-name = Firefox
-app-version = 3.0
about = About { -brand-name }.
update-successful = { -brand-name } has been updated to version { -app-version }.
Terms enforce brand consistency: every message referencing -brand-name gets the same value. When the brand name changes, update the term once.
Parameterized Terms
Terms accept named arguments from referencing messages. This enables grammatical case handling in morphologically rich languages:
# Polish: brand name declines by case
-brand-name =
{ $case ->
*[nominative] Firefox
[locative] Firefoksie
[genitive] Firefoksa
}
about = Informacje o { -brand-name(case: "locative") }.
download = Pobierz { -brand-name(case: "genitive") }.
welcome = { -brand-name } jest gotowy.
When no arguments are passed (as in welcome), the $case variable is unset, so the *[nominative] default variant is selected.
Rule: Named argument values MUST be literals. { -brand-name(case: "locative") } is valid. { -brand-name(case: $userCase) } is NOT valid.
Parameterized Terms with Dynamic Values
Terms can construct values from parameters:
-https = https://{ $host }
visit = Visit { -https(host: "example.com") } for more information.
Term Attributes
Grammatical Metadata
Term attributes store private metadata used for selector-based logic in other messages:
-brand-name = Aurora
.gender = feminine
update-successful =
{ -brand-name.gender ->
[masculine] { -brand-name } został zaktualizowany.
[feminine] { -brand-name } została zaktualizowana.
*[other] Program { -brand-name } został zaktualizowany.
}
The .gender attribute on -brand-name is used as a selector in update-successful. The attribute value itself is NEVER exposed to the application -- it controls FTL-internal branching only.
Multiple Attributes
A term can have multiple attributes for different grammatical properties:
-product-name = Thunderbird
.gender = masculine
.animacy = inanimate
.starts-with-vowel = no
Each attribute is available as a selector via -product-name.gender, -product-name.animacy, etc.
Decision Tree
When to Use a Term vs a Message
Is this text displayed directly to the user as a standalone string?
├── YES → Use a message (no hyphen prefix)
└── NO → Is it referenced by other messages for consistency?
├── YES → Use a term (hyphen prefix)
└── NO → Is it grammatical metadata (gender, case forms)?
├── YES → Use a term with attributes
└── NO → It may not need to exist at all
When to Use Parameterized Terms
Does the term need different forms based on grammatical context?
├── YES → Use a parameterized term with named arguments
│ Example: -brand-name with $case selector
└── NO → Does the term need dynamic construction?
├── YES → Use a parameterized term with value parameters
│ Example: -https with $host parameter
└── NO → Use a simple term (-brand-name = Firefox)
When to Use Term Attributes
Do other messages need to branch based on a property of this term?
├── YES → Add an attribute to the term (.gender = feminine)
│ Then use as selector: { -term.attribute -> ... }
└── NO → Do not add attributes; keep the term simple
EBNF Grammar
Term ::= "-" Identifier blank_inline? "=" blank_inline? Pattern Attribute*
TermReference ::= "-" Identifier AttributeAccessor? CallArguments?
CallArguments ::= blank? "(" blank? argument_list blank? ")"
NamedArgument ::= Identifier blank? ":" blank? (StringLiteral | NumberLiteral)
Key constraints from the grammar:
- Term MUST have a value --
Pattern is required (not optional like in Message)
- TermReference can combine attribute access and call arguments --
{ -term.attr(key: "val") } is syntactically valid
- Named argument values are restricted -- ONLY
StringLiteral or NumberLiteral, NEVER variable references
Reference Links
Official Sources