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

Jstranslations Bundle Laravel Package

funddy/jstranslations-bundle

A Symfony bundle that exposes application translations to JavaScript, letting front-end code reuse the same translation catalog as server-side templates. It helps keep client-side and server-side messages in sync with minimal setup and configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: funddy/jstranslations-bundle is a Symfony bundle, not a Laravel package. It registers itself through Symfony's AppKernel/DI container and ships a Bundle class, a DI extension, and (typically) a console command or controller that dumps the Symfony translator catalogue into JavaScript. Running composer require funddy/jstranslations-bundle inside a Laravel app gets you files in vendor/, not a working feature — there is no service provider, no facade, no config publish. With 8 stars, 0 dependents, and a nearly decade-old code base, treat it as a reference implementation of a pattern, not a dependency you can install and forget.

What v2.0.3 changes: the release note is a single line — "Update Translator interface to lastest Symfony version". In practice that means the bundle's shipped JavaScript translator now mirrors the modern Symfony translator contract: trans(id, parameters, domain, locale) with pluralisation driven by a %count% parameter, and transChoice() dropped. It is a breaking change for any client-side JS written against the old interface, and it does nothing to make the package Laravel-compatible — no service provider, no facade, no config. Your install decision is unchanged; only the shape of the helper you copy from it has moved.

If you are working in a legacy Symfony app that already uses it, look at these three things first:

  1. The bundle class (Funddy\JsTranslationsBundle\...Bundle) and how it is registered in app/AppKernel.php.
  2. The DI extension / services.xml — this tells you whether translations are exposed via a route, a dumped asset, or both.
  3. The shipped JavaScript runtime — as of v2.0.3 it defines a trans(id, params, domain, locale) global and no longer exposes transChoice; check how placeholders, %count%, and domains are resolved on the client.

