localize-0.2.0.0: Text/Localize/Load.hs
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, ExistentialQuantification, DeriveDataTypeable, OverloadedStrings, RecordWildCards #-}
-- | This module contains definitions for loading translation catalogs.
module Text.Localize.Load
( -- * Data types
LocatePolicy (..), Facet,
-- * Main functions
loadTranslations, locateTranslations,
-- * Commonly used location policies
linuxLocation, localLocation
) where
import Control.Monad
import Control.Monad.Trans
import Data.Default
import qualified Data.Map as M
import qualified Data.Text.Lazy as T
import qualified Data.Gettext as Gettext
import Data.Text.Format.Heavy
import System.Directory
import System.FilePath
import System.FilePath.Glob
import Text.Localize.Types
-- | Load translations when path to each translation file is known.
loadTranslations :: [(LanguageId, FilePath)] -> IO Translations
loadTranslations pairs = do
res <- forM pairs $ \(lang, path) -> do
gmo <- Gettext.loadCatalog path
return (lang, gmo)
return $ Translations $ M.fromList res
-- | Locale facet (@LC_MESSAGES@ and siblings).
type Facet = String
-- | This data type defines where to search for catalog files (@.mo@ or @.gmo@) in the file system.
data LocatePolicy = LocatePolicy {
lcBasePaths :: [FilePath] -- ^ Paths to directory with translations, e.g. @"\/usr\/share\/locale"@. Defaults to @"locale"@.
, lcName :: String -- ^ Catalog file name (in gettext this is also known as text domain). Defaults to @"messages"@.
, lcFacet :: Facet -- ^ Locale facet. Defaults to @LC_MESSAGES@.
, lcFormat :: Format -- ^ File path format. The following variables can be used:
--
-- * @{base}@ - path to directory with translations;
-- * @{language}@ - language code;
-- * @{facet}@ - locale facet;
-- * @{name}@ - file name (text domain), without extension.
--
-- Please note: assumption is made that the @{language}@ variable is used only once.
--
-- Defaults to @"{base}\/{language}\/{facet}\/{name}.mo"@.
}
deriving (Show)
instance Default LocatePolicy where
def = LocatePolicy {
lcBasePaths = ["locale"],
lcName = "messages",
lcFacet = "LC_MESSAGES",
lcFormat = "{base}/{language}/{facet}/{name}.mo"
}
-- | Usual Linux translations location policy.
-- Catalog files are found under @\/usr\/[local\/]share\/locale\/{language}\/LC_MESSAGES\/{name}.mo@.
linuxLocation :: String -- ^ Catalog file name (text domain)
-> LocatePolicy
linuxLocation name = def {lcBasePaths = ["/usr/share/locale", "/usr/local/share/locale"], lcName = name}
-- | Simple translations location polciy, assuming all catalog files located at
-- @{base}\/{language}.mo@.
localLocation :: FilePath -- ^ Path to directory with translations
-> LocatePolicy
localLocation base = def {lcBasePaths = [base], lcFormat = "{base}/{language}.mo"}
-- | Locate and load translations according to specified policy.
locateTranslations :: MonadIO m => LocatePolicy -> m Translations
locateTranslations (LocatePolicy {..}) = liftIO $ do
basePaths <- mapM makeAbsolute lcBasePaths
pairs <- forM basePaths $ \basePath -> do
let vars = M.fromList $
[("base", basePath),
("language", "*"),
("facet", lcFacet),
("name", lcName)] :: M.Map T.Text String
Format fmtItems = lcFormat
(fmtBase, fmtTail) = breakFormat fmtItems
pathGlob = T.unpack (format lcFormat vars)
pathBaseLen = fromIntegral $ T.length (format (Format fmtBase) vars)
pathTailLen = fromIntegral $ T.length (format (Format fmtTail) vars)
paths <- glob pathGlob
forM paths $ \path -> do
let pathWithoutBase = drop pathBaseLen path
languageLen = length pathWithoutBase - pathTailLen
language = take languageLen pathWithoutBase
return (language, path)
loadTranslations $ concat pairs
where
breakFormat items =
let (hd, tl) = break isLanguage items
in case tl of
[] -> (hd, [])
_ -> (hd, tail tl)
isLanguage (FVariable name _) = name == "language"
isLanguage _ = False