wazza/dom-translate

A Laravel Package that will use the build-in Blade Directive to define specific phrases for auto-translation before being rendered to the screen.

Maintainers

Package info

github.com/wazzac/laravel-translate

Homepage

Issues

pkg:composer/wazza/dom-translate

Statistics

Installs: 939

Dependents: 0

Suggesters: 0

Stars: 4

v2.5.0 2026-05-27 11:34 UTC

README

Version Tests Laravel PHP License Issues Buy me a coffee

Laravel Translate Package

A Laravel package that uses Blade directives to provide real-time translations for any phrase in your views — backed by a smart caching layer (session → database → cloud API) to minimise API calls and costs.

Write your Blade templates in English, serve every visitor in their own language — automatically.

Table of Contents

How it Works

Three database tables (domt_languages, domt_phrases, domt_translations) power a three-level cache:

  1. Session (fastest) — if DOM_TRANSLATE_USE_SESSION=true, translations are stored in the user's session after the first retrieval.
  2. Database (fast) — if DOM_TRANSLATE_USE_DATABASE=true (default), translations are stored and retrieved from the DB. Each phrase is hashed (SHA-256 HMAC) and stored in an indexed column for sub-millisecond lookups.
  3. Cloud API (fallback) — only when no cached translation exists. The result is immediately stored in the DB (and session) to prevent future API calls.

Laravel caches compiled Blade views, so unchanged pages make zero database or API calls after the first render.

Requirements

Requirement Version
PHP ^8.2
Laravel ^12.0
GuzzleHTTP ^7.8 (auto-installed)
Cloud API key Google or Bing (at least one)

Installation

composer require wazza/dom-translate

Publish the config and migrations, then run the migrations:

php artisan vendor:publish --tag="dom-translate-config"
php artisan vendor:publish --tag="dom-translate-migrations"
php artisan migrate

Add your API key(s) to .env:

DOM_TRANSLATE_GOOGLE_KEY=your_google_key_here
# or
DOM_TRANSLATE_BING_KEY=your_bing_key_here
DOM_TRANSLATE_PROVIDER=bing

Clear config cache:

php artisan config:clear

Auto-discovery: The service provider is auto-discovered. No manual registration is needed in bootstrap/providers.php unless you have disabled package discovery.

Configuration

After publishing, the config file is at config/dom_translate.php. All settings are also controllable via .env:

# Caching layers
DOM_TRANSLATE_USE_SESSION=false         # Cache translations in the user's session
DOM_TRANSLATE_USE_DATABASE=true         # Cache translations in the database (recommended)

# Translation provider
DOM_TRANSLATE_PROVIDER=google           # 'google' or 'bing'
DOM_TRANSLATE_GOOGLE_KEY=               # Google Cloud Translation API v2 key
DOM_TRANSLATE_BING_KEY=                 # Azure Cognitive Services subscription key

# Languages
DOM_TRANSLATE_LANG_SRC=en              # Default source language (ISO 639-1)
DOM_TRANSLATE_LANG_DEST=af             # Default destination language (ISO 639-1)

# Phrase hashing
DOM_TRANSLATE_HASH_SALT=DzBQ2DxKhNaF  # Change this — used in HMAC hash
DOM_TRANSLATE_HASH_ALGO=sha256

# Logging (0=off, 1=high, 2=mid, 3=verbose)
DOM_TRANSLATE_LOG_LEVEL=1
DOM_TRANSLATE_LOG_INDICATOR=dom-translate

# Language preference routes
DOM_TRANSLATE_ROUTES_ENABLED=true
DOM_TRANSLATE_ROUTES_PREFIX=api/translate
DOM_TRANSLATE_ROUTES_MIDDLEWARE=web

# Session/cookie key for language preference
DOM_TRANSLATE_SESSION_KEY=app_language_code

# SetLocale middleware
DOM_TRANSLATE_MIDDLEWARE_ENABLED=true
DOM_TRANSLATE_MIDDLEWARE_AUTO_APPLY=true

Security note: Change DOM_TRANSLATE_HASH_SALT to a unique value per application. Do not reuse the default across projects.

Translation Providers

Google Cloud Translation Setup

  1. Visit Google Cloud Console and create a project.
  2. Enable Cloud Translation API under APIs & Services → Library.
  3. Create an API key under APIs & Services → Credentials.
  4. Restrict the key to Cloud Translation API to limit exposure.
  5. Add to .env:
    DOM_TRANSLATE_PROVIDER=google
    DOM_TRANSLATE_GOOGLE_KEY=your_key_here

