Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Translator Laravel Package

laravel-lang/translator

laravel-lang/translator is a Laravel package for streamlining translation of language files. It provides tooling to generate and manage locale strings, helping maintain multilingual applications and keep translations consistent across supported languages.

View on GitHub
Deep Wiki
Context7

Getting Started

This is an alpha package (1.0-ALPHA6), so "getting started" means reading the source before reading the docs — the README is thin, and the release notes for this version only advertise Laravel 12 / dependency hygiene.

1. Install (pinned, alpha stability)

composer require laravel-lang/translator:"1.0.0-alpha6" --with-all-dependencies

If Composer refuses the constraint, add "minimum-stability": "alpha" plus "prefer-stable": true in composer.json rather than globally loosening stability. Pin the exact version — alpha releases in this repo have already churned illuminate/*, pestphp/pest, and symfony/var-dumper constraints between tags, and there is no documented upgrade path.

2. Find the config and the contract first

Before writing any code, look at these three things in the vendor directory:

  • config/translator.php — the driver list, default driver, and (likely) source/target locale defaults.
  • src/Contracts/ — the translator interface; this tells you what every driver returns and how errors surface.
  • src/Drivers/ — which providers actually exist (Google / DeepL / Yandex / Bing-style HTTP drivers) and what credentials each expects.

Publish the config if the package registers a publish tag:

php artisan vendor:publish --tag=translator-config
php artisan list | grep -i translat   # confirm the exact artisan signature, if one ships

3. Credentials in .env, not in config

TRANSLATOR_DRIVER=google
TRANSLATOR_GOOGLE_KEY=xxxxxxxx
TRANSLATOR_SOURCE_LOCALE=en
TRANSLATOR_TIMEOUT=15

Keep the API key out of config/translator.php and reference it via env() in the config file so config:cache behaves correctly.

First use case: pick one low-risk locale (e.g. de) and one file (lang/en/auth.php), translate it, and diff the result against the English source by hand. Do this before wiring anything into a pipeline — it is the fastest way to learn whether placeholders and plural pipes survive the round trip.

Implementation Patterns

Direct service use for one-off strings

Resolve from the container so the configured driver is respected; don't new a driver class directly or you bypass config and HTTP client binding.

use LaravelLang\Translator\Contracts\Translator;

$translator = app(Translator::class);

$de = $translator->translate('The :attribute field is required.', 'de', 'en');

Exact method names vary by alpha — confirm against src/Contracts/Translator.php before committing code.

File-level workflow (the day-to-day pattern)

Treat translation as a diff-generating operation, not an in-place mutation:

$source = require lang_path('en/validation.php');   // nested arrays
$target = file_exists(lang_path("{$locale}/validation.php"))
    ? require lang_path("{$locale}/validation.php")
    : [];

foreach (Arr::dot($source) as $key => $value) {
    if (blank($target[$key] ?? null)) {              // only fill gaps
        $target[$key] = $translate($value);
    }
}

file_put_contents(
    lang_path("{$locale}/validation.php"),
    '<?php return '.var_export($target, true).';'
);

Key properties of this pattern:

  • Keys are never translated. Only values cross the wire. This is the whole point — Laravel lookups break otherwise.
  • Idempotent: re-running only fills blanks, so CI diffs stay small and reviewable.
  • Version-control friendly: one commit per locale keeps PR review tractable and lets a human veto bad machine output per-locale.

Protect placeholders before the call

Machine translators will happily localise :attribute into something unusable. Wrap substitution tokens in an opaque sentinel before sending and restore afterwards:

$guarded = preg_replace_callback('/:[a-z_]+/i', function ($m) use (&$map) {
    $token = '__PH'.count($map).'__';
    $map[$token] = $m[0];
    return $token;
}, $value);

$translated = strtr($translate($guarded), $map);

abort_if(
    preg_match_all('/:[a-z_]+/i', $value) !== preg_match_all('/:[a-z_]+/i', $translated),
    500, 'Placeholder count mismatch'
);

Same idea for {name}, %s, @, and HTML tags (<a href=":url">).

Split plural variants before translating

Laravel's trans_choice uses | separators, and locales need different segment counts (2 for en/de, 3 for ru/pl, 6 for ar). Never send "one apple|many apples" as a single sentence.

$variants = explode('|', $value);
$translated = implode('|', array_map($translate, $variants));

Then assert substr_count($result, '|') === substr_count($value, '|').

Queue it for anything bigger than a few hundred keys

final class TranslateLocaleFile implements ShouldQueue
{
    use Queueable;

    public int $tries = 3;
    public int $backoff = 30;
    public int $timeout = 120;

    public function __construct(
        public string $locale,
        public string $file,
    ) {}

    public function handle(): void { /* per-file loop above */ }
}

