133 lines
5.0 KiB
Java
133 lines
5.0 KiB
Java
package pl.polskalokalnie.i18n;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import java.io.IOException;
|
|
import java.net.URI;
|
|
import java.net.URLEncoder;
|
|
import java.net.http.HttpClient;
|
|
import java.net.http.HttpRequest;
|
|
import java.net.http.HttpResponse;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.time.Duration;
|
|
import java.util.ArrayList;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.web.server.ResponseStatusException;
|
|
import pl.polskalokalnie.user.PreferredLanguage;
|
|
|
|
@Service
|
|
public class RuntimeTranslationService {
|
|
|
|
private static final int MAX_TEXTS_PER_REQUEST = 180;
|
|
private static final int MAX_TEXT_LENGTH = 400;
|
|
|
|
private final HttpClient httpClient = HttpClient.newBuilder()
|
|
.connectTimeout(Duration.ofSeconds(6))
|
|
.build();
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
private final Map<String, String> cache = new ConcurrentHashMap<>();
|
|
|
|
public Map<String, String> translateBatch(PreferredLanguage language, List<String> texts) {
|
|
if (texts == null || texts.isEmpty()) {
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Brak tekstów do tłumaczenia");
|
|
}
|
|
if (texts.size() > MAX_TEXTS_PER_REQUEST) {
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Zbyt wiele tekstów do tłumaczenia w jednym żądaniu");
|
|
}
|
|
|
|
if (language == PreferredLanguage.PL) {
|
|
Map<String, String> passthrough = new LinkedHashMap<>();
|
|
texts.forEach((text) -> passthrough.put(text, text));
|
|
return passthrough;
|
|
}
|
|
|
|
String targetCode = toGoogleLang(language);
|
|
Map<String, String> results = new LinkedHashMap<>();
|
|
List<String> toTranslate = new ArrayList<>();
|
|
|
|
for (String text : texts) {
|
|
String safeText = text != null ? text : "";
|
|
if (safeText.length() > MAX_TEXT_LENGTH) {
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Jeden z tekstów jest zbyt długi do tłumaczenia");
|
|
}
|
|
|
|
String cacheKey = targetCode + "|" + safeText;
|
|
String cached = cache.get(cacheKey);
|
|
if (cached != null) {
|
|
results.put(safeText, cached);
|
|
} else {
|
|
toTranslate.add(safeText);
|
|
}
|
|
}
|
|
|
|
for (String text : toTranslate) {
|
|
String translated = translateSingle(text, targetCode);
|
|
cache.put(targetCode + "|" + text, translated);
|
|
results.put(text, translated);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
private String translateSingle(String source, String targetCode) {
|
|
if (source.isBlank()) {
|
|
return source;
|
|
}
|
|
|
|
String encoded = URLEncoder.encode(source, StandardCharsets.UTF_8);
|
|
String url = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=pl&tl="
|
|
+ targetCode + "&dt=t&q=" + encoded;
|
|
|
|
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
|
|
.GET()
|
|
.timeout(Duration.ofSeconds(10))
|
|
.header("Accept", "application/json")
|
|
.header("User-Agent", "PolskaLokalnie/1.0")
|
|
.build();
|
|
|
|
try {
|
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
|
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "Usługa tłumaczeń jest chwilowo niedostępna");
|
|
}
|
|
return parseTranslatedText(response.body(), source);
|
|
} catch (InterruptedException ex) {
|
|
Thread.currentThread().interrupt();
|
|
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "Nie udało się pobrać tłumaczenia", ex);
|
|
} catch (IOException ex) {
|
|
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "Nie udało się pobrać tłumaczenia", ex);
|
|
}
|
|
}
|
|
|
|
private String parseTranslatedText(String payload, String fallback) throws IOException {
|
|
JsonNode root = objectMapper.readTree(payload);
|
|
JsonNode topSegments = root.path(0);
|
|
if (!topSegments.isArray() || topSegments.isEmpty()) {
|
|
return fallback;
|
|
}
|
|
StringBuilder translated = new StringBuilder();
|
|
for (JsonNode segment : topSegments) {
|
|
JsonNode part = segment.path(0);
|
|
if (part.isTextual()) {
|
|
translated.append(part.asText());
|
|
}
|
|
}
|
|
String value = translated.toString();
|
|
return value.isBlank() ? fallback : value;
|
|
}
|
|
|
|
private String toGoogleLang(PreferredLanguage language) {
|
|
return switch (language) {
|
|
case EN -> "en";
|
|
case UK -> "uk";
|
|
case DE -> "de";
|
|
case PL -> "pl";
|
|
};
|
|
}
|
|
}
|