Added command to auto translate the app
php artisan translate
This commit is contained in:
parent
8b6981945d
commit
e63b3a902a
|
@ -0,0 +1,358 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use JsonException;
|
||||
use Throwable;
|
||||
|
||||
interface TokenProviderInterface
|
||||
{
|
||||
|
||||
public function generateToken(string $source, string $target, string $text): string;
|
||||
}
|
||||
|
||||
class GoogleTokenGenerator implements TokenProviderInterface
|
||||
{
|
||||
|
||||
public function generateToken(string $source, string $target, string $text): string
|
||||
{
|
||||
$tkk = ['406398', 2087938574];
|
||||
|
||||
for ($d = [], $e = 0, $f = 0; $f < $this->length($text); $f++) {
|
||||
$g = $this->charCodeAt($text, $f);
|
||||
if ($g < 128) {
|
||||
$d[$e++] = $g;
|
||||
} else {
|
||||
if ($g < 2048) {
|
||||
$d[$e++] = $g >> 6 | 192;
|
||||
} else {
|
||||
if ($g & 64512 === 55296 && $f + 1 < $this->length($text) && ($this->charCodeAt($text, $f + 1) & 64512) === 56320) {
|
||||
$g = 65536 + (($g & 1023) << 10) + ($this->charCodeAt($text, ++$f) & 1023);
|
||||
$d[$e++] = $g >> 18 | 240;
|
||||
$d[$e++] = $g >> 12 & 63 | 128;
|
||||
} else {
|
||||
$d[$e++] = $g >> 12 | 224;
|
||||
}
|
||||
$d[$e++] = $g >> 6 & 63 | 128;
|
||||
}
|
||||
$d[$e++] = $g & 63 | 128;
|
||||
}
|
||||
}
|
||||
|
||||
$a = $tkk[0];
|
||||
foreach ($d as $value) {
|
||||
$a += $value;
|
||||
$a = $this->rl($a, '+-a^+6');
|
||||
}
|
||||
$a = $this->rl($a, '+-3^+b+-f');
|
||||
$a ^= $tkk[1];
|
||||
if ($a < 0) {
|
||||
$a = ($a & 2147483647) + 2147483648;
|
||||
}
|
||||
$a = fmod($a, 1000000);
|
||||
|
||||
return $a . '.' . ($a ^ $tkk[0]);
|
||||
}
|
||||
|
||||
private function rl(int $a, string $b): int
|
||||
{
|
||||
for ($c = 0; $c < strlen($b) - 2; $c += 3) {
|
||||
$d = $b[$c + 2];
|
||||
$d = $d >= 'a' ? ord($d[0]) - 87 : (int) $d;
|
||||
$d = $b[$c + 1] === '+' ? $this->unsignedRightShift($a, $d) : $a << $d;
|
||||
$a = $b[$c] === '+' ? ($a + $d & 4294967295) : $a ^ $d;
|
||||
}
|
||||
|
||||
return $a;
|
||||
}
|
||||
|
||||
private function unsignedRightShift(int $a, int $b): int
|
||||
{
|
||||
if ($b >= 32 || $b < -32) {
|
||||
$m = (int) ($b / 32);
|
||||
$b -= ($m * 32);
|
||||
}
|
||||
|
||||
if ($b < 0) {
|
||||
$b += 32;
|
||||
}
|
||||
|
||||
if ($b === 0) {
|
||||
return (($a >> 1) & 0x7fffffff) * 2 + (($a >> $b) & 1);
|
||||
}
|
||||
|
||||
if ($a < 0) {
|
||||
$a >>= 1;
|
||||
$a &= 2147483647;
|
||||
$a |= 0x40000000;
|
||||
$a >>= ($b - 1);
|
||||
} else {
|
||||
$a >>= $b;
|
||||
}
|
||||
|
||||
return $a;
|
||||
}
|
||||
|
||||
private function charCodeAt(string $string, int $index): int
|
||||
{
|
||||
return mb_ord(mb_substr($string, $index, 1));
|
||||
}
|
||||
|
||||
private function length(string $string): int
|
||||
{
|
||||
return mb_strlen($string);
|
||||
}
|
||||
}
|
||||
|
||||
class GoogleTranslate
|
||||
{
|
||||
|
||||
protected Client $client;
|
||||
|
||||
protected ?string $source;
|
||||
|
||||
protected ?string $target;
|
||||
|
||||
protected ?string $lastDetectedSource;
|
||||
|
||||
protected string $url = 'https://translate.google.com/translate_a/single';
|
||||
|
||||
protected array $options = [];
|
||||
|
||||
protected array $urlParams = [
|
||||
'client' => 'gtx',
|
||||
'hl' => 'en',
|
||||
'dt' => [
|
||||
't',
|
||||
'bd',
|
||||
'at',
|
||||
'ex',
|
||||
'ld',
|
||||
'md',
|
||||
'qca',
|
||||
'rw',
|
||||
'rm',
|
||||
'ss'
|
||||
],
|
||||
'sl' => null,
|
||||
'tl' => null,
|
||||
'q' => null,
|
||||
'ie' => 'UTF-8',
|
||||
'oe' => 'UTF-8',
|
||||
'multires' => 1,
|
||||
'otf' => 0,
|
||||
'pc' => 1,
|
||||
'trs' => 1,
|
||||
'ssel' => 0,
|
||||
'tsel' => 0,
|
||||
'kc' => 1,
|
||||
'tk' => null,
|
||||
];
|
||||
|
||||
protected array $resultRegexes = [
|
||||
'/,+/' => ',',
|
||||
'/\[,/' => '[',
|
||||
];
|
||||
|
||||
protected TokenProviderInterface $tokenProvider;
|
||||
|
||||
public function __construct(string $target = 'en', string $source = null, array $options = [], TokenProviderInterface $tokenProvider = null)
|
||||
{
|
||||
$this->client = new Client();
|
||||
$this->setTokenProvider($tokenProvider ?? new GoogleTokenGenerator)
|
||||
->setOptions($options)
|
||||
->setSource($source)
|
||||
->setTarget($target);
|
||||
}
|
||||
|
||||
public function setTarget(string $target): self
|
||||
{
|
||||
$this->target = $target;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSource(string $source = null): self
|
||||
{
|
||||
$this->source = $source ?? 'auto';
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUrl(string $url): self
|
||||
{
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setClient(string $client): self
|
||||
{
|
||||
$this->urlParams['client'] = $client;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setOptions(array $options = []): self
|
||||
{
|
||||
$this->options = $options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTokenProvider(TokenProviderInterface $tokenProvider): self
|
||||
{
|
||||
$this->tokenProvider = $tokenProvider;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLastDetectedSource(): ?string
|
||||
{
|
||||
return $this->lastDetectedSource;
|
||||
}
|
||||
|
||||
public static function trans(string $string, string $target = 'en', string $source = null, array $options = [], TokenProviderInterface $tokenProvider = null): ?string
|
||||
{
|
||||
return (new self)
|
||||
->setTokenProvider($tokenProvider ?? new GoogleTokenGenerator)
|
||||
->setOptions($options)
|
||||
->setSource($source)
|
||||
->setTarget($target)
|
||||
->translate($string);
|
||||
}
|
||||
|
||||
public function translate(string $string): ?string
|
||||
{
|
||||
|
||||
if ($this->source === $this->target) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
$responseArray = $this->getResponse($string);
|
||||
|
||||
if (empty($responseArray[0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$detectedLanguages = [];
|
||||
|
||||
foreach ($responseArray as $item) {
|
||||
if (is_string($item)) {
|
||||
$detectedLanguages[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($responseArray[count($responseArray) - 2][0][0])) {
|
||||
$detectedLanguages[] = $responseArray[count($responseArray) - 2][0][0];
|
||||
}
|
||||
|
||||
$this->lastDetectedSource = null;
|
||||
|
||||
foreach ($detectedLanguages as $lang) {
|
||||
if ($this->isValidLocale($lang)) {
|
||||
$this->lastDetectedSource = $lang;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_string($responseArray)) {
|
||||
return $responseArray;
|
||||
}
|
||||
|
||||
if (is_array($responseArray[0])) {
|
||||
return (string) array_reduce($responseArray[0], static function ($carry, $item) {
|
||||
$carry .= $item[0];
|
||||
return $carry;
|
||||
});
|
||||
}
|
||||
|
||||
return (string) $responseArray[0];
|
||||
}
|
||||
|
||||
public function getResponse(string $string): array
|
||||
{
|
||||
$queryArray = array_merge($this->urlParams, [
|
||||
'sl' => $this->source,
|
||||
'tl' => $this->target,
|
||||
'tk' => $this->tokenProvider->generateToken($this->source, $this->target, $string),
|
||||
'q' => $string
|
||||
]);
|
||||
|
||||
$queryUrl = preg_replace('/%5B\d+%5D=/', '=', http_build_query($queryArray));
|
||||
|
||||
try {
|
||||
$response = $this->client->get($this->url, [
|
||||
'query' => $queryUrl,
|
||||
] + $this->options);
|
||||
} catch (GuzzleException $e) {
|
||||
match ($e->getCode()) {
|
||||
429, 503 => throw new RateLimitException($e->getMessage(), $e->getCode()),
|
||||
413 => throw new LargeTextException($e->getMessage(), $e->getCode()),
|
||||
default => throw new TranslationRequestException($e->getMessage(), $e->getCode()),
|
||||
};
|
||||
} catch (Throwable $e) {
|
||||
throw new TranslationRequestException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
$body = $response->getBody();
|
||||
|
||||
$bodyJson = preg_replace(array_keys($this->resultRegexes), array_values($this->resultRegexes), $body);
|
||||
|
||||
try {
|
||||
$bodyArray = json_decode($bodyJson, true, flags: JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException) {
|
||||
throw new TranslationDecodingException('Data cannot be decoded or it is deeper than the recursion limit');
|
||||
}
|
||||
|
||||
return $bodyArray;
|
||||
}
|
||||
|
||||
protected function isValidLocale(string $lang): bool
|
||||
{
|
||||
return (bool) preg_match('/^([a-z]{2,3})(-[A-Za-z]{2,4})?$/', $lang);
|
||||
}
|
||||
}
|
||||
|
||||
class Translate extends Command
|
||||
{
|
||||
protected $signature = 'translate';
|
||||
|
||||
protected $description = 'Translate language files';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$locales = ['de', 'es'];
|
||||
$sourceLocale = 'en';
|
||||
|
||||
foreach ($locales as $locale) {
|
||||
$langPath = resource_path("lang/{$locale}");
|
||||
if (!File::exists($langPath)) {
|
||||
File::makeDirectory($langPath, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
$sourceFile = resource_path("lang/{$sourceLocale}/messages.php");
|
||||
$sourceTranslations = require($sourceFile);
|
||||
|
||||
foreach ($locales as $locale) {
|
||||
$targetFile = resource_path("lang/{$locale}/messages.php");
|
||||
$targetTranslations = File::exists($targetFile) ? require($targetFile) : [];
|
||||
|
||||
$tr = new GoogleTranslate();
|
||||
$tr->setSource($sourceLocale);
|
||||
$tr->setTarget($locale);
|
||||
|
||||
foreach ($sourceTranslations as $key => $value) {
|
||||
if (!array_key_exists($key, $targetTranslations)) {
|
||||
$translatedValue = $tr->translate($value);
|
||||
$targetTranslations[$key] = $translatedValue;
|
||||
}
|
||||
}
|
||||
|
||||
$content = '<?php' . PHP_EOL . PHP_EOL;
|
||||
$content .= 'return ' . var_export($targetTranslations, true) . ';' . PHP_EOL;
|
||||
|
||||
file_put_contents($targetFile, $content);
|
||||
|
||||
$this->info("Translations for '{$locale}' created successfully.");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue