Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.68% covered (success)
93.68%
89 / 95
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
GoogleTranslationProvider
93.68% covered (success)
93.68%
89 / 95
57.14% covered (warning)
57.14%
4 / 7
22.12
0.00% covered (danger)
0.00%
0 / 1
 getProviderName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doTranslate
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
6
 doTranslateBatch
86.96% covered (success)
86.96%
20 / 23
0.00% covered (danger)
0.00%
0 / 1
6.08
 translateBatch
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 supportsLanguagePair
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSupportedLanguages
100.00% covered (success)
100.00%
43 / 43
100.00% covered (success)
100.00%
1 / 1
1
 mapLanguageCode
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Google Cloud Translation API provider.
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public License,
9 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
10 * obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * @package   phpMyFAQ
13 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
14 * @copyright 2026 phpMyFAQ Team
15 * @license   http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
16 * @link      https://www.phpmyfaq.de
17 * @since     2026-01-17
18 */
19
20namespace phpMyFAQ\Translation\Provider;
21
22use Exception;
23use phpMyFAQ\Translation\AbstractTranslationProvider;
24use phpMyFAQ\Translation\Exception\ApiException;
25use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
26
27/**
28 * Class GoogleTranslationProvider
29 *
30 * Google Cloud Translation API implementation.
31 */
32class GoogleTranslationProvider extends AbstractTranslationProvider
33{
34    private const string API_URL = 'https://translation.googleapis.com/language/translate/v2';
35
36    /**
37     * @inheritDoc
38     */
39    public function getProviderName(): string
40    {
41        return 'Google Cloud Translation';
42    }
43
44    /**
45     * @inheritDoc
46     */
47    protected function doTranslate(string $text, string $sourceLang, string $targetLang): string
48    {
49        $apiKey = $this->configuration->get('translation.googleApiKey');
50
51        if ((string) $apiKey === '') {
52            throw new ApiException('Google Cloud Translation API key not configured');
53        }
54
55        try {
56            $response = $this->httpClient->request('POST', self::API_URL, [
57                'query' => ['key' => $apiKey],
58                'json' => [
59                    'q' => $text,
60                    'source' => $this->mapLanguageCode($sourceLang),
61                    'target' => $this->mapLanguageCode($targetLang),
62                    'format' => 'text',
63                ],
64            ]);
65
66            $data = $response->toArray();
67            $payload = $data['data'] ?? [];
68            $translations = is_array($payload) ? $payload['translations'] ?? [] : [];
69            $firstTranslation = is_array($translations) ? $translations[0] ?? [] : [];
70
71            return is_array($firstTranslation) ? (string) ($firstTranslation['translatedText'] ?? '') : '';
72        } catch (Exception|TransportExceptionInterface $e) {
73            throw new ApiException('Google Translation API error: ' . $e->getMessage());
74        }
75    }
76
77    /**
78     * @inheritDoc
79     */
80    protected function doTranslateBatch(array $texts, string $sourceLang, string $targetLang): array
81    {
82        $apiKey = $this->configuration->get('translation.googleApiKey');
83
84        if ((string) $apiKey === '') {
85            throw new ApiException('Google Cloud Translation API key not configured');
86        }
87
88        try {
89            $response = $this->httpClient->request('POST', self::API_URL, [
90                'query' => ['key' => $apiKey],
91                'json' => [
92                    'q' => $texts,
93                    'source' => $this->mapLanguageCode($sourceLang),
94                    'target' => $this->mapLanguageCode($targetLang),
95                    'format' => 'text',
96                ],
97            ]);
98
99            $data = $response->toArray();
100            $payload = $data['data'] ?? [];
101            $translations = is_array($payload) ? $payload['translations'] ?? [] : [];
102
103            return array_map(
104                static fn(mixed $translation): string => is_array($translation)
105                    ? (string) ($translation['translatedText'] ?? '')
106                    : '',
107                is_array($translations) ? array_values($translations) : [],
108            );
109        } catch (Exception|TransportExceptionInterface $e) {
110            throw new ApiException('Google Translation API error: ' . $e->getMessage());
111        }
112    }
113
114    /**
115     * @inheritDoc
116     */
117    public function translateBatch(
118        array $texts,
119        string $sourceLang,
120        string $targetLang,
121        bool $preserveHtml = false,
122    ): array {
123        if ($preserveHtml) {
124            // Process each text individually with HTML preservation
125            return array_map(fn($text) => $this->translate($text, $sourceLang, $targetLang, true), $texts);
126        }
127
128        return $this->doTranslateBatch($texts, $sourceLang, $targetLang);
129    }
130
131    /**
132     * @inheritDoc
133     */
134    public function supportsLanguagePair(string $sourceLang, string $targetLang): bool
135    {
136        // Google Cloud Translation supports most language pairs
137        return true;
138    }
139
140    /**
141     * @inheritDoc
142     */
143    public function getSupportedLanguages(): array
144    {
145        // Common languages supported by Google Cloud Translation
146        return [
147            'ar',
148            'bn',
149            'bs',
150            'cs',
151            'cy',
152            'da',
153            'de',
154            'el',
155            'en',
156            'es',
157            'eu',
158            'fa',
159            'fi',
160            'fr',
161            'he',
162            'hi',
163            'hu',
164            'id',
165            'it',
166            'ja',
167            'ko',
168            'lt',
169            'lv',
170            'mn',
171            'ms',
172            'nb',
173            'nl',
174            'pl',
175            'pt',
176            'ro',
177            'ru',
178            'sk',
179            'sl',
180            'sr',
181            'sv',
182            'th',
183            'tr',
184            'uk',
185            'ur',
186            'vi',
187            'zh',
188        ];
189    }
190
191    /**
192     * @inheritDoc
193     */
194    protected function mapLanguageCode(string $pmfLangCode): string
195    {
196        // Google uses standard ISO 639-1, with special cases
197        return match ($pmfLangCode) {
198            'zh' => 'zh-CN',
199            'pt' => 'pt-BR',
200            'nb' => 'no',
201            default => $pmfLangCode,
202        };
203    }
204}