GoogleChrome/lighthouse · error
Content cannot appear outside plural or select ICU messages.
Error message
Content cannot appear outside plural or select ICU messages. Instead, repeat that content in each option (message: '${lhlMessage}') What it means
Thrown by _lhlValidityChecks().validate() when a plural or select ICU argument shares its message with sibling content (elements.length > 1). Per ICU rules, complex argument types (plural/select) cannot have text before or after them — all surrounding content must be repeated inside each option.
Source
Thrown at core/scripts/i18n/collect-strings.js:213
let parsedMessageElements;
try {
parsedMessageElements = MessageParser.parse(escapeIcuMessage(lhlMessage), {ignoreTag: true});
} catch (err) {
if (err.name !== 'SyntaxError') throw err;
throw new Error(`[${err.message}] Did not find the expected syntax in message: ${err.originalMessage}`);
}
/**
* @param {MessageParser.MessageFormatElement[]} elements
*/
function validate(elements) {
for (const element of elements) {
if (element.type === MessageParser.TYPE.plural || element.type === MessageParser.TYPE.select) {
// `plural`/`select` arguments can't have content before or after them.
// See http://userguide.icu-project.org/formatparse/messages#TOC-Complex-Argument-Types
// e.g. https://github.com/GoogleChrome/lighthouse/pull/11068#discussion_r451682796
if (elements.length > 1) {
throw new Error(`Content cannot appear outside plural or select ICU messages. Instead, repeat that content in each option (message: '${lhlMessage}')`);
}
for (const option of Object.values(element.options)) {
validate(option.value);
}
}
}
}
validate(parsedMessageElements);
}
/**
* Convert code spans into placeholders with examples.
*
* @param {IncrementalCtc} icu
*/
function _processPlaceholderMarkdownCode(icu) {View on GitHub (pinned to 9515cd4e58)
Solutions
- Restructure so the entire message is inside the plural/select, e.g. `{itemCount, plural, =1 {You have 1 item remaining.} other {You have # items remaining.}}`.
- Repeat the surrounding content verbatim in every plural/select option.
- If two independent clauses are needed, split into two separate message keys instead.
Example fix
// before
message: 'You have {itemCount, plural, =1 {1 item} other {# items}} remaining.'
// after
message: '{itemCount, plural, =1 {You have 1 item remaining.} other {You have # items remaining.}}' Defensive patterns
Strategy: validation
Validate before calling
const {parse, TYPE} = require('intl-messageformat');
function hasContentOutsideComplexArg(msg) {
const els = parse(msg, {ignoreTag: true});
return els.some(el =>
(el.type === TYPE.plural || el.type === TYPE.select) && els.length > 1
);
} Prevention
- When using plural/select, put the entire sentence inside the argument.
- Repeat fixed text in every plural/select option.
- Split multi-clause sentences into separate message keys.
When it happens
Trigger: Writing a message like `You have {itemCount, plural, =1 {1 item} other {# items}} remaining` where text ('You have', 'remaining') appears outside the plural argument.
Common situations: Translating natural-language sentences that embed a plural mid-sentence; refactoring messages that previously used separate concatenation.
Related errors
- [${err.message}] Did not find the expected syntax in message
- must use same format and formatType for a given name. Invali
- unsupported JSDoc comment: ${JSON.stringify(comment)}
- Incorrectly formatted @example: "${rawExample}"
- Ctc messages cannot contain double dollar: ${icu.message}
AI-assisted analysis of GoogleChrome/lighthouse@9515cd4e58 (2026-08-13).
Data as JSON: /api/errors/21fdbe1061f09a47.
Report an issue: GitHub.