feat(back-end front-end): traslate alt and description images
This commit is contained in:
@@ -24,7 +24,8 @@ public class PublicMediaController {
|
||||
|
||||
@GetMapping("/usages")
|
||||
public ResponseEntity<List<PublicMediaUsageDto>> getUsageMedia(@RequestParam String usageType,
|
||||
@RequestParam String usageKey) {
|
||||
return ResponseEntity.ok(publicMediaQueryService.getUsageMedia(usageType, usageKey));
|
||||
@RequestParam String usageKey,
|
||||
@RequestParam(required = false) String lang) {
|
||||
return ResponseEntity.ok(publicMediaQueryService.getUsageMedia(usageType, usageKey, lang));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.printcalculator.dto;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.Map;
|
||||
|
||||
public class AdminCreateMediaUsageRequest {
|
||||
private String usageType;
|
||||
@@ -10,6 +11,7 @@ public class AdminCreateMediaUsageRequest {
|
||||
private Integer sortOrder;
|
||||
private Boolean isPrimary;
|
||||
private Boolean isActive;
|
||||
private Map<String, MediaTextTranslationDto> translations;
|
||||
|
||||
public String getUsageType() {
|
||||
return usageType;
|
||||
@@ -66,4 +68,12 @@ public class AdminCreateMediaUsageRequest {
|
||||
public void setIsActive(Boolean active) {
|
||||
isActive = active;
|
||||
}
|
||||
|
||||
public Map<String, MediaTextTranslationDto> getTranslations() {
|
||||
return translations;
|
||||
}
|
||||
|
||||
public void setTranslations(Map<String, MediaTextTranslationDto> translations) {
|
||||
this.translations = translations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.printcalculator.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AdminMediaUsageDto {
|
||||
@@ -12,6 +13,7 @@ public class AdminMediaUsageDto {
|
||||
private Integer sortOrder;
|
||||
private Boolean isPrimary;
|
||||
private Boolean isActive;
|
||||
private Map<String, MediaTextTranslationDto> translations;
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
public UUID getId() {
|
||||
@@ -78,6 +80,14 @@ public class AdminMediaUsageDto {
|
||||
isActive = active;
|
||||
}
|
||||
|
||||
public Map<String, MediaTextTranslationDto> getTranslations() {
|
||||
return translations;
|
||||
}
|
||||
|
||||
public void setTranslations(Map<String, MediaTextTranslationDto> translations) {
|
||||
this.translations = translations;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.printcalculator.dto;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.Map;
|
||||
|
||||
public class AdminUpdateMediaUsageRequest {
|
||||
private String usageType;
|
||||
@@ -10,6 +11,7 @@ public class AdminUpdateMediaUsageRequest {
|
||||
private Integer sortOrder;
|
||||
private Boolean isPrimary;
|
||||
private Boolean isActive;
|
||||
private Map<String, MediaTextTranslationDto> translations;
|
||||
|
||||
public String getUsageType() {
|
||||
return usageType;
|
||||
@@ -66,4 +68,12 @@ public class AdminUpdateMediaUsageRequest {
|
||||
public void setIsActive(Boolean active) {
|
||||
isActive = active;
|
||||
}
|
||||
|
||||
public Map<String, MediaTextTranslationDto> getTranslations() {
|
||||
return translations;
|
||||
}
|
||||
|
||||
public void setTranslations(Map<String, MediaTextTranslationDto> translations) {
|
||||
this.translations = translations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.printcalculator.dto;
|
||||
|
||||
public class MediaTextTranslationDto {
|
||||
private String title;
|
||||
private String altText;
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getAltText() {
|
||||
return altText;
|
||||
}
|
||||
|
||||
public void setAltText(String altText) {
|
||||
this.altText = altText;
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,30 @@ public class MediaUsage {
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive;
|
||||
|
||||
@Column(name = "title_it", length = Integer.MAX_VALUE)
|
||||
private String titleIt;
|
||||
|
||||
@Column(name = "title_en", length = Integer.MAX_VALUE)
|
||||
private String titleEn;
|
||||
|
||||
@Column(name = "title_de", length = Integer.MAX_VALUE)
|
||||
private String titleDe;
|
||||
|
||||
@Column(name = "title_fr", length = Integer.MAX_VALUE)
|
||||
private String titleFr;
|
||||
|
||||
@Column(name = "alt_text_it", length = Integer.MAX_VALUE)
|
||||
private String altTextIt;
|
||||
|
||||
@Column(name = "alt_text_en", length = Integer.MAX_VALUE)
|
||||
private String altTextEn;
|
||||
|
||||
@Column(name = "alt_text_de", length = Integer.MAX_VALUE)
|
||||
private String altTextDe;
|
||||
|
||||
@Column(name = "alt_text_fr", length = Integer.MAX_VALUE)
|
||||
private String altTextFr;
|
||||
|
||||
@ColumnDefault("now()")
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private OffsetDateTime createdAt;
|
||||
@@ -121,6 +145,70 @@ public class MediaUsage {
|
||||
isActive = active;
|
||||
}
|
||||
|
||||
public String getTitleIt() {
|
||||
return titleIt;
|
||||
}
|
||||
|
||||
public void setTitleIt(String titleIt) {
|
||||
this.titleIt = titleIt;
|
||||
}
|
||||
|
||||
public String getTitleEn() {
|
||||
return titleEn;
|
||||
}
|
||||
|
||||
public void setTitleEn(String titleEn) {
|
||||
this.titleEn = titleEn;
|
||||
}
|
||||
|
||||
public String getTitleDe() {
|
||||
return titleDe;
|
||||
}
|
||||
|
||||
public void setTitleDe(String titleDe) {
|
||||
this.titleDe = titleDe;
|
||||
}
|
||||
|
||||
public String getTitleFr() {
|
||||
return titleFr;
|
||||
}
|
||||
|
||||
public void setTitleFr(String titleFr) {
|
||||
this.titleFr = titleFr;
|
||||
}
|
||||
|
||||
public String getAltTextIt() {
|
||||
return altTextIt;
|
||||
}
|
||||
|
||||
public void setAltTextIt(String altTextIt) {
|
||||
this.altTextIt = altTextIt;
|
||||
}
|
||||
|
||||
public String getAltTextEn() {
|
||||
return altTextEn;
|
||||
}
|
||||
|
||||
public void setAltTextEn(String altTextEn) {
|
||||
this.altTextEn = altTextEn;
|
||||
}
|
||||
|
||||
public String getAltTextDe() {
|
||||
return altTextDe;
|
||||
}
|
||||
|
||||
public void setAltTextDe(String altTextDe) {
|
||||
this.altTextDe = altTextDe;
|
||||
}
|
||||
|
||||
public String getAltTextFr() {
|
||||
return altTextFr;
|
||||
}
|
||||
|
||||
public void setAltTextFr(String altTextFr) {
|
||||
this.altTextFr = altTextFr;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
@@ -128,4 +216,58 @@ public class MediaUsage {
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getTitleForLanguage(String language) {
|
||||
if (language == null) {
|
||||
return null;
|
||||
}
|
||||
return switch (language.trim().toLowerCase()) {
|
||||
case "it" -> titleIt;
|
||||
case "en" -> titleEn;
|
||||
case "de" -> titleDe;
|
||||
case "fr" -> titleFr;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public void setTitleForLanguage(String language, String value) {
|
||||
if (language == null) {
|
||||
return;
|
||||
}
|
||||
switch (language.trim().toLowerCase()) {
|
||||
case "it" -> titleIt = value;
|
||||
case "en" -> titleEn = value;
|
||||
case "de" -> titleDe = value;
|
||||
case "fr" -> titleFr = value;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getAltTextForLanguage(String language) {
|
||||
if (language == null) {
|
||||
return null;
|
||||
}
|
||||
return switch (language.trim().toLowerCase()) {
|
||||
case "it" -> altTextIt;
|
||||
case "en" -> altTextEn;
|
||||
case "de" -> altTextDe;
|
||||
case "fr" -> altTextFr;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public void setAltTextForLanguage(String language, String value) {
|
||||
if (language == null) {
|
||||
return;
|
||||
}
|
||||
switch (language.trim().toLowerCase()) {
|
||||
case "it" -> altTextIt = value;
|
||||
case "en" -> altTextEn = value;
|
||||
case "de" -> altTextDe = value;
|
||||
case "fr" -> altTextFr = value;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.printcalculator.dto.AdminCreateMediaUsageRequest;
|
||||
import com.printcalculator.dto.AdminMediaAssetDto;
|
||||
import com.printcalculator.dto.AdminMediaUsageDto;
|
||||
import com.printcalculator.dto.AdminMediaVariantDto;
|
||||
import com.printcalculator.dto.MediaTextTranslationDto;
|
||||
import com.printcalculator.dto.AdminUpdateMediaAssetRequest;
|
||||
import com.printcalculator.dto.AdminUpdateMediaUsageRequest;
|
||||
import com.printcalculator.entity.MediaAsset;
|
||||
@@ -78,6 +79,7 @@ public class AdminMediaControllerService {
|
||||
private static final Set<String> ALLOWED_UPLOAD_MIME_TYPES = Set.of(
|
||||
"image/jpeg", "image/png", "image/webp"
|
||||
);
|
||||
private static final List<String> SUPPORTED_MEDIA_LANGUAGES = List.of("it", "en", "de", "fr");
|
||||
private static final Map<String, String> GENERATED_FORMAT_MIME_TYPES = Map.of(
|
||||
FORMAT_JPEG, "image/jpeg",
|
||||
FORMAT_WEBP, "image/webp",
|
||||
@@ -261,6 +263,7 @@ public class AdminMediaControllerService {
|
||||
String usageType = requireUsageType(payload.getUsageType());
|
||||
String usageKey = requireUsageKey(payload.getUsageKey());
|
||||
boolean isPrimary = Boolean.TRUE.equals(payload.getIsPrimary());
|
||||
Map<String, MediaTextTranslationDto> translations = requireTranslations(payload.getTranslations());
|
||||
|
||||
if (isPrimary) {
|
||||
unsetPrimaryForScope(usageType, usageKey, payload.getOwnerId(), null);
|
||||
@@ -275,6 +278,7 @@ public class AdminMediaControllerService {
|
||||
usage.setIsPrimary(isPrimary);
|
||||
usage.setIsActive(payload.getIsActive() == null || payload.getIsActive());
|
||||
usage.setCreatedAt(OffsetDateTime.now());
|
||||
applyTranslations(usage, translations);
|
||||
|
||||
MediaUsage saved = mediaUsageRepository.save(usage);
|
||||
return toUsageDto(saved);
|
||||
@@ -309,6 +313,9 @@ public class AdminMediaControllerService {
|
||||
if (payload.getIsPrimary() != null) {
|
||||
usage.setIsPrimary(payload.getIsPrimary());
|
||||
}
|
||||
if (payload.getTranslations() != null) {
|
||||
applyTranslations(usage, requireTranslations(payload.getTranslations()));
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(usage.getIsPrimary())) {
|
||||
unsetPrimaryForScope(usage.getUsageType(), usage.getUsageKey(), usage.getOwnerId(), usage.getId());
|
||||
@@ -525,6 +532,7 @@ public class AdminMediaControllerService {
|
||||
dto.setSortOrder(usage.getSortOrder());
|
||||
dto.setIsPrimary(usage.getIsPrimary());
|
||||
dto.setIsActive(usage.getIsActive());
|
||||
dto.setTranslations(extractTranslations(usage));
|
||||
dto.setCreatedAt(usage.getCreatedAt());
|
||||
return dto;
|
||||
}
|
||||
@@ -639,6 +647,96 @@ public class AdminMediaControllerService {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private Map<String, MediaTextTranslationDto> requireTranslations(Map<String, MediaTextTranslationDto> translations) {
|
||||
if (translations == null || translations.isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "translations are required.");
|
||||
}
|
||||
|
||||
Map<String, MediaTextTranslationDto> normalized = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, MediaTextTranslationDto> entry : translations.entrySet()) {
|
||||
String language = normalizeTranslationLanguage(entry.getKey());
|
||||
if (normalized.containsKey(language)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Duplicate translation language: " + language + ".");
|
||||
}
|
||||
normalized.put(language, entry.getValue());
|
||||
}
|
||||
|
||||
if (!normalized.keySet().equals(new LinkedHashSet<>(SUPPORTED_MEDIA_LANGUAGES))) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"translations must include exactly: " + String.join(", ", SUPPORTED_MEDIA_LANGUAGES) + "."
|
||||
);
|
||||
}
|
||||
|
||||
LinkedHashMap<String, MediaTextTranslationDto> result = new LinkedHashMap<>();
|
||||
for (String language : SUPPORTED_MEDIA_LANGUAGES) {
|
||||
MediaTextTranslationDto translation = normalized.get(language);
|
||||
if (translation == null) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing translation for language " + language + ".");
|
||||
}
|
||||
|
||||
String title = normalizeRequiredTranslationValue(translation.getTitle(), language, "title");
|
||||
String altText = normalizeRequiredTranslationValue(translation.getAltText(), language, "altText");
|
||||
|
||||
MediaTextTranslationDto dto = new MediaTextTranslationDto();
|
||||
dto.setTitle(title);
|
||||
dto.setAltText(altText);
|
||||
result.put(language, dto);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String normalizeTranslationLanguage(String language) {
|
||||
if (language == null || language.isBlank()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Translation language is required.");
|
||||
}
|
||||
String normalized = language.trim().toLowerCase(Locale.ROOT);
|
||||
if (!SUPPORTED_MEDIA_LANGUAGES.contains(normalized)) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Unsupported translation language: " + normalized + "."
|
||||
);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String normalizeRequiredTranslationValue(String value, String language, String fieldName) {
|
||||
String normalized = normalizeText(value);
|
||||
if (normalized == null || normalized.isBlank()) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Translation " + fieldName + " is required for language " + language + "."
|
||||
);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private void applyTranslations(MediaUsage usage, Map<String, MediaTextTranslationDto> translations) {
|
||||
for (String language : SUPPORTED_MEDIA_LANGUAGES) {
|
||||
MediaTextTranslationDto translation = translations.get(language);
|
||||
usage.setTitleForLanguage(language, translation.getTitle());
|
||||
usage.setAltTextForLanguage(language, translation.getAltText());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, MediaTextTranslationDto> extractTranslations(MediaUsage usage) {
|
||||
LinkedHashMap<String, MediaTextTranslationDto> translations = new LinkedHashMap<>();
|
||||
String fallbackTitle = usage.getMediaAsset() != null ? usage.getMediaAsset().getTitle() : null;
|
||||
String fallbackAltText = usage.getMediaAsset() != null ? usage.getMediaAsset().getAltText() : null;
|
||||
|
||||
for (String language : SUPPORTED_MEDIA_LANGUAGES) {
|
||||
MediaTextTranslationDto dto = new MediaTextTranslationDto();
|
||||
dto.setTitle(firstNonBlank(usage.getTitleForLanguage(language), fallbackTitle));
|
||||
dto.setAltText(firstNonBlank(usage.getAltTextForLanguage(language), fallbackAltText));
|
||||
translations.put(language, dto);
|
||||
}
|
||||
return translations;
|
||||
}
|
||||
|
||||
private String firstNonBlank(String preferred, String fallback) {
|
||||
return StringUtils.hasText(preferred) ? preferred : normalizeText(fallback);
|
||||
}
|
||||
|
||||
private String normalizeText(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class MediaFfmpegService {
|
||||
private final Set<String> availableEncoders;
|
||||
|
||||
public MediaFfmpegService(@Value("${media.ffmpeg.path:ffmpeg}") String ffmpegPath) {
|
||||
this.ffmpegExecutable = sanitizeExecutable(ffmpegPath);
|
||||
this.ffmpegExecutable = resolveExecutable(ffmpegPath);
|
||||
this.availableEncoders = Collections.unmodifiableSet(loadAvailableEncoders());
|
||||
}
|
||||
|
||||
@@ -147,7 +147,11 @@ public class MediaFfmpegService {
|
||||
}
|
||||
return parseAvailableEncoders(output);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Unable to inspect FFmpeg encoders. Falling back to empty encoder list.", e);
|
||||
logger.warn(
|
||||
"Unable to inspect FFmpeg encoders for executable '{}'. Falling back to empty encoder list. {}",
|
||||
ffmpegExecutable,
|
||||
e.getMessage()
|
||||
);
|
||||
return Set.of();
|
||||
}
|
||||
}
|
||||
@@ -186,6 +190,34 @@ public class MediaFfmpegService {
|
||||
}
|
||||
}
|
||||
|
||||
static String resolveExecutable(String configuredExecutable) {
|
||||
String candidate = sanitizeExecutable(configuredExecutable);
|
||||
|
||||
try {
|
||||
Path configuredPath = Path.of(candidate);
|
||||
if (!configuredPath.isAbsolute()) {
|
||||
return candidate;
|
||||
}
|
||||
if (Files.isExecutable(configuredPath)) {
|
||||
return configuredPath.toString();
|
||||
}
|
||||
|
||||
Path filename = configuredPath.getFileName();
|
||||
String fallbackExecutable = filename == null ? null : filename.toString();
|
||||
if (fallbackExecutable != null && !fallbackExecutable.isBlank()) {
|
||||
logger.warn(
|
||||
"Configured FFmpeg executable '{}' not found or not executable. Falling back to '{}' from PATH.",
|
||||
configuredPath,
|
||||
fallbackExecutable
|
||||
);
|
||||
return fallbackExecutable;
|
||||
}
|
||||
return candidate;
|
||||
} catch (InvalidPathException e) {
|
||||
throw new IllegalArgumentException("media.ffmpeg.path is not a valid executable path.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path sanitizeMediaPath(Path path, String label, boolean requireExistingFile) throws IOException {
|
||||
if (path == null) {
|
||||
throw new IllegalArgumentException("Media " + label + " path is required.");
|
||||
|
||||
@@ -31,6 +31,7 @@ public class PublicMediaQueryService {
|
||||
private static final String FORMAT_JPEG = "JPEG";
|
||||
private static final String FORMAT_WEBP = "WEBP";
|
||||
private static final String FORMAT_AVIF = "AVIF";
|
||||
private static final List<String> SUPPORTED_MEDIA_LANGUAGES = List.of("it", "en", "de", "fr");
|
||||
|
||||
private final MediaUsageRepository mediaUsageRepository;
|
||||
private final MediaVariantRepository mediaVariantRepository;
|
||||
@@ -44,9 +45,10 @@ public class PublicMediaQueryService {
|
||||
this.mediaStorageService = mediaStorageService;
|
||||
}
|
||||
|
||||
public List<PublicMediaUsageDto> getUsageMedia(String usageType, String usageKey) {
|
||||
public List<PublicMediaUsageDto> getUsageMedia(String usageType, String usageKey, String language) {
|
||||
String normalizedUsageType = normalizeUsageType(usageType);
|
||||
String normalizedUsageKey = normalizeUsageKey(usageKey);
|
||||
String normalizedLanguage = normalizeLanguage(language);
|
||||
|
||||
List<MediaUsage> usages = mediaUsageRepository
|
||||
.findByUsageTypeAndUsageKeyAndIsActiveTrueOrderBySortOrderAscCreatedAtAsc(
|
||||
@@ -80,7 +82,8 @@ public class PublicMediaQueryService {
|
||||
return usages.stream()
|
||||
.map(usage -> toDto(
|
||||
usage,
|
||||
variantsByAssetId.getOrDefault(usage.getMediaAsset().getId(), List.of())
|
||||
variantsByAssetId.getOrDefault(usage.getMediaAsset().getId(), List.of()),
|
||||
normalizedLanguage
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
@@ -92,7 +95,7 @@ public class PublicMediaQueryService {
|
||||
&& VISIBILITY_PUBLIC.equals(asset.getVisibility());
|
||||
}
|
||||
|
||||
private PublicMediaUsageDto toDto(MediaUsage usage, List<MediaVariant> variants) {
|
||||
private PublicMediaUsageDto toDto(MediaUsage usage, List<MediaVariant> variants, String language) {
|
||||
Map<String, Map<String, MediaVariant>> variantsByPresetAndFormat = variants.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
MediaVariant::getVariantName,
|
||||
@@ -101,8 +104,8 @@ public class PublicMediaQueryService {
|
||||
|
||||
PublicMediaUsageDto dto = new PublicMediaUsageDto();
|
||||
dto.setMediaAssetId(usage.getMediaAsset().getId());
|
||||
dto.setTitle(usage.getMediaAsset().getTitle());
|
||||
dto.setAltText(usage.getMediaAsset().getAltText());
|
||||
dto.setTitle(resolveLocalizedValue(usage.getTitleForLanguage(language), usage.getMediaAsset().getTitle()));
|
||||
dto.setAltText(resolveLocalizedValue(usage.getAltTextForLanguage(language), usage.getMediaAsset().getAltText()));
|
||||
dto.setUsageType(usage.getUsageType());
|
||||
dto.setUsageKey(usage.getUsageKey());
|
||||
dto.setSortOrder(usage.getSortOrder());
|
||||
@@ -145,4 +148,23 @@ public class PublicMediaQueryService {
|
||||
}
|
||||
return usageKey.trim();
|
||||
}
|
||||
|
||||
private String normalizeLanguage(String language) {
|
||||
if (language == null || language.isBlank()) {
|
||||
return "it";
|
||||
}
|
||||
|
||||
String normalized = language.trim().toLowerCase(Locale.ROOT);
|
||||
return SUPPORTED_MEDIA_LANGUAGES.contains(normalized) ? normalized : "it";
|
||||
}
|
||||
|
||||
private String resolveLocalizedValue(String preferred, String fallback) {
|
||||
if (preferred != null && !preferred.isBlank()) {
|
||||
return preferred;
|
||||
}
|
||||
if (fallback != null && !fallback.isBlank()) {
|
||||
return fallback.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user