Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
109 / 109
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
ContentTranslationService
100.00% covered (success)
100.00%
109 / 109
100.00% covered (success)
100.00%
5 / 5
45
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 translateFaq
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
11
 translateCustomPage
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
14
 translateCategory
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
8
 translateNews
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
11
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Service for translating content across different entity types.
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;
21
22use phpMyFAQ\Configuration;
23use phpMyFAQ\Translation\DTO\TranslationRequest;
24use phpMyFAQ\Translation\DTO\TranslationResult;
25use phpMyFAQ\Translation\Exception\TranslationException;
26
27/**
28 * Class ContentTranslationService
29 *
30 * High-level service for translating different content types (FAQ, Custom Pages, Categories, News).
31 */
32class ContentTranslationService
33{
34    private ?TranslationProviderInterface $provider = null;
35
36    /**
37     * Constructor.
38     *
39     * @param Configuration $configuration phpMyFAQ configuration
40     */
41    public function __construct(
42        private readonly Configuration $configuration,
43    ) {
44        $this->provider = $this->configuration->getTranslationProvider();
45    }
46
47    /**
48     * Translate FAQ fields (question, answer, keywords).
49     *
50     * @param TranslationRequest $request Translation request
51     * @return TranslationResult Translation result
52     * @throws TranslationException
53     */
54    public function translateFaq(TranslationRequest $request): TranslationResult
55    {
56        if (!$this->provider) {
57            throw new TranslationException('No translation provider configured');
58        }
59
60        $fields = $request->getFields();
61        $result = [];
62
63        // Translate question (plain text)
64        if (array_key_exists('question', $fields) && $fields['question'] !== null && $fields['question'] !== '') {
65            $result['question'] = $this->provider->translate(
66                $fields['question'],
67                $request->getSourceLang(),
68                $request->getTargetLang(),
69                false,
70            );
71        }
72
73        // Translate answer (HTML content)
74        if (array_key_exists('answer', $fields) && $fields['answer'] !== null && $fields['answer'] !== '') {
75            $result['answer'] = $this->provider->translate(
76                $fields['answer'],
77                $request->getSourceLang(),
78                $request->getTargetLang(),
79                true, // Preserve HTML
80            );
81        }
82
83        // Translate keywords (plain text)
84        if (array_key_exists('keywords', $fields) && $fields['keywords'] !== null && $fields['keywords'] !== '') {
85            $result['keywords'] = $this->provider->translate(
86                $fields['keywords'],
87                $request->getSourceLang(),
88                $request->getTargetLang(),
89                false,
90            );
91        }
92
93        return new TranslationResult($result, true);
94    }
95
96    /**
97     * Translate Custom Page fields (pageTitle, content, seoTitle, seoDescription).
98     *
99     * @param TranslationRequest $request Translation request
100     * @return TranslationResult Translation result
101     * @throws TranslationException
102     */
103    public function translateCustomPage(TranslationRequest $request): TranslationResult
104    {
105        if (!$this->provider) {
106            throw new TranslationException('No translation provider configured');
107        }
108
109        $fields = $request->getFields();
110        $result = [];
111
112        // Translate page title
113        if (array_key_exists('pageTitle', $fields) && $fields['pageTitle'] !== null && $fields['pageTitle'] !== '') {
114            $result['pageTitle'] = $this->provider->translate(
115                $fields['pageTitle'],
116                $request->getSourceLang(),
117                $request->getTargetLang(),
118                false,
119            );
120        }
121
122        // Translate content (HTML)
123        if (array_key_exists('content', $fields) && $fields['content'] !== null && $fields['content'] !== '') {
124            $result['content'] = $this->provider->translate(
125                $fields['content'],
126                $request->getSourceLang(),
127                $request->getTargetLang(),
128                true, // Preserve HTML
129            );
130        }
131
132        // Translate SEO title
133        if (array_key_exists('seoTitle', $fields) && $fields['seoTitle'] !== null && $fields['seoTitle'] !== '') {
134            $result['seoTitle'] = $this->provider->translate(
135                $fields['seoTitle'],
136                $request->getSourceLang(),
137                $request->getTargetLang(),
138                false,
139            );
140        }
141
142        // Translate SEO description
143        if (
144            array_key_exists('seoDescription', $fields)
145            && $fields['seoDescription'] !== null
146            && $fields['seoDescription'] !== ''
147        ) {
148            $result['seoDescription'] = $this->provider->translate(
149                $fields['seoDescription'],
150                $request->getSourceLang(),
151                $request->getTargetLang(),
152                false,
153            );
154        }
155
156        return new TranslationResult($result, true);
157    }
158
159    /**
160     * Translate Category fields (name, description).
161     *
162     * @param TranslationRequest $request Translation request
163     * @return TranslationResult Translation result
164     * @throws TranslationException
165     */
166    public function translateCategory(TranslationRequest $request): TranslationResult
167    {
168        if (!$this->provider) {
169            throw new TranslationException('No translation provider configured');
170        }
171
172        $fields = $request->getFields();
173        $result = [];
174
175        // Translate name
176        if (array_key_exists('name', $fields) && $fields['name'] !== null && $fields['name'] !== '') {
177            $result['name'] = $this->provider->translate(
178                $fields['name'],
179                $request->getSourceLang(),
180                $request->getTargetLang(),
181                false,
182            );
183        }
184
185        // Translate description
186        if (
187            array_key_exists('description', $fields)
188            && $fields['description'] !== null
189            && $fields['description'] !== ''
190        ) {
191            $result['description'] = $this->provider->translate(
192                $fields['description'],
193                $request->getSourceLang(),
194                $request->getTargetLang(),
195                false,
196            );
197        }
198
199        return new TranslationResult($result, true);
200    }
201
202    /**
203     * Translate News fields (header, message, linkTitle).
204     *
205     * @param TranslationRequest $request Translation request
206     * @return TranslationResult Translation result
207     * @throws TranslationException
208     */
209    public function translateNews(TranslationRequest $request): TranslationResult
210    {
211        if (!$this->provider) {
212            throw new TranslationException('No translation provider configured');
213        }
214
215        $fields = $request->getFields();
216        $result = [];
217
218        // Translate header
219        if (array_key_exists('header', $fields) && $fields['header'] !== null && $fields['header'] !== '') {
220            $result['header'] = $this->provider->translate(
221                $fields['header'],
222                $request->getSourceLang(),
223                $request->getTargetLang(),
224                false,
225            );
226        }
227
228        // Translate message
229        if (array_key_exists('message', $fields) && $fields['message'] !== null && $fields['message'] !== '') {
230            $result['message'] = $this->provider->translate(
231                $fields['message'],
232                $request->getSourceLang(),
233                $request->getTargetLang(),
234                false,
235            );
236        }
237
238        // Translate link title
239        if (array_key_exists('linkTitle', $fields) && $fields['linkTitle'] !== null && $fields['linkTitle'] !== '') {
240            $result['linkTitle'] = $this->provider->translate(
241                $fields['linkTitle'],
242                $request->getSourceLang(),
243                $request->getTargetLang(),
244                false,
245            );
246        }
247
248        return new TranslationResult($result, true);
249    }
250}