{-# LANGUAGE PatternGuards, CPP #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Text.CSL.Parser
-- Copyright   :  (c) Andrea Rossato
-- License     :  BSD-style (see LICENSE)
--
-- Maintainer  :  Andrea Rossato <andrea.rossato@unitn.it>
-- Stability   :  unstable
-- Portability :  unportable
--
-- The CS Language parsers
--
-----------------------------------------------------------------------------

module Text.CSL.Parser where

import Text.CSL.Reference ( readNum )
import Text.CSL.Style
import Text.CSL.Pickle
#ifdef EMBED_DATA_FILES
import Data.FileEmbed
import qualified Data.ByteString as S
#else
import Paths_citeproc_hs ( getDataFileName )
import System.Directory  ( doesFileExist   )
#endif

import Data.Char        ( isUpper, toUpper, toLower )
import Data.Maybe       ( catMaybes                 )
import qualified Data.ByteString.Lazy as L
import Data.ByteString.Lazy.UTF8 ( fromString )

#ifdef USE_NETWORK
import Network.HTTP ( getResponseBody, mkRequest, RequestMethod(..) )
import Network.Browser ( browse, setAllowRedirects, setUserAgent, request )
import Network.URI ( parseURI, URI(..) )
#endif

-- | Read and parse a CSL style file into the internal style
-- representation, the 'Style'.
readCSLFile :: FilePath -> IO Style
readCSLFile src = do
#ifdef USE_NETWORK
  let readURI u = do rsp <- browse $ do
                              setAllowRedirects True
                              setUserAgent "citeproc-hs"
                              request $ mkRequest GET u
                     getResponseBody (Right $ snd rsp)
  f <- case parseURI src of
         Just u | uriScheme u `elem` ["http:","https:"] -> readURI u
         _                                              -> readFile' src
#else
  f <- readFile' src
#endif
  parseCSL' f

-- | Parse a 'String' into a fully localized 'Style'
parseCSL :: String -> IO Style
parseCSL = parseCSL' . fromString

parseCSL' :: L.ByteString -> IO Style
parseCSL' f = do
  let s = readXmlString xpStyle f
  l <- readLocaleFile (styleDefaultLocale s)
  return s { styleLocale = mergeLocales (styleDefaultLocale s) l (styleLocale s)}

#ifdef EMBED_DATA_FILES
localeFiles :: [(FilePath, S.ByteString)]
localeFiles = $(embedDir "locales/")
#endif

readLocaleFile :: String -> IO Locale
readLocaleFile s = do
#ifdef EMBED_DATA_FILES
  f <- case s of
         x | length x == 2 -> let fn = ("locales-" ++
                                        maybe "en-US"
                                        id (lookup x langBase) ++ ".xml")
                              in case lookup fn localeFiles of
                                   Just x' -> return x'
                                   _       -> error "could not load the locale file"
            | otherwise     -> case lookup ("locales-" ++ take 5 x ++ ".xml") localeFiles of
                                 Just x' -> return x'
                                 _       -> error "could not load the locale file"
  return $ readXmlString xpLocale $ L.fromChunks [f]
#else
  f <- case s of
         x | length x == 2 -> getDataFileName ("locales/locales-" ++
                                               maybe "en-US"
                                               id (lookup x langBase) ++ ".xml")
           | otherwise     -> getDataFileName ("locales/locales-" ++ take 5 x ++ ".xml")
  b <- doesFileExist f
  if b
     then readXmlFile xpLocale f
     else readLocaleFile $ take 2 s
#endif

instance XmlPickler Layout where
    xpickle = xpWrap (uncurry3 Layout, \(Layout f d e) -> (f,d,e)) $
              xpIElem "layout" $
              xpTriple xpickle xpDelimiter xpickle

instance XmlPickler Element where
    xpickle = xpAlt tag ps
        where
          tag (Choose       {}) =  0
          tag (Macro        {}) =  1
          tag (Const        {}) =  2
          tag (Variable     {}) =  4
          tag (Term         {}) =  5
          tag (Label        {}) =  6
          tag (Names        {}) =  7
          tag (Substitute   {}) =  9
          tag (Group        {}) = 10
          tag (Number       {}) = 11
          tag (Date         {}) = 12
          ps = [ xpChoose
               , xpMacro
               , xpConst
               , xpVariable
               , xpTerm
               , xpLabel
               , xpNames
               , xpSubStitute
               , xpGroup
               , xpNumber
               , xpDate
               ]

instance XmlPickler IfThen where
    xpickle = xpWrap (uncurry3 IfThen, \(IfThen c m e) -> (c,m,e)) $
              xpTriple xpickle xpickle xpickle

instance XmlPickler Condition where
    xpickle = xpWrap ( \ ((t,v,n),(d,p,a,l)) ->
                           Condition (words t) (words v) (words n)
                                     (words d) (words p) (words a) (words l),
                       \ (Condition t v n d p a l) ->
                           ((unwords t,unwords v,unwords n)
                           ,(unwords d,unwords p,unwords a,unwords l))) $
              xpPair (xpTriple (xpAttrText' "type"             )
                               (xpAttrText' "variable"         )
                               (xpAttrText' "is-numeric"       ))
                     (xp4Tuple (xpAttrText' "is-uncertain-date")
                               (xpAttrText' "position"         )
                               (xpAttrText' "disambiguate"     )
                               (xpAttrText' "locator"          ))

instance XmlPickler Formatting where
    xpickle = xpWrap ( \(((p,s,ff),(fs,fv,fw)),(td,va,tc,d),(q,sp))
                         -> Formatting p s ff fs fv fw td va tc d
                            (if q then NativeQuote else NoQuote) sp False False
                     , \(Formatting p s ff fs fv fw td va tc d _ sp _ _)
                         -> (((p,s,ff),(fs,fv,fw)),(td,va,tc,d),(False,sp))) $
              xpTriple (xpPair (xpTriple (xpAttrText' "prefix"      )
                                         (xpAttrText' "suffix"      )
                                         (xpAttrText' "font-family" ))
                               (xpTriple (xpAttrText' "font-style"  )
                                         (xpAttrText' "font-variant")
                                         (xpAttrText' "font-weight" )))
                       (xp4Tuple (xpAttrText' "text-decoration")
                                 (xpAttrText' "vertical-align" )
                                 (xpAttrText' "text-case"      )
                                 (xpAttrText' "display"        ))
                       (xpPair   (xpAttrWithDefault False "quotes"        xpickle)
                                 (xpAttrWithDefault False "strip-periods" xpickle))

instance XmlPickler Sort where
    xpickle = xpAlt tag ps
        where
          readSort = read . flip (++) " \"\"" . toRead
          tag (SortVariable {}) = 0
          tag (SortMacro    {}) = 1
          ps = [ xpWrap ( \(v,s) -> SortVariable v (readSort s)
                        , \(SortVariable v s) -> (v,toShow $ show s)) $
                 xpElem "key" $
                 xpPair (xpAttrText "variable")
                        (xpAttrWithDefault "ascending" "sort" xpText)

               , xpWrap ( \(v,s,a,b,c) -> SortMacro v (readSort s) (readNum a) (readNum b) c
                        , \(SortMacro v s a b c) -> (v,toShow $ show s,show a,show b, c)) $
                 xpElem "key" $
                 xp5Tuple (xpAttrText "macro")
                          (xpAttrWithDefault "ascending" "sort"            xpText)
                          (xpAttrWithDefault ""          "names-min"       xpText)
                          (xpAttrWithDefault ""          "names-use-first" xpText)
                          (xpAttrWithDefault ""          "names-use-last"  xpText)
               ]

instance XmlPickler Bool where
    xpickle = xpWrap readable xpText

instance XmlPickler Gender where
    xpickle = xpWrap readable xpText

instance XmlPickler Form where
    xpickle = xpWrap readable
                     (xpAttrWithDefault "long" "form" xpText)

instance XmlPickler NumericForm where
    xpickle = xpWrap readable
                     (xpAttrWithDefault "numeric" "form" xpText)

instance XmlPickler DateForm where
    xpickle = xpWrap (read . toRead . flip (++) "-date", const [])
                     (xpAttrWithDefault "no-form" "form" xpText)

instance XmlPickler Match where
    xpickle = xpWrap readable
                     (xpAttrWithDefault "all" "match" xpText)

instance XmlPickler DatePart where
    xpickle = xpWrap (uncurry4 DatePart, \(DatePart s f d fm) -> (s,f,d,fm)) $
              xpElem "date-part" $
              xp4Tuple (xpAttrText "name")
                       (xpAttrWithDefault "long" "form"            xpText)
                       (xpAttrWithDefault "-"    "range-delimiter" xpText)
                        xpickle

instance XmlPickler Name where
    xpickle = xpAlt tag ps
        where
          tag (Name      {}) = 0
          tag (NameLabel {}) = 1
          tag (EtAl      {}) = 2
          ps = [ xpWrap (uncurry5 Name, \(Name f fm nas d nps) -> (f,fm,nas,d,nps)) $
                 xpElem "name"  $ xp5Tuple xpNameForm xpickle xpNameAttrs xpDelimiter xpickle
               , xpWrap (uncurry3 NameLabel, \(NameLabel f fm p) -> (f, fm,p)) $
                 xpElem "label" $ xpTriple xpickle xpickle xpPlural
               , xpWrap (uncurry EtAl, \(EtAl fm t) -> (fm,t)) $
                 xpElem "et-al" $ xpPair xpickle $ xpAttrText' "term"
               ]
          xpNameForm = xpWrap readable $ xpAttrWithDefault "not-set" "form" xpText

instance XmlPickler NamePart where
    xpickle = xpWrap (uncurry NamePart, \(NamePart s fm) -> (s,fm)) $
              xpElem "name-part" $
              xpPair (xpAttrText "name")
                      xpickle

instance XmlPickler CSInfo where
    xpickle = xpWrap ( \ ((t,i,u),(a,c)) -> CSInfo t a c i u
                     , \ s -> ((csiTitle s,  csiId s, csiUpdated s)
                              ,(csiAuthor s, csiCategories s))) $
              xpPair (xpTriple (get "title"  )
                               (get "id"     )
                               (get "updated"))
                     (xpPair   (xpIElemWithDefault (CSAuthor   "" "" "") "author" xpickle)
                               (xpDefault [] $ xpList $ xpIElem "category" xpickle))
                  where
                    get = flip xpIElem xpText

instance XmlPickler CSAuthor where
    xpickle = xpWrap   (uncurry3 CSAuthor, \(CSAuthor a b c) -> (a, b, c)) $
              xpTriple (xpIElemWithDefault [] "name"  xpText)
                       (xpIElemWithDefault [] "email" xpText)
                       (xpIElemWithDefault [] "uri"   xpText)

instance XmlPickler CSCategory where
    xpickle = xpWrap   (uncurry3 CSCategory, \(CSCategory a b c) -> (a, b, c)) $
              xpTriple (xpAttrText  "term"  )
                       (xpAttrText' "schema")
                       (xpAttrText' "label" )

xpStyle :: PU Style
xpStyle
    = xpWrap ( \ ((v,sc,si,sl,l),(o,m,c,b))   -> Style v sc si sl l [] o m c b
             , \ (Style v sc si sl l _ o m c b) -> ((v,sc,si,sl,l),(o,m,c,b))) $
      xpIElem "style" $
      xpPair (xp5Tuple (xpAttrText "version")
                       (xpAttrText "class")
                        xpInfo
                       (xpAttrWithDefault "en-US" "default-locale" xpText)
                       (xpList xpLocale))
             (xp4Tuple  xpStyleOpts
                        xpMacros
                        xpCitation
                       (xpOption xpBibliography))

xpInfo :: PU (Maybe CSInfo)
xpInfo  = xpOption . xpIElem "info" $ xpickle

xpLocale :: PU Locale
xpLocale
    = xpWrap ( \ ((v,l),(o,t,d))   -> Locale v l o t d
             , \ (Locale v l o t d) -> ((v,l),(o,t,d))) $
      xpIElem "locale" $
      xpPair (xpPair   (xpAttrText' "version" )
                       (xpAttrText' "lang"))
             (xpTriple (xpIElemWithDefault [] "style-options" $  xpOpt "punctuation-in-quote")
                        xpTerms
                        (xpList xpLocaleDate))

xpTerms :: PU [CslTerm]
xpTerms
    = xpWrap (concat,return) $ xpList $
      xpIElem "terms" $ xpList $ xpElem "term" $
      xpWrap (\((n,f,g,gf,m),(s,p)) -> CT n f g gf s p m,
             undefined) $
             xpPair (xp5Tuple (xpAttrText "name")
                              xpickle
                              (xpAttrWithDefault Neuter "gender" xpickle)
                              (xpAttrWithDefault Neuter "gender-form" xpickle)
                              (xpAttrText' "match"))
                    (xpChoice (xpWrap (\s -> (s,s), fst)  xpText0)
                              (xpPair (xpIElem "single"   xpText0)
                              (xpIElem "multiple" xpText0))
                              xpLift)

xpMacros :: PU [MacroMap]
xpMacros
    = xpList $ xpIElem "macro" $
      xpPair (xpAttrText "name") xpickle

xpCitation :: PU Citation
xpCitation
    = xpWrap (uncurry3 Citation, \(Citation o s l) -> (o,s,l)) $
      xpIElem "citation" $
      xpTriple xpCitOpts xpSort xpickle

xpBibliography :: PU Bibliography
xpBibliography
    = xpWrap (uncurry3 Bibliography, \(Bibliography o s l) -> (o,s,l)) $
      xpIElem "bibliography" $
      xpTriple xpBibOpts xpSort xpickle

xpOpt :: String -> PU [Option]
xpOpt n
    = xpWrap (\a -> filter ((/=) [] . snd) $ [(n,a)], const []) $
      xpAttrText' n

xpNamesOpt :: PU [Option]
xpNamesOpt = xpOpt "names-delimiter"

xpNameFormat :: PU [Option]
xpNameFormat
    = xpWrap (\(a,b,c,d,e,f) ->
                  catMaybes [ checkOpt "and"                     a
                            , checkOpt "delimiter-precedes-last" b
                            , checkOpt "sort-separator"          c
                            , checkOpt "initialize"              d
                            , checkOpt "initialize-with"         e
                            , checkOpt "name-as-sort-order"      f
                            ] , const (Nothing, Nothing, Nothing, Nothing, Nothing, Nothing)) $
      xp6Tuple (getOpt "and")
               (getOpt "delimiter-precedes-last")
               (getOpt "sort-separator")
               (getOpt "initialize")
               (getOpt "initialize-with")
               (getOpt "name-as-sort-order")

    where
      getOpt n = xpOption $ xpAttr n xpText
      checkOpt _ Nothing  = Nothing
      checkOpt n (Just s) = Just (n,s)

xpNameAttrs :: PU NameAttrs
xpNameAttrs
    = xpWrap (\((a,b,c,d,e),(f,g)) ->
                  filter ((/=) [] . snd) [("et-al-min",a)
                                         ,("et-al-use-first",b)
                                         ,("et-al-subsequent-min",c)
                                         ,("et-al-subsequent-use-first",d)
                                         ,("et-al-use-last",e)
                                         ,("delimiter-precedes-et-al",f)] ++ g
             , const (([],[],[],[],[]),([],[]))) $
      xpPair (xp5Tuple (xpAttrText' "et-al-min")
                       (xpAttrText' "et-al-use-first")
                       (xpAttrText' "et-al-subsequent-min")
                       (xpAttrText' "et-al-subsequent-use-first")
                       (xpAttrText' "et-al-use-last")) $
              xpPair   (xpAttrText' "delimiter-precedes-et-al")
                       xpNameFormat

xpNameOpt :: PU [Option]
xpNameOpt
    = xpWrap (\(a,b,c) ->
                  filter ((/=) [] . snd) $ a ++ [("name-delimiter",b)
                                                ,("name-form",c)], const ([],[],[])) $
      xpTriple  xpNameAttrs
               (xpAttrText' "name-delimiter")
               (xpAttrText' "name-form")

xpBibOpts :: PU [Option]
xpBibOpts
    = xpWrap ( \((a,b,c,d,e,f),(g,h)) ->
                 filter ((/=) [] . snd) $ [("hanging-indent",a)
                                          ,("second-field-align",b)
                                          ,("subsequent-author-substitute",c)
                                          ,("subsequent-author-substitute-rule",d)
                                          ,("line-spacing",e)
                                          ,("entry-spacing",f)] ++ g ++ h
                , const (([],[],[],[],[],[]),([],[]))) $
      xpPair (xp6Tuple (xpAttrText' "hanging-indent")
                       (xpAttrText' "second-field-align")
                       (xpAttrText' "subsequent-author-substitute")
                       (xpAttrText' "subsequent-author-substitute-rule")
                       (xpAttrText' "line-spacing")
                       (xpAttrText' "entry-spacing")) $
             xpPair xpNameOpt xpNamesOpt

xpCitOpts :: PU [Option]
xpCitOpts
    = xpWrap ( \((a,b,c),(d,e,f,g,h,i),(j,k)) ->
                 filter ((/=) [] . snd) $ [("disambiguate-add-names",a)
                                          ,("disambiguate-add-givenname",b)
                                          ,("disambiguate-add-year-suffix",c)
                                          ,("givenname-disambiguation-rule",d)
                                          ,("collapse",e)
                                          ,("cite-group-delimiter",f)
                                          ,("year-suffix-delimiter",g)
                                          ,("after-collapse-delimiter",h)
                                          ,("near-note-distance",i)] ++ j ++ k
                , const (([],[],[]),([],[],[],[],[],[]),([],[]))) $
      xpTriple (xpTriple (xpAttrText' "disambiguate-add-names")
                         (xpAttrText' "disambiguate-add-givenname")
                         (xpAttrText' "disambiguate-add-year-suffix"))
               (xp6Tuple (xpAttrText' "givenname-disambiguation-rule")
                         (xpAttrText' "collapse")
                         (xpAttrText' "cite-group-delimiter")
                         (xpAttrText' "year-suffix-delimiter")
                         (xpAttrText' "after-collapse-delimiter")
                         (xpAttrText' "near-note-distance"))
               (xpPair xpNameOpt xpNamesOpt)

xpStyleOpts :: PU [Option]
xpStyleOpts
    = xpWrap ( \((a,b,c),(d,e)) ->
                 filter ((/=) [] . snd) $ [("page-range-format",a)
                                          ,("demote-non-dropping-particle",b)
                                          ,("initialize-with-hyphen",c)] ++ d ++ e
                , const (([],[],[]),([],[]))) $
      xpPair (xpTriple (xpAttrText' "page-range-format")
                       (xpAttrText' "demote-non-dropping-particle")
                       (xpAttrText' "initialize-with-hyphen")) $
               (xpPair xpNameOpt xpNamesOpt)

xpSort :: PU [Sort]
xpSort
    = xpDefault [] $ xpElem "sort" $ xpList xpickle

xpChoose :: PU Element
xpChoose
    = xpWrap (uncurry3 Choose, \(Choose b t e) -> (b,t,e)) $
      xpElem "choose" $
      xpTriple (                        xpElem "if"      xpickle)
               (xpDefault [] $ xpList $ xpElem "else-if" xpickle)
               (xpDefault []          $ xpElem "else"    xpickle)

xpMacro :: PU Element
xpMacro
    = xpWrap (uncurry Macro, \(Macro s fm) -> (s,fm)) $
      xpTextElem $ xpPair (xpAttrText "macro") xpickle

xpConst :: PU Element
xpConst
    = xpWrap (uncurry Const, \(Const s fm) -> (s,fm)) $
      xpTextElem $ xpPair (xpAttrText "value") xpickle

xpVariable :: PU Element
xpVariable
    = xpWrap ( \((v,f,fm),d)        -> Variable (words v) f fm d
             , \(Variable v f fm d) -> ((unwords v,f,fm),d)) $
      xpTextElem $ xpPair (xpCommon "variable") xpDelimiter

xpTerm :: PU Element
xpTerm
    = xpWrap ( \((t,f,fm),p)    -> Term t f fm p
             , \(Term t f fm p) -> ((t,f,fm),p)) $
      xpTextElem $ xpPair (xpCommon "term") $
                   xpAttrWithDefault True "plural" xpickle

xpNames :: PU Element
xpNames
    = xpWrap ( \((a,n,fm),d,sb)     -> Names (words a) n fm d sb
             , \(Names a n fm d sb) -> ((unwords a,n,fm),d,sb)) $
      xpElem "names" $ xpTriple names xpDelimiter xpickle
    where names    = xpTriple (xpAttrText "variable") xpName xpickle
          xpName   = xpChoice xpZero xpickle check
          check [] = xpLift [Name NotSet emptyFormatting [] [] []]
          check  l = if any isName l then xpLift l else xpZero

xpLabel :: PU Element
xpLabel
    = xpWrap ( uncurry4 Label
             , \(Label s f fm p) -> (s,f,fm,p)) $
      xpElem "label" $
      xp4Tuple (xpAttrText' "variable")
                xpickle xpickle xpPlural

xpSubStitute :: PU Element
xpSubStitute
    = xpWrap (Substitute, \(Substitute es) -> es) $
      xpElem "substitute" xpickle

xpGroup :: PU Element
xpGroup
    = xpWrap (uncurry3 Group, \(Group fm d e) -> (fm,d,e)) $
      xpElem "group" $
      xpTriple xpickle xpDelimiter xpickle

xpNumber :: PU Element
xpNumber
    = xpWrap (uncurry3 Number, \(Number s f fm) -> (s,f,fm)) $
      xpElem "number" $ xpCommon "variable"

xpDate :: PU Element
xpDate
    = xpWrap ( \((s,f,fm),(d,dp,dp'))    -> Date (words s) f fm d dp dp'
             , \(Date s f fm d dp dp') -> ((unwords s,f,fm),(d,dp,dp'))) $
      xpElem  "date" $
      xpPair (xpCommon "variable")
             (xpTriple xpDelimiter xpickle (xpAttrText' "date-parts"))

xpLocaleDate :: PU Element
xpLocaleDate
    = xpWrap ( \((s,f,fm),(d,dp,dp'))    -> Date (words s) f fm d dp dp'
             , \(Date s f fm d dp dp') -> ((unwords s,f,fm),(d,dp,dp'))) $
      xpIElem  "date" $
      xpPair  (xpTriple (xpLift []) xpickle xpickle)
              (xpTriple xpDelimiter xpickle (xpLift []))

xpTextElem :: PU a -> PU a
xpTextElem = xpElem "text"

xpDelimiter :: PU String
xpDelimiter = xpAttrText' "delimiter"

xpPlural :: PU Plural
xpPlural = xpWrap readable $ xpAttrWithDefault "contextual" "plural" xpText

xpCommon :: (XmlPickler b, XmlPickler c) => String -> PU (String,b,c)
xpCommon s = xpTriple (xpAttrText s) xpickle xpickle

-- | For mandatory attributes.
xpAttrText :: String -> PU String
xpAttrText n = xpAttr n xpText

-- | For optional attributes.
xpAttrText' ::  String -> PU String
xpAttrText' n = xpAttrWithDefault [] n xpText

xpAttrWithDefault :: Eq a => a -> String -> PU a -> PU a
xpAttrWithDefault d n = xpDefault d . xpAttr n

xpIElemWithDefault :: Eq a => a -> String -> PU a -> PU a
xpIElemWithDefault d n = xpDefault d . xpIElem n

readable :: (Read a, Show b) => (String -> a, b -> String)
readable =  (read . toRead, toShow . show)

toShow :: String -> String
toShow = foldr g [] . f
    where g    x xs  = if isUpper x then '-' : toLower x : xs else x : xs
          f (  x:xs) = toLower x : xs
          f       [] = []

toRead :: String -> String
toRead    []  = []
toRead (s:ss) = toUpper s : camel ss
    where
      camel x
          | '-':y:ys <- x = toUpper y : camel ys
          | '_':y:ys <- x = toUpper y : camel ys
          |     y:ys <- x =         y : camel ys
          | otherwise     = []

langBase :: [(String, String)]
langBase
    = [("af", "af-ZA")
      ,("ar", "ar-AR")
      ,("bg", "bg-BG")
      ,("ca", "ca-AD")
      ,("cs", "cs-CZ")
      ,("da", "da-DK")
      ,("de", "de-DE")
      ,("el", "el-GR")
      ,("en", "en-US")
      ,("es", "es-ES")
      ,("et", "et-EE")
      ,("fa", "fa-IR")
      ,("fi", "fi-FI")
      ,("fr", "fr-FR")
      ,("he", "he-IL")
      ,("hr", "hr-HR")
      ,("hu", "hu-HU")
      ,("is", "is-IS")
      ,("it", "it-IT")
      ,("ja", "ja-JP")
      ,("km", "km-KH")
      ,("ko", "ko-KR")
      ,("lt", "lt-LT")
      ,("lv", "lv-LV")
      ,("mn", "mn-MN")
      ,("nb", "nb-NO")
      ,("nl", "nl-NL")
      ,("nn", "nn-NO")
      ,("pl", "pl-PL")
      ,("pt", "pt-PT")
      ,("ro", "ro-RO")
      ,("ru", "ru-RU")
      ,("sk", "sk-SK")
      ,("sl", "sl-SI")
      ,("sr", "sr-RS")
      ,("sv", "sv-SE")
      ,("th", "th-TH")
      ,("tr", "tr-TR")
      ,("uk", "uk-UA")
      ,("vi", "vi-VN")
      ,("zh", "zh-CN")
      ]