Review languages and locales; fix start/end dates and timings formats
This commit is contained in:
@@ -144,6 +144,12 @@
|
|||||||
<artifactId>jakarta.mail</artifactId>
|
<artifactId>jakarta.mail</artifactId>
|
||||||
<version>1.6.7</version>
|
<version>1.6.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- utils -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.12.0</version>
|
||||||
|
</dependency>
|
||||||
<!-- auth -->
|
<!-- auth -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.pac4j</groupId>
|
<groupId>org.pac4j</groupId>
|
||||||
|
@@ -21,6 +21,14 @@ class TranslationTool {
|
|||||||
}.let { Pair(iso, it) }
|
}.let { Pair(iso, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val defaultCountry = Translator.providedLanguages.associate { iso ->
|
||||||
|
when (iso) {
|
||||||
|
"en" -> "gb"
|
||||||
|
"zh" -> "cn"
|
||||||
|
else -> iso
|
||||||
|
}.let { Pair(iso, it) }
|
||||||
|
}
|
||||||
|
|
||||||
fun url(request: HttpServletRequest, lang: String): String {
|
fun url(request: HttpServletRequest, lang: String): String {
|
||||||
val out = StringBuilder()
|
val out = StringBuilder()
|
||||||
out.append(request.requestURL.replaceFirst(Regex("://"), "://$lang/"))
|
out.append(request.requestURL.replaceFirst(Regex("://"), "://$lang/"))
|
||||||
@@ -29,7 +37,12 @@ class TranslationTool {
|
|||||||
return out.toString()
|
return out.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun datepickerLocale(language: String, locale: String) =
|
||||||
|
if (datepickerLocales.contains(locale)) locale
|
||||||
|
else language
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val datepickerLocales = setOf("ar-DZ", "ar", "ar-TN", "az", "bg", "bm", "bn", "br", "bs", "ca", "cs", "cy", "da", "de", "el", "en-AU", "en-CA", "en-GB", "en-IE", "en-NZ", "en-ZA", "eo", "es", "et", "eu", "fa", "fi", "fo", "fr-CH", "fr", "gl", "he", "hi", "hr", "hu", "hy", "id", "is", "it-CH", "it", "ja", "ka", "kk", "km", "ko", "lt", "lv", "me", "mk", "mn", "mr", "ms", "nl-BE", "nl", "no", "oc", "pl", "pt-BR", "pt", "ro", "ru", "si", "sk", "sl", "sq", "sr", "sr-latn", "sv", "sw", "ta", "tg", "th", "tk", "tr", "uk", "uz-cyrl", "uz-latn", "vi", "zh-CN", "zh-TW")
|
||||||
val translator = ThreadLocal<Translator>()
|
val translator = ThreadLocal<Translator>()
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -2,8 +2,10 @@ package org.jeudego.pairgoth.web
|
|||||||
|
|
||||||
import org.jeudego.pairgoth.util.Translator
|
import org.jeudego.pairgoth.util.Translator
|
||||||
import org.jeudego.pairgoth.util.Translator.Companion.defaultLanguage
|
import org.jeudego.pairgoth.util.Translator.Companion.defaultLanguage
|
||||||
|
import org.jeudego.pairgoth.util.Translator.Companion.defaultLocale
|
||||||
import org.jeudego.pairgoth.util.Translator.Companion.providedLanguages
|
import org.jeudego.pairgoth.util.Translator.Companion.providedLanguages
|
||||||
import org.jeudego.pairgoth.view.TranslationTool
|
import org.jeudego.pairgoth.view.TranslationTool
|
||||||
|
import java.util.*
|
||||||
import javax.servlet.Filter
|
import javax.servlet.Filter
|
||||||
import javax.servlet.FilterChain
|
import javax.servlet.FilterChain
|
||||||
import javax.servlet.FilterConfig
|
import javax.servlet.FilterConfig
|
||||||
@@ -29,52 +31,56 @@ class LanguageFilter : Filter {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val match = langPattern.matchEntire(uri)
|
||||||
|
val askedLanguage = match?.groupValues?.get(1)
|
||||||
|
val target = match?.groupValues?.get(2) ?: uri
|
||||||
|
|
||||||
val reqLang = request.getAttribute("lang") as String?
|
val reqLang = request.getAttribute("lang") as String?
|
||||||
if (reqLang != null) {
|
if (reqLang != null) {
|
||||||
|
// this is a forwarded request, language and locale should already have been set
|
||||||
TranslationTool.translator.set(Translator.getTranslator(reqLang))
|
TranslationTool.translator.set(Translator.getTranslator(reqLang))
|
||||||
chain.doFilter(request, response)
|
chain.doFilter(request, response)
|
||||||
} else {
|
} else {
|
||||||
val match = langPattern.matchEntire(uri)
|
val (preferredLanguage, preferredLocale) = parseLanguageHeader(request)
|
||||||
val lang = match?.groupValues?.get(1)
|
if (askedLanguage != null && providedLanguages.contains(askedLanguage)) {
|
||||||
val target = match?.groupValues?.get(2) ?: uri
|
|
||||||
|
|
||||||
if (lang != null && providedLanguages.contains(lang)) {
|
|
||||||
// the target URI contains a language we provide
|
// the target URI contains a language we provide
|
||||||
request.setAttribute("lang", lang)
|
request.setAttribute("lang", askedLanguage)
|
||||||
|
request.setAttribute("loc",
|
||||||
|
if (askedLanguage == preferredLanguage) preferredLocale
|
||||||
|
else askedLanguage
|
||||||
|
)
|
||||||
request.setAttribute("target", target)
|
request.setAttribute("target", target)
|
||||||
filterConfig!!.servletContext.getRequestDispatcher(target).forward(request, response)
|
filterConfig!!.servletContext.getRequestDispatcher(target).forward(request, response)
|
||||||
} else {
|
} else {
|
||||||
// the request must be redirected
|
// the request must be redirected
|
||||||
val preferredLanguage = getPreferredLanguage(request)
|
val destination = if (askedLanguage != null) target else uri
|
||||||
val destination = if (lang != null) target else uri
|
|
||||||
response.sendRedirect("/${preferredLanguage}${destination}")
|
response.sendRedirect("/${preferredLanguage}${destination}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPreferredLanguage(request: HttpServletRequest): String {
|
/**
|
||||||
var lang = request.session.getAttribute("lang") as String?
|
* Returns Pair(language, locale)
|
||||||
if (lang == null) {
|
*/
|
||||||
parseLanguageHeader(request)
|
private fun parseLanguageHeader(request: HttpServletRequest): Pair<String, String> {
|
||||||
lang = request.session.getAttribute("lang") as String?
|
|
||||||
}
|
|
||||||
return lang ?: defaultLanguage
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseLanguageHeader(request: HttpServletRequest) {
|
|
||||||
langHeaderParser.findAll(request.getHeader("Accept-Language") ?: "").filter {
|
langHeaderParser.findAll(request.getHeader("Accept-Language") ?: "").filter {
|
||||||
providedLanguages.contains(it.groupValues[1])
|
providedLanguages.contains(it.groupValues[1])
|
||||||
}.sortedByDescending {
|
}.sortedByDescending {
|
||||||
it.groupValues[3].toDoubleOrNull() ?: 1.0
|
it.groupValues[3].toDoubleOrNull() ?: 1.0
|
||||||
}.firstOrNull()?.let {
|
}.firstOrNull()?.let { match ->
|
||||||
it.groupValues[1]
|
val lang = match.groupValues[1].let { if (it == "*") defaultLanguage else it }
|
||||||
|
val variant = match.groupValues.getOrNull(2)?.lowercase(Locale.ROOT)
|
||||||
|
// by convention, the variant is only kept if different from the language (fr-FR => fr)
|
||||||
|
val locale = variant?.let { if (lang == variant) lang else "$lang-${variant.uppercase(Locale.ROOT)}" } ?: lang
|
||||||
|
return Pair(lang, locale)
|
||||||
}
|
}
|
||||||
|
return Pair(defaultLanguage, defaultLanguage)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun destroy() {}
|
override fun destroy() {}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val langPattern = Regex("/([a-z]{2})(/.+)")
|
private val langPattern = Regex("/([a-z]{2})(/.+)")
|
||||||
private val langHeaderParser = Regex("(?:\\b(\\*|[a-z]{2})(?:(?:_|-)\\w+)?)(?:;q=([0-9.]+))?") ADD locale group captue
|
private val langHeaderParser = Regex("(?:\\b(\\*|[a-z]{2})(?:(?:_|-)(\\w+))?)(?:;q=([0-9.]+))?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -70,6 +70,7 @@
|
|||||||
<link rel="stylesheet" href="/css/main.css"/>
|
<link rel="stylesheet" href="/css/main.css"/>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const lang = '${request.lang}';
|
const lang = '${request.lang}';
|
||||||
|
const locale = '${request.locale}';
|
||||||
// #[[
|
// #[[
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
$('#lang').on('click', e => {
|
$('#lang').on('click', e => {
|
||||||
|
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
<toolbox scope="application">
|
<toolbox scope="application">
|
||||||
<tool key="translate" class="org.jeudego.pairgoth.view.TranslationTool"/>
|
<tool key="translate" class="org.jeudego.pairgoth.view.TranslationTool"/>
|
||||||
|
<tool key="strings" class="org.apache.commons.lang3.StringUtils"/>
|
||||||
<!--
|
<!--
|
||||||
<tool key="number" format="#0.00"/>
|
<tool key="number" format="#0.00"/>
|
||||||
<tool key="date" locale="fr_FR" format="yyyy-MM-dd"/>
|
<tool key="date" locale="fr_FR" format="yyyy-MM-dd"/>
|
||||||
<tool key="inflector" class="org.atteo.evo.inflector.English"/>
|
<tool key="inflector" class="org.atteo.evo.inflector.English"/>
|
||||||
<tool key="strings" class="org.apache.commons.lang3.StringUtils"/>
|
|
||||||
-->
|
-->
|
||||||
</toolbox>
|
</toolbox>
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
#macro(toHMS $value)#if($value)#set($hh = $value / 3600)#set($mm = ($value - $hh) / 60)#set($ss = $value % 60)$hh:$mm:$ss#end#end
|
#macro(twoDigits $value)$strings.leftPad($value, 2, '0')#end
|
||||||
|
#macro(toHMS $value)#if($value)#set($hh = $value / 3600)#set($mm = ($value % 3600) / 60)#set($ss = $value % 60)#twoDigits($hh):#twoDigits($mm):#twoDigits($ss)#end#end
|
||||||
#macro(levels $sel)
|
#macro(levels $sel)
|
||||||
#foreach($d in [8..0])
|
#foreach($d in [8..0])
|
||||||
#set($dan = $d + 1)
|
#set($dan = $d + 1)
|
||||||
@@ -37,19 +38,20 @@
|
|||||||
<label>Dates</label>
|
<label>Dates</label>
|
||||||
<span id="date-range">
|
<span id="date-range">
|
||||||
from
|
from
|
||||||
<input type="date" name="startDate" required class="date" placeholder="start date" #if($tour) value="$tour.startDate" #end/>
|
<input type="text" name="startDate" required class="date" placeholder="start date" #if($tour) value="$tour.startDate" #end/>
|
||||||
to
|
to
|
||||||
<input type="date" name="endDate" required class="date" placeholder="end date" #if($tour) value="$tour.startDate" #end/>
|
<input type="text" name="endDate" required class="date" placeholder="end date" #if($tour) value="$tour.startDate" #end/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="two stackable fields">
|
<div class="two stackable fields">
|
||||||
<div class="seven wide field">
|
<div class="seven wide field">
|
||||||
<label>Country</label>
|
<label>Country</label>
|
||||||
<select name="country">
|
<select name="country" placeholder="country">
|
||||||
<option></option>
|
<option></option>
|
||||||
|
#set($defaultCountry = $translate.defaultCountry[$request.lang])
|
||||||
#foreach($country in $countries.countries)
|
#foreach($country in $countries.countries)
|
||||||
<option value="$country.key" #if($tour && $country.key == $tour.country || !$tour && $country.key == $request.lang) selected #end>$country.value</option>
|
<option value="$country.key" #if($tour && $country.key == $tour.country || !$tour && $country.key == $defaultCountry) selected #end>$country.value</option>
|
||||||
#end
|
#end
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -216,24 +218,29 @@
|
|||||||
#if($tour)
|
#if($tour)
|
||||||
const tour_id = ${tour.id};
|
const tour_id = ${tour.id};
|
||||||
#end
|
#end
|
||||||
// #[[
|
#set($datepickerLocale = $translate.datepickerLocale($request.lang, $request.loc))
|
||||||
|
const datepickerLocale = '$datepickerLocale';
|
||||||
|
// #[[ale
|
||||||
const safeRegex = /^[-a-zA-Z0-9_.]+$/;
|
const safeRegex = /^[-a-zA-Z0-9_.]+$/;
|
||||||
function parseDate(value) {
|
function parseDate(value) {
|
||||||
return value;
|
let format = Datepicker.locales[datepickerLocale]?.format || 'mm/dd/yyyy';
|
||||||
/*
|
let date = Datepicker.parseDate(value, format, datepickerLocale);
|
||||||
let locale = Datepicker.locales[lang];
|
|
||||||
if (locale) {
|
|
||||||
let date = Datepicker.parseDate(value, locale.format, locale);
|
|
||||||
return Datepicker.formatDate(date, 'yyyy-mm-dd')
|
return Datepicker.formatDate(date, 'yyyy-mm-dd')
|
||||||
}
|
}
|
||||||
else return undefined;
|
function formatDate(value) {
|
||||||
*/
|
let format = Datepicker.locales[datepickerLocale]?.format || 'mm/dd/yyyy';
|
||||||
|
let date = Datepicker.parseDate(value, 'yyyy-mm-dd', datepickerLocale);
|
||||||
|
return Datepicker.formatDate(date, format)
|
||||||
}
|
}
|
||||||
function fromHMS(value) {
|
function fromHMS(value) {
|
||||||
if (value && /\d+:\d+:\d+/.test(value)) {
|
if (value && /\d+:\d+:\d+/.test(value)) {
|
||||||
let parts = value.split(':');
|
let parts = value.split(':');
|
||||||
return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
let seconds = parts[0] * 3600 + parts[1] * 60 + parts[2] * 1;
|
||||||
|
console.log(`${value} => ${seconds}`);
|
||||||
|
return seconds;
|
||||||
}
|
}
|
||||||
|
console.log(`invalid hh:mm:ss value: ${value}`);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
|
|
||||||
@@ -265,9 +272,15 @@
|
|||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for(let name of ['startDate', 'endDate']) {
|
||||||
|
let control = $(`input[name="${name}"]`)[0];
|
||||||
|
if (control.value) {
|
||||||
|
control.value = formatDate(control.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
new DateRangePicker($('#date-range')[0], {
|
new DateRangePicker($('#date-range')[0], {
|
||||||
autohide: true,
|
autohide: true,
|
||||||
language: lang
|
language: datepickerLocale || 'en'
|
||||||
});
|
});
|
||||||
|
|
||||||
$('input[name="online"]').on('change', e => {
|
$('input[name="online"]').on('change', e => {
|
||||||
@@ -361,13 +374,14 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ]]#
|
// ]]#
|
||||||
</script>
|
</script>
|
||||||
<!--
|
<script type="text/javascript" src="/lib/datepicker-1.3.4/datepicker-full#*.min*#.js"></script>
|
||||||
<script type="text/javascript" src="/lib/datepicker-1.3.3/datepicker-full.min.js"></script>
|
#if($datepickerLocale != 'en')
|
||||||
<script type="text/javascript" src="/lib/datepicker-1.3.3/locales/${request.lang}.js"></script>
|
<script type="text/javascript" src="/lib/datepicker-1.3.4/locales/${datepickerLocale}.js"></script>
|
||||||
<link rel="stylesheet" href="/lib/datepicker-1.3.3/datepicker.min.css">
|
#end
|
||||||
-->
|
<link rel="stylesheet" href="/lib/datepicker-1.3.4/datepicker.min.css">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.ui.form input[type=checkbox][name=online] {
|
.ui.form input[type=checkbox][name=online] {
|
||||||
vertical-align: initial;
|
vertical-align: initial;
|
||||||
|
Reference in New Issue
Block a user