-------------------------------------------------------------------------------- {-# LANGUAGE OverloadedStrings #-} import Control.Monad (filterM) import Data.Maybe (fromMaybe) import Data.Monoid (mappend) import Data.Time import Data.Time.Format (parseTimeM, defaultTimeLocale, formatTime) import Hakyll -------------------------------------------------------------------------------- main :: IO () main = hakyll $ do match "images/**" $ do route idRoute compile copyFileCompiler match "lib/**" $ do route idRoute compile copyFileCompiler match "css/*" $ do route idRoute compile compressCssCompiler match (fromList ["about.rst", "contact.markdown"]) $ do route $ setExtension "html" compile $ pandocCompiler >>= loadAndApplyTemplate "templates/default.html" staticPageContext >>= relativizeUrls match "posts/**/*" $ do route $ setExtension "html" compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language pandocCompiler >>= loadAndApplyTemplate "templates/post.html" (postCtx lang) >>= loadAndApplyTemplate "templates/default.html" (postCtx lang) >>= relativizeUrls match "events/**/*" $ do route $ setExtension "html" compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language pandocCompiler >>= loadAndApplyTemplate "templates/event.html" (postCtx lang) >>= loadAndApplyTemplate "templates/default.html" (postCtx lang) >>= relativizeUrls match "pages/en/japan/language/**" $ do route $ setExtension "html" compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language pandocCompiler >>= loadAndApplyTemplate "templates/japan/language-note.html" (postCtx lang) >>= loadAndApplyTemplate "templates/default.html" (postCtx lang) >>= relativizeUrls match "pages/*/plamo/equipment/**" $ do route $ setExtension "html" compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language pandocCompiler >>= loadAndApplyTemplate "templates/equipment.html" (postCtx lang) >>= loadAndApplyTemplate "templates/default.html" (postCtx lang) >>= relativizeUrls match "pages/*/plamo/model-kits/**" $ do route $ setExtension "html" compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language let dateValue = lookupString "date" metadata purchaseDateValue = lookupString "purchase_date" metadata formattedPurchaseDate = maybe "" (formatDate lang) purchaseDateValue modelKitTemplate = if dateValue == Just "1990-01-01" then "templates/model-kit-not-ready.html" else "templates/model-kit.html" extendedCtx = constField "formatted_purchase_date" formattedPurchaseDate <> (postCtx lang) pandocCompiler >>= loadAndApplyTemplate modelKitTemplate extendedCtx >>= loadAndApplyTemplate "templates/default.html" extendedCtx >>= relativizeUrls match "pages/*/radio/**/*" $ do route $ setExtension "html" compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language pandocCompiler >>= loadAndApplyTemplate "templates/radio.html" (postCtx lang) >>= loadAndApplyTemplate "templates/default.html" (postCtx lang) >>= relativizeUrls match "pages/*/software/**" $ do route $ setExtension "html" compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language pandocCompiler -- >>= loadAndApplyTemplate "templates/software-post.html" (postCtx lang) >>= loadAndApplyTemplate "templates/default.html" (postCtx lang) >>= relativizeUrls match (fromList [ "nl/plamo.html" , "jp/plamo.html" , "en/plamo.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language let url = "pages/" ++ lang ++ "/plamo/model-kits/**" kits <- recentFirst =<< loadAll (fromGlob (url)) let plamoCtx = listField "kits" (kitCtx lang) (return kits) <> lastUpdateField lang <> langDict lang <> defaultContext getResourceBody >>= applyAsTemplate plamoCtx >>= loadAndApplyTemplate "templates/default.html" plamoCtx >>= relativizeUrls match (fromList [ "nl/japan.html" , "jp/japan.html" , "en/japan.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language notes <- loadAll "pages/en/japan/language/notes/*" teforms <- loadAll "pages/en/japan/language/te-form/*" particles <- loadAll "pages/en/japan/language/particles/*" adverbial_particles <- loadAll "pages/en/japan/language/particles/adverbial-particles/*" binding_particles <- loadAll "pages/en/japan/language/particles/binding-particles/*" verbs <- loadAll "pages/en/japan/language/verbs/*" other <- loadAll "pages/en/japan/language/other/*" let japanCtx = listField "notes" (postCtx lang) (return notes) <> listField "teforms" (postCtx lang) (return teforms) <> listField "particles" (postCtx lang) (return particles) <> listField "adverbial_particles" (postCtx lang) (return adverbial_particles) <> listField "binding_particles" (postCtx lang) (return binding_particles) <> listField "verbs" (postCtx lang) (return verbs) <> listField "other" (postCtx lang) (return other) <> lastUpdateField lang <> langDict lang <> defaultContext getResourceBody >>= applyAsTemplate japanCtx >>= loadAndApplyTemplate "templates/default.html" japanCtx >>= relativizeUrls match (fromList [ "nl/radio.html" , "jp/radio.html" , "en/radio.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language antenna <- loadAll $ (fromGlob ("pages/" ++ lang ++ "/radio/antenna/*")) fielddays <- loadAll $ (fromGlob ("pages/" ++ lang ++ "/radio/fielddays/*")) fielddays <- recentFirst fielddays let radioCtx = listField "antenna" (postCtx lang) (return antenna) <> listField "fielddays" (postCtx lang) (return fielddays) <> lastUpdateField lang <> langDict lang <> defaultContext getResourceBody >>= applyAsTemplate radioCtx >>= loadAndApplyTemplate "templates/default.html" radioCtx >>= relativizeUrls match (fromList [ "nl/software.html" , "jp/software.html" , "en/software.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language projects <- loadAll $ (fromGlob ("pages/" ++ lang ++ "/software/projects/*")) projects <- recentFirst projects let softwareCtx = listField "projects" (postCtx lang) (return projects) <> lastUpdateField lang <> langDict lang <> defaultContext getResourceBody >>= applyAsTemplate softwareCtx >>= loadAndApplyTemplate "templates/default.html" softwareCtx >>= relativizeUrls match (fromList [ "nl/code/blazor.html" , "jp/code/blazor.html" , "en/code/blazor.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language blazorPages <- loadAll "pages/en/software/code/blazor/*" let softwareCtx = listField "blazorPages" (postCtx lang) (return blazorPages) <> lastUpdateField lang <> langDict lang <> defaultContext pandocCompiler >>= loadAndApplyTemplate "templates/code.html" softwareCtx >>= loadAndApplyTemplate "templates/default.html" softwareCtx >>= relativizeUrls match (fromList [ "nl/code/elm.html" , "jp/code/elm.html" , "en/code/elm.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language elmPages <- loadAll "pages/en/software/code/elm/*" let softwareCtx = listField "elmPages" (postCtx lang) (return elmPages) <> lastUpdateField lang <> langDict lang <> defaultContext pandocCompiler >>= loadAndApplyTemplate "templates/code.html" softwareCtx >>= loadAndApplyTemplate "templates/default.html" softwareCtx >>= relativizeUrls match (fromList [ "nl/code/haskell.html" , "jp/code/haskell.html" , "en/code/haskell.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language haskellPages <- loadAll "pages/en/software/code/haskell/*" let softwareCtx = listField "haskellPages" (postCtx lang) (return haskellPages) <> lastUpdateField lang <> langDict lang <> defaultContext pandocCompiler >>= loadAndApplyTemplate "templates/code.html" softwareCtx >>= loadAndApplyTemplate "templates/default.html" softwareCtx >>= relativizeUrls match (fromList [ "nl/equipment.html" , "jp/equipment.html" , "en/equipment.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language equipment <- loadAll $ (fromGlob ("pages/" ++ lang ++ "/plamo/equipment/*")) let equipmentCtx = listField "equipment" (postCtx lang) (return equipment) <> lastUpdateField lang <> langDict lang <> defaultContext getResourceBody >>= applyAsTemplate equipmentCtx >>= loadAndApplyTemplate "templates/default.html" equipmentCtx >>= relativizeUrls match (fromList [ "nl/paints.html" , "jp/paints.html" , "en/paints.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language let paintsCtx = lastUpdateField lang <> langDict lang <> defaultContext getResourceBody >>= applyAsTemplate paintsCtx >>= loadAndApplyTemplate "templates/default.html" paintsCtx >>= relativizeUrls match (fromList [ "nl/archive-events.html" , "jp/archive-events.html" , "en/archive-events.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language events <- recentFirst =<< loadAll (fromGlob ("events/" ++ lang ++ "/*")) let eventsCtx = listField "events" (postCtx lang) (return events) <> lastUpdateField lang <> langDict lang <> defaultContext getResourceBody >>= applyAsTemplate eventsCtx >>= loadAndApplyTemplate "templates/default.html" eventsCtx >>= relativizeUrls match (fromList [ "nl/archive-posts.html" , "jp/archive-posts.html" , "en/archive-posts.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language posts <- recentFirst =<< loadAll (fromGlob ("posts/" ++ lang ++ "/*")) let postsCtx = listField "posts" (postCtx lang) (return posts) <> lastUpdateField lang <> langDict lang <> defaultContext getResourceBody >>= applyAsTemplate postsCtx >>= loadAndApplyTemplate "templates/default.html" postsCtx >>= relativizeUrls match (fromList [ "index.html" , "nl/index.html" , "jp/index.html" , "en/index.html" ]) $ do route idRoute compile $ do identifier <- getUnderlying metadata <- getMetadata identifier let language = lookupString "language" metadata let lang = fromMaybe "en" language posts <- recentFirst =<< loadAll (fromGlob ("posts/" ++ lang ++ "/*")) now <- unsafeCompiler getCurrentTime ident <- getUnderlying --language <- getMetadataField' ident "language" -- Seven days from today. let cutoff = addUTCTime (7 * 24 * 60 * 60) now events <- loadAll (fromGlob ("events/" ++ lang ++ "/*")) >>= filterM (isUpcoming cutoff) >>= chronological let indexCtx = -- (<> is the modern version of `mappend`.) listField "posts" (postCtx lang) (return posts) <> listField "events" (postCtx lang) (return events) <> lastUpdateField lang <> langDict lang <> defaultContext getResourceBody >>= applyAsTemplate indexCtx >>= loadAndApplyTemplate "templates/default.html" indexCtx >>= relativizeUrls match "templates/*" $ compile templateBodyCompiler match "templates/**/*" $ compile templateBodyCompiler -------------------------------------------------------------------------------- staticPageContext :: Context String staticPageContext = lastUpdateField "en" <> langDict "en" <> defaultContext localeFor :: String -> TimeLocale localeFor "nl" = dutchLocale localeFor "jp" = japaneseLocale localeFor _ = defaultTimeLocale dutchLocale :: TimeLocale dutchLocale = defaultTimeLocale { months = [ ("januari", "jan"), ("februari", "feb"), ("maart", "mrt") , ("april", "apr"), ("mei", "mei"), ("juni", "jun") , ("juli", "jul"), ("augustus", "aug"), ("september", "sep") , ("oktober", "okt"), ("november", "nov"), ("december", "dec") ] } japaneseLocale :: TimeLocale japaneseLocale = defaultTimeLocale { months = [ ("1月","1月"), ("2月","2月"), ("3月","3月") , ("4月","4月"), ("5月","5月"), ("6月","6月") , ("7月","7月"), ("8月","8月"), ("9月","9月") , ("10月","10月"), ("11月","11月"), ("12月","12月") ] } formatStringFor :: String -> String formatStringFor "en" = "%e %B %Y" formatStringFor "nl" = "%e %B %Y" formatStringFor "jp" = "%Y年 %m月 %e日" formatStringFor _ = "%e %B %Y" formatDate :: String -> String -> String formatDate lang s = case parseTimeM True defaultTimeLocale "%Y-%m-%d" s :: Maybe Day of Just day -> formatTime (localeFor lang) (formatStringFor lang) day Nothing -> s lastUpdateField :: String -> Context a lastUpdateField lang = field "last-update" $ \_ -> do now <- unsafeCompiler getCurrentTime let dayString = formatTime defaultTimeLocale "%Y-%m-%d" now pure $ formatDate lang dayString postCtx :: String -> Context String postCtx language = let -- Custom field for formatted date formattedDateField :: Context String formattedDateField = field "formatted_date" $ \item -> do -- Get the metadata for this item meta <- getMetadata (itemIdentifier item) let mDate = lookupString "date" meta return $ maybe "" (formatDate language) mDate in -- Keep the original "date" field. dateField "date" "%e %B %Y" <> constField "language" language <> formattedDateField <> lastUpdateField language <> langDict language <> defaultContext kitCtx :: String -> Context String kitCtx language = (field "formatted_purchase_date" $ \item -> do metadata <- getMetadata (itemIdentifier item) let purchaseDateValue = lookupString "purchase_date" metadata return $ maybe "" (formatDate language) purchaseDateValue ) <> (postCtx language) isUpcoming :: UTCTime -> Item a -> Compiler Bool isUpcoming cutoff item = do metadata <- getMetadata (itemIdentifier item) case lookupString "date" metadata of Nothing -> return False Just ds -> case parseTimeM True defaultTimeLocale "%Y-%m-%d" ds of Nothing -> return False Just date -> return (date >= cutoff) langDict :: String -> Context a langDict "nl" = constField "filter" "Filter" <> constField "filter-plamo-in-box" "Nog in het doosje" <> constField "filter-plamo-building" "Aan het bouwen" <> constField "filter-plamo-built" "Gebouwd" <> constField "filter-plamo-complete" "Compleet" <> constField "filter-plamo-series-msg" "Mobile Suit Gundam" <> constField "filter-plamo-series-msg-pocket" "Mobile Suit Gundam 0080: War in the Pocket" <> constField "filter-plamo-series-msg-stardust" "Mobile Suit Gundam 0083: Stardust Memory" <> constField "filter-plamo-series-msg-char" "Mobile Suit Gundam: Char's Counterattack" <> constField "filter-plamo-series-msg-gqx" "Mobile Suit Gundam GQuuuuuuX" <> constField "filter-plamo-series-msg-ironblood" "Mobile Suit Gundam Iron Blooded Orphans" <> constField "filter-plamo-series-msg-seed" "Mobile Suit Gundam SEED" <> constField "filter-plamo-series-msg-seed-destiny" "Mobile Suit Gundam SEED Destiny" <> constField "filter-plamo-series-msg-seed-freedom" "Mobile Suit Gundam SEED Freedom" <> constField "filter-plamo-series-msg-witch" "Mobile Suit Gundam the Witch from Mercury" <> constField "filter-plamo-series-msg-unicorn" "Mobile Suit Gundam Unicorn" <> constField "filter-plamo-series-msg-zz" "Mobile Suit Gundam ZZ" <> constField "filter-plamo-series-msg-zeta" "Mobile Suit Zeta Gundam" <> constField "filter-plamo-series-gundam-wing-endless-waltz" "Gundam Wing: Endless Waltz" <> constField "filter-plamo-series-mfg-gundam" "Mobile Fighter G Gundam" <> constField "filter-plamo-series-gundam-bf-try" "Gundam Build Fighters Try" <> constField "switcher" "Taal" <> constField "title-japan" "Japan" <> constField "title-plamo" "Plamo" <> constField "title-radio" "Radio" <> constField "title-software" "Software" <> constField "posted-on" "Geplaatst op:" <> constField "text-plamo-not-ready" "Deze model-kit heeft nog geen gepubliceerd werk." <> constField "text-plamo-back" "Terug naar plamo kits" <> constField "text-radio-back" "Terug naar radio overzicht" <> constField "table-plamo-model-info" "Model-informatie" <> constField "table-plamo-status" "Status" <> constField "table-plamo-model" "Model" <> constField "table-plamo-scale" "Schaal" <> constField "table-plamo-series" "Serie" <> constField "table-plamo-manufacturer" "Fabrikant" <> constField "table-plamo-release-year" "Release jaar" <> constField "table-plamo-kit-number" "Kit nummer" <> constField "table-plamo-purchase-information" "Aankoop-informatie" <> constField "table-plamo-date" "Datum" <> constField "table-plamo-price" "Prijs" <> constField "table-plamo-store" "Winkel" <> constField "table-plamo-venue" "Venue" <> constField "table-plamo-location" "Locatie" <> constField "footer" "Deze website is gebouwd met Hakyll en Bootstrap, met Docker containers en Nginx op een Strato VPS." <> constField "last-update-text" "Website geüpdate" <> mempty langDict "jp" = constField "filter" "フィルター" <> constField "filter-plamo-in-box" "未開封" <> constField "filter-plamo-building" "製作中" <> constField "filter-plamo-built" "組立完了" <> constField "filter-plamo-complete" "完成" <> constField "filter-plamo-series-msg" "機動戦士ガンダム" <> constField "filter-plamo-series-msg-pocket" "機動戦士ガンダム0080 ポケットの中の戦争" <> constField "filter-plamo-series-msg-stardust" "機動戦士ガンダム0083スターダストメモリー" <> constField "filter-plamo-series-msg-char" "機動戦士ガンダム 逆襲のシャア" <> constField "filter-plamo-series-msg-gqx" "機動戦士ガンダム ジークアクス" <> constField "filter-plamo-series-msg-ironblood" "機動戦士ガンダム 鉄血のオルフェンズ" <> constField "filter-plamo-series-msg-seed" "機動戦士ガンダム SEED" <> constField "filter-plamo-series-msg-seed-destiny" "機動戦士ガンダム SEED Destiny" <> constField "filter-plamo-series-msg-seed-freedom" "機動戦士ガンダム SEED Freedom" <> constField "filter-plamo-series-msg-witch" "機動戦士ガンダム 水星の魔女" <> constField "filter-plamo-series-msg-unicorn" "機動戦士ガンダムユニコーン" <> constField "filter-plamo-series-gundam-wing-endless-waltz" "新機動戦記ガンダムウイング・エンドレス ワルツ" <> constField "filter-plamo-series-msg-zz" "機動戦士ガンダムΖΖ" <> constField "filter-plamo-series-msg-zeta" "機動戦士Ζガンダム" <> constField "filter-plamo-series-mfg-gundam" "機動武闘伝Gガンダム" <> constField "filter-plamo-series-gundam-bf-try" "ガンダムビルドファイターズトライ" <> constField "switcher" "言語" <> constField "title-japan" "日本" <> constField "title-plamo" "プラモ" <> constField "title-radio" "ラジオ" <> constField "title-software" "ソフトウェア" <> constField "posted-on" "投稿日:" <> constField "text-plamo-not-ready" "この模型キットには、まだ制作例が公開されていません。" <> constField "text-plamo-back" "プラモデルキットに戻る" <> constField "text-radio-back" "ラジオに戻る" <> constField "table-plamo-model-info" "モデル情報" <> constField "table-plamo-status" "現在の状態" <> constField "table-plamo-model" "モデル" <> constField "table-plamo-scale" "スケール" <> constField "table-plamo-series" "シリーズ" <> constField "table-plamo-manufacturer" "メーカー" <> constField "table-plamo-release-year" "発売年" <> constField "table-plamo-kit-number" "キット番号" <> constField "table-plamo-purchase-information" "購入情報" <> constField "table-plamo-date" "日" <> constField "table-plamo-price" "値段" <> constField "table-plamo-store" "店" <> constField "table-plamo-venue" "購入場所" <> constField "table-plamo-location" "場所" <> constField "footer" "HakyllBootstrap を使用し、Docker コンテナと Nginx を用いた Strato VPS 上で誇りをもって生成されています。" <> constField "last-update-text" "最終更新" <> mempty langDict "en" = constField "filter" "Filter" <> constField "filter-plamo-in-box" "Still in box" <> constField "filter-plamo-building" "Building" <> constField "filter-plamo-built" "Built" <> constField "filter-plamo-complete" "Complete" <> constField "filter-plamo-series-msg" "Mobile Suit Gundam" <> constField "filter-plamo-series-msg-pocket" "Mobile Suit Gundam 0080: War in the Pocket" <> constField "filter-plamo-series-msg-stardust" "Mobile Suit Gundam 0083: Stardust Memory" <> constField "filter-plamo-series-msg-char" "Mobile Suit Gundam: Char's Counterattack" <> constField "filter-plamo-series-msg-gqx" "Mobile Suit Gundam GQuuuuuuX" <> constField "filter-plamo-series-msg-ironblood" "Mobile Suit Gundam Iron Blooded Orphans" <> constField "filter-plamo-series-msg-seed" "Mobile Suit Gundam SEED" <> constField "filter-plamo-series-msg-seed-destiny" "Mobile Suit Gundam SEED Destiny" <> constField "filter-plamo-series-msg-seed-freedom" "Mobile Suit Gundam SEED Freedom" <> constField "filter-plamo-series-msg-witch" "Mobile Suit Gundam the Witch from Mercury" <> constField "filter-plamo-series-msg-unicorn" "Mobile Suit Gundam Unicorn" <> constField "filter-plamo-series-msg-zz" "Mobile Suit Gundam ZZ" <> constField "filter-plamo-series-msg-zeta" "Mobile Suit Zeta Gundam" <> constField "filter-plamo-series-gundam-wing-endless-waltz" "Gundam Wing: Endless Waltz" <> constField "filter-plamo-series-mfg-gundam" "Mobile Fighter G Gundam" <> constField "filter-plamo-series-gundam-bf-try" "Gundam Build Fighters Try" <> constField "switcher" "Language" <> constField "title-japan" "Japan" <> constField "title-plamo" "Plamo" <> constField "title-radio" "Radio" <> constField "title-software" "Software" <> constField "posted-on" "Posted on:" <> constField "text-plamo-not-ready" "This model kit has no work published for it yet." <> constField "text-plamo-back" "Back to plamo kits" <> constField "text-radio-back" "Back to radio overview" <> constField "table-plamo-model-info" "Model information" <> constField "table-plamo-status" "Status" <> constField "table-plamo-model" "Model" <> constField "table-plamo-scale" "Scale" <> constField "table-plamo-series" "Series" <> constField "table-plamo-manufacturer" "Manufacturer" <> constField "table-plamo-release-year" "Release year" <> constField "table-plamo-kit-number" "Kit number" <> constField "table-plamo-purchase-information" "Purchase information" <> constField "table-plamo-date" "Date" <> constField "table-plamo-price" "Price" <> constField "table-plamo-store" "Store" <> constField "table-plamo-venue" "Venue" <> constField "table-plamo-location" "Location" <> constField "footer" "Site proudly generated by Hakyll and Bootstrap, using Docker containers with Nginx on a Strato VPS." <> constField "last-update-text" "Last update" <> mempty langDict _ = mempty