snaplet-i18n 0.0.3 → 0.0.3.1
raw patch · 11 files changed
+236/−217 lines, 11 filesdep +snap-loader-dynamicdep +snap-loader-staticdep ~heistdep ~snapdep ~snap-core
Dependencies added: snap-loader-dynamic, snap-loader-static
Dependency ranges changed: heist, snap, snap-core, xmlhtml
Files
- README.md +9/−7
- example/snap.hs +120/−0
- example/snaplets/heist/templates/index.tpl +30/−0
- example/snaplets/i18n/message-en_US.cfg +2/−0
- example/snaplets/i18n/message-zh_CN.cfg +2/−0
- snaplet-i18n.cabal +13/−9
- src/Snap/Snaplet/I18N.hs +60/−46
- test/data/message-en_US.cfg +0/−2
- test/data/message-zh_CN.cfg +0/−2
- test/snap.hs +0/−121
- test/snaplets/heist/templates/index.tpl +0/−30
README.md view
@@ -1,22 +1,25 @@- [](http://travis-ci.org/HaskellCNOrg/snaplet-i18n) ## snaplet-i18n -1. create config below into `data/message_en.cfg`+ 1. create config below into `data/message_en.cfg` ~~~ hello = "Hello" shanghai = "ShangHai" ~~~ -2. use tag in heist template+ 2. use tag in heist template ~~~ <i18n name="shanghai"></i18n>+<i18nSpan name="shanghai"></i18n>+<i18n name="shanghai">+ <a><i18nValue /></a>+</i18n> ~~~ -**see test at test/snap.hs***+**see example at example dir** ## Snaplet @@ -27,6 +30,5 @@ ## TODO - maybe multiple locale support at run time.-- build failed at Hakcage- - because heist does not specify version of `transformers`, which will conflick with `transformer` of `mtl`- - Upgrade heist would be fine.+- use getDataDir to retrieve locale msg. [snaplet tutorial](http://snapframework.com/docs/tutorials/snaplets-tutorial)+
+ example/snap.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RankNTypes #-}+++module Main where++------------------------------------------------------------------------------+import Control.Category+import Control.Monad+import Control.Exception (SomeException, try)+import Data.ByteString (ByteString)+import Data.Maybe+import Prelude hiding ((.))+import Snap+import qualified Data.Configurator as CF+import qualified Data.Configurator.Types as CF+import Snap.Core+import Snap.Http.Server+import Snap.Snaplet.Heist+import Snap.Snaplet.Config+import Snap.Util.FileServe+import System.IO+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy as LBS+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Text.XmlHtml as X+import Text.Templating.Heist+import Text.XmlHtml hiding (render)++#ifdef DEVELOPMENT+import Snap.Loader.Dynamic+#else+import Snap.Loader.Static+#endif++import Snap.Snaplet.I18N++------------------------------------------------------------------------------++data App = App+ { _heist :: Snaplet (Heist App)+ , _i18n :: Snaplet I18NSnaplet+ }++makeLens ''App++instance HasHeist App where+ heistLens = subSnaplet heist++instance HasI18N App where+ i18nLens = i18n++type AppHandler = Handler App App++------------------------------------------------------------------------------++decodedParam :: MonadSnap m => ByteString -> m ByteString+decodedParam p = fromMaybe "" <$> getParam p++------------------------------------------------------------------------------+++testSplice :: Splice AppHandler+testSplice = do+ locale <- liftIO $ getDefaultLocale+ --liftIO $ print locale+ textSplice $ T.pack "test"++index :: AppHandler ()+index = do+ heistLocal (bindSplice "testSplice" testSplice) $ render "index"++-- | wrap to element span+-- ++------------------------------------------------------------------------------++-- | The application's routes.+routes :: [(ByteString, Handler App App ())]+routes = [ ("/", index)+ , ("", with heist heistServe)+ ]++-- | The application initializer.+app :: SnapletInit App App+app = makeSnaplet "app" "An snaplet example application." Nothing $ do+ h <- nestSnaplet "heist" heist $ heistInit "templates"+ locale <- liftIO $ getDefaultLocale+ i <- nestSnaplet "i18n" i18n $ initI18NSnaplet (Just "zh_CN")+ addRoutes routes+ return $ App h i++getDefaultLocale :: IO (Maybe String)+--getDefaultLocale = return $ Just "zh_CN"+getDefaultLocale = liftM getLocale getConf++------------------------------------------------------------------------------++main :: IO ()+main = do+ (conf, site, cleanup) <- $(loadSnapTH [| getConf |]+ 'getActions+ ["heist/templates"])++ _ <- try $ httpServe conf $ site :: IO (Either SomeException ())+ cleanup++getConf :: IO (Config Snap AppConfig)+getConf = commandLineAppConfig defaultConfig++getActions :: Config Snap AppConfig -> IO (Snap (), IO ())+getActions conf = do+ (msgs, site, cleanup) <- runSnaplet+ (appEnvironment =<< getOther conf) app+ hPutStrLn stderr $ T.unpack msgs+ return (site, cleanup)+
+ example/snaplets/heist/templates/index.tpl view
@@ -0,0 +1,30 @@+<!DOCTYPE html> +<html>+<head>++<style>+ p > span { display: block; }+</style>++</head>++<body>++<h2>Test I18N</h2>++<testSplice />++<p>+<i18n name="shanghai"></i18n>+<i18nSpan name="hello" class="test"></i18nSpan>+<i18n name="invalidekey"></i18n>+</p>+ + <p>+ <i18n name="shanghai">+ <input type="submit" value="${i18nValue}" />+ </i18n>+</p>++</body>+</html>
+ example/snaplets/i18n/message-en_US.cfg view
@@ -0,0 +1,2 @@+hello = "Hello"+shanghai = "ShangHai"
+ example/snaplets/i18n/message-zh_CN.cfg view
@@ -0,0 +1,2 @@+hello = "你好"+shanghai = "上海"
snaplet-i18n.cabal view
@@ -3,7 +3,7 @@ -- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr. -- The name of the package. Name: snaplet-i18n-Version: 0.0.3+Version: 0.0.3.1 Description: A light weight i18n snaplet. Synopsis: snaplet-i18n Homepage: https://github.com/HaskellCNOrg/snaplet-i18n@@ -19,13 +19,13 @@ Extra-source-files: README.md- test/snap.hs- test/snaplets/heist/templates/*.tpl- test/data/*.cfg+ example/snap.hs+ example/snaplets/heist/templates/*.tpl+ example/snaplets/i18n/*.cfg Source-Repository head Type: git- Location: https://github.com/HaskellCNOrg/snaplet-i18n+ Location: git://github.com/HaskellCNOrg/snaplet-i18n.git Library@@ -33,12 +33,16 @@ Exposed-modules: Snap.Snaplet.I18N + Other-modules: Paths_snaplet_i18n+ Build-Depends: base >= 4 && < 5,- snap >= 0.9.1 && < 0.10,- snap-core == 0.9.0,- xmlhtml == 0.2.0,- heist == 0.8.1,+ snap == 0.9.*,+ snap-core == 0.9.*,+ snap-loader-dynamic == 0.9.*,+ snap-loader-static == 0.9.*,+ xmlhtml >= 0.2,+ heist >= 0.8.1 && < 0.9, bytestring >= 0.9 && < 1.0, data-lens-template >= 2.1 && < 2.2, data-lens >= 2.0 && < 2.1,
src/Snap/Snaplet/I18N.hs view
@@ -1,8 +1,7 @@-{-# LANGUAGE OverloadedStrings, FlexibleInstances, MultiParamTypeClasses #-}-{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-} -module Snap.Snaplet.I18N +module Snap.Snaplet.I18N ( I18NSnaplet , HasI18N (..) , I18NMessage (..)@@ -12,22 +11,24 @@ ) where import Control.Monad+import qualified Data.Configurator as Config+import qualified Data.Configurator.Types as Config import Data.Lens.Common import Data.Maybe-import System.Directory+import qualified Data.Text as T import System.FilePath.Posix import Text.Templating.Heist-import Text.XmlHtml hiding (render)-import qualified Data.Configurator as Config-import qualified Data.Configurator.Types as Config-import qualified Data.Text as T-import qualified Text.XmlHtml as X+import Text.XmlHtml hiding (render)+import qualified Text.XmlHtml as X -import Snap-import Snap.Snaplet.Heist+import Paths_snaplet_i18n+import Snap+import Snap.Snaplet.Heist --------------------------------------------------------+----------------------------------------------------------------------+-- Types+---------------------------------------------------------------------- type Locale = String type MessageFile = String@@ -36,87 +37,95 @@ defaultLocale = "en_US" -- | ?? could be multiple message files--- +-- defaultMessageFilePrefix :: MessageFile-defaultMessageFilePrefix = "data/message"+defaultMessageFilePrefix = "message" -data I18NConfig = I18NConfig { _getLocale :: Locale -- ^ locale, default "en"- , _getMessageFile :: MessageFile -- ^ message file name, default to "message"+data I18NConfig = I18NConfig { _getLocale :: Locale+ -- ^ locale, default "en"+ , _getMessageFile :: MessageFile+ -- ^ message file name, default to "message" } deriving (Show) --- | A simple mapping to hold i18n messages--- -data I18NMessage = I18NMessage Config.Config+-- | Message content.+--+newtype I18NMessage = I18NMessage Config.Config -- | data type--- +-- data I18NSnaplet = I18NSnaplet { _getI18NConfig :: I18NConfig , _getI18NMessage :: I18NMessage- } + } -- | Compose App with a I18N Snaplet.--- +-- class HasI18N b where i18nLens :: Lens b (Snaplet I18NSnaplet)- + -- | Util functions--- +-- getI18NSnaplet :: HasI18N b => Handler b b I18NSnaplet getI18NSnaplet = with i18nLens Snap.get -- | Get the @I18NMessage@--- +-- getI18NMessages :: HasI18N b => Handler b b I18NMessage getI18NMessages = liftM _getI18NMessage getI18NSnaplet -- | Look up a value in, usuallly Handler Monad--- +-- lookupI18NValue :: HasI18N b => T.Text -> Handler b b T.Text lookupI18NValue key = do (I18NMessage msg) <- getI18NMessages liftIO $ Config.lookupDefault "Error: no value found." msg key --------------------------------------------------------+----------------------------------------------------------------------+-- Init Snaplet+---------------------------------------------------------------------- -- | Init this I18NSnaplet snaplet.--- +-- initI18NSnaplet :: (HasHeist b, HasI18N b) => Maybe Locale -- ^ Locale, default to @defaultLocale@ -> SnapletInit b I18NSnaplet-initI18NSnaplet l = makeSnaplet "I18NSnaplet" "" Nothing $ do+initI18NSnaplet l = makeSnaplet "i18n" description datadir $ do let i18nConfig = I18NConfig (fromMaybe defaultLocale l) defaultMessageFilePrefix- config <- liftIO $ readMessageFile i18nConfig+ fp <- getSnapletFilePath+ msg <- liftIO $ readMessageFile fp i18nConfig addDefaultSplices- return $ I18NSnaplet i18nConfig $ I18NMessage config+ return $ I18NSnaplet i18nConfig msg where addDefaultSplices = addSplices [ ("i18n", liftHeist i18nSplice) , ("i18nSpan", liftHeist i18nSpanSplice)]+ -- config dir for snaplet+ datadir = Just $ liftM (++ "/resources") getDataDir+ description = "light weight i18n snaplet" ---------------------------------------------------------- +-- -- | Load file -- server will not be able to start up if dir doesnt exists. -- Thus, no additional validation check so far.--- -readMessageFile :: I18NConfig -> IO Config.Config-readMessageFile config = do- base <- getCurrentDirectory+--+readMessageFile :: FilePath -> I18NConfig -> IO I18NMessage+readMessageFile base config = do let fullname = base </> file config- Config.load [Config.Required fullname]- where + fmap I18NMessage (Config.load [Config.Required fullname])+ where -- file fullname will be like message-en_US.cfg- -- FIXME: Maybe replace "-" with "_" in locale for typo+ -- FIXME: Maybe replace "-" with "_" in locale in case typo file c = _getMessageFile c ++ "-" ++ _getLocale c ++ ".cfg" ---------------------------------------------------------+----------------------------------------------------------------------+-- Splices+---------------------------------------------------------------------- -- | element attribute used for looking up i18n value. -- e.g. <i18n name="hello" />--- +-- i18nSpliceAttr :: T.Text i18nSpliceAttr = "name" @@ -124,8 +133,11 @@ -- | Splices just wrap value fonud at l10n message. -- When it is used for wrap around other elements, a.k.a children is not empty, -- binding `i18nValue`.+-- e.g.+-- <i18n name="hello" />+-- <i18n name="hello"><p><i18nValue/></p></i18n> ----- FIXME: Turns out that it is not possible to fail at compilation if value is Nothing but runtime. +-- FIXME: Turns out that it is not possible to fail at compilation if value is Nothing but runtime. i18nSplice :: HasI18N b => Splice (Handler b b) i18nSplice = do input <- getParamNode@@ -135,7 +147,7 @@ _ -> runChildrenWithText [("i18nValue", value)] -- | Splices. use 'span' html element wrap result.--- +-- i18nSpanSplice :: HasI18N b => Splice (Handler b b) i18nSpanSplice = do input <- getParamNode@@ -143,8 +155,10 @@ return [X.Element "span" (elementAttrs input) [X.TextNode value]] -- | Look up 'name' attribute value.--- +-- getNameAttr :: Node -> T.Text getNameAttr n = case getAttribute i18nSpliceAttr n of Just x -> x- _ -> "" + _ -> ""++----------------------------------------------------------------------
− test/data/message-en_US.cfg
@@ -1,2 +0,0 @@-hello = "Hello"-shanghai = "ShangHai"
− test/data/message-zh_CN.cfg
@@ -1,2 +0,0 @@-hello = "你好"-shanghai = "上海"
− test/snap.hs
@@ -1,121 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE RankNTypes #-}---module Main where---------------------------------------------------------------------------------import Control.Category-import Control.Monad-import Control.Exception (SomeException, try)-import Data.ByteString (ByteString)-import Data.Maybe-import Network.HTTP.Conduit (responseBody)-import Network.HTTP.Types (renderSimpleQuery)-import Prelude hiding ((.))-import Snap-import qualified Data.Configurator as CF-import qualified Data.Configurator.Types as CF-import Snap.Core-import Snap.Http.Server-import Snap.Snaplet.Heist-import Snap.Snaplet.OAuth-import Snap.Util.FileServe-import System.IO-import qualified Data.ByteString.Char8 as BS-import qualified Data.ByteString.Lazy as LBS-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Text.XmlHtml as X-import Text.Templating.Heist-import Text.XmlHtml hiding (render)--#ifdef DEVELOPMENT-import Snap.Loader.Devel-#else-import Snap.Loader.Prod-#endif--import Snap.Snaplet.I18N----------------------------------------------------------------------------------data App = App- { _heist :: Snaplet (Heist App)- , _i18n :: Snaplet I18NSnaplet- }--makeLens ''App--instance HasHeist App where- heistLens = subSnaplet heist--instance HasI18N App where- i18nLens = i18n--type AppHandler = Handler App App----------------------------------------------------------------------------------decodedParam :: MonadSnap m => ByteString -> m ByteString-decodedParam p = fromMaybe "" <$> getParam p-----------------------------------------------------------------------------------testSplice :: Splice AppHandler-testSplice = do- locale <- liftIO $ getDefaultLocale- liftIO $ print locale- textSplice $ T.pack "test"--index :: AppHandler ()-index = do- heistLocal (bindSplice "testSplice" testSplice) $ render "index"---- | wrap to element span--- ------------------------------------------------------------------------------------ | The application's routes.-routes :: [(ByteString, Handler App App ())]-routes = [ ("/", index)- , ("", with heist heistServe)- ]---- | The application initializer.-app :: SnapletInit App App-app = makeSnaplet "app" "An snaplet example application." Nothing $ do- h <- nestSnaplet "heist" heist $ heistInit "templates"- locale <- liftIO $ getDefaultLocale- i <- nestSnaplet "i18n" i18n $ initI18NSnaplet (Just "zh_CN")- addRoutes routes- return $ App h i--getDefaultLocale :: IO (Maybe String)---getDefaultLocale = return $ Just "zh_CN"-getDefaultLocale = liftM getLocale getConf----------------------------------------------------------------------------------main :: IO ()-main = do- (conf, site, cleanup) <- $(loadSnapTH [| getConf |]- 'getActions- ["heist/templates"])-- _ <- try $ httpServe conf $ site :: IO (Either SomeException ())- cleanup--getConf :: IO (Config Snap ())-getConf = commandLineConfig defaultConfig--getActions :: Config Snap () -> IO (Snap (), IO ())-getActions _ = do- (msgs, site, cleanup) <- runSnaplet app- hPutStrLn stderr $ T.unpack msgs- return (site, cleanup)-
− test/snaplets/heist/templates/index.tpl
@@ -1,30 +0,0 @@-<!DOCTYPE html> -<html>-<head>--<style>- p > span { display: block; }-</style>--</head>--<body>--<h2>Test I18N</h2>--<testSplice />--<p>-<i18n name="shanghai"></i18n>-<i18nSpan name="hello" class="test"></i18nSpan>-<i18n name="invalidekey"></i18n>-</p>- - <p>- <i18n name="shanghai">- <input type="submit" value="${i18nValue}" />- </i18n>-</p>--</body>-</html>