---
title: "Exceptions"
description: "Which failures raise which exception – a dead provider, a misconfigured strategy, a missing key, a language the site never registered."
canonical_url: "https://kirby.tools/docs/content-translator/php-classes/exceptions"
---

# Exceptions

> Which failures raise which exception – a dead provider, a misconfigured strategy, a missing key, a language the site never registered.

`TranslationException` is the plugin's own; the rest are Kirby's. All extend `Kirby\Exception\Exception` and serialize cleanly to API responses. `TranslationException` fires only when zero units could be translated – partial failures emit a `:warning` hook instead.

## `TranslationException`

`JohannSchopplich\ContentTranslator\Translation\Exception\TranslationException` extends `Kirby\Exception\Exception`. Built-in strategies throw it when **zero units survived** – per-unit failures keep the source text and only emit a `:warning` hook.

```php
final class TranslationException extends \Kirby\Exception\Exception
{
    protected static string $defaultKey = 'content-translator.translation';
    protected static int $defaultHttpCode = 502;

    public function __construct(
        string $strategy,
        string $reason,
        int $unitsAttempted,
        int $unitsTranslated = 0,
    );
}
```

### Details Payload

The exception carries structured details for logging:

<field-group>
<field name="strategy" type="String">

The strategy identifier (`deepl`, `copilot-ai`, or your own).

</field>

<field name="unitsAttempted" type="Int">

How many units the strategy was asked to translate.

</field>

<field name="unitsTranslated" type="Int">

How many succeeded before the strategy gave up. Always `0` when this exception is thrown.

</field>
</field-group>

For per-unit error context, listen to `content-translator.translate:warning` – it fires for each failed unit with the underlying `Throwable` before the strategy decides whether enough units survived.

### Catching

```php
use JohannSchopplich\ContentTranslator\Translation\Exception\TranslationException;

try {
    $translator->translateContent('de', 'de', 'en');
} catch (TranslationException $error) {
    $details = $error->getDetails();
    // ['strategy' => 'deepl', 'unitsAttempted' => 12, 'unitsTranslated' => 0]
}
```

The default HTTP code 502 surfaces when an unhandled `TranslationException` bubbles up through the Panel.

## `LogicException`

Configuration errors throw `Kirby\Exception\LogicException`:

<table>
<thead>
  <tr>
    <th>
      Trigger
    </th>
    
    <th>
      Message
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        'strategy' => 'ai'
      </code>
      
       without kirby-copilot installed
    </td>
    
    <td>
      <code>
        Strategy "ai" requires the kirby-copilot plugin
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        'strategy' => 'banana'
      </code>
    </td>
    
    <td>
      <code>
        Unknown strategy "banana"
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Kirby language DeepL cannot name
    </td>
    
    <td>
      <code>
        Cannot resolve a DeepL target language for Kirby language "…"
      </code>
    </td>
  </tr>
</tbody>
</table>

A language that resolves to no DeepL code needs a [language code override](/docs/content-translator/providers/deepl#language-code-overrides). DeepL's HTTP error responses map to `LogicException` as well – see the [DeepL client](/docs/content-translator/php-classes/deepl-client#error-handling) reference.

## `InvalidArgumentException`

`Kirby\Exception\InvalidArgumentException` is thrown for a language code the site does not know:

<table>
<thead>
  <tr>
    <th>
      Trigger
    </th>
    
    <th>
      Message
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Unregistered code on a multi-language site
    </td>
    
    <td>
      <code>
        Unknown language code "…"; not registered in Kirby languages.
      </code>
    </td>
  </tr>
</tbody>
</table>

Both the `translate()` helper and `Translator::translateText()` resolve their `$targetLanguage` and `$sourceLanguage` against Kirby's language registry, so a typo in a script surfaces here rather than as a failed request. A single-language site skips the check – any code is passed on to the strategy.

## `AuthException`

`Kirby\Exception\AuthException` is thrown for missing API keys:

<table>
<thead>
  <tr>
    <th>
      Trigger
    </th>
    
    <th>
      Message
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Missing DeepL API key
    </td>
    
    <td>
      <code>
        Missing DeepL API key
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Missing Copilot AI provider API key
    </td>
    
    <td>
      <code>
        Missing API key in "johannschopplich.copilot.providers.<name>.apiKey"
      </code>
    </td>
  </tr>
</tbody>
</table>

<callout color="info" icon="i-ri-arrow-right-line" to="/docs/content-translator/advanced/hooks#content-translatortranslatewarning">

For per-unit translation drops, listen to `content-translator.translate:warning` instead of catching exceptions.

</callout>

---

Every page of this site as Markdown: <https://kirby.tools/sitemap.md>
