fix(i18n): custom language inference

This commit is contained in:
Gregor Kleen 2019-10-21 14:02:09 +02:00
parent 97a29ec68c
commit 205d7688bf
4 changed files with 69 additions and 31 deletions

View File

@ -1,4 +1,4 @@
FilesSelected: Dateien ausgewählt FilesSelected: Dateien ausgewählt
SelectFile: Datei auswählen SelectFile: Datei auswählen
SelectFiles: Datei(en) auswählen SelectFiles: Datei(en) auswählen
AsyncFormFailure: Da ist etwas schief gelaufen, das tut uns Leid. Falls das erneut passiert schicke uns gerne eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben. Vielen Dank für deine Hilfe! AsyncFormFailure: Da ist etwas schief gelaufen, das tut uns Leid. Falls das erneut passiert schicken Sie uns bitte eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben. Vielen Dank für Ihre Hilfe!

View File

@ -253,12 +253,12 @@ maybeToMessage _ Nothing _ = mempty
maybeToMessage before (Just x) after = before <> (toMessage x) <> after maybeToMessage before (Just x) after = before <> (toMessage x) <> after
-- Messages creates type UniWorXMessage and RenderMessage UniWorX instance -- Messages creates type UniWorXMessage and RenderMessage UniWorX instance
mkMessage "UniWorX" "messages/uniworx" "de" mkMessage "UniWorX" "messages/uniworx" "de-de-formal"
mkMessageVariant "UniWorX" "Campus" "messages/campus" "de" mkMessageVariant "UniWorX" "Campus" "messages/campus" "de"
mkMessageVariant "UniWorX" "Dummy" "messages/dummy" "de" mkMessageVariant "UniWorX" "Dummy" "messages/dummy" "de"
mkMessageVariant "UniWorX" "PWHash" "messages/pw-hash" "de" mkMessageVariant "UniWorX" "PWHash" "messages/pw-hash" "de"
mkMessageVariant "UniWorX" "Button" "messages/button" "de" mkMessageVariant "UniWorX" "Button" "messages/button" "de"
mkMessageVariant "UniWorX" "Frontend" "messages/frontend" "de" mkMessageVariant "UniWorX" "Frontend" "messages/frontend" "de-de-formal"
instance RenderMessage UniWorX TermIdentifier where instance RenderMessage UniWorX TermIdentifier where
renderMessage foundation ls TermIdentifier{..} = case season of renderMessage foundation ls TermIdentifier{..} = case season of
@ -299,11 +299,11 @@ instance RenderMessage UniWorX Load where
newtype MsgLanguage = MsgLanguage Lang newtype MsgLanguage = MsgLanguage Lang
deriving (Eq, Ord, Show, Read) deriving (Eq, Ord, Show, Read)
instance RenderMessage UniWorX MsgLanguage where instance RenderMessage UniWorX MsgLanguage where
renderMessage foundation ls (MsgLanguage lang@(Text.splitOn "-" -> lang')) renderMessage foundation ls (MsgLanguage lang@(map mk . Text.splitOn "-" -> lang'))
| ["de", "DE"] <- lang' = mr MsgGermanGermany | ("de" : "DE" : _) <- lang' = mr MsgGermanGermany
| ("de" : _) <- lang' = mr MsgGerman | ("de" : _) <- lang' = mr MsgGerman
| ["en", "EU"] <- lang' = mr MsgEnglishEurope | ("en" : "EU" : _) <- lang' = mr MsgEnglishEurope
| ("en" : _) <- lang' = mr MsgEnglish | ("en" : _) <- lang' = mr MsgEnglish
| otherwise = lang | otherwise = lang
where where
mr = renderMessage foundation ls mr = renderMessage foundation ls
@ -511,13 +511,13 @@ instance Button UniWorX ButtonSubmit where
getTimeLocale' :: [Lang] -> TimeLocale getTimeLocale' :: [Lang] -> TimeLocale
getTimeLocale' = $(timeLocaleMap [("de", "de_DE.utf8"), ("en", "en_IE.utf8")]) getTimeLocale' = $(timeLocaleMap [("de-de", "de_DE.utf8"), ("en-eu", "en_IE.utf8")])
appTZ :: TZ appTZ :: TZ
appTZ = $(includeSystemTZ "Europe/Berlin") appTZ = $(includeSystemTZ "Europe/Berlin")
appLanguages :: NonEmpty Lang appLanguages :: NonEmpty Lang
appLanguages = "de" :| ["en"] appLanguages = "de-de-formal" :| ["en-eu"]
appLanguagesOpts :: ( MonadHandler m appLanguagesOpts :: ( MonadHandler m
, HandlerSite m ~ UniWorX , HandlerSite m ~ UniWorX
@ -1571,7 +1571,7 @@ instance Yesod UniWorX where
-- b) Validates that incoming write requests include that token in either a header or POST parameter. -- b) Validates that incoming write requests include that token in either a header or POST parameter.
-- To add it, chain it together with the defaultMiddleware: yesodMiddleware = defaultYesodMiddleware . defaultCsrfMiddleware -- To add it, chain it together with the defaultMiddleware: yesodMiddleware = defaultYesodMiddleware . defaultCsrfMiddleware
-- For details, see the CSRF documentation in the Yesod.Core.Handler module of the yesod-core package. -- For details, see the CSRF documentation in the Yesod.Core.Handler module of the yesod-core package.
yesodMiddleware = headerMessagesMiddleware . defaultYesodMiddleware . normalizeRouteMiddleware . defaultCsrfMiddleware . updateFavouritesMiddleware yesodMiddleware = languagesMiddleware appLanguages . headerMessagesMiddleware . defaultYesodMiddleware . normalizeRouteMiddleware . defaultCsrfMiddleware . updateFavouritesMiddleware
where where
updateFavouritesMiddleware :: Handler a -> Handler a updateFavouritesMiddleware :: Handler a -> Handler a
updateFavouritesMiddleware handler = (*> handler) . runMaybeT $ do updateFavouritesMiddleware handler = (*> handler) . runMaybeT $ do
@ -3632,24 +3632,25 @@ updateUserLanguage (Just lang) = do
updateUserLanguage Nothing updateUserLanguage Nothing
Nothing -> do Nothing -> do
setLangCookie lang setLangCookie lang
setLanguage lang
return $ Just lang return $ Just lang
updateUserLanguage Nothing = runMaybeT $ do updateUserLanguage Nothing = runMaybeT $ do
uid <- MaybeT maybeAuthId uid <- MaybeT maybeAuthId
User{..} <- MaybeT $ get uid User{..} <- MaybeT $ get uid
setLangs <- nub . filter (`elem` appLanguages) <$> languages setLangs <- toList . selectLanguages appLanguages <$> languages
let userLanguages' = nub . filter (`elem` appLanguages) <$> userLanguages ^? _Just . _Wrapped highPrioSetLangs <- toList . selectLanguages appLanguages <$> highPrioRequestedLangs
lang <- case (userLanguages', setLangs) of let userLanguages' = toList . selectLanguages appLanguages <$> userLanguages ^? _Just . _Wrapped
(Just (l : _), _) lang <- case (userLanguages', setLangs, highPrioSetLangs) of
(_, _, hpl : _)
-> lift $ hpl <$ update uid [ UserLanguages =. Just (Languages highPrioSetLangs) ]
(Just (l : _), _, _)
-> return l -> return l
(Nothing, l : _) (Nothing, l : _, _)
-> lift $ l <$ update uid [ UserLanguages =. Just (Languages setLangs) ] -> lift $ l <$ update uid [ UserLanguages =. Just (Languages setLangs) ]
(Just [], l : _) (Just [], l : _, _)
-> return l -> return l
(_, []) (_, [], _)
-> mzero -> mzero
setLangCookie lang setLangCookie lang
setLanguage lang
return lang return lang

View File

@ -3,11 +3,22 @@ module Utils.Lang where
import ClassyPrelude.Yesod import ClassyPrelude.Yesod
import qualified Data.List.NonEmpty as NonEmpty import qualified Data.List.NonEmpty as NonEmpty
import Data.List.NonEmpty (NonEmpty(..)) import Data.List.NonEmpty (NonEmpty(..), nonEmpty)
import qualified Data.Text as Text import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.List as List import qualified Data.List as List
import qualified Data.CaseInsensitive as CI
import Control.Lens (none)
import Yesod.Core.Types (HandlerData(handlerRequest), YesodRequest(reqLangs))
import qualified Network.Wai.Parse as NWP
import Control.Monad.Trans.Maybe (MaybeT(..))
import Control.Monad.Reader.Class (local)
selectLanguage :: MonadHandler m selectLanguage :: MonadHandler m
=> NonEmpty Lang -- ^ Available translations, first is default => NonEmpty Lang -- ^ Available translations, first is default
@ -17,18 +28,44 @@ selectLanguage avL = selectLanguage' avL <$> languages
selectLanguage' :: NonEmpty Lang -- ^ Available translations, first is default selectLanguage' :: NonEmpty Lang -- ^ Available translations, first is default
-> [Lang] -- ^ Languages in preference order -> [Lang] -- ^ Languages in preference order
-> Lang -> Lang
selectLanguage' (defL :| _) [] = defL selectLanguage' avL ls = NonEmpty.head $ selectLanguages avL ls
selectLanguage' avL (l:ls)
selectLanguages :: NonEmpty Lang -> [Lang] -> NonEmpty Lang
selectLanguages (defL :| _) [] = defL :| []
selectLanguages avL (l:ls)
| not $ null l | not $ null l
, Just lParts <- NonEmpty.nonEmpty $ Text.splitOn "-" l , Just lParts <- nonEmpty $ matchesFor l
, found <- [ l' | lParts' <- reverse . List.inits $ NonEmpty.toList lParts , found <- List.nub
, l' <- NonEmpty.toList avL [ l'' | lParts' <- NonEmpty.toList lParts
, langMatches (Text.intercalate "-" lParts') l' , l' <- NonEmpty.toList avL
] , l'' <- matchesFor l'
= fromMaybe (selectLanguage' avL ls) . listToMaybe $ sortOn (Down . length) found , langMatches lParts' l''
| otherwise = selectLanguage' avL ls ]
= let now = nonEmpty . filter (\l' -> none (((==) `on` CI.mk) l') ls) $ 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 langMatches :: Lang -- ^ Needle
-> Lang -- ^ Haystack -> Lang -- ^ Haystack
-> Bool -> Bool
langMatches = isPrefixOf `on` Text.splitOn "-" 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 $
[ lookupGetParams "_LANG"
, lookupCookies "_LANG"
, fmap pure . MaybeT $ lookupSession "_LANG"
]
lowPrioRequestedLangs = fromMaybe [] . fmap (mapMaybe (either (const Nothing) Just . Text.decodeUtf8') . NWP.parseHttpAccept) <$> lookupHeader "Accept-Language"
languagesMiddleware :: forall site a. NonEmpty Lang -> HandlerFor site a -> HandlerFor site a
languagesMiddleware avL act = do
pLangs <- fmap List.nub $ (<>) <$> 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