akeneo/crowdin-api
A PHP client for the Crowdin API that makes it easy to manage translation projects, files, strings, and localizations from your application. Provides a clean, typed wrapper around Crowdin endpoints for automating localization workflows.
Architecture fit. Unchanged in shape: still a thin, standalone SDK at the boundary of the localization domain, still belongs behind a first-party TranslationProvider interface rather than being imported directly by controllers or jobs. The 2.0.0 change is a dependency substitution, not an architectural one — but it materially improves the fit with modern Laravel. Swapping Guzzle for symfony/http-client means the SDK now programs against HttpClientInterface and can accept an injected PSR-18/PSR-17 client rather than constructing its own handler stack. That is the single most useful thing this release could have done for observability, proxying, and testability, and it is a better fit for a Laravel app than a hard Guzzle coupling was.
Integration feasibility. Still low-friction on the happy path, with one caveat: symfony/http-client is not a default Laravel dependency (Laravel pulls symfony/console, http-foundation, process, etc., but not the HTTP client). It will be added to the tree for this package alone. That is a modest footprint and a well-maintained component, but it is a new transitive family to track. The real work remains workflow design — source extraction, project/file/branch mapping, pull cadence, write-back — and that has not changed.
Technical risk. The lifecycle risk profile has improved but not disappeared. A 2.0.0 after a ~two-year quiet period is evidence that the maintainer is still engaged and willing to make hard, breaking calls; "internal tool published for convenience" no longer reads as "abandoned." It is still one organizational maintainer (Akeneo) with incidental interest, zero dependents, and a small star count — you remain the primary owner of any defect. Two risks are retired by this release: the uncontrolled Guzzle constraint and the Guzzle-vs-Laravel HTTP client collision. Two risks are introduced or shifted:
symfony/http-client ^6, and the host app is on Laravel 11/12 (Symfony 7 components), you get a split-Symfony-major dependency tree. Distinct Symfony component packages can coexist across majors, but it is fragile and composer resolution may force downgrades elsewhere. Verify the constraint allows ^6.4|^7.0.symfony/http-client throws Symfony\Contracts\HttpClient\Exception\*, not GuzzleHttp\Exception\RequestException. Any error classification, 429 backoff, or retry logic written against Guzzle exception types is now wrong. Default timeout and redirect behavior also differ from Guzzle and must be set explicitly.A major version bump for what reads as a single dependency swap strongly implies the public constructor/type-hint surface changed (a HttpClientInterface parameter replacing a Guzzle client). Assume 1.x call sites break at instantiation, not at the call site. MIT license keeps the fork escape hatch open, which still de-risks all of the above.
Key questions for the maintainer/spike.
symfony/http-client constraint — does it permit ^7.0, or does it pin ^6 and therefore complicate Laravel 11/12 adoption?ClientInterface, or specifically HttpClientInterface? The former is more portable; the latter gives you Symfony's async/streaming.RetryableHttpClient (and is it configurable), or is retry still absent?MockHttpClient, or is it still untested against a live API?crowdin/crowdin-api-client-php today — the build-vs-adopt call is still open and still decides the engagement.Stack fit. Laravel 10/11/12 on PHP 8.1+ is the target; Symfony 6 requires PHP 8.1, so the floor is unchanged in practice. The package should still live behind a CrowdinServiceProvider binding a configured client singleton from config/crowdin.php/env, and you should now also bind the underlying HTTP client so you can decorate it — TraceableHttpClient for the profiler, ScopingHttpClient for base-URI and per-host timeouts, RetryableHttpClient for backoff. Because the client is injectable, you can register it in the container once and reuse the same connection pool and configuration across Crowdin calls.
Migration path. If you piloted or shipped 1.x, this is a contained-but-real upgrade:
HttpClientInterface (or a PSR-18 adapter) instead of a Guzzle client.HandlerStack middleware (logging, retry, proxy) and re-express it as Symfony decorators.catch (RequestException ...) into Symfony's exception contract, or wrap it once in a package-level exception and catch that everywhere.MockHandler-based tests with MockHttpClient; this is a net gain — MockHttpClient takes a callable and is easier to assert against than a handler queue.timeout and max_duration explicitly; do not inherit defaults across the swap.If you have not adopted either version yet, start directly on 2.0 — there is no reason to onboard onto a Guzzle-coupled 1.x.
Compatibility. Pin the exact 2.0.x version. Audit composer why symfony/http-client and confirm the resolved Symfony majors are coherent with the rest of the app; run composer update --dry-run against the full tree before merging. Check whether existing Guzzle-dependent packages (Socialite, Flysystem S3, mail transports) are unaffected — they will be, since Guzzle is no longer shared — and confirm nothing depended on the SDK transitively providing Guzzle.
Sequencing.
list projects) and one write (upload source file) with an injected HttpClientInterface — confirms the new wiring works.MockHttpClient so feature work isn't blocked on Crowdin credentials.lang/ or DB) — lowest blast radius.Maintenance. Still treat this as a vendored dependency. The 2.0.0 release is a genuine positive signal — someone is home — but the bus factor is one and the release cadence is unknown. Mitigations unchanged: fork into a private VCS or use a Composer patch plugin, keep the adapter interface thin, and calendar a quarterly check against both Crowdin's API changelog and the Symfony HTTP client's release notes. Add one new watch item: when Symfony 8 lands, this package's constraint becomes your upgrade blocker.
Support. Still community-only, best-effort, no SLA, no security disclosure process to rely on. One improvement worth noting: because the HTTP client is now injectable, you can fix transport-layer problems (timeouts, proxies, TLS, retries) yourself without forking the SDK. That shrinks the surface you'd need a maintainer for to the endpoint wrappers and DTOs.
Scaling. The SDK is stateless and not the bottleneck; Crowdin rate limits are. The new client does unlock something real: symfony/http-client supports concurrent requests via stream()/AsyncResponse, so a full-project translation pull no longer has to be strictly sequential. Use it behind a per-project rate limiter with jittered exponential backoff, and keep every Crowdin interaction inside a queued job — never in a request cycle.
Failure modes to instrument.
timeout and max_duration.composer validate --strict and a dry-run against the production lock.Ramp-up. Still low for a mid-level PHP engineer — the package is small enough to read in an afternoon. Add half a day for the Symfony HTTP client idiom: exception contracts, RetryableHttpClient, MockHttpClient, and async streams. Engineers fluent in Guzzle will find their instincts wrong in exactly the places that matter (error handling and retry), so budget that explicitly. The dominant learning curve remains Crowdin domain fluency — project/file/branch/namespace hierarchy, translation vs. approval state, TM behavior — and that still drives the integration timeline far more than the SDK does.
How can I help you explore Laravel packages today?