Provider APIs are rate-limited and billed per character. One job per file (not per locale, not per string) gives you natural back-pressure and a resumable unit of work.

Cache aggressively

$key = 'tr:'.md5($driver.$from.$to.$source);
$result = Cache::remember($key, now()->addMonth(), fn () => $translator->translate(...));

This is the single biggest cost lever: validation.php and auth.php change rarely, and re-running a whole-locale backfill should be nearly free after the first pass.

Fake the translator in tests

Bind a stub implementation of the contract in the test container that prefixes input (e.g. "de:$text") and leaves placeholders intact. Your tests then assert file structure and placeholder integrity, not translation quality — which is the only thing you can actually assert.

Gotchas and Tips

Alpha means the API moves. There is no stable 1.x, no documented BC policy between alpha tags, and the last release notes are entirely dependency updates. Pin the exact version, commit the vendor diff review to your PR checklist, and keep the package behind your own thin adapter class so a rename in src/ costs you one file, not fifty.

Laravel 12 is the only signal in this release. illuminate/* constraints were bumped for Laravel 12 and Pest/symfony/var-dumper were refreshed. If you are on Laravel 10/11, prove resolution with composer why-not laravel-lang/translator before assuming backwards compatibility — the release notes do not claim it.

config:cache bites you on credentials. Changing TRANSLATOR_DRIVER or a key in .env does nothing until you php artisan config:cache (or config:clear) in cached environments. This is the #1 "why is it still using Google?" cause.

Locale code mismatch. Providers want pt-BR, zh-CN, pt_BR, or plain pt depending on the vendor, and Laravel locales are often pt_BR. Normalise explicitly in your adapter (str_replace('_', '-', $locale)) rather than letting the driver guess. A wrong code often returns the source text unchanged instead of throwing — silent failure.

Untranslated-but-successful responses. Several free/cheap endpoints return HTTP 200 with the input echoed back, or with the text truncated at a character limit. Assert $result !== $input for non-cognate locales and log a warning when they match, otherwise you commit English into lang/de/ and nobody notices.

JSON vs PHP lang files need different writers. For lang/de.json, encode with JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT; for PHP files, prefer var_export over hand-rolled string building so apostrophes and backslashes in translated text don't produce syntax errors. Always php -l each generated file in CI.

Ordering churn. Laravel's own lang files are alphabetically sorted; a naive Arr::dot round-trip can reshuffle keys and produce a 900-line diff for a 12-string change. Sort keys on write (ksort per level) to keep diffs reviewable.

Don't translate vendor/ lang files. Those are published by laravel-lang/publisher and overwritten on update. Target lang/ (or whatever lang_path() resolves to) only.

Timeouts and retries belong in the HTTP client. Set Http::timeout(15)->retry(3, 200) in your driver wrapper rather than around the whole job; a single hung request inside a 40-file loop will otherwise kill the run with no partial progress recorded.

Extension point: custom driver. If your provider (or an internal MT service) isn't in src/Drivers/, implement the package's translator contract and register the class in config/translator.php under the drivers map, then select it by name via TRANSLATOR_DRIVER. That is the intended extension seam and is far safer than forking.

Debug by logging the raw payload. Add a TRANSLATOR_DEBUG=true branch that logs {driver, from, to, source_hash, response_hash, http_status} — not the full text, to avoid leaking customer strings into logs. When a locale suddenly regresses, the driver + status tuple is usually enough to identify quota exhaustion versus a real API change.

Human review is not optional. Machine output should land on a branch, get diffed per locale, and be reviewed by someone who reads the target language — at minimum for auth, passwords, and validation, where a mistranslation changes security-facing UX. Treat the package as a first-draft generator, not an authoritative localiser.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource