fradrive/src/Utils/Lang.hs
2022-10-12 09:35:16 +02:00

118 lines
4.6 KiB
Haskell

-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Utils.Lang where
import ClassyPrelude.Yesod
import Model.Types.Languages
import Utils.Cookies.Registered
import Utils.Parameters
import Utils.Session
import qualified Data.List.NonEmpty as NonEmpty
import Data.List.NonEmpty (NonEmpty(..), nonEmpty)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.List as List
import qualified Data.CaseInsensitive as CI
import Yesod.Core.Types (HandlerData(handlerRequest))
import qualified Network.Wai.Parse as NWP
import Control.Monad.Trans.Maybe (MaybeT(..))
import Control.Monad.Reader.Class (local)
import qualified Data.HashMap.Strict as HashMap
import Data.Containers.ListUtils
isDe :: Lang -> Bool
isDe = isPrefixOf "de"
isEn :: Lang -> Bool
isEn = isPrefixOf "en"
selectDeEn :: Maybe Languages -> Lang
selectDeEn = selectLanguage' availableLanguages . concatMap getLanguages
where
availableLanguages = "de" :| ["en"] -- for now, we only have german and english, with german being the default language
selectEnDe :: Maybe Languages -> Lang
selectEnDe = selectLanguage' availableLanguages . concatMap getLanguages
where
availableLanguages = "en" :| ["de"]
selectLanguage :: MonadHandler m
=> NonEmpty Lang -- ^ Available translations, first is default
-> m Lang
selectLanguage avL = selectLanguage' avL <$> languages
selectLanguage' :: NonEmpty Lang -- ^ Available translations, first is default
-> [Lang] -- ^ Languages in preference order
-> Lang
selectLanguage' avL ls = NonEmpty.head $ selectLanguages avL ls
selectLanguages :: NonEmpty Lang -> [Lang] -> NonEmpty Lang
selectLanguages (defL :| _) [] = defL :| []
selectLanguages avL (l:ls)
| not $ null l
, Just lParts <- nonEmpty $ matchesFor l
, found <- nubOrd
[ l'' | lParts' <- NonEmpty.toList lParts
, l' <- NonEmpty.toList avL
, l'' <- matchesFor l'
, langMatches lParts' l''
]
= let now = nonEmpty $ sortOn (Down . length) found
others = selectLanguages avL ls
in maybe id (\now' others' -> NonEmpty.fromList $ toList now' ++ filter (`notElem` toList now') (toList others')) now others
| otherwise = selectLanguages avL ls
langMatches :: Lang -- ^ Needle
-> Lang -- ^ Haystack
-> Bool
langMatches (CI.foldCase -> needle) (CI.foldCase -> haystack) = needle `elem` matchesFor haystack
matchesFor :: Lang -> [Lang]
matchesFor = mapMaybe (\frags -> Text.intercalate "-" frags <$ guard (not $ null frags)) . reverse . List.inits . Text.splitOn "-"
highPrioRequestedLangs, lowPrioRequestedLangs :: forall m. MonadHandler m => m [Lang]
highPrioRequestedLangs = fmap (concatMap $ fromMaybe []) . mapM runMaybeT $
[ lookupGlobalGetParams GetLang
, lookupRegisteredCookies pure CookieLang
, fmap pure . MaybeT $ lookupSessionKey SessionLang
]
lowPrioRequestedLangs = maybe [] (mapMaybe (either (const Nothing) Just . Text.decodeUtf8') . NWP.parseHttpAccept) <$> lookupHeader hAcceptLanguage
languagesMiddleware :: forall site a. NonEmpty Lang -> HandlerFor site a -> HandlerFor site a
languagesMiddleware avL act = do
pLangs <- fmap nubOrd $ (<>) <$> highPrioRequestedLangs <*> lowPrioRequestedLangs
let langs = toList $ selectLanguages avL pLangs
setLangs hData = hData{ handlerRequest = (handlerRequest hData){ reqLangs = langs } }
local setLangs $ ($logDebugS "languages" . tshow . (pLangs,langs,) =<< languages) *> act
renderMessageDispatch :: forall msg site.
Lang -- ^ Default language
-> (site -> [Lang] -> msg -> Text) -- ^ Default renderer
-> HashMap Lang (Either (site -> [Lang] -> msg -> Maybe Text) (site -> [Lang] -> msg -> Text)) -- ^ All renderers
-> site -> [Lang] -> msg -> Text
renderMessageDispatch defLang defRender extraRenders app langs msg = go $ selectLanguages avLangs langs
where
avLangs = defLang :| HashMap.keys extraRenders
go (l :| []) = case HashMap.lookup l extraRenders of
Nothing -> defRender app langs msg
Just (Left pRender) -> fromMaybe (defRender app langs msg) $ pRender app langs msg
Just (Right tRender) -> tRender app langs msg
go (l1 :| l2 : ls) = case HashMap.lookup l1 extraRenders of
Nothing -> go (l2 :| ls)
Just (Left pRender) -> fromMaybe (go $ l2 :| ls) $ pRender app langs msg
Just (Right tRender) -> tRender app langs msg
{-# INLINE renderMessageDispatch #-}