Laravel langsys/langsys-php-laravel

A thin Laravel binding over langsys/langsys-php: the @t Blade directive, the t() helper, and locale middleware. The phrase in your code is both the lookup key and the base-language default — no keys file, no extraction step. Translation happens on the server, so the page arrives already localized.

Backend SDK, so no in-browser playground on this page — the browser can't run PHP. The demo app is the live version of every example below: clone it and php artisan serve, or open it in a Codespace — a real PHP container that installs and serves it for you. Full package reference, including automatic page translation and Inertia seeding, lives in the Laravel SDK docs.

Using an AI coding agent? Give it this prompt
Add Langsys translation to this Laravel app.

1. Read https://docs.langsys.dev/llms.txt, then follow
   https://docs.langsys.dev/learn/sdk/laravel/ for the install and setup.
2. The English text in the code is the translation key. Wrap user-facing
   strings as they are: no keys file, no extraction step, and no
   hand-written plural or gender branches (Langsys generates those).
3. Read the Langsys project id and API key from environment variables.
   The write key is for development and servers only; anything shipped
   to the browser gets the read-only key.
4. Keep brand names and user-generated content out of translation.

Install

composer require langsys/langsys-php-laravel
php artisan vendor:publish --tag=langsys-config
.env
# The server can hold a WRITE key — unlike a browser app, it's never
# shipped to visitors. A write key is what lets new phrases register.
LANGSYS_API_KEY=your-api-key
LANGSYS_PROJECT_ID=your-project-id

No project or keys yet? The Quickstart gets you both in two minutes.

Middleware

Two pieces of middleware do the framework work: DetectLocale resolves the visitor's locale (query param, cookie, session, then Accept-Language) and persists their choice; FlushPendingRegistrations sends newly discovered phrases to Langsys after the response goes out, so registration never slows a page down.

View source ↗
// bootstrap/app.php (Laravel 11/12)
->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        // Resolves ?locale= / cookie / session / Accept-Language,
        // then persists the visitor's choice.
        \Langsys\Laravel\Http\Middleware\DetectLocale::class,
        // After the response is sent, registers any phrases
        // rendered for the first time.
        \Langsys\Laravel\Http\Middleware\FlushPendingRegistrations::class,
    ]);
})

Translate in Blade

One directive: @t(phrase, category?, params?, locale?). Params interpolate with {name} markers — numbers and dates come out locale-formatted — and the output is HTML-escaped.

@t — inline string, params included View source ↗
<h1>@t('Welcome to our store', 'Home')</h1>
<p>@t('Hello, {name}! You have {count} new messages.', 'Greetings',
    ['name' => $name, 'count' => $count])</p>

Anywhere in PHP

@t is just the t() helper with escaping — the helper itself works anywhere: controllers, Livewire components, jobs, mail, validation messages.

t() — the same seam, no Blade required View source ↗
// Controllers, Livewire components, jobs, mail — anywhere:
$title = t('Order confirmed', 'Checkout');
$body  = t('Your order {id} ships on {date}.', 'Checkout', [
    'id'   => (string) $order->number, // strings skip number formatting
    'date' => $order->ships_at,        // DateTime → locale-formatted
]);

Plurals

Watch · 1:14 Pluralization

Write the sentence flat: Langsys generates each locale's plural forms during translation — the base locale included — and the SDK renders the branch that matches your count (full ICU via ext-intl). Same in every SDK, so phrases stay portable between your backend and frontend.

View source ↗
// Written flat. Langsys generates each locale's plural forms during
// translation (the base locale included) and the SDK picks the branch.
t('You have {count} items in your cart', 'Cart', ['count' => $count]);

The plural forms Langsys generates reach PHP from langsys/langsys-php 1.3.2; earlier versions render the plural form for every count, including 1.

Gender agreement

Spanish, French, or Russian inflect {username} has been invited for the person it’s about. Langsys generates those branches during translation — the same way it generates plurals — and the argument is named after the variable, with _gender appended: male, female, or other. Pass it whenever you know it. How it works, and how to find the phrases that need it →

Passing the gender argument View source ↗
{{-- Pass the agreement argument alongside the value it inflects. --}}
@t('{username} has been invited', 'Team', [
    'username'        => $post->author->name,
    'username_gender' => $post->author->gender, // 'male' | 'female' | 'other'
])

Don’t know the gender? Pass 'other' explicitly — it’s the neutral branch. If it is missing anyway, the base SDK takes the other branch for you (langsys/langsys-php 1.3.1 and later, with or without ext-intl), so the sentence still reads correctly.

Categories

The category scopes a phrase, so the same words can translate differently in different contexts.

{{-- Same word, two meanings — the category disambiguates. --}}
<strong>@t('Home', 'Main Menu')</strong>      {{-- "Inicio" --}}
<strong>@t('Home', 'Home repairs')</strong>   {{-- "Casa" --}}

One marker style: {name}. In the JS SDKs, markup params use %name% because single braces collide with JSX and Vue template syntax. Blade has no such collision, so PHP-land uses the canonical {name} form everywhere — the same form t() uses in every SDK.

Which one do I use?

You’re translating… Use Why
Text in a Blade template. @t(...) The t() helper plus HTML escaping — safe for direct template output.
A string anywhere else — controllers, Livewire, jobs, mail. t() Returns a plain string with params interpolated and ICU plurals resolved.
Something that must never translate — brand names, code, IDs. leave it unwrapped Coverage is explicit: only strings you wrap are translated; untagged output passes through verbatim.
A whole HTML document you already have as a string. Langsys::client()->translatePage() Drops to the raw PHP SDK's full-page pipeline — title, meta, and body text in one call.

Rule of thumb: Blade → @t · PHP → t() · untouchable → don’t wrap it.

Token discovery on the server

Render a phrase that Langsys hasn't seen and it's queued, then registered after the response terminates — the visitor sees your base-language text immediately, and the phrase appears in your dashboard ready to translate. This needs a write key; on a read-only key the queue is skipped silently, which is exactly what you want in environments that shouldn't mutate the catalog.

Pairing with the JS SDKs

Running Inertia or a hybrid app? Hand the JS SDKs the catalog the server already fetched, so the client doesn't fetch it again — the same initialTranslations handoff the frontend SDK pages describe.

Server — share the catalog View source ↗
// app/Http/Middleware/HandleInertiaRequests.php
use Langsys\Laravel\Support\InertiaSsrProps;

public function share(Request $request): array
{
    return [...parent::share($request), ...InertiaSsrProps::share()];
}
Client — hydrate without a refetch
// resources/js — Vue shown; same shape for React/Svelte
import { LangsysApp, useLocaleStore } from 'langsys-js-vue';

const { store } = useLocaleStore(props.langsys.initialTranslationsLocale);
LangsysApp.init({
    projectid: import.meta.env.VITE_LANGSYS_PROJECT_ID,
    key: import.meta.env.VITE_LANGSYS_API_KEY, // read-only key on the client
    UserLocaleStore: store,
    initialTranslations: props.langsys.initialTranslations,
    initialTranslationsLocale: props.langsys.initialTranslationsLocale,
});

The facade & the raw client

The Langsys facade mirrors t(), and Langsys::client() hands you the underlying langsys/langsys-php client for everything beyond phrase lookup — catalog reads, cache control, content blocks.

View source ↗
use Langsys\Laravel\Facades\Langsys;

Langsys::translate('Save', 'UI');            // same seam as t()
Langsys::client()->getTranslations('es-es'); // the vanilla langsys/langsys-php client
Langsys::client()->clearCache('es-es');      // pick up dashboard edits before the TTL
Search the docs & Learning Center