PHPackages                             klickmanufaktur/statamic-translator - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Localization &amp; i18n](/categories/localization)
4. /
5. klickmanufaktur/statamic-translator

ActiveStatamic-addon[Localization &amp; i18n](/categories/localization)

klickmanufaktur/statamic-translator
===================================

Provider-agnostic automatic translations and translated URLs for Statamic sites.

v1.0.4(1mo ago)00proprietaryPHPPHP ^8.3

Since May 29Pushed 1mo agoCompare

[ Source](https://github.com/klickmanufaktur/statamic-translator)[ Packagist](https://packagist.org/packages/klickmanufaktur/statamic-translator)[ RSS](/packages/klickmanufaktur-statamic-translator/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (3)Versions (6)Used By (0)

Statamic Translator
===================

[](#statamic-translator)

Provider-independent automatic translations for Statamic without Multisite. The addon creates locale-specific URLs such as `/en/service/lifesituations`, translates rendered HTML at runtime, rewrites internal links, and provides tags for frontend language switchers.

Installation
------------

[](#installation)

Install the addon from Packagist:

```
composer require klickmanufaktur/statamic-translator
php artisan migrate
```

Optionally publish the configuration into the project:

```
php artisan vendor:publish --tag=statamic-translator-config
```

If the package is not auto-discovered in your Statamic project, add the service provider manually to `bootstrap/providers.php` in Laravel 11 or newer:

```
return [
    App\Providers\AppServiceProvider::class,
    Klickmanufaktur\StatamicTranslator\ServiceProvider::class,
];
```

For older Laravel versions, register the provider in the `providers` array of `config/app.php`.

### Local Development Installation

[](#local-development-installation)

When working on the addon locally, add it as a Composer path repository in your project instead:

```
{
  "repositories": [
    {
      "type": "path",
      "url": "../_packages/statamic-translator",
      "options": {
        "symlink": true
      }
    }
  ],
  "require": {
    "klickmanufaktur/statamic-translator": "*"
  }
}
```

Then install or update the local package:

```
composer update klickmanufaktur/statamic-translator --with-dependencies --minimal-changes
```

Configuration
-------------

[](#configuration)

Languages are configured in `config/statamic-translator.php`.

```
'source' => [
    'locale' => 'de',
    'name' => 'Deutsch',
    'label' => 'DE',
    'switcher_name' => 'German',
    'native_name' => 'Deutsch',
    'translation_notice' => [
        'title' => 'Hinweis zur automatisierten Übersetzung',
        'body' => 'Die ausgewählte Sprachfassung wird automatisiert erstellt. Trotz sorgfältiger technischer Umsetzung können Fehler, Auslassungen oder Abweichungen auftreten. Maßgeblich und rechtlich verbindlich ist ausschließlich die deutsche Originalfassung.',
        'cancel_label' => 'Abbrechen',
        'confirm_label' => 'Verstanden, fortfahren',
    ],
],

'locales' => [
    'en' => [
        'name' => 'English',
        'label' => 'EN',
        'switcher_name' => 'English',
        'native_name' => 'English',
        'deepl_target' => 'EN-US',
        'translation_notice' => [
            'title' => 'Notice about automatic translation',
            'body' => 'The selected language version is generated automatically. Despite careful technical implementation, errors, omissions or deviations may occur. Only the German original version is authoritative and legally binding.',
            'cancel_label' => 'Cancel',
            'confirm_label' => 'I understand, continue',
        ],
    ],
    'fr' => [
        'name' => 'Français',
        'label' => 'FR',
        'switcher_name' => 'French',
        'native_name' => 'français',
        'deepl_target' => 'FR',
        'translation_notice' => [
            'title' => 'Remarque concernant la traduction automatique',
            'body' => 'La version linguistique sélectionnée est générée automatiquement. Malgré une mise en œuvre technique soignée, des erreurs, omissions ou divergences peuvent survenir. Seule la version originale allemande fait foi et est juridiquement contraignante.',
            'cancel_label' => 'Annuler',
            'confirm_label' => 'J’ai compris, continuer',
        ],
    ],
],
```

`switcher_name` is the highlighted language name in the switcher. `native_name` is the language name in that language. `translation_notice` is emitted per target language and should therefore be maintained in the target language.

Enable DeepL through `.env`:

```
STATAMIC_TRANSLATOR_ENABLED=true
DEEPL_API_KEY=your-key
STATAMIC_TRANSLATOR_PROVIDER=deepl
STATAMIC_TRANSLATOR_SOURCE_LOCALE=de
STATAMIC_TRANSLATOR_FALLBACK_TO_SOURCE_ON_ERROR=true
```

Set `STATAMIC_TRANSLATOR_ENABLED=false` to disable the translator completely. Locale routes return 404, HTML translation is skipped, URL generation and prewarming commands stop early, and `translator:enabled` returns `false`. This can be used to hide the frontend language switcher. If Statamic Static Caching is enabled, also run `php please static:clear` when disabling the translator so previously generated static locale pages are no longer served.

`STATAMIC_TRANSLATOR_FALLBACK_TO_SOURCE_ON_ERROR=true` prevents locale URLs from failing with a 500 response when the provider or API key is unavailable. The source-language page is rendered and the error is logged.

The DeepL endpoint is selected automatically. API keys ending in `:fx` use `https://api-free.deepl.com`; all other keys use `https://api.deepl.com`. You can override the endpoint explicitly:

```
DEEPL_API_URL=https://api.deepl.com/v2/translate
```

Authentication uses DeepL's `Authorization: DeepL-Auth-Key ...` header. The legacy `auth_key` request-body parameter is not used.

Text translations are sent as JSON:

```
{
  "text": ["Hallo Welt"],
  "target_lang": "EN-US",
  "source_lang": "DE",
  "preserve_formatting": true
}
```

Requests are split automatically according to DeepL limits: at most 50 texts and at most 128 KiB request body size.

Slugs and Protected Terms
-------------------------

[](#slugs-and-protected-terms)

For stable or cheaper URLs, define slug translations manually. Segments in `preserve_segments` are never translated and are copied as-is, which is useful for place names.

```
'slugs' => [
    'preserve_segments' => [
        'service',
        'weiler',
        'haubersbronn',
    ],
    'overrides' => [
        'en' => [
            'lebenslagen' => 'lifesituations',
        ],
    ],
],
```

Proper nouns can be configured as protected terms. They are replaced with internal placeholders before the provider request and restored after translation. This keeps district names, person names, brand names, and similar terms unchanged in body text.

```
'protected_terms' => [
    'global' => [
        'Schorndorf',
        'Weiler',
        'Haubersbronn',
    ],
    'locales' => [
        'en' => [
            'Rems-Murr-Kreis',
        ],
    ],
],
```

Address fields and address-like texts are excluded centrally from translation. This affects cost estimates, prewarming, and frontend HTML translation.

```
'content' => [
    'ignored_keys' => [
        'address',
        'address_street',
        'address_city',
        'address_district',
        'street',
        'postal_code',
        'city',
        'ort',
        'district',
        'location',
    ],
    'ignored_text_patterns' => [
        '~\b\d{5}\s+[\pL][\pL\s.\'-]{1,80}\b~u',
        '~\b[\pL][\pL\s.\'-]*(?:straße|strasse|weg|platz|gasse|allee|ring)\s+\d+[a-z]?\b~iu',
    ],
],
```

Values such as `Burgstraße 67, Schorndorf` or `73614 Schorndorf` remain unchanged. For intentionally untranslated template sections, add `translate="no"` or `data-no-translate="true"`.

Switching Providers
-------------------

[](#switching-providers)

A new provider must implement `Klickmanufaktur\StatamicTranslator\Contracts\TranslationProvider`. Register it in the config:

```
'provider' => env('STATAMIC_TRANSLATOR_PROVIDER', 'custom'),

'providers' => [
    'custom' => [
        'class' => App\Translation\CustomTranslationProvider::class,
    ],
],
```

The rest of the addon stays unchanged because all translations go through the provider interface.

Commands
--------

[](#commands)

Estimate translation costs:

```
php artisan translator:estimate --locale=en
php artisan translator:estimate --collection=pages --locale=en
```

Generate translated URLs ahead of time:

```
php artisan translator:urls --locale=en
php artisan translator:urls --collection=pages --locale=en
```

Existing locale URLs are refreshed automatically by the frontend tag when `routing.refresh_existing_urls` is enabled. This corrects old fallback URLs once translated slugs are available. If a provider error occurs, no new fallback URL is stored; an existing link is only used as a fallback display value.

Internal links in translated HTML responses are rewritten to the active locale when a translated target URL exists. Missing link URLs are not generated through DeepL during normal page rendering by default, so a first page view does not trigger many slug requests.

To avoid bare default-language links, missing entry links are rendered as `/{locale}{source-url}`. The addon controller recognizes these fallback URLs, creates the canonical translated URL, and redirects with a 301. For SEO-clean links without this fallback step, run the bulk URL builder:

```
php artisan translator:urls --locale=fr
```

You may enable `routing.create_missing_link_urls` if needed, but this is only recommended for small page sets.

Prewarm text translations. Without `--execute`, the command is a dry run:

```
php artisan translator:warm --locale=en
php artisan translator:warm --locale=en --execute --max-chars=100000
```

Frontend Tags
-------------

[](#frontend-tags)

Current language:

```
{{ translator:locale }}
{{ translator:label }}
{{ translator:name }}
```

URL for a specific language:

```
English
```

Translate text directly in templates. This is useful for Statamic `nocache` fragments because they may be rendered after response-level HTML translation:

```
{{ translator:translate text="Zum Veranstaltungskalender" context="event:button" }}
{{ translator:translate :text="dynamic_label" context="event:button" }}
```

For translated HTML attributes, use the escaped variant:

```

```

Localize an internal source URL directly in templates:

```
Events
Events
```

Language list for a switcher:

```
{{ translator:languages }}
    {{ if available }}

            {{ name }} ({{ label }})

    {{ else }}
        {{ name }}
    {{ /if }}
{{ /translator:languages }}
```

Each item contains `locale`, `name`, `label`, `url`, `selected`, `is_current`, `is_source`, and `available`. It also exposes `switcher_name`, `native_name`, `notice_title`, `notice_body`, `notice_cancel_label`, and `notice_confirm_label` for language switchers and translation notices.

Alternate links for the document head:

```

    {{ seo }}
    {{ translator:alternate_links }}

```

This outputs `rel="alternate"` links for the source locale, `x-default`, and every configured translated locale. Add it to the base layout so the source-language pages expose the same `hreflang` links as translated addon routes.

Breadcrumbs on translated addon routes:

```
{{ if {translator:is_translated} }}
    {{ translator:breadcrumbs include_home="false" }}
        {{ title }}
    {{ /translator:breadcrumbs }}
{{ /if }}
```

The breadcrumb tag builds the path from the German source URI and resolves links for the active language. This is necessary because Statamic's `nav:breadcrumbs` does not see translated addon routes such as `/fr/...` as normal collection routes.

SEO
---

[](#seo)

The addon sets `lang`, `canonical`, and `hreflang` links on translated responses, translates relevant meta tags, and provides a sitemap per language at `/{locale}/sitemap.xml`. Use `{{ translator:alternate_links }}` in your base layout so source-language pages also output `hreflang` alternates. Internal links to known entries are rewritten to the matching locale URL.

Static Caching
--------------

[](#static-caching)

Locale routes use both `web` and `statamic.web` by default. This allows Statamic Static Caching to cache translated URLs. The first request to a locale URL creates the translated HTML response; Statamic can then cache that response per URL, for example separately for `/en/...` and `/fr/...`.

Recommended after content, slug, or config changes:

```
php artisan config:clear
php please static:clear
```

When using full static caching, the web server must be configured to serve Statamic's static files as usual. Previously cached locale pages remain available until the static cache is cleared or invalidated.

Cost Control
------------

[](#cost-control)

- Run `translator:estimate` before translating or prewarming content.
- Limit commands to the collections and locales you actually need.
- Keep `translator:warm` as a dry run until the estimated character count is acceptable.
- Use slug overrides for frequent or SEO-critical URL segments.
- Empty pages cause almost no text costs and will be translated later once content is added.

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance89

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

5

Last Release

56d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b80f38d3fb1038793a61dc319681b9e6ee954acefca113cf37781171e90beb7f?d=identicon)[klickmanufaktur](/maintainers/klickmanufaktur)

---

Top Contributors

[![klickmanufaktur](https://avatars.githubusercontent.com/u/30047394?v=4)](https://github.com/klickmanufaktur "klickmanufaktur (5 commits)")

### Embed Badge

![Health badge](/badges/klickmanufaktur-statamic-translator/health.svg)

```
[![Health](https://phpackages.com/badges/klickmanufaktur-statamic-translator/health.svg)](https://phpackages.com/packages/klickmanufaktur-statamic-translator)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45444.2k1](/packages/pressbooks-pressbooks)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

255.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
