snaplet-i18n (empty) → 0.0.1
raw patch · 9 files changed
+405/−0 lines, 9 filesdep +basedep +bytestringdep +configuratorsetup-changed
Dependencies added: base, bytestring, configurator, data-lens, data-lens-template, directory, filepath, heist, snap, snap-core, text, xmlhtml
Files
- LICENSE +30/−0
- README.md +22/−0
- Setup.hs +2/−0
- snaplet-i18n.cabal +63/−0
- src/Snap/Snaplet/I18N.hs +139/−0
- test/data/message-en_US.cfg +2/−0
- test/data/message-zh_CN.cfg +2/−0
- test/snap.hs +121/−0
- test/snaplets/heist/templates/index.tpl +24/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2012, Haisheng,Wu++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Haisheng,Wu nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,22 @@+## snaplet-i18n++1. create config below into `data/message_en.cfg`++~~~+hello = "Hello"+shanghai = "ShangHai"+~~~++2. use tag in heist template++~~~+<i18n name="shanghai"></i18n>+~~~++**see test at test/snap.hs***++## Snaplet++- [what is snaplet]++[what is snaplet]: http://snapframework.com/docs/tutorials/snaplets-tutorial
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ snaplet-i18n.cabal view
@@ -0,0 +1,63 @@+-- snaplet-i10n.cabal auto-generated by cabal init. For additional+-- options, see+-- 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.1+Description: A light weight i18n snaplet.+Synopsis: snaplet-i18n+Homepage: freizl.github.com+License: BSD3+License-file: LICENSE+Author: Haisheng,Wu+Maintainer: freizl@gmail.com+Copyright: Haisheng Wu+Category: Web+Build-type: Simple+Stability: Experience+Cabal-version: >=1.6+++Extra-source-files: + README.md+ test/snap.hs+ test/snaplets/heist/templates/*.tpl+ test/data/*.cfg++Source-Repository head+ Type: git+ Location: https://github.com/HaskellCNOrg/snaplet-i18n+++Library+ Hs-Source-Dirs: src+ Exposed-modules: + Snap.Snaplet.I18N++ Build-Depends: + base >= 4 && < 5,+ snap >= 0.8 && < 0.9,+ snap-core >= 0.8 && < 0.9,+ bytestring >= 0.9 && < 1.0,+ data-lens-template >= 2.1 && < 2.2,+ data-lens >= 2.0 && < 2.1,+ filepath >= 1.2 && < 1.3,+ directory >= 1.1 && < 1.2,+ configurator >= 0.2 && < 0.3,+ text >= 0.11 && < 0.12,+ xmlhtml >= 0.1.7 && < 0.2,+ heist >= 0.8 && < 0.9++ if impl(ghc >= 6.12.0)+ ghc-options: -Wall -fwarn-tabs -funbox-strict-fields+ -fno-warn-orphans -fno-warn-unused-do-bind+ else+ ghc-options: -Wall -fwarn-tabs -funbox-strict-fields+ -fno-warn-orphans + + -- Modules not exported by this package.+ -- Other-modules: + + -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.+ -- Build-tools: +
+ src/Snap/Snaplet/I18N.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE OverloadedStrings, FlexibleInstances, MultiParamTypeClasses #-}+{-# LANGUAGE DeriveDataTypeable #-}+++module Snap.Snaplet.I18N where++import Control.Monad+import Data.Lens.Common+import Data.Maybe+import System.Directory+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 Snap+import Snap.Snaplet.Heist+-------------------------------------------------------+-- +--+-- +-- +-------------------------------------------------------++type Locale = String+type MessageFile = String++defaultLocale :: Locale+defaultLocale = "en_US"++defaultMessageFilePrefix :: MessageFile+defaultMessageFilePrefix = "data/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++-- | 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+-- +i18nLens' :: HasI18N b => Lens (Snaplet b) (Snaplet I18NSnaplet)+i18nLens' = subSnaplet i18nLens++getI18NSnaplet :: HasI18N b => Handler b b I18NSnaplet+getI18NSnaplet = with i18nLens Snap.get++getI18NMessages :: HasI18N b => Handler b b I18NMessage+getI18NMessages = liftM _getI18NMessage getI18NSnaplet++-------------------------------------------------------++-- | Default I18N snaplet+-- +defaultI18NSnaplet :: (HasHeist b, HasI18N b) => SnapletInit b I18NSnaplet+defaultI18NSnaplet = initI18NSnaplet Nothing Nothing++-- | Init this I18NSnaplet snaplet.+-- +initI18NSnaplet :: (HasHeist b, HasI18N b)+ => Maybe Locale -- ^ Locale, default to @defaultLocale@+ -> Maybe MessageFile -- ^ Message file prefix, default to @defaultMessageFilePrefix@+ -> SnapletInit b I18NSnaplet+initI18NSnaplet l m = makeSnaplet "I18NSnaplet" "" Nothing $ do+ i18nConfig <- return $ I18NConfig (fromMaybe defaultLocale l) (fromMaybe defaultMessageFilePrefix m)+ config <- liftIO $ readMessageFile i18nConfig+ defaultSplices+ return $ I18NSnaplet i18nConfig $ I18NMessage config+ where defaultSplices = addSplices [(i18nSpliceName, liftHeist i18nSplice)]++-------------------------------------------------------+-- ++-- | 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+ fullname <- return $ base </> (file config)+ Config.load [Config.Required fullname]+ where + -- file fullname will be like message-en_US.cfg+ file c = _getMessageFile c ++ "-" ++ _getLocale c ++ ".cfg"+++-------------------------------------------------------++-- | Splice name used in Heist template.+-- e.g. <i18n name="hello" />+-- +i18nSpliceName :: T.Text+i18nSpliceName = "i18n"++-- | Output as `span` HTML element for i18n message.+-- e.g. <span>Shanghai</span>+-- +i18nSpliceElement :: T.Text+i18nSpliceElement = "span"++-- | element attribute used for looking up i18n value.+-- e.g. <i18n name="hello" />+-- +i18nSpliceAttr :: T.Text+i18nSpliceAttr = "name"++-- | Splices+-- +i18nSplice :: HasI18N b => Splice (Handler b b)+i18nSplice = do+ input <- getParamNode+ (I18NMessage messages) <- lift getI18NMessages+ value <- liftIO $ getValue messages input+ -- FIXME: Turns out that it is not possible to fail at compilation if value is Nothing but runtime.+ return [X.Element i18nSpliceElement [] [X.TextNode $ T.pack value]]+ where getValue :: Config.Config -> Node -> IO String+ getValue m i = Config.lookupDefault "Error: no value found." m (getAttr' i)+ getAttr' i = case getAttribute i18nSpliceAttr i of+ Just x -> x+ _ -> ""
+ test/data/message-en_US.cfg view
@@ -0,0 +1,2 @@+hello = "Hello"+shanghai = "ShangHai"
+ test/data/message-zh_CN.cfg view
@@ -0,0 +1,2 @@+hello = "你好"+shanghai = "上海"
+ test/snap.hs view
@@ -0,0 +1,121 @@+{-# 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 locale Nothing+ 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 view
@@ -0,0 +1,24 @@+<!DOCTYPE html> +<html>+<head>++<style>+ p > span { display: block; }+</style>++</head>++<body>++<h2>Test I18N</h2>++<testSplice />++<p>+<i18n name="shanghai"></i18n>+<i18n name="hello"></i18n>+<i18n name="invalidekey"></i18n>+</p>+ +</body>+</html>