Skip to content

LangFactory detects the language on every call, even when the adapter is cached #654

Open

Description

@armanist

Problem

Quantum\Lang\Factories\LangFactory::resolve() detects the language on every call, even when the adapter is already cached, and a few details around adapter creation are redundant.

  1. detectLanguage() runs on every resolve(), before the cache is checked. _t() and current_lang() go through LangFactory::get(), so every translation repeats the query-parameter, URL-segment and Accept-Language checks and then throws the result away: the detected language is only used when the adapter is first created. It can also throw LangException::misconfiguredDefaultConfig() when an adapter is already cached (no language detected and no lang.default_locale).
  2. The cache ignores a later language. The adapter is cached by name and keeps the language it was created with. For the web app this is by design: PrepareRequestStage calls LangFactory::get() once per request, which fixes the language for that app context, and Lang::setLang() changes it. This is not documented in the code.
  3. The factory builds an HttpClient the adapters already create. createInstance() uses new $adapterClass($lang, $langConfig) for the file adapter and new $adapterClass($lang, $langConfig, new HttpClient()) for the others, but DeepLAdapter and GoogleTranslateAdapter take ?HttpClient $httpClient = null and default to new HttpClient() themselves.

Proposal (non-breaking)

  • In LangFactory::resolve(), return the cached instance before calling detectLanguage(). Behaviour for callers does not change: today the detected value is already ignored once the adapter exists.
  • Document on resolve() that the language is fixed when the adapter is first created, and changes with Lang::setLang().
  • In createInstance(), build all three adapters with new $adapterClass($lang, $langConfig) and drop the HttpClient import.

No signature changes.

Tests

  • New, must fail without the fix: after one LangFactory::get(), remove lang.default_locale and the detection sources, call LangFactory::get() again and assert the same instance is returned and nothing throws (today the second call throws).
  • New: after the first call, change the lang query parameter and assert the second call still returns the instance with the first language and does not read the request again.
  • The existing LangFactoryTest cases for DeepL and Google keep passing without the explicit HttpClient.

Not in this ticket (3.2.0)

The language is also stored twice at creation: the adapter constructor sets $this->lang, then Lang::__construct() calls setLang(), which calls $adapter->setLang() with the same value. It is harmless, and removing it means changing the adapter constructors to take only the config, which is a public signature change. To be handled with a 3.2.0 or later change to the adapter factories.

Acceptance

  • A cached adapter is returned without any language detection.
  • composer cs:check, composer phpstan and vendor/bin/phpunit tests/Unit/Lang pass.
  • CHANGELOG.md entry.

Activity

  1. added this to the 3.1.2 milestone on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions