Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog.

## [3.1.0] - TBD

### Added
- Added adapter-based Lang provider support with `DeepL` and `Google Translate` adapters plus shared remote request/caching infrastructure (#533)

### Changed
- Refactored the Lang package to resolve adapter instances through `LangFactory` configuration and load file translations lazily on first use instead of preloading them during web boot (#533)
- **BREAKING:** Reshaped Lang configuration so `lang.default` now selects the adapter, locale fallback moved to `lang.default_locale`, and the unused `lang.enabled` toggle was removed (#533)
- **BREAKING:** Removed `Lang::isEnabled()` from the public Lang API because it no longer affected runtime behavior (#533)

## [3.0.3] - 2026-07-10

### Changed
Expand Down
2 changes: 0 additions & 2 deletions src/App/Adapters/WebAppAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ public function start(): ?int
return ExitCode::SUCCESS;
}

Comment thread
andrey-smaelov marked this conversation as resolved.
$this->loadLanguage();

$this->logDebugInfo();

$viewCache = $this->setupViewCache();
Expand Down
14 changes: 0 additions & 14 deletions src/App/Traits/WebAppTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
use Quantum\Config\Exceptions\ConfigException;
use Quantum\Loader\Exceptions\LoaderException;
use Quantum\Router\Exceptions\RouteException;
use Quantum\Lang\Exceptions\LangException;
use Quantum\App\Exceptions\BaseException;
use Quantum\Lang\Factories\LangFactory;
use Quantum\Di\Exceptions\DiException;
use Quantum\ResourceCache\ViewCache;
use Quantum\Router\RouteCollection;
Expand Down Expand Up @@ -52,18 +50,6 @@ private function resolveRoute(): ?MatchedRoute
return $matchedRoute;
}

/**
* @throws LangException|ConfigException|DiException|BaseException|ReflectionException
*/
private function loadLanguage(): void
{
$lang = LangFactory::get();

if ($lang->isEnabled()) {
$lang->load();
}
}

/**
* @throws DiException|ReflectionException
*/
Expand Down
109 changes: 109 additions & 0 deletions src/Lang/Adapters/DeepLAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

/**
* Quantum PHP Framework
* An open-source software development framework for PHP
* @link https://quantumphp.io
*/

namespace Quantum\Lang\Adapters;

use Quantum\Lang\Contracts\LangAdapterInterface;
use Quantum\Lang\Traits\RemoteAdapterTrait;
use Quantum\Lang\Exceptions\LangException;
use Quantum\HttpClient\HttpClient;

class DeepLAdapter implements LangAdapterInterface
{
use RemoteAdapterTrait;

public const API_URL = 'https://api.deepl.com/v2/translate';

protected string $lang;

/**
* @param array<string, mixed> $params
*/
public function __construct(string $lang, array $params, ?HttpClient $httpClient = null)
{
$this->lang = $lang;
$this->params = $params;
$this->httpClient = $httpClient ?? new HttpClient();
}

public function setLang(string $lang): LangAdapterInterface
{
$this->lang = $lang;
return $this;
}

/**
* @param array<int|string, mixed>|string|null $params
*/
public function get(string $key, $params = null): string
{
$text = $this->buildSourceText($key, $params);

if ($text === '') {
return $text;
}

$cached = $this->getCachedTranslation('deepl', $text);

if ($cached !== null) {
return $cached;
}

$authKey = (string) ($this->params['auth_key'] ?? '');

if ($authKey === '') {
throw LangException::missingConfig('lang.deepl.auth_key');
}

$payload = [
'text' => [$text],
'target_lang' => strtoupper($this->lang),
];

if (!empty($this->params['source_locale'])) {
$payload['source_lang'] = strtoupper((string) $this->params['source_locale']);
}

$payloadJson = json_encode($payload);

if ($payloadJson === false) {
throw LangException::invalidProviderResponse('DeepL');
}

$response = $this->sendRequest(
(string) ($this->params['api_url'] ?? self::API_URL),
$payloadJson,
[
'Authorization' => 'DeepL-Auth-Key ' . $authKey,
'Content-Type' => 'application/json',
]
);

if (
!is_object($response)
|| !isset($response->translations)
|| !is_array($response->translations)
|| !isset($response->translations[0]->text)
|| !is_string($response->translations[0]->text)
) {
throw LangException::invalidProviderResponse('DeepL');
}

$translation = $response->translations[0]->text;

$this->setCachedTranslation('deepl', $text, $translation);

return $translation;
}

public function flush(): void
{
}
}
76 changes: 45 additions & 31 deletions src/Lang/Translator.php → src/Lang/Adapters/FileAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
* @link https://quantumphp.io
*/

namespace Quantum\Lang;
namespace Quantum\Lang\Adapters;

use Quantum\Lang\Contracts\LangAdapterInterface;
use Quantum\Config\Exceptions\ConfigException;
use Quantum\Loader\Exceptions\LoaderException;
use Quantum\Lang\Exceptions\LangException;
Expand All @@ -18,23 +19,37 @@
use Dflydev\DotAccessData\Data;
use ReflectionException;

/**
* Class Translator
* @package Quantum\Lang
*/
class Translator
class FileAdapter implements LangAdapterInterface
{
protected string $lang;

/**
* @var array<string, mixed>
*/
protected array $params = [];

private ?Data $translations = null;

public function __construct(string $lang)
/**
* @param array<string, mixed> $params
*/
public function __construct(string $lang, array $params = [])
{
$this->lang = $lang;
$this->params = $params;
}

public function setLang(string $lang): LangAdapterInterface
{
if ($this->lang !== $lang) {
$this->lang = $lang;
$this->flush();
}

return $this;
}

/**
* Load translation files
* @throws LangException|LoaderException|ConfigException|DiException|BaseException|ReflectionException
*/
public function loadTranslations(): void
Expand Down Expand Up @@ -68,31 +83,14 @@ public function loadTranslations(): void
}

/**
* Load translations
* @param array<string> $files
* @throws ConfigException|DiException|BaseException|ReflectionException
* @param array<int|string, mixed>|string|null $params
*/
private function loadFiles(array $files): void
public function get(string $key, array|string $params = null): string
{
if ($this->translations === null) {
return;
$this->loadTranslations();
}

foreach ($files as $file) {
$fileName = fs()->fileName($file);

$this->translations->import([
$fileName => fs()->require($file),
]);
}
}

/**
* Get translation by key
* @param array<int|string, mixed>|string|null $params
*/
public function get(string $key, $params = null): string
{
if ($this->translations && $this->translations->has($key)) {
$message = $this->translations->get($key);
return $params ? _message($message, $params) : $message;
Expand All @@ -101,11 +99,27 @@ public function get(string $key, $params = null): string
return $key;
}

/**
* Reset translations
*/
public function flush(): void
{
$this->translations = null;
}

/**
* @param array<string> $files
* @throws ConfigException|DiException|BaseException|ReflectionException
*/
private function loadFiles(array $files): void
{
if ($this->translations === null) {
return;
}

foreach ($files as $file) {
$fileName = fs()->fileName($file);

$this->translations->import([
$fileName => fs()->require($file),
]);
}
}
}
105 changes: 105 additions & 0 deletions src/Lang/Adapters/GoogleTranslateAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

declare(strict_types=1);

/**
* Quantum PHP Framework
* An open-source software development framework for PHP
* @link https://quantumphp.io
*/

namespace Quantum\Lang\Adapters;

use Quantum\Lang\Contracts\LangAdapterInterface;
use Quantum\Lang\Traits\RemoteAdapterTrait;
use Quantum\Lang\Exceptions\LangException;
use Quantum\HttpClient\HttpClient;

class GoogleTranslateAdapter implements LangAdapterInterface
{
use RemoteAdapterTrait;

public const API_URL = 'https://translation.googleapis.com/language/translate/v2';

protected string $lang;

/**
* @param array<string, mixed> $params
*/
public function __construct(string $lang, array $params, ?HttpClient $httpClient = null)
{
$this->lang = $lang;
$this->params = $params;
$this->httpClient = $httpClient ?? new HttpClient();
}

public function setLang(string $lang): LangAdapterInterface
{
$this->lang = $lang;
return $this;
}

/**
* @param array<int|string, mixed>|string|null $params
*/
public function get(string $key, $params = null): string
{
$text = $this->buildSourceText($key, $params);

if ($text === '') {
return $text;
}

$cached = $this->getCachedTranslation('google_translate', $text);

if ($cached !== null) {
return $cached;
}

$apiKey = (string) ($this->params['api_key'] ?? '');

if ($apiKey === '') {
throw LangException::missingConfig('lang.google_translate.api_key');
}

$query = [
'q' => $text,
'target' => $this->lang,
'format' => 'text',
'key' => $apiKey,
];

if (!empty($this->params['source_locale'])) {
$query['source'] = (string) $this->params['source_locale'];
}

$response = $this->sendRequest(
(string) ($this->params['api_url'] ?? self::API_URL) . '?' . http_build_query($query, '', '&'),
Comment thread
armanist marked this conversation as resolved.
Outdated
null,
[],
'POST'
);

if (
!is_object($response)
|| !isset($response->data)
|| !is_object($response->data)
|| !isset($response->data->translations)
|| !is_array($response->data->translations)
|| !isset($response->data->translations[0]->translatedText)
|| !is_string($response->data->translations[0]->translatedText)
) {
throw LangException::invalidProviderResponse('Google Translate');
}

$translation = html_entity_decode($response->data->translations[0]->translatedText, ENT_QUOTES | ENT_HTML5, 'UTF-8');

$this->setCachedTranslation('google_translate', $text, $translation);

return $translation;
}

public function flush(): void
{
}
}
Loading
Loading