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.

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:

strategy
String
The strategy identifier (deepl, copilot-ai, or your own).
unitsAttempted
Int
How many units the strategy was asked to translate.
unitsTranslated
Int
How many succeeded before the strategy gave up. Always 0 when this exception is thrown.

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

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:

TriggerMessage
'strategy' => 'ai' without kirby-copilot installedStrategy "ai" requires the kirby-copilot plugin
'strategy' => 'banana'Unknown strategy "banana"
Kirby language DeepL cannot nameCannot resolve a DeepL target language for Kirby language "…"

A language that resolves to no DeepL code needs a language code override. DeepL's HTTP error responses map to LogicException as well – see the DeepL client reference.

InvalidArgumentException

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

TriggerMessage
Unregistered code on a multi-language siteUnknown language code "…"; not registered in Kirby languages.

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:

TriggerMessage
Missing DeepL API keyMissing DeepL API key
Missing Copilot AI provider API keyMissing API key in "johannschopplich.copilot.providers.<name>.apiKey"
For per-unit translation drops, listen to content-translator.translate:warning instead of catching exceptions.