diff --git a/spec/data-model/README.md b/spec/data-model/README.md
index 421854aae9..08e511167e 100644
--- a/spec/data-model/README.md
+++ b/spec/data-model/README.md
@@ -135,24 +135,33 @@ interface LiteralExpression {
type: "expression";
arg: Literal;
annotation?: FunctionAnnotation | UnsupportedAnnotation;
+ attributes?: Attribute[];
}
interface VariableExpression {
type: "expression";
arg: VariableRef;
annotation?: FunctionAnnotation | UnsupportedAnnotation;
+ attributes?: Attribute[];
}
interface FunctionExpression {
type: "expression";
arg?: never;
annotation: FunctionAnnotation;
+ attributes?: Attribute[];
}
interface UnsupportedExpression {
type: "expression";
arg?: never;
annotation: UnsupportedAnnotation;
+ attributes?: Attribute[];
+}
+
+interface Attribute {
+ name: string;
+ value?: Literal | VariableRef;
}
```
@@ -219,7 +228,7 @@ that the implementation attaches to that _annotation_.
```ts
interface UnsupportedAnnotation {
type: "unsupported-annotation";
- sigil: "!" | "@" | "%" | "^" | "&" | "*" | "+" | "<" | ">" | "?" | "~";
+ sigil: "!" | "%" | "^" | "&" | "*" | "+" | "<" | ">" | "?" | "~";
source: string;
}
```
@@ -241,6 +250,7 @@ interface MarkupOpen {
kind: "open";
name: string;
options?: Option[];
+ attributes?: Attribute[];
}
interface MarkupStandalone {
@@ -248,12 +258,14 @@ interface MarkupStandalone {
kind: "standalone";
name: string;
options?: Option[];
+ attributes?: Attribute[];
}
interface MarkupClose {
type: "markup";
kind: "close";
name: string;
+ attributes?: Attribute[];
}
```
diff --git a/spec/data-model/message.dtd b/spec/data-model/message.dtd
index 35a3fc6290..c6cb86c324 100644
--- a/spec/data-model/message.dtd
+++ b/spec/data-model/message.dtd
@@ -24,8 +24,8 @@
@@ -43,8 +43,11 @@
+
+
+
-
+
", "/", "?", "~"]
+ "enum": ["!", "#", "%", "^", "&", "*", "<", ">", "/", "?", "~"]
},
"source": { "type": "string" }
},
@@ -68,7 +84,8 @@
"properties": {
"type": { "const": "expression" },
"arg": { "$ref": "#/$defs/literal" },
- "annotation": { "$ref": "#/$defs/annotation" }
+ "annotation": { "$ref": "#/$defs/annotation" },
+ "attributes": { "$ref": "#/$defs/attributes" }
},
"required": ["type", "arg"]
},
@@ -77,7 +94,8 @@
"properties": {
"type": { "const": "expression" },
"arg": { "$ref": "#/$defs/variable" },
- "annotation": { "$ref": "#/$defs/annotation" }
+ "annotation": { "$ref": "#/$defs/annotation" },
+ "attributes": { "$ref": "#/$defs/attributes" }
},
"required": ["type", "arg"]
},
@@ -85,7 +103,8 @@
"type": "object",
"properties": {
"type": { "const": "expression" },
- "annotation": { "$ref": "#/$defs/function-annotation" }
+ "annotation": { "$ref": "#/$defs/function-annotation" },
+ "attributes": { "$ref": "#/$defs/attributes" }
},
"required": ["type", "annotation"]
},
@@ -93,7 +112,8 @@
"type": "object",
"properties": {
"type": { "const": "expression" },
- "annotation": { "$ref": "#/$defs/unsupported-annotation" }
+ "annotation": { "$ref": "#/$defs/unsupported-annotation" },
+ "attributes": { "$ref": "#/$defs/attributes" }
},
"required": ["type", "annotation"]
},
@@ -112,7 +132,8 @@
"type": { "const": "markup" },
"kind": { "const": "open" },
"name": { "type": "string" },
- "options": { "$ref": "#/$defs/options" }
+ "options": { "$ref": "#/$defs/options" },
+ "attributes": { "$ref": "#/$defs/attributes" }
},
"required": ["type", "kind", "name"]
},
@@ -122,7 +143,8 @@
"type": { "const": "markup" },
"kind": { "const": "standalone" },
"name": { "type": "string" },
- "options": { "$ref": "#/$defs/options" }
+ "options": { "$ref": "#/$defs/options" },
+ "attributes": { "$ref": "#/$defs/attributes" }
},
"required": ["type", "kind", "name"]
},
@@ -131,7 +153,8 @@
"properties": {
"type": { "const": "markup" },
"kind": { "const": "close" },
- "name": { "type": "string" }
+ "name": { "type": "string" },
+ "attributes": { "$ref": "#/$defs/attributes" }
},
"required": ["type", "kind", "name"]
},
diff --git a/spec/formatting.md b/spec/formatting.md
index e65547d623..ba6d83a7e4 100644
--- a/spec/formatting.md
+++ b/spec/formatting.md
@@ -652,6 +652,10 @@ MUST be an empty string.
Implementations MAY offer functionality for customizing this,
such as by emitting XML-ish tags for each _markup_.
+_Attributes_ are reserved for future standardization.
+Other than checking for valid syntax, they SHOULD NOT
+affect the processing or output of a _message_.
+
### Examples
_This section is non-normative._
diff --git a/spec/message.abnf b/spec/message.abnf
index 6ed784a284..15383232aa 100644
--- a/spec/message.abnf
+++ b/spec/message.abnf
@@ -19,16 +19,18 @@ selector = expression
variant = key *(s key) [s] quoted-pattern
key = literal / "*"
-expression = literal-expression / variable-expression / annotation-expression
-literal-expression = "{" [s] literal [s annotation] [s] "}"
-variable-expression = "{" [s] variable [s annotation] [s] "}"
-annotation-expression = "{" [s] annotation [s] "}"
+expression = literal-expression
+ / variable-expression
+ / annotation-expression
+literal-expression = "{" [s] literal [s annotation] *(s attribute) [s] "}"
+variable-expression = "{" [s] variable [s annotation] *(s attribute) [s] "}"
+annotation-expression = "{" [s] annotation *(s attribute) [s] "}"
annotation = function
/ private-use-annotation
/ reserved-annotation
-markup = "{" [s] markup-open [s] ["/"] "}"
- / "{" [s] markup-close [s] "}"
+markup = "{" [s] markup-open *(s attribute) [s] ["/"] "}"
+ / "{" [s] markup-close *(s attribute) [s] "}"
markup-open = "#" identifier *(s option)
markup-close = "/" identifier
@@ -36,6 +38,7 @@ literal = quoted / unquoted
variable = "$" name
function = ":" identifier *(s option)
option = identifier [s] "=" [s] (literal / variable)
+attribute = "@" identifier [[s] "=" [s] (literal / variable)]
input = %s".input"
local = %s".local"
@@ -70,14 +73,14 @@ reserved-keyword = "." name
; Reserve additional sigils for use by future versions of this specification.
reserved-annotation = reserved-annotation-start reserved-body
-reserved-annotation-start = "!" / "@" / "%" / "*" / "+"
- / "<" / ">" / "?" / "~"
+reserved-annotation-start = "!" / "%" / "*" / "+" / "<" / ">" / "?" / "~"
reserved-body = *([s] 1*(reserved-char / reserved-escape / quoted))
reserved-char = %x00-08 ; omit HTAB and LF
/ %x0B-0C ; omit CR
/ %x0E-19 ; omit SP
- / %x21-5B ; omit \
+ / %x21-3F ; omit @
+ / %x41-5B ; omit \
/ %x5D-7A ; omit { | }
/ %x7E-2FFF ; omit IDEOGRAPHIC SPACE
/ %x3001-D7FF ; omit surrogates
diff --git a/spec/syntax.md b/spec/syntax.md
index dbfa1c4c13..96af226216 100644
--- a/spec/syntax.md
+++ b/spec/syntax.md
@@ -427,6 +427,7 @@ An _expression_ MUST begin with U+007B LEFT CURLY BRACKET `{`
and end with U+007D RIGHT CURLY BRACKET `}`.
An _expression_ MUST NOT be empty.
An _expression_ cannot contain another _expression_.
+An _expression_ MAY contain one more _attributes_.
A **_literal-expression_** contains a _literal_,
optionally followed by an _annotation_.
@@ -438,9 +439,9 @@ An **_annotation-expression_** contains an _annotation_ without an _o
```abnf
expression = literal-expression / variable-expression / annotation-expression
-literal-expression = "{" [s] literal [s annotation] [s] "}"
-variable-expression = "{" [s] variable [s annotation] [s] "}"
-annotation-expression = "{" [s] annotation [s] "}"
+literal-expression = "{" [s] literal [s annotation] *(s attribute) [s] "}"
+variable-expression = "{" [s] variable [s annotation] *(s attribute) [s] "}"
+annotation-expression = "{" [s] annotation *(s attribute) [s] "}"
```
There are several types of _expression_ that can appear in a _message_.
@@ -638,14 +639,14 @@ unrecognized _reserved-annotations_ or _private-use-annotations_ have no meaning
```abnf
reserved-annotation = reserved-annotation-start reserved-body
-reserved-annotation-start = "!" / "@" / "%" / "*" / "+"
- / "<" / ">" / "?" / "~"
+reserved-annotation-start = "!" / "%" / "*" / "+" / "<" / ">" / "?" / "~"
reserved-body = *([s] 1*(reserved-char / reserved-escape / quoted))
reserved-char = %x00-08 ; omit HTAB and LF
/ %x0B-0C ; omit CR
/ %x0E-19 ; omit SP
- / %x21-5B ; omit \
+ / %x21-3F ; omit @
+ / %x41-5B ; omit \
/ %x5D-7A ; omit { | }
/ %x7E-D7FF ; omit surrogates
/ %xE000-10FFFF
@@ -656,7 +657,12 @@ reserved-char = %x00-08 ; omit HTAB and LF
**_Markup_** _placeholders_ are _pattern_ parts
that can be used to represent non-language parts of a _message_,
such as inline elements or styling that should apply to a span of parts.
-Markup comes in three forms:
+
+_Markup_ MUST begin with U+007B LEFT CURLY BRACKET `{`
+and end with U+007D RIGHT CURLY BRACKET `}`.
+_Markup_ MAY contain one more _attributes_.
+
+_Markup_ comes in three forms:
**_Markup-open_** starts with U+0023 NUMBER SIGN `#` and
represents an opening element within the _message_,
@@ -673,8 +679,8 @@ is a _pattern_ part ending a span.
Unlike the other forms, it does not include _options_.
```abnf
-markup = "{" [s] markup-open [s] ["/"] "}"
- / "{" [s] markup-close [s] "}"
+markup = "{" [s] markup-open *(s attribute) [s] ["/"] "}"
+ / "{" [s] markup-close *(s attribute) [s] "}"
markup-open = "#" identifier *(s option)
markup-close = "/" identifier
```
@@ -691,6 +697,42 @@ _Markup_ _placeholders_ can appear in any order without making the _message_ inv
However, specifications or implementations defining _markup_ might impose requirements
on the pairing, ordering, or contents of _markup_ during _formatting_.
+## Attributes
+
+**_Attributes_ are reserved for standardization by future versions of this specification.**
+Examples in this section are meant to be illustrative and
+might not match future requirements or usage.
+
+An **_attribute_** is an _identifier_ with an optional value
+that appears in an _expression_ or in _markup_.
+
+_Attributes_ are prefixed by a U+0040 COMMERCIAL AT `@` sign,
+followed by an _identifier_.
+An _attribute_ MAY have a _value_ which is separated from the _identifier_
+by an U+003D EQUALS SIGN `=` along with optional whitespace.
+The _value_ of an _attribute_ can be either a _literal_ or a _variable_.
+
+Multiple _attributes_ are permitted in an _expression_ or _markup_.
+Each _attribute_ is separated by whitespace.
+
+```abnf
+attribute = "@" identifier [[s] "=" [s] (literal / variable)]
+```
+
+> Examples of _expressions_ and _markup_ with _attributes_:
+>
+> A _message_ including a _literal_ that should not be translated:
+>
+> ```
+> In French, "{|bonjour| @translate=no}" is a greeting
+> ```
+>
+> A _message_ with _markup_ that should not be copied:
+>
+> ```
+> Have a {+span @can-copy}great and wonderful{-span @can-copy} birthday!
+> ```
+
## Other Syntax Elements
This section defines common elements used to construct _messages_.