symfony/css-selector · error · ExpressionErrorException

Expected a single string or identifier for :lang(), got

Error message

Expected a single string or identifier for :lang(), got 

What it means

ExpressionErrorException thrown by HtmlExtension::translateLang (HTML-aware translator) when any argument token of :lang() is neither a string nor an identifier token. The HTML variant emits an XPath condition matching case-insensitive lang/xml:lang attributes with prefix matching, which still requires a literal string/identifier value.

Solutions

  1. Quote the argument: ':lang("en-us")'.
  2. Use a simple identifier: ':lang(en)'.
  3. Match only one language tag per :lang(); combine selectors for several.
  4. Catch ExpressionErrorException around the translation call.

Example fix

// before
':lang(01)'
// after
':lang("en")'
Defensive patterns

Strategy: validation

Validate before calling

function isValidLangArg(?string $arg): bool {
    return null !== $arg && (bool) preg_match('/^("[^"]*"|\'[A-Za-z-]+\'|[A-Za-z][\w-]*)$/', $arg);
}

Try / catch

try {
    $xpath = $translator->cssToXPath($css); // with HtmlExtension registered
} catch (ExpressionErrorException $e) {
    if (str_contains($e->getMessage(), ':lang()')) {
        // reject selector
    }
}

Prevention

When it happens

Trigger: Using Translator with HtmlExtension enabled and translating ':lang(42)', ':lang("a" "b")', or any selector whose :lang() token list contains a non-string/non-identifier token.

Common situations: Same as the base :lang() error but in HTML documents: unquoted numeric or malformed language codes, programmatically generated selectors with wrong token types, CSS4-style wildcard arguments like ':lang("*-Latn")' typed in a way that mis-tokenizes.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of symfony/css-selector@08e2905152 (2026-09-14). Data as JSON: /api/errors/5e9cd2bf67d848e7. Report an issue: GitHub.

Appendix: source

Thrown at XPath/Extension/HtmlExtension.php:148

                .')'
                .' and not(@disabled or '.self::DISABLING_FIELDSET.')'
            .') or ('
                ."name(.) = 'option' and not("
                    .'@disabled or ancestor::optgroup[@disabled]'
                .')'
            .')'
        );
    }

    /**
     * @throws ExpressionErrorException
     */
    public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr
    {
        $arguments = $function->getArguments();
        foreach ($arguments as $token) {
            if (!($token->isString() || $token->isIdentifier())) {
                throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments));
            }
        }

        return $xpath->addCondition(\sprintf(
            'ancestor-or-self::*[@lang][1][starts-with(concat('
            ."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')"
            .', %s)]',
            'lang',
            Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-')
        ));
    }

    public function translateSelected(XPathExpr $xpath): XPathExpr
    {
        return $xpath->addCondition("(@selected and name(.) = 'option')");
    }

    public function translateInvalid(XPathExpr $xpath): XPathExpr

View on GitHub (pinned to 08e2905152)