diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Revision history for gettext-th
 
+## 0.2.0.0
+
+* duplicates are filtered out
+* utf8 support
+
 ## 0.1.0.0 -- YYYY-mm-dd
 
 * First version. Released on an unsuspecting world.
diff --git a/gettext-th.cabal b/gettext-th.cabal
--- a/gettext-th.cabal
+++ b/gettext-th.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               gettext-th
-version:            0.1.0.3
+version:            0.2.0.0
 
 synopsis: gettext-th can internationalise a haskell program without runtime dependencies
 
@@ -17,7 +17,7 @@
 
 -- A copyright notice.
 -- copyright:
-category: i18n Text
+category: i18n, Text
 extra-source-files: CHANGELOG.md, README.md
 
 library
@@ -30,6 +30,7 @@
     -- other-extensions:
     build-depends:    base  >= 4 && < 5
                     , bytestring
+                    , containers
                     , directory
                     , filepath
                     , haskell-gettext
diff --git a/src/I18N/Gettext/TH.hs b/src/I18N/Gettext/TH.hs
--- a/src/I18N/Gettext/TH.hs
+++ b/src/I18N/Gettext/TH.hs
@@ -4,6 +4,8 @@
 
 import Language.Haskell.TH
 import Language.Haskell.TH.Quote
+import Language.Haskell.TH.Syntax
+
 import Instances.TH.Lift()
 import System.IO.Unsafe
 import System.Directory
@@ -12,6 +14,8 @@
 import Data.Bifunctor
 import Data.Char (isSpace)
 import Data.List
+import Data.Set (Set)
+import qualified Data.Set as S
 
 import qualified Data.ByteString as B
 import qualified Data.Text as T
@@ -21,11 +25,11 @@
 import qualified Data.Gettext as G
 import Data.Gettext (Catalog, loadCatalog)
 import System.FilePath.Posix
-
+import System.IO
 
-{-# NOINLINE firstCall #-}
-firstCall :: IORef Bool
-firstCall = unsafePerformIO $ newIORef True
+{-# NOINLINE knownMsgs #-}
+knownMsgs :: IORef (Set String)
+knownMsgs = unsafePerformIO $ newIORef S.empty
 
 potFileName :: FilePath
 potFileName = "po/messages.pot"
@@ -61,57 +65,80 @@
   "\"Language-Team: LANGUAGE <LL@li.org>\\n\"",
   "\"Language: \\n\"",
   "\"MIME-Version: 1.0\\n\"",
-  "\"Content-Type: text/plain; charset=CHARSET\\n\"",
+  "\"Content-Type: text/plain; charset=utf-8\\n\"",
   "\"Content-Transfer-Encoding: 8bit\\n\""
   ]
 
+
+
+
+writeFileUtf8 :: FilePath -> IOMode -> String -> IO ()
+writeFileUtf8 f mode txt = withFile f mode (\ hdl -> do
+                                               hSetEncoding hdl utf8
+                                               hPutStr hdl txt)  
+
 createPotFile :: Q ()
 createPotFile = do
-  runIO $ do
-    f <- readIORef firstCall
-    when f $ do
-      writeIORef firstCall False
+  fn <- runIO $ do
       createDirectoryIfMissing True (takeDirectory potFileName)
       potE <- doesFileExist potFileName    
       when potE $
         renameFile potFileName (potFileName ++ ".bak")
 
-      writeFile potFileName header
-    --addDependentFile poFileName
+      writeFileUtf8 potFileName WriteMode header
+      makeAbsolute moFileName
+  addDependentFile fn
 
 
 packStr :: String -> B.ByteString
 packStr = encodeUtf8 . T.pack
 
-
 gettextQ  :: String -> Q Exp
 gettextQ str = do
-  createPotFile
-  loc <- location
-  runIO $ appendFile potFileName $ unlines $ poEntry loc str
+  kmsgs <- runIO $ do
+     kmsgs <- readIORef knownMsgs
+     writeIORef knownMsgs (S.insert str kmsgs)
+     return kmsgs
+
+  when (S.null kmsgs) createPotFile
+  when (str `S.notMember` kmsgs) $ do
+    loc <- location
+    runIO $ writeFileUtf8 potFileName AppendMode $ unlines $ poEntry loc str
   let trans = TL.toStrict $ G.gettext catalog (packStr str)
   [| trans |]
 
+quote :: String -> String
+quote s =  '"':escape s
+  where escape [] = "\""
+        escape ('"':s') = '\\':'"':escape s'
+        escape ('\n':s') = '\\':'n':escape s'
+        escape ('\r':s') = escape s'
+        escape (c:s') = c:escape s'
+        
 
 poEntry :: Loc -> String -> [String]
 poEntry loc msg = [
       "",
       "#: " ++ (loc_filename loc) ++ ":0", -- TODO line nr or char pos
-      "msgid " ++ show msg,
-      "msgstr " ++ show msg 
+      "msgid " ++ quote msg,
+      "msgstr " ++ quote msg 
       ]
 
 gettextsDecs  :: String -> Q [Dec]
 gettextsDecs str = do
-  createPotFile
-  loc <- location
   let msgs = map splitKeyMsg $ parseLines str  
-  runIO $ appendFile potFileName $ unlines $ concat [ poEntry loc msg | (_, msg) <- msgs ]    
+  kmsgs <- runIO $ do
+     kmsgs <- readIORef knownMsgs
+     writeIORef knownMsgs (foldl' (\ acc (_, msg) -> msg `S.insert` acc) kmsgs msgs)
+     return kmsgs
+  when (S.null kmsgs) createPotFile
+  loc <- location
 
+  runIO $ writeFileUtf8 potFileName AppendMode $ unlines $ concat [ poEntry loc msg | (_, msg) <- msgs, msg `S.notMember` kmsgs ]    
+
   forM msgs $ \ (key, msg) ->
               let trans = TL.toStrict $ G.gettext catalog (packStr msg) in do
-                 e <-  [| trans |]
-                 return $ FunD (mkName key) [Clause [] (NormalB e) []]
+                 funD (mkName key) [clause [] (normalB [| trans |]) []]
                  
 
          
@@ -132,7 +159,7 @@
          join acc cl = (intercalate "\n" $ reverse cl):acc
 
 splitKeyMsg :: String -> (String, String)        
-splitKeyMsg line =  bimap trim (trim . tail)$ span (/= ':') line 
+splitKeyMsg line =  bimap trim (trim . tail) $ span (/= ':') line 
   
 
 trim :: String -> String
@@ -143,7 +170,7 @@
 gettext :: QuasiQuoter
 gettext = QuasiQuoter
   { quoteExp  = gettextQ
-  , quotePat  = error "Usage as a parttern is not supported"
+  , quotePat  = error "Usage as a pattern is not supported"
   , quoteType = error "Usage as a type is not supported"
   , quoteDec = gettextsDecs
   }