Free tier: 500,000 characters/month. Billing account required even for free tier.

Test with curl:

curl -X POST \
  "https://www.googleapis.com/language/translate/v2?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q":"Hello world","target":"fr","source":"en"}'

Bing / Azure Cognitive Translator Setup

  1. Sign in to the Azure Portal.
  2. Create a Cognitive Services resource (or a dedicated Translator resource).
  3. Copy one of the subscription keys from Keys and Endpoint.
  4. Add to .env:
    DOM_TRANSLATE_PROVIDER=bing
    DOM_TRANSLATE_BING_KEY=your_azure_key_here

Free tier: 2,000,000 characters/month (F0 tier).

Blade Directives

@transl8(phrase[, dest[, src]])

The primary directive. Translates a phrase to the destination language.

{{-- Use config defaults for src/dest languages --}}
<p>@transl8("I like this feature.")</p>

{{-- Specify destination --}}
<p>@transl8("We need to test it in staging.", "de")</p>

{{-- Specify both source and destination --}}
<p>@transl8("Wie weet waar Willem Wouter woon?", "en", "af")</p>

Language-Specific Shortcuts

Pre-registered directives for common languages:

<p>@transl8fr("Translated to French")</p>
<p>@transl8de("Translated to German")</p>
<p>@transl8nl("Translated to Dutch")</p>
<p>@transl8es("Translated to Spanish")</p>
<p>@transl8it("Translated to Italian")</p>
<p>@transl8pt("Translated to Portuguese")</p>
<p>@transl8ru("Translated to Russian")</p>
<p>@transl8zhcn("Translated to Chinese Simplified")</p>
<p>@transl8zhtw("Translated to Chinese Traditional")</p>
<p>@transl8af("Translated to Afrikaans")</p>
<p>@transl8ar("Translated to Arabic")</p>

Adding more languages in your AppServiceProvider::register():

use Wazza\DomTranslate\Controllers\TranslateController;

Blade::directive('transl8ja', function ($string) {
    return "<?= app(" . TranslateController::class . "::class)->translate({$string}, 'ja', 'en'); ?>";
});

@transl8auto(phrase)

Translates to whatever language is stored in the user's session or cookie. See Auto-Translation below.

<h1>@transl8auto("Welcome to our website!")</h1>
<p>@transl8auto("This content adapts to the visitor's preferred language.")</p>

Auto-Translation (@transl8auto)

@transl8auto is the most powerful directive. It reads the user's language preference (set via a language switcher) and translates accordingly — no per-phrase language specification needed.

Language priority order

  1. Sessionsession('app_language_code')
  2. Cookiecookie('app_language_code')
  3. Config defaultdom_translate.language.dest
  4. App localeconfig('app.locale')
  5. English — final fallback

Building a language switcher

1. Add a selector to your layout:

<select id="languageSelector">
    <option value="en">English</option>
    <option value="fr">Français</option>
    <option value="de">Deutsch</option>
    <option value="es">Español</option>
    <option value="nl">Nederlands</option>
    <option value="af">Afrikaans</option>
</select>

2. Wire it up with JavaScript:

const selector = document.getElementById('languageSelector');

// Restore current preference on load
fetch('/api/translate/get-language')
    .then(r => r.json())
    .then(data => { selector.value = data.language; });

// Persist change and reload
selector.addEventListener('change', async function () {
    await fetch('/api/translate/set-language', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
        },
        body: JSON.stringify({ language: this.value })
    });
    window.location.reload();
});

Vue 3 Component Example

<template>
  <select v-model="language" @change="switchLanguage">
    <option value="en">English</option>
    <option value="fr">Français</option>
    <option value="de">Deutsch</option>
    <option value="es">Español</option>
  </select>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'

const language = ref('en')

onMounted(async () => {
    const { data } = await axios.get('/api/translate/get-language')
    language.value = data.language
})

async function switchLanguage() {
    await axios.post('/api/translate/set-language', { language: language.value })
    window.location.reload()
}
</script>

Language Preference API Routes

Enabled by default. Can be disabled with DOM_TRANSLATE_ROUTES_ENABLED=false.

POST /api/translate/set-language

