Erweiterungen
This commit is contained in:
@@ -116,12 +116,8 @@ public class MessagingConfig {
|
|||||||
// Send success response to the now-authenticated session
|
// Send success response to the now-authenticated session
|
||||||
// locationTrackingEnabled: true = client should send position updates
|
// locationTrackingEnabled: true = client should send position updates
|
||||||
// appUserId: wird an den Client gesendet für Referenz
|
// appUserId: wird an den Client gesendet für Referenz
|
||||||
Map<String, Object> authResponse = Map.of(
|
Map<String, Object> authResponse = Map.of("success", true, "message", response.getMessage(),
|
||||||
"success", true,
|
"locationTrackingEnabled", true, "appUserId", appUserId);
|
||||||
"message", response.getMessage(),
|
|
||||||
"locationTrackingEnabled", true,
|
|
||||||
"appUserId", appUserId
|
|
||||||
);
|
|
||||||
byte[] responseBytes = objectMapper.writeValueAsBytes(authResponse);
|
byte[] responseBytes = objectMapper.writeValueAsBytes(authResponse);
|
||||||
log.info("[Messaging] Sending auth response to appUserId: {}", appUserId);
|
log.info("[Messaging] Sending auth response to appUserId: {}", appUserId);
|
||||||
webSocketService.sendToClient(appUserId, "auth", responseBytes);
|
webSocketService.sendToClient(appUserId, "auth", responseBytes);
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
package de.assecutor.votianlt.messaging;
|
package de.assecutor.votianlt.messaging;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import de.assecutor.votianlt.dto.JobWithRelatedDataDTO;
|
||||||
|
import de.assecutor.votianlt.model.CargoItem;
|
||||||
|
import de.assecutor.votianlt.model.task.BaseTask;
|
||||||
|
import de.assecutor.votianlt.model.task.ConfirmationTask;
|
||||||
|
import de.assecutor.votianlt.service.TranslationService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// Force recompile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publishing helper to send JSON payloads to clients via WebSocket.
|
* Publishing helper to send JSON payloads to clients via WebSocket.
|
||||||
@@ -19,10 +29,13 @@ class MessagingPublisherImpl implements MessagingPublisher {
|
|||||||
|
|
||||||
private final WebSocketService webSocketService;
|
private final WebSocketService webSocketService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
private final TranslationService translationService;
|
||||||
|
|
||||||
public MessagingPublisherImpl(WebSocketService webSocketService, ObjectMapper objectMapper) {
|
public MessagingPublisherImpl(WebSocketService webSocketService, ObjectMapper objectMapper,
|
||||||
|
TranslationService translationService) {
|
||||||
this.webSocketService = webSocketService;
|
this.webSocketService = webSocketService;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
|
this.translationService = translationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -37,20 +50,133 @@ class MessagingPublisherImpl implements MessagingPublisher {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String json = objectMapper.writeValueAsString(payload);
|
// Verarbeite Payload und füge Übersetzungen hinzu wenn nötig
|
||||||
|
Object processedPayload = processPayloadWithTranslations(payload);
|
||||||
|
|
||||||
|
String json = objectMapper.writeValueAsString(processedPayload);
|
||||||
byte[] data = json.getBytes(StandardCharsets.UTF_8);
|
byte[] data = json.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
webSocketService.sendToClient(clientId, messageType, data)
|
webSocketService.sendToClient(clientId, messageType, data).thenRun(() -> {
|
||||||
.thenRun(() -> {
|
log.debug("[Messaging] Successfully sent {}/{} to client {}", messageType, clientId);
|
||||||
log.debug("[Messaging] Successfully sent {}/{} to client {}", messageType, clientId);
|
}).exceptionally(ex -> {
|
||||||
})
|
log.error("[Messaging] Failed to deliver to {}/{}: {}", clientId, messageType, ex.getMessage(), ex);
|
||||||
.exceptionally(ex -> {
|
return null;
|
||||||
log.error("[Messaging] Failed to deliver to {}/{}: {}", clientId, messageType, ex.getMessage(), ex);
|
});
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[Messaging] Failed to publish to {}/{}: {}", clientId, messageType, e.getMessage(), e);
|
log.error("[Messaging] Failed to publish to {}/{}: {}", clientId, messageType, e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Processes the payload and adds translations for job remarks, task
|
||||||
|
* descriptions/texts, and cargo item descriptions.
|
||||||
|
*/
|
||||||
|
private Object processPayloadWithTranslations(Object payload) {
|
||||||
|
try {
|
||||||
|
// Handle single JobWithRelatedDataDTO
|
||||||
|
if (payload instanceof JobWithRelatedDataDTO dto) {
|
||||||
|
return convertToTranslatedJson(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle list of JobWithRelatedDataDTO
|
||||||
|
if (payload instanceof List<?> list && !list.isEmpty() && list.get(0) instanceof JobWithRelatedDataDTO) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<JobWithRelatedDataDTO> dtoList = (List<JobWithRelatedDataDTO>) list;
|
||||||
|
return dtoList.stream().map(this::convertToTranslatedJson).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[Messaging] Failed to process translations: {}", e.getMessage());
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts JobWithRelatedDataDTO to JSON with translated fields.
|
||||||
|
*/
|
||||||
|
private ObjectNode convertToTranslatedJson(JobWithRelatedDataDTO dto) {
|
||||||
|
// Convert to JSON tree
|
||||||
|
ObjectNode root = objectMapper.valueToTree(dto);
|
||||||
|
|
||||||
|
// Translate job remark
|
||||||
|
if (dto.getJob() != null && dto.getJob().getRemark() != null && !dto.getJob().getRemark().isBlank()) {
|
||||||
|
List<TranslationService.Translation> translations = translationService
|
||||||
|
.translateToAllLanguages(dto.getJob().getRemark());
|
||||||
|
root.withObject("job").set("remark", createTranslationArray(translations));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate task descriptions, displayNames and button texts
|
||||||
|
if (dto.getTasks() != null && !dto.getTasks().isEmpty()) {
|
||||||
|
ArrayNode tasksNode = root.withArray("tasks");
|
||||||
|
for (int i = 0; i < dto.getTasks().size(); i++) {
|
||||||
|
BaseTask task = dto.getTasks().get(i);
|
||||||
|
ObjectNode taskNode = (ObjectNode) tasksNode.get(i);
|
||||||
|
|
||||||
|
// Translate description
|
||||||
|
if (task.getDescription() != null && !task.getDescription().isBlank()) {
|
||||||
|
List<TranslationService.Translation> translations = translationService
|
||||||
|
.translateToAllLanguages(task.getDescription());
|
||||||
|
taskNode.set("description", createTranslationArray(translations));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate displayName
|
||||||
|
if (task.getDisplayName() != null && !task.getDisplayName().isBlank()) {
|
||||||
|
List<TranslationService.Translation> translations = translationService
|
||||||
|
.translateToAllLanguages(task.getDisplayName());
|
||||||
|
taskNode.set("displayName", createTranslationArray(translations));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate buttonText in taskSpecificData for ConfirmationTask
|
||||||
|
if (task instanceof ConfirmationTask confirmationTask) {
|
||||||
|
if (confirmationTask.getButtonText() != null && !confirmationTask.getButtonText().isBlank()) {
|
||||||
|
// Translate buttonText at task level
|
||||||
|
List<TranslationService.Translation> translations = translationService
|
||||||
|
.translateToAllLanguages(confirmationTask.getButtonText());
|
||||||
|
taskNode.set("buttonText", createTranslationArray(translations));
|
||||||
|
|
||||||
|
// Also translate buttonText in taskSpecificData if present
|
||||||
|
if (taskNode.has("taskSpecificData")) {
|
||||||
|
ObjectNode taskSpecificData = (ObjectNode) taskNode.get("taskSpecificData");
|
||||||
|
if (taskSpecificData.has("buttonText")) {
|
||||||
|
taskSpecificData.set("buttonText", createTranslationArray(translations));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate cargo item descriptions
|
||||||
|
if (dto.getCargoItems() != null && !dto.getCargoItems().isEmpty()) {
|
||||||
|
ArrayNode cargoItemsNode = root.withArray("cargoItems");
|
||||||
|
for (int i = 0; i < dto.getCargoItems().size(); i++) {
|
||||||
|
CargoItem item = dto.getCargoItems().get(i);
|
||||||
|
ObjectNode itemNode = (ObjectNode) cargoItemsNode.get(i);
|
||||||
|
|
||||||
|
if (item.getDescription() != null && !item.getDescription().isBlank()) {
|
||||||
|
List<TranslationService.Translation> translations = translationService
|
||||||
|
.translateToAllLanguages(item.getDescription());
|
||||||
|
itemNode.set("description", createTranslationArray(translations));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a JSON array from translations.
|
||||||
|
*/
|
||||||
|
private ArrayNode createTranslationArray(List<TranslationService.Translation> translations) {
|
||||||
|
ArrayNode array = objectMapper.createArrayNode();
|
||||||
|
for (TranslationService.Translation t : translations) {
|
||||||
|
ObjectNode node = objectMapper.createObjectNode();
|
||||||
|
node.put("language", t.language());
|
||||||
|
node.put("text", t.text());
|
||||||
|
array.add(node);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -131,8 +131,7 @@ public class WebSocketService extends TextWebSocketHandler {
|
|||||||
WebSocketSession session = clientSessions.get(clientId);
|
WebSocketSession session = clientSessions.get(clientId);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
log.warn("[WebSocket] No session found for client {}", clientId);
|
log.warn("[WebSocket] No session found for client {}", clientId);
|
||||||
return CompletableFuture
|
return CompletableFuture.failedFuture(new IOException("No WebSocket session for client: " + clientId));
|
||||||
.failedFuture(new IOException("No WebSocket session for client: " + clientId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session.isOpen()) {
|
if (!session.isOpen()) {
|
||||||
@@ -140,8 +139,7 @@ public class WebSocketService extends TextWebSocketHandler {
|
|||||||
// Session aus der Map entfernen
|
// Session aus der Map entfernen
|
||||||
clientSessions.remove(clientId);
|
clientSessions.remove(clientId);
|
||||||
sessionToClient.remove(session.getId());
|
sessionToClient.remove(session.getId());
|
||||||
return CompletableFuture
|
return CompletableFuture.failedFuture(new IOException("WebSocket session closed for client: " + clientId));
|
||||||
.failedFuture(new IOException("WebSocket session closed for client: " + clientId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -154,6 +152,7 @@ public class WebSocketService extends TextWebSocketHandler {
|
|||||||
|
|
||||||
String wireJson = objectMapper.writeValueAsString(wireMessage);
|
String wireJson = objectMapper.writeValueAsString(wireMessage);
|
||||||
log.info("[WebSocket OUT] {} to client {} (session open: {})", topic, clientId, session.isOpen());
|
log.info("[WebSocket OUT] {} to client {} (session open: {})", topic, clientId, session.isOpen());
|
||||||
|
log.debug("[WebSocket OUT] {} -> {}", topic, wireJson);
|
||||||
|
|
||||||
sendToSession(session, wireJson);
|
sendToSession(session, wireJson);
|
||||||
log.debug("[WebSocket] Message sent successfully to client {}", clientId);
|
log.debug("[WebSocket] Message sent successfully to client {}", clientId);
|
||||||
|
|||||||
@@ -73,15 +73,14 @@ public final class MainLayout extends AppLayout {
|
|||||||
// Always build the drawer; keep references and toggle visibility on attach and
|
// Always build the drawer; keep references and toggle visibility on attach and
|
||||||
// after navigation
|
// after navigation
|
||||||
headerRef = createHeader();
|
headerRef = createHeader();
|
||||||
|
|
||||||
// Scroller für Navigation mit maximaler Höhe
|
// Scroller für Navigation mit maximaler Höhe
|
||||||
Component sideNav = createSideNav();
|
Component sideNav = createSideNav();
|
||||||
navRef = new Scroller(sideNav);
|
navRef = new Scroller(sideNav);
|
||||||
|
|
||||||
userMenuRef = createUserMenu();
|
userMenuRef = createUserMenu();
|
||||||
addToDrawer(headerRef, navRef, userMenuRef);
|
addToDrawer(headerRef, navRef, userMenuRef);
|
||||||
|
|
||||||
|
|
||||||
updateDrawerVisibility();
|
updateDrawerVisibility();
|
||||||
|
|
||||||
// Re-check on attach (new UI/session) and on every navigation cycle
|
// Re-check on attach (new UI/session) and on every navigation cycle
|
||||||
@@ -361,20 +360,12 @@ public final class MainLayout extends AppLayout {
|
|||||||
// Drawer-Layout anpassen nach kurzer Verzögerung
|
// Drawer-Layout anpassen nach kurzer Verzögerung
|
||||||
ui.access(() -> {
|
ui.access(() -> {
|
||||||
getElement().executeJs(
|
getElement().executeJs(
|
||||||
"setTimeout(() => {" +
|
"setTimeout(() => {" + " const drawer = this.shadowRoot?.querySelector('[part=drawer]');"
|
||||||
" const drawer = this.shadowRoot?.querySelector('[part=drawer]');" +
|
+ " if (drawer) {" + " drawer.style.display = 'flex';"
|
||||||
" if (drawer) {" +
|
+ " drawer.style.flexDirection = 'column';" + " drawer.style.height = '100vh';"
|
||||||
" drawer.style.display = 'flex';" +
|
+ " const scroller = drawer.querySelector('vaadin-scroller');" + " if (scroller) {"
|
||||||
" drawer.style.flexDirection = 'column';" +
|
+ " scroller.style.flex = '1 1 auto';" + " scroller.style.minHeight = '0';"
|
||||||
" drawer.style.height = '100vh';" +
|
+ " }" + " }" + "}, 100);");
|
||||||
" const scroller = drawer.querySelector('vaadin-scroller');" +
|
|
||||||
" if (scroller) {" +
|
|
||||||
" scroller.style.flex = '1 1 auto';" +
|
|
||||||
" scroller.style.minHeight = '0';" +
|
|
||||||
" }" +
|
|
||||||
" }" +
|
|
||||||
"}, 100);"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply user's preferred language immediately after login
|
// Apply user's preferred language immediately after login
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package de.assecutor.votianlt.service;
|
||||||
|
|
||||||
|
import de.assecutor.votianlt.ai.service.LlmRestClient;
|
||||||
|
import de.assecutor.votianlt.model.Language;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for translating text into all available languages. Uses LLM API for
|
||||||
|
* translations with caching.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class TranslationService {
|
||||||
|
|
||||||
|
private static final int MAX_CACHE_SIZE = 1000;
|
||||||
|
|
||||||
|
private final LlmRestClient llmRestClient;
|
||||||
|
|
||||||
|
// Cache: Original Text -> List of all translations (language -> text)
|
||||||
|
private final Map<String, List<Translation>> translationCache;
|
||||||
|
|
||||||
|
public TranslationService(LlmRestClient llmRestClient) {
|
||||||
|
this.llmRestClient = llmRestClient;
|
||||||
|
// LinkedHashMap with accessOrder=true for LRU behavior
|
||||||
|
this.translationCache = new LinkedHashMap<String, List<Translation>>(MAX_CACHE_SIZE, 0.75f, true) {
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<String, List<Translation>> eldest) {
|
||||||
|
if (size() > MAX_CACHE_SIZE) {
|
||||||
|
log.debug("[TranslationCache] Removing oldest entry, cache size: {}", size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a translation with language code and text.
|
||||||
|
*/
|
||||||
|
public record Translation(String language, String text) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all available languages for translation.
|
||||||
|
*/
|
||||||
|
public List<Language> getAvailableLanguages() {
|
||||||
|
return Arrays.asList(Language.DE, Language.EN, Language.FR, Language.ES, Language.TR, Language.PL, Language.RU,
|
||||||
|
Language.EE, Language.LV, Language.LT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given text into all available languages. Uses cache to avoid
|
||||||
|
* redundant LLM calls.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* the text to translate
|
||||||
|
* @return list of translations for all available languages
|
||||||
|
*/
|
||||||
|
public List<Translation> translateToAllLanguages(String text) {
|
||||||
|
if (text == null || text.isBlank()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cache first
|
||||||
|
List<Translation> cachedTranslations = translationCache.get(text);
|
||||||
|
if (cachedTranslations != null) {
|
||||||
|
log.debug("[TranslationCache] Cache hit for '{}'", text);
|
||||||
|
return cachedTranslations;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache miss - translate via LLM
|
||||||
|
log.debug("[TranslationCache] Cache miss for '{}', calling LLM", text);
|
||||||
|
List<Translation> translations = translateViaLlm(text);
|
||||||
|
|
||||||
|
// Store in cache
|
||||||
|
translationCache.put(text, translations);
|
||||||
|
log.debug("[TranslationCache] Stored translations for '{}', cache size: {}", text, translationCache.size());
|
||||||
|
|
||||||
|
return translations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates text to all languages via LLM.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* the text to translate
|
||||||
|
* @return list of translations for all languages
|
||||||
|
*/
|
||||||
|
private List<Translation> translateViaLlm(String text) {
|
||||||
|
List<Translation> translations = new ArrayList<>();
|
||||||
|
|
||||||
|
// Translate to each language
|
||||||
|
for (Language lang : getAvailableLanguages()) {
|
||||||
|
String translatedText = translateTextToLanguage(text, lang);
|
||||||
|
translations.add(new Translation(lang.name().toLowerCase(), translatedText));
|
||||||
|
}
|
||||||
|
|
||||||
|
return translations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates a single text to the target language using LLM.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* the text to translate
|
||||||
|
* @param targetLanguage
|
||||||
|
* the target language
|
||||||
|
* @return the translated text
|
||||||
|
*/
|
||||||
|
private String translateTextToLanguage(String text, Language targetLanguage) {
|
||||||
|
String languageName = getLanguageName(targetLanguage);
|
||||||
|
|
||||||
|
String systemPrompt = "You are a professional translator. Translate the given text to " + languageName
|
||||||
|
+ ". Return ONLY the translated text, nothing else. Do not add quotes or explanations.";
|
||||||
|
|
||||||
|
try {
|
||||||
|
String result = llmRestClient.chat(systemPrompt, text, 0.3, 500);
|
||||||
|
|
||||||
|
if (result != null && !result.isBlank()) {
|
||||||
|
// Clean up the response (remove quotes if present)
|
||||||
|
result = result.trim();
|
||||||
|
if (result.startsWith("\"") && result.endsWith("\"")) {
|
||||||
|
result = result.substring(1, result.length() - 1);
|
||||||
|
}
|
||||||
|
return result.trim();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Translation failed for {}: {}", languageName, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: return original text
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the human-readable language name.
|
||||||
|
*/
|
||||||
|
private String getLanguageName(Language language) {
|
||||||
|
return switch (language) {
|
||||||
|
case DE -> "German";
|
||||||
|
case EN -> "English";
|
||||||
|
case FR -> "French";
|
||||||
|
case ES -> "Spanish";
|
||||||
|
case TR -> "Turkish";
|
||||||
|
case PL -> "Polish";
|
||||||
|
case RU -> "Russian";
|
||||||
|
case EE -> "Estonian";
|
||||||
|
case LV -> "Latvian";
|
||||||
|
case LT -> "Lithuanian";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns cache statistics for monitoring.
|
||||||
|
*/
|
||||||
|
public int getCacheSize() {
|
||||||
|
return translationCache.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the translation cache.
|
||||||
|
*/
|
||||||
|
public void clearCache() {
|
||||||
|
translationCache.clear();
|
||||||
|
log.info("[TranslationCache] Cache cleared");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user