symfony/translation · error · LogicException

Circular reference detected when adding a fallback…

Error message

Circular reference detected when adding a fallback catalogue for locale "%s".

What it means

addFallbackCatalogue links a fallback catalogue under this one. Before linking, it walks the incoming catalogue's existing fallback chain; if any catalogue in that chain has the same locale as $this, linking would create a circular fallback reference, so a LogicException is thrown.

Solutions

  1. Break the cycle: remove the fallback link that points back to the ancestor locale before adding.
  2. Only chain strictly distinct locales (en -> fr -> de) when assembling fallback catalogues manually.
  3. Build a fresh fallback chain from configuration instead of mutating an existing catalogue graph.
  4. If using the Translator, let it compute fallback locales via fallbackLocales rather than wiring catalogues by hand.

Example fix

// before: en falls back to fr, which falls back to en
$fr->addFallbackCatalogue($en);
$en->addFallbackCatalogue($fr); // throws

// after: one-directional chain
$fr->addFallbackCatalogue($en);
Defensive patterns

Strategy: validation

Validate before calling

$seen = [];
for ($c = $catalogue; null !== $c; $c = $c->getFallbackCatalogue()) {
    $seen[$c->getLocale()] = true;
}
if (isset($seen[$this->getLocale()])) { /* cycle detected */ }

Try / catch

try {
    $catalogue->addFallbackCatalogue($fallback);
} catch (\LogicException $e) {
    // rebuild the fallback chain without the cycle
}

Prevention

When it happens

Trigger: Calling $a->addFallbackCatalogue($b) where $b (or any of $b's fallback ancestors) has the same locale as $a, e.g. $a('en') -> addFallbackCatalogue($b('fr')) when $b already falls back to an 'en' catalogue.

Common situations: Programmatically wiring fallback chains from config where locales cycle (en -> fr -> en); reusing the same catalogue object twice in a chain; tests replicating the translator's circular-reference detection.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of symfony/translation@ae9e8a51bc (2026-09-15). Data as JSON: /api/errors/b7f0fd4c3814ad33. Report an issue: GitHub.

Appendix: source

Thrown at MessageCatalogue.php:176

        if ($catalogue instanceof MetadataAwareInterface) {
            $metadata = $catalogue->getMetadata('', '');
            $this->addMetadata($metadata);
        }

        if ($catalogue instanceof CatalogueMetadataAwareInterface) {
            $catalogueMetadata = $catalogue->getCatalogueMetadata('', '');
            $this->addCatalogueMetadata($catalogueMetadata);
        }
    }

    public function addFallbackCatalogue(MessageCatalogueInterface $catalogue): void
    {
        // detect circular references
        $c = $catalogue;
        while ($c = $c->getFallbackCatalogue()) {
            if ($c->getLocale() === $this->getLocale()) {
                throw new LogicException(\sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
            }
        }

        $c = $this;
        do {
            if ($c->getLocale() === $catalogue->getLocale()) {
                throw new LogicException(\sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
            }

            foreach ($catalogue->getResources() as $resource) {
                $c->addResource($resource);
            }
        } while ($c = $c->parent);

        $catalogue->parent = $this;
        $this->fallbackCatalogue = $catalogue;

        foreach ($catalogue->getResources() as $resource) {

View on GitHub (pinned to ae9e8a51bc)