Your first Laravel use case is the same job the bundle does: make lang/{locale}/*.php available to front-end code without an AJAX round-trip per key. The shortest path is a small Artisan command that flattens the catalogue and writes a JS/JSON file consumed by Vite or a <script> tag.

// app/Console/Commands/DumpJsTranslations.php
public function handle(): int
{
    $locale = config('app.locale');
    $lines = collect(glob(lang_path("{$locale}/*.php")))
        ->flatMap(fn ($file) => [
            basename($file, '.php') => require $file, // "messages" => [...]
        ])
        ->all();

    file_put_contents(
        public_path("js/translations.{$locale}.js"),
        'window.__translations = '.json_encode($lines, JSON_UNESCAPED_UNICODE).';'
    );

    return self::SUCCESS;
}

Call it from your deploy script (php artisan translations:dump) and include the file before your app bundle.

Implementation Patterns

Two delivery models, pick one deliberately.

  • Build-time dump (what the Symfony bundle favours for production): the catalogue is compiled once per deploy into a static JS/JSON asset, cached by the browser, zero runtime PHP cost. Best when your key set is large and changes only on release.
  • Runtime endpoint: a route returns the catalogue for the negotiated locale. Simpler to keep in sync during development, but it adds a request and you must handle caching headers yourself. A Laravel equivalent is a controller returning response()->json($catalogue)->header('Cache-Control', 'public, max-age=3600'), loaded before first render.

Keep the JS call signature close to the PHP one — and to v2.0.3's shape. The bundle's whole value is that the client helper feels like $translator->trans('key', ['%name%' => 'x'], 'messages'). The new release aligns that interface with current Symfony, so if you are porting or copying the helper, adopt the four-argument form rather than the old two-argument shortcut:

// mirrors v2.0.3: trans(id, params = {}, domain = 'messages', locale = null)
window.app = window.app || {};
window.app.trans = (id, params = {}, domain = 'messages', locale = null) => {
  const catalogue = locale ? window.__translations?.[locale] : window.__translations;
  let raw = catalogue?.[domain]?.[id] ?? id;

  // pluralisation: %count% in params selects the form, Symfony-style
  if ('%count%' in params) {
    const forms = raw.split('|');
    const n = Number(params['%count%']);
    raw = forms.length === 1 ? forms[0] : (n === 1 ? forms[0] : forms[1]);
  }

  return raw.replace(/%(\w+)%/g, (_, k) => params[k] ?? `%${k}%`)
            .replace(/:(\w+)/g, (_, k) => params[k] ?? `:${k}`);
};

Note the placeholder gap: Symfony uses %name%, Laravel uses :name. If you port JS written against the bundle, either normalise keys at dump time or accept both as above. Half-migrated placeholders that silently render as literal %name% (or :name) is the single most common regression in this kind of port — and v2.0.3 makes it more likely, because copy-pasted snippets from older blog posts will still call transChoice() and now simply throw TypeError: transChoice is not a function.

Group by domain, not by one giant blob. The bundle maps Symfony's domain concept onto a nested object. Mirror that with Laravel's file-per-group convention (messages.php, validation.php, auth.php) so a component can ship only the groups it needs. For large SPAs, dump one file per domain and lazy-load with dynamic import().

Interpolate after escaping. Build the string on the server or in the helper, then let the view layer decide whether it lands in textContent or innerHTML. Never concatenate raw user input into a translation string and inject it as HTML — translated strings are trusted, the parameters usually are not.

Locale handling. Resolve the locale from app()->getLocale() (or the request's Accept-Language if you set it that way) and generate only that locale's bundle. The new optional locale argument on the client interface is for multi-locale pages and tests — it is not a licence to ship every locale to every visitor, which remains the most common performance mistake in hand-rolled ports of this pattern.

Gotchas and Tips

  • Framework incompatibility is still the headline. A bundle first published in 2015 will pin Symfony 2 components and PHP ~5.x constraints, and a "latest Symfony interface" bump inside that lineage means Symfony 2.7/2.8-era semantics, not Symfony 6/7. Laravel already depends on symfony/* packages at far newer majors, so Composer will either refuse to resolve or drag in a conflicting tree. Do not try to force it with --ignore-platform-reqs; you will break your framework.
  • v2.0.3 is a breaking change on the JS side. transChoice() is gone; pluralisation now goes through trans(id, {'%count%': n}, domain). Grep your front-end for transChoice before you copy the new runtime in, and note the interface change is not covered by any PHP-side deprecation shim — it fails at call time, in the browser.
  • No Laravel integration points. No service provider, no facade, no publishable config, no Blade directive. Anything resembling @trans or Translator in your views has to be written by you. v2.0.3 adds none of this.
  • Global namespace collisions. The bundle exposes a browser-global translator (commonly Translator). That name is generic enough to clash with other libraries. Namespace your own helper (window.app.trans) rather than copying the global — the release does not namespace it for you.
  • Stale dumps are invisible failures. A translation added to lang/en/messages.php but not re-dumped simply falls back to the raw key on the client, with no error. Add the dump command to your deploy pipeline and, in local dev, either re-run it on file change or use the runtime-endpoint mode instead.
  • Missing-key behaviour differs. Symfony's translator returns the key (or the domain-qualified key) when a message is absent; your port must match, or you will get undefined rendering as an empty string in the UI and spend an afternoon on it.
  • Unicode and encoding. Always pass JSON_UNESCAPED_UNICODE (and JSON_UNESCAPED_SLASHES if your strings contain URLs). Without it, non-Latin locales arrive as \uXXXX escapes — functionally fine, but painful to debug in the network tab.
  • Pluralisation does not survive the port for free. Symfony and Laravel both support plural forms, but the client-side helper must implement the same selection rule — and must agree with whatever %count% convention v2.0.3 expects. If you use ICU/MessageFormatter on the server, a naive replace() or a two-form | split will silently produce the wrong plural form for locales with three or more forms (Polish, Russian, Arabic).
  • Caching. If you serve the catalogue from a route, remember Laravel's config/route caches do not touch your generated JS. Version the filename (translations.en.abc123.js) or append ?v=filemtime(...) so browsers do not serve last release's strings — doubly important now that a dependency bump can change the runtime contract under an unchanged URL.
  • Extension point worth copying: the build-time dump command is the right seam. It is where you can add key-linting (fail the build on duplicate keys), exclude internal/admin domains from the public bundle, and emit a manifest for cache-busting — none of which the original bundle gives you, and none of which v2.0.3 addresses, but all of which fit naturally into the Laravel command you write in its place.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core