---
title: "CopilotAIStrategy"
description: "Context-aware AI translation through any Copilot provider – batched, validated, and resilient to malformed model responses."
canonical_url: "https://kirby.tools/docs/content-translator/php-classes/strategies/copilot-ai-strategy"
---

# CopilotAIStrategy

> Context-aware AI translation through any Copilot provider – batched, validated, and resilient to malformed model responses.

Selected via `'strategy' => 'ai'` in the plugin config – or by passing a `CopilotAIStrategy` instance directly. Provider credentials stay in [Kirby Copilot](/copilot); this plugin never talks to an AI provider itself.

<warning>

Selecting `'strategy' => 'ai'` without the Copilot plugin installed throws `LogicException`.

</warning>

## Behavior

Translations come back as a schema-constrained array. Chunks where the response length doesn't match the input fall back to source text. Units are packed into chunks bounded by 50 items and 100,000 bytes – a single oversized unit rides alone, never split.

<note>

The budget counts bytes, not characters. Scripts outside ASCII cost 2–4 bytes per character in UTF-8, so a Japanese or Greek page fills a chunk several times sooner than an English one.

</note>

LLM batching preserves per-item context naturally, so table cells ride along with prose.

`<cN/>` KirbyTag placeholders are validated after the strategy returns, so that guarantee holds for every strategy rather than this one – see [KirbyTags](/docs/content-translator/advanced/kirbytags#when-a-model-breaks-a-placeholder).

## Construction

```php
public function __construct(
    Client|null $client = null,
    string|null $systemPrompt = null,
)
```

<field-group>
<field name="client" type="\\JohannSchopplich\\Copilot\\AI\\Client | null">

Inject a Copilot client. Defaults to `Client::instance()`.

</field>

<field name="systemPrompt" type="String | null">

Override the system prompt for this instance. Falls back to `johannschopplich.content-translator.ai.systemPrompt`, then to the built-in default.

</field>
</field-group>

## Usage

<tabs :default-value="config">
<tabs-item label="Via config" value="config">

```php [config.php]
return [
    'johannschopplich.content-translator' => [
        'strategy' => 'ai',
    ],
    'johannschopplich.copilot' => [
        'provider' => 'openai',
        'providers' => [
            'openai' => ['apiKey' => env('OPENAI_API_KEY')],
        ],
    ],
];
```

</tabs-item>

<tabs-item label="Per call" value="per-call">

```php
use JohannSchopplich\ContentTranslator\Translation\Strategies\CopilotAIStrategy;
use JohannSchopplich\ContentTranslator\Translator;

Translator::translateText('Hello', 'de', 'en', new CopilotAIStrategy());
```

</tabs-item>

<tabs-item label="Custom prompt" value="prompt">

```php
use JohannSchopplich\ContentTranslator\Translation\Strategies\CopilotAIStrategy;

return [
    'johannschopplich.content-translator' => [
        'strategy' => new CopilotAIStrategy(
            systemPrompt: 'You are a medical translator. Preserve clinical terminology and abbreviations.',
        ),
    ],
];
```

</tabs-item>
</tabs>

<callout color="info" icon="i-ri-arrow-right-line" to="/docs/content-translator/providers/ai-translation#custom-system-prompt">

For the full default system prompt and a copy-paste-safe customization template, see the **AI Translation** docs.

</callout>

## Drop Reasons

Each dropped unit emits a `content-translator.translate:warning` event with one of these reasons:

<table>
<thead>
  <tr>
    <th>
      Reason
    </th>
    
    <th>
      Cause
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        <upstream error message>
      </code>
    </td>
    
    <td>
      Upstream provider call threw (rate limit, network, auth)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        response length mismatch
      </code>
    </td>
    
    <td>
      The AI returned more or fewer translations than units sent
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        empty or non-string translation
      </code>
    </td>
    
    <td>
      An empty string or non-string value appeared in the response array
    </td>
  </tr>
</tbody>
</table>

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

Wire `content-translator.translate:warning` to logging or alerting to surface drops in production.

</callout>

---

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