;; See the file LICENCE for licence information. (in-package :ucw) ;;;;;;;;;;;;;;; ;;; application (defclass l10n-application-mixin () ((default-locale :initform nil :initarg :default-locale :type list :accessor default-locale-of :documentation "Something that the cl-l10n locale function understands") (resource-package :initarg :resource-package :accessor resource-package-of :documentation "When bound cl-l10n's *resource-package* is bound to this value.") (accepted-locales :initform '() :initarg :accepted-locales :accessor accepted-locales-of :documentation "When not nil the user-requested locales will be filtered according to this list.")) (:documentation "Application class which can handle l10n requests.")) (defmethod initialize-instance :after ((app l10n-application-mixin) &key) ;; explicitly call our customized setf's to resolve the locale name (awhen (default-locale-of app) (setf (default-locale-of app) it)) (awhen (accepted-locales-of app) (setf (accepted-locales-of app) it))) (defmethod (setf default-locale-of) :around (locale (app l10n-application-mixin)) (typecase locale (locale (setf (default-locale-of app) (normalize-locale-list (list locale)))) (list (call-next-method (normalize-locale-list locale) app)) (t (with-resource-package (resource-package-of app) (setf (default-locale-of app) (normalize-locale-list (list (locale locale)))))))) (defmethod (setf accepted-locales-of) :around ((locales list) (app l10n-application-mixin)) (with-resource-package (resource-package-of app) (call-next-method (mapcar #'locale locales) app))) ;;;;;;;;;;; ;;; session (defclass l10n-session-mixin () ((client-timezone :accessor client-timezone-of :initarg :client-timezone :initform *default-timezone*))) (defmethod session-class list ((app l10n-application-mixin)) 'l10n-session-mixin) (defmethod handle-action :around (action application (session l10n-session-mixin) frame) (let ((*client-timezone* (client-timezone-of session))) (call-next-method))) (defun render-client-timezone-probe () "Renders an input field with a callback that will set the CLIENT-TIMEZONE slot of the session when the form is submitted." (let ((id (js:gen-js-name-string))) (<:input :id id :type "hidden" :name (register-callback (lambda (value) (if (and value (not (zerop (length value)))) (let ((local-time (parse-rfc3339-timestring value))) (ucw.l10n.debug "Setting client timezone from ~A" local-time) (setf (client-timezone-of (context.session *context*)) (timezone-of local-time))) (progn (ucw.l10n.warn "Unable to parse the client timezone: ~S" value) (setf (client-timezone-of (context.session *context*)) +utc-zone+)))))) ( (length it) +maximum-accept-language-value-length+) (ucw.l10n.warn "Refusing to parse Accept-Language header value, its length is ~S" (length it)) (return-from process-accept-language nil)) (let ((langs (parse-accept-language-header it))) (ucw.l10n.debug "Parsed language header ~S, app default locale is ~S, app accepted locales are ~S" langs (default-locale-of app) (accepted-locales-of app)) (iter (with accepted-locales = (accepted-locales-of app)) (for (lang weigth) in langs) (for locale = (locale lang :errorp nil)) (ucw.l10n.debug "Looked up locale ~S from ~S" locale lang) (unless locale (next-iteration)) (when (and (or (not accepted-locales) (member locale accepted-locales)) (not (member locale result :key #'car))) (collect (cons locale weigth) :into result)) (finally (setf result (mapcar #'car (sort result #'> :key #'cdr))) (awhen (default-locale-of app) (setf result (nconc result (copy-list it)))) (ucw.l10n.debug "The final sorted locale list is ~S" result) (return result))))))) (defclass l10n-application (standard-application l10n-application-mixin) () (:documentation "See L10N-APPLICATION-MIXIN for details."))