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

  1. 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.}}`.
  2. Repeat the surrounding content verbatim in every plural/select option.
  3. 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 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


AI-assisted analysis of GoogleChrome/lighthouse@9515cd4e58 (2026-08-13). Data as JSON: /api/errors/21fdbe1061f09a47. Report an issue: GitHub.