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

Api Platform Stream Translate Bundle Laravel Package

johnkhansrc/api-platform-stream-translate-bundle

A Symfony bundle for API Platform that integrates stream-based translation, helping expose translated API resources and content through configurable workflows. A lightweight option for adding translation support to API Platform projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is a small, single-purpose extension: it lets you mark entity properties with an annotation/attribute so their values are translated on the way out through API Platform's serializer. Treat the README as a starting hypothesis rather than documentation — at 0 stars and a very small user base, you are effectively the first real user.

Current version: 3.2.0, a maintenance/bugfix-line release whose only changelog entry is "Chore/upgrade symfony". That matters more than it sounds: it means the maintainer is alive and the Symfony dependency floor has moved, but it also means the changelog tells you nothing about which Symfony versions. Read composer.json in vendor/ before you commit.

Minimum steps

composer require johnkhansrc/api-platform-stream-translate-bundle

If Flex doesn't auto-register it (very likely, given the low install count), add it manually:

// config/bundles.php
return [
    // ...
    Johnkhansrc\ApiPlatformStreamTranslateBundle\ApiPlatformStreamTranslateBundle::class => ['all' => true],
];

Then verify what the bundle actually ships before writing code:

  1. vendor/johnkhansrc/api-platform-stream-translate-bundle/README.md — usually the only doc.
  2. src/Attribute/ (or src/Annotation/) — this is where the attribute class lives and where the real contract is. Read its constructor signature: it will tell you what the translation domain, key prefix, and locale args are named.
  3. src/DependencyInjection/ — check whether a configuration tree exists at all. Many tiny bundles have zero config, which means the attribute arguments are the entire API surface.
  4. composer.json — read the require block for the supported api-platform/*, symfony/translation, and PHP versions. Because 3.2.0 changed the Symfony constraint, this is now the single most important file to read: it is the only place the upgrade is documented.

First use case

The canonical flow: an entity property holds a translation key (or a raw string), and the bundle replaces it with the translated value in the JSON response.

use ApiPlatform\Metadata\ApiResource;
use Johnkhansrc\ApiPlatformStreamTranslateBundle\Attribute\Translate;

#[ApiResource]
class Product
{
    #[Translate(domain: 'product')]
    public string $status = 'in_stock'; // catalogue key in messages.product.en.yaml
}
curl -H 'Accept-Language: fr' https://api.example.com/api/products/1
# expect: {"status": "En stock"}

If that works, the bundle is doing what it claims. If not, move straight to the Gotchas section — the failure mode is almost always the attribute name or argument names differ from what you guessed.

Implementation Patterns

Attribute on the property, translation in the catalogue. The intended pattern is to keep the stored value as a stable key and let Symfony's translator resolve it per request locale. This keeps your database locale-agnostic, which is the main reason to use the bundle over manual normalization.

# translations/product.fr.yaml
in_stock: En stock
out_of_stock: Rupture

Locale resolution. The bundle almost certainly reads the locale from the Request (RequestStack) or from TranslatorInterface::getLocale(). The latter is set by Symfony's LocaleListener from _locale / Accept-Language, so both usually agree. If you support a user-preferred locale override, set it in a kernel request listener before the serializer runs — do not try to pass a locale through the attribute. Since 3.2.0 bumped Symfony, re-verify this assumption after upgrading: LocaleListener behaviour and translator wiring are exactly the kind of thing a Symfony upgrade touches.

Combine with serialization groups, not instead of them. The attribute is a value-level transform, not a visibility mechanism. Keep using #[Groups] for field exposure and let #[Translate] only handle the value. Mixing the two concerns leads to "why is this field translated but missing" debugging sessions.

Collection endpoints. This is where the bundle earns its keep versus a custom normalizer: GET /api/products will translate every item's marked properties without extra work, because the transform runs per normalized object. Confirm this on a collection early — a bundle that only hooks into item normalization is a much smaller win.

Deduplicating logic with a fallback custom normalizer. If you need a translated value that also embeds other data (e.g. "in_stock (3 left)"), don't fight the attribute. Decorate the normalizer instead:

final class ProductNormalizer implements NormalizerInterface
{
    public function __construct(
        private NormalizerInterface $decorated,
        private TranslatorInterface $translator,
    ) {}

    public function normalize(mixed $object, ?string $format = null, array $context = []): array
    {
        $data = $this->decorated->normalize($object, $format, $context);
        $data['stockLabel'] = sprintf(
            '%s (%d)',
            $this->translator->trans('in_stock', [], 'product'),
            $object->stock
        );
        return $data;
    }
}

Register it with decorates: on the inner ApiPlatform normalizer and a priority above the bundle's own normalizer so both run.

Write path caution. The bundle's stated purpose is the output flow. Do not assume denormalization translates incoming payloads back into keys — if you accept "En stock" on POST/PUT, you will store the French string and break every other locale. Normalize input on the client side (send keys) or add an explicit mapping listener.

Upgrading from 3.1.x. The 3.2.0 release is a dependency chore, not a feature release. Treat it as a routine composer update with a smoke test: pin ^3.2 explicitly, run your translation functional tests (item and collection, at least two locales), and check debug:container --tag=serializer.normalizer still lists the bundle's normalizer afterwards. A Symfony minor-to-minor bump is usually safe; a Symfony major bump inside a "chore" commit is not, and the changelog won't tell you which one happened.

Where it fits in a larger stack. If you already run a first-class translation layer (e.g. symfony/translation + a custom TranslatableNormalizer, or a full i18n bundle with per-locale DB columns), this bundle is redundant. Its value is strictly "I have keys in properties and want them translated with one annotation and zero boilerplate."

Gotchas and Tips

Verify compatibility before adopting, and again after every upgrade. 3.2.0 upgraded the Symfony side, which is good news for stack compatibility, but the project still predates API Platform 4 and sits across the API Platform 3 serializer changes. Check composer.json constraints and, more importantly, run one integration test against your exact api-platform/core version. A bundle of this size is unlikely to have CI covering multiple API Platform majors, and "upgrade symfony" in a changelog is not a statement about API Platform support.

The attribute name and arguments are the entire API. There is no meaningful documentation surface here. Read the attribute class source first; guessing #[Translate] vs #[Translatable] or domain: vs translationDomain: will cost you an hour of silent no-ops. A decorator that fails to match does not throw — it just doesn't translate.

Silent failure is the default. If a property value doesn't match a catalogue key, Symfony's translator returns the key unchanged. Your API responds 200 with "in_stock" and nothing looks broken. Add an assertion in a functional test:

$this->assertSame('En stock', $response->toArray()['status']);

and run it with the locale explicitly set, not inherited from the test environment.

Translation cache warmup. Values are resolved through Symfony's catalogue cache. In production with cache:clear/cache:warmup in the deploy, new translation files are picked up; in a long-running worker (RoadRunner, Swoole, Messenger), the translator is memoized and stale catalogues persist until restart. If translations "don't update" right after upgrading to 3.2.0, restart the worker before blaming the bundle — a Symfony upgrade frequently invalidates the compiled container/catalogue in ways a long-running process won't notice on its own.

Null, empty, and non-string properties. The plugin presumably calls trans() on the property value. If a property is nullable, an int, or an enum, translation will either coerce it to a string or blow up. Guard by only annotating properties you know are string keys, or check the bundle's handling in src/ — many small bundles skip this check entirely.

Locale and Accept-Language parsing. Symfony's LocaleListener picks the first accepted language it can satisfy, which may not be what your clients expect with quality weights (fr;q=0.9,en;q=0.8). If locale selection matters, implement a request listener that sets the locale deterministically and let the bundle read it.

Debugging technique. Since the transform is a normalizer/event listener, bin/console debug:event-dispatcher and bin/console debug:container --tag=serializer.normalizer will reveal whether the bundle registered anything at all. If nothing appears, the bundle isn't enabled or its compiler pass didn't fire — the problem is installation, not usage. This is also your first check after a version bump.

Extension points if the bundle falls short. The realistic escape hatches, in order of preference: (1) decorate the Symfony translator to customize key resolution; (2) decorate the API Platform normalizer to post-process output; (3) drop the bundle and write a 40-line normalizer of your own. Given the project's maturity level, option 3 remains a legitimate outcome — the bundle is small enough that vendoring its logic into your app removes a very-low-traffic dependency from your composer.json.

Maintenance risk — revised, slightly better. The earlier "single release, December 2023" reading is out of date: 3.2.0 implies a version line (it supersedes 3.1.3), and the maintainer shipped a dependency upgrade, which is the main signal you want from a small bundle. There is still no dependents list and no visible CI, so the risk is reduced but not gone. Pin the version (^3.2 at minimum, or an exact 3.2.0 if you are cautious), keep a translation functional test in your suite so an upgrade can't silently no-op, and if the bundle becomes load-bearing in a production API, consider forking it into a namespace you control so a future PHP or API Platform upgrade doesn't block you.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect