--- a/src/main/kotlin/de/uapcore/lightpit/AbstractServlet.kt Fri Nov 24 00:07:36 2023 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/AbstractServlet.kt Sat Jan 06 20:31:14 2024 +0100 @@ -28,6 +28,7 @@ import de.uapcore.lightpit.DataSourceProvider.Companion.SC_ATTR_NAME import de.uapcore.lightpit.dao.DataAccessObject import de.uapcore.lightpit.dao.createDataAccessObject +import jakarta.servlet.http.Cookie import jakarta.servlet.http.HttpServlet import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -35,6 +36,10 @@ import java.util.* abstract class AbstractServlet : HttpServlet() { + + companion object { + const val LANGUAGE_COOKIE_NAME = "lpit_language" + } protected val logger = MyLogger() @@ -100,22 +105,6 @@ // the very first thing to do is to force UTF-8 req.characterEncoding = "UTF-8" - // choose the requested language as session language (if available) or fall back to english, otherwise - if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) { - val availableLanguages = availableLanguages() - val reqLocale = req.locale - val sessionLocale = if (availableLanguages.contains(reqLocale)) reqLocale else availableLanguages.first() - session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, sessionLocale) - resp.locale = sessionLocale - logger.debug( - "Setting language for new session {0}: {1}", session.id, sessionLocale.displayLanguage - ) - } else { - val sessionLocale = session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) as Locale - resp.locale = sessionLocale - logger.trace("Continuing session {0} with language {1}", session.id, sessionLocale) - } - // set some internal request attributes val http = HttpRequest(req, resp) val fullPath = req.servletPath + Optional.ofNullable(req.pathInfo).orElse("") @@ -126,6 +115,29 @@ req.setAttribute(Constants.REQ_ATTR_REFERER, it) } + // choose the requested language as session language (if available) + if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) { + // language selection stored in cookie + val cookieLocale = cookieLanguage(http) + + // if no cookie, fall back to request locale a.k.a "Browser Language" + val reqLocale = cookieLocale ?: req.locale + + val availableLanguages = availableLanguages() + val sessionLocale = if (availableLanguages.contains(reqLocale)) reqLocale else availableLanguages.first() + + // select the language (this will also refresh the cookie max-age) + selectLanguage(http, sessionLocale) + + logger.debug( + "Setting language for new session {0}: {1}", session.id, sessionLocale.displayLanguage + ) + } else { + val sessionLocale = session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) as Locale + resp.locale = sessionLocale + logger.trace("Continuing session {0} with language {1}", session.id, sessionLocale) + } + // if this is an error path, bypass the normal flow if (fullPath.startsWith("/error/")) { http.styleSheets = listOf("error") @@ -182,4 +194,23 @@ return locales.ifEmpty { listOf(Locale.ENGLISH) } } + private fun cookieLanguage(http: HttpRequest): Locale? = + http.request.cookies?.firstOrNull { c -> c.name == LANGUAGE_COOKIE_NAME } + ?.runCatching {Locale.forLanguageTag(this.value)}?.getOrNull() + + protected fun selectLanguage(http: HttpRequest, locale: Locale) { + http.response.locale = locale + http.session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, locale) + // delete cookie if language selection matches request locale, otherwise set cookie + val cookie = Cookie(LANGUAGE_COOKIE_NAME, "") + cookie.isHttpOnly = true + cookie.path = http.request.contextPath + if (http.request.locale.language == locale.language) { + cookie.maxAge = 0 + } else { + cookie.value = locale.language + cookie.maxAge = 2592000 // 30 days + } + http.response.addCookie(cookie) + } }