POST /api/translate/set-language
Content-Type: application/json

{ "language": "fr" }

Response:

{
    "message": "Language preference set successfully.",
    "language": "fr"
}

Also sets a 1-year, httpOnly cookie with the preference.

GET /api/translate/get-language

GET /api/translate/get-language

Response:

{ "language": "fr" }

Disable routes and roll your own

DOM_TRANSLATE_ROUTES_ENABLED=false
use Wazza\DomTranslate\Helpers\TranslateHelper;

class LanguageController extends Controller
{
    public function set(Request $request)
    {
        $request->validate(['language' => 'required|string|min:2|max:5']);
        return TranslateHelper::setLanguage($request->input('language'));
    }

    public function get()
    {
        return TranslateHelper::getLanguage();
    }
}

SetLocale Middleware

SetLocaleMiddleware automatically syncs Laravel's application locale — and Carbon's locale — to the user's language preference on every request.

This ensures __(), trans(), @lang, validation messages, and Carbon date formatting all use the same language as the translate directives.

Enabled by default. To disable:

DOM_TRANSLATE_MIDDLEWARE_ENABLED=false

To keep the middleware but not auto-apply it to the web group:

DOM_TRANSLATE_MIDDLEWARE_AUTO_APPLY=false

Then apply it manually where needed:

// routes/web.php
Route::middleware(['web', 'dom-translate.locale'])->group(function () {
    // your routes
});

Using the Helper Directly

TranslateHelper and PhraseHelper are also available for use in controllers, jobs, or anywhere outside Blade:

use Wazza\DomTranslate\Helpers\TranslateHelper;
use Wazza\DomTranslate\Controllers\TranslateController;

// Translate directly
$translated = app(TranslateController::class)->translate('Hello world', 'fr', 'en');

// Auto-translate using current user's preference
$translated = TranslateHelper::autoTransl8('Hello world');

// Get current user's language code
$lang = TranslateHelper::currentDefinedLanguageCode();

// Set language preference (returns JsonResponse with cookie)
return TranslateHelper::setLanguage('de');

Upgrading to v2.5

If you are upgrading from v2.4.x:

  1. Re-publish migrations — the migration files have been updated to anonymous class syntax (required by Laravel 12). If you have already run the old migrations, no action is needed for existing databases. For fresh installs, re-publish:

    php artisan vendor:publish --tag="dom-translate-migrations" --force
    php artisan migrate
  2. No breaking changes — all Blade directives, config keys, and .env variables remain identical.

  3. BingTranslate is now implemented — if you set DOM_TRANSLATE_PROVIDER=bing in your .env, it will now work with the Azure Cognitive Translator v3 API.

  4. Cookie security — the language preference cookie is now httpOnly=true (XSS protection). This is transparent to your application.

  5. DOM_TRANSLATE_HASH_SALT — if you are using a custom salt, ensure it remains set in your .env to preserve existing hash lookups in the database.

Troubleshooting

E_STRICT deprecation warnings during composer install

Deprecation Notice: Constant E_STRICT is deprecated in /usr/share/php/Composer/Util/Silencer.php

This is not caused by this package. It is caused by an outdated system-level Composer installation (typically /usr/share/php/Composer/). Update your server's Composer:

sudo composer self-update
# or, on Debian/Ubuntu:
sudo apt-get install --only-upgrade composer

Translations not showing / returning original phrase

  1. Check your API key is set: DOM_TRANSLATE_GOOGLE_KEY or DOM_TRANSLATE_BING_KEY.
  2. Check DOM_TRANSLATE_LOG_LEVEL=3 and review Laravel's log for API errors.
  3. Ensure the destination language code (DOM_TRANSLATE_LANG_DEST) exists in the domt_languages table.
  4. Clear config and view cache:
    php artisan config:clear
    php artisan view:clear

MissingAppKeyException in tests

Ensure APP_KEY is set in phpunit.xml or your .env.testing. The package's test suite sets a dummy key automatically for its own tests.

Language not persisting between requests

Ensure DOM_TRANSLATE_USE_SESSION=true or that cookies are being sent with requests. For AJAX requests, verify the X-CSRF-TOKEN header is included in POST calls.

Contributing

Contributions are welcome. Please read CONTRIBUTING.md before opening a pull request. For security vulnerabilities, see SECURITY.md.

Made with ☕ by Warren Coetzee