Skip to content

Commit 9558bba

Browse files
committed
feat: add translation of templates with defaultLang env variable
1 parent ff0919b commit 9558bba

File tree

11 files changed

+723
-3
lines changed

11 files changed

+723
-3
lines changed

src/main/java/es/in2/vcverifier/config/FrontendConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public String getFaviconSrc() {
4646
return defaultIfBlank(properties.faviconSrc(), "dome_logo_favicon.png");
4747
}
4848

49+
public String getDefaultLang() { return defaultIfBlank(properties.defaultLang(), "en"); }
50+
4951
private String defaultIfBlank(String value, String defaultValue) {
5052
return (value == null || value.trim().isEmpty()) ? defaultValue : value;
5153
}

src/main/java/es/in2/vcverifier/config/properties/FrontendProperties.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ public record FrontendProperties(
1313
@NotNull @NestedConfigurationProperty Urls urls,
1414
@NestedConfigurationProperty Colors colors,
1515
@NotBlank String logoSrc,
16-
String faviconSrc) {
16+
String faviconSrc,
17+
String defaultLang) {
1718

1819
public record Urls(
1920
@NotBlank @URL String onboarding,

src/main/java/es/in2/vcverifier/controller/ClientErrorController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public String showErrorPage(@RequestParam("errorCode") String errorCode,
3535
model.addAttribute("secondaryContrast", frontendConfig.getSecondaryContrastColor());
3636
model.addAttribute("faviconSrc", frontendConfig.getFaviconSrc());
3737
// Return the view name
38-
return "client-authentication-error";
38+
return "client-authentication-error-" + frontendConfig.getDefaultLang();
3939
}
4040

4141
}

src/main/java/es/in2/vcverifier/controller/LoginQrController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public String showQrLogin(@RequestParam("authRequest") String authRequest, @Requ
4949
} catch (Exception e) {
5050
throw new QRCodeGenerationException(e.getMessage());
5151
}
52-
return "login";
52+
return "login-" + frontendConfig.getDefaultLang();
5353
}
5454

5555
private String generateQRCodeImageBase64(String barcodeText) {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package es.in2.vcverifier.service;
2+
3+
public interface TranslationService {
4+
public String getLocale();
5+
public String translate(String code, Object... args);
6+
}
7+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package es.in2.vcverifier.service.impl;
2+
3+
import es.in2.vcverifier.config.FrontendConfig;
4+
import es.in2.vcverifier.service.TranslationService;
5+
import lombok.RequiredArgsConstructor;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.context.MessageSource;
8+
import org.springframework.context.NoSuchMessageException;
9+
import org.springframework.stereotype.Service;
10+
11+
import java.util.List;
12+
import java.util.Locale;
13+
14+
@Slf4j
15+
@Service
16+
@RequiredArgsConstructor
17+
public class TranslationServiceImpl implements TranslationService {
18+
19+
private final FrontendConfig frontendConfig;
20+
private final MessageSource messageSource;
21+
22+
private static final List<String> SUPPORTED_LANGS = List.of("en", "es");
23+
24+
@Override
25+
public String getLocale() {
26+
String locale = frontendConfig.getDefaultLang();
27+
log.info("Default lang from config: {}", locale);
28+
29+
if (locale == null || locale.isBlank()) {
30+
log.warn("No default language configured. Using fallback: 'en'");
31+
return "en";
32+
}
33+
34+
locale = locale.trim().toLowerCase();
35+
36+
if (!SUPPORTED_LANGS.contains(locale)) {
37+
log.warn("Unsupported language '{}'. Falling back to 'en'", locale);
38+
return "en";
39+
}
40+
log.info("Using locale: {}", locale);
41+
return locale;
42+
}
43+
44+
@Override
45+
public String translate(String code, Object... args) {
46+
var locale = Locale.forLanguageTag(getLocale());
47+
try {
48+
log.info("code: {}", code);
49+
log.info("args: {}", args);
50+
log.info("locale: {}", locale);
51+
return messageSource.getMessage(code, args, locale);
52+
} catch (NoSuchMessageException e) {
53+
log.warn("Message code '{}' not found for locale {}. Falling back to code.", code, locale);
54+
return code;
55+
}
56+
}
57+
}
58+

src/main/resources/application.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ verifier:
7474
logoSrc:
7575
# Placeholder for favicon source path (OPTIONAL)
7676
faviconSrc:
77+
# Default language for visible texts (OPTIONAL)
78+
defaultLang: en
7779
backend:
7880
# Placeholder for backend URL (REQUIRED)
7981
url:
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<!DOCTYPE html>
2+
<html xmlns:th="http://www.thymeleaf.org" lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Error de autenticación</title>
6+
<link href="https://fonts.googleapis.com/css2?family=Blinker:wght@100;200;300;400;600;700;800;900&display=swap" rel="stylesheet">
7+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
8+
<link rel="icon" th:href="@{/img/{file}(file=${faviconSrc})}" type="image/png">
9+
<style>
10+
:root {
11+
--fore-color-primary: [[${primaryContrast}]];
12+
--bg-color-primary:[[${secondary}]];
13+
--highlight-color: [[${secondaryContrast}]];
14+
--error-box-background: #e0e0e0;
15+
}
16+
17+
html, body {
18+
height: 100%;
19+
margin: 0;
20+
padding: 0;
21+
}
22+
23+
body {
24+
font-family: 'Blinker', sans-serif;
25+
background-color: var(--bg-color-primary);
26+
color: var(--fore-color-primary);
27+
display: flex;
28+
justify-content: center;
29+
align-items: center;
30+
}
31+
32+
.container {
33+
text-align: center;
34+
padding: 3rem;
35+
border: 3px solid var(--highlight-color);
36+
border-radius: 8px;
37+
background-color: var(--bg-color-primary);
38+
max-width: 800px;
39+
}
40+
41+
.title {
42+
font-size: 4rem;
43+
font-weight: bold;
44+
color: var(--fore-color-primary);
45+
margin-bottom: 2rem;
46+
}
47+
48+
.error-message {
49+
font-size: 1.75rem;
50+
color: var(--fore-color-primary);
51+
margin-bottom: 2rem;
52+
text-align: justify;
53+
text-justify: inter-word;
54+
}
55+
56+
.info-box {
57+
position: relative;
58+
display: inline-flex;
59+
padding: 1.5rem;
60+
border: 2px solid #333;
61+
border-radius: 8px;
62+
background-color: var(--error-box-background);
63+
text-align: left;
64+
max-width: 90%;
65+
}
66+
67+
.info-box-content {
68+
flex-grow: 1;
69+
margin-right: 1.5rem;
70+
}
71+
72+
.info-box-item {
73+
font-size: 1.25rem;
74+
margin-bottom: 1rem;
75+
color: #333;
76+
}
77+
78+
.info-box-item:last-child {
79+
margin-bottom: 0;
80+
}
81+
82+
.shortened-url {
83+
display: inline-block;
84+
overflow: hidden;
85+
text-overflow: ellipsis;
86+
white-space: nowrap;
87+
max-width: 400px;
88+
vertical-align: middle;
89+
}
90+
91+
.copy-button {
92+
position: absolute;
93+
top: 5px;
94+
right: 5px;
95+
background-color: var(--error-box-background);
96+
color: #333;
97+
border: none;
98+
padding: 0.3rem;
99+
border-radius: 4px;
100+
cursor: pointer;
101+
font-size: 1.4rem;
102+
transition: background-color 0.3s, color 0.3s, border-color 0.3s;
103+
display: flex;
104+
align-items: center;
105+
justify-content: center;
106+
}
107+
108+
.copy-button:hover {
109+
background-color: #333;
110+
color: var(--error-box-background);
111+
}
112+
113+
.copy-icon {
114+
margin: 0;
115+
font-size: 1.3rem; /* Slightly larger icon size */
116+
}
117+
118+
.help {
119+
font-size: 1.25rem;
120+
color: var(--fore-color-primary);
121+
margin-top: 2rem;
122+
}
123+
124+
.help a {
125+
color: var(--highlight-color);
126+
text-decoration: none;
127+
}
128+
129+
.help a:hover {
130+
text-decoration: underline;
131+
}
132+
</style>
133+
</head>
134+
<body>
135+
<div class="container">
136+
<div class="title">Error de Autenticación</div>
137+
<div class="error-message" th:text="${errorMessage}">
138+
El alcance solicitado no contiene 'learcredential'. Actualmente, solo admitimos este alcance y 'email', 'profile' como opcionales.
139+
</div>
140+
<div class="info-box">
141+
<div class="info-box-content">
142+
<div class="info-box-item">
143+
<p><strong>Código de Error</strong>: <span th:text="${errorCode}">627d4af8-2ea8-4069-b3c2-b3fdaae3958d</span></p>
144+
</div>
145+
<div class="info-box-item">
146+
<p><strong>Cliente</strong>: <span th:text="${clientUrl}">a2ef4ab9-737d-485f-85bb-278764e7088b</span></p>
147+
</div>
148+
<div class="info-box-item">
149+
<p><strong>Solicitud Original</strong>: <span class="shortened-url" th:utext="${originalRequestURL}">http://localhost:9000/oidc/authorize?...</span></p>
150+
</div>
151+
</div>
152+
<div class="copy-button" onclick="copyInfoBoxToClipboard()">
153+
<i class="fas fa-clipboard copy-icon"></i>
154+
</div>
155+
</div>
156+
<div class="help">
157+
¿Necesitas ayuda? <a th:href="${supportUri}">Contáctanos</a> y proporciónanos la información anterior para que podamos asistirte.
158+
</div>
159+
</div>
160+
161+
<script th:inline="javascript">
162+
/*<![CDATA[*/
163+
let originalUrl = /*[[${originalRequestURL}]]*/ "";
164+
/*]]>*/
165+
</script>
166+
167+
<script>
168+
function copyInfoBoxToClipboard() {
169+
// Select the text of each field
170+
const errorCode = document.querySelector('.info-box-item:nth-child(1) span').innerText;
171+
const clientUrl = document.querySelector('.info-box-item:nth-child(2) span').innerText;
172+
173+
// Concatenating all the information to copy
174+
const fullText = `Código de Error: ${errorCode}\nCliente: ${clientUrl}\nSolicitud Original: ${originalUrl}`;
175+
176+
// Copy to clipboard
177+
navigator.clipboard.writeText(fullText).then(() => {
178+
const copyButton = document.querySelector('.copy-button');
179+
copyButton.style.backgroundColor = 'var(--error-box-background)';
180+
copyButton.style.color = '#333';
181+
}).catch(err => {
182+
console.error('Failed to copy the info box: ', err);
183+
});
184+
}
185+
</script>
186+
187+
</body>
188+
</html>

0 commit comments

Comments
 (0)