diff --git a/Data/Text/ICU/Converter.hs b/Data/Text/ICU/Converter.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/ICU/Converter.hs
@@ -0,0 +1,235 @@
+{-# LANGUAGE CPP, ForeignFunctionInterface #-}
+-- |
+-- Module      : Data.Text.ICU.Converter
+-- Copyright   : (c) Bryan O'Sullivan 2009
+--
+-- License     : BSD-style
+-- Maintainer  : bos@serpentine.com
+-- Stability   : experimental
+-- Portability : GHC
+--
+-- Character set conversion functions for Unicode, implemented as
+-- bindings to the International Components for Unicode (ICU)
+-- libraries.
+
+module Data.Text.ICU.Converter
+    (
+    -- * Character set conversion
+      Converter
+    -- ** Basic functions
+    , open
+    , fromUnicode
+    , toUnicode
+    -- ** Converter metadata
+    , getName
+    , usesFallback
+    , setFallback
+    , isAmbiguous
+    -- * Functions for controlling global behavior
+    , getDefaultName
+    , setDefaultName
+    -- * Miscellaneous functions
+    , compareNames
+    , aliases
+    -- * Metadata
+    , converterNames
+    , standardNames
+    ) where
+
+import Data.ByteString.Internal (ByteString, createAndTrim)
+import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
+import Data.Int (Int32)
+import Data.Text (Text)
+import Data.Text.Foreign (fromPtr, lengthWord16, useAsPtr)
+import Data.Text.ICU.Converter.Internal
+import Data.Text.ICU.Error.Internal (UErrorCode, handleError)
+import Data.Word (Word16)
+import Foreign.C.String (CString, peekCString, withCString)
+import Foreign.C.Types (CInt)
+import Foreign.ForeignPtr (newForeignPtr)
+import Foreign.Marshal.Array (allocaArray)
+import Foreign.Ptr (FunPtr, Ptr, castPtr, nullPtr)
+import System.IO.Unsafe (unsafePerformIO)
+import Data.Text.ICU.Internal (UBool, UChar, asBool, asOrdering)
+
+-- | Do a fuzzy compare of two converter/alias names.  The comparison
+-- is case-insensitive, ignores leading zeroes if they are not
+-- followed by further digits, and ignores all but letters and digits.
+-- Thus the strings @\"UTF-8\"@, @\"utf_8\"@, @\"u*T\@f08\"@ and
+-- @\"Utf 8\"@ are exactly equivalent.  See section 1.4, Charset Alias
+-- Matching in Unicode Technical Standard #22 at
+-- <http://www.unicode.org/reports/tr22/>
+compareNames :: String -> String -> Ordering
+compareNames a b =
+  unsafePerformIO . withCString a $ \aptr ->
+    fmap asOrdering . withCString b $ ucnv_compareNames aptr
+
+-- | Create a 'Converter' with the name of a coded character set
+-- specified as a string.  The actual name will be resolved with the
+-- alias file using a case-insensitive string comparison that ignores
+-- leading zeroes and all non-alphanumeric characters.  E.g., the
+-- names @\"UTF8\"@, @\"utf-8\"@, @\"u*T\@f08\"@ and @\"Utf 8\"@ are
+-- all equivalent (see also 'compareNames').  If an empty string is
+-- passed for the converter name, it will create one with the
+-- 'getDefaultName' return value.
+--
+-- A converter name may contain options like a locale specification to
+-- control the specific behavior of the newly instantiated converter.
+-- The meaning of the options depends on the particular converter.  If
+-- an option is not defined for or recognized by a given converter,
+-- then it is ignored.
+--
+-- Options are appended to the converter name string, with a comma
+-- between the name and the first option and also between adjacent
+-- options.
+--
+-- If the alias is ambiguous, then the preferred converter is used.
+--
+-- The conversion behavior and names can vary between platforms. ICU
+-- may convert some characters differently from other
+-- platforms. Details on this topic are in the ICU User's Guide at
+-- <http://icu-project.org/userguide/conversion.html>. Aliases
+-- starting with a @\"cp\"@ prefix have no specific meaning other than
+-- its an alias starting with the letters @\"cp\"@. Please do not
+-- associate any meaning to these aliases.
+open :: String -> IO Converter
+open name =
+  fmap Converter . newForeignPtr ucnv_close =<< named (handleError . ucnv_open)
+  where named act
+            | null name = act nullPtr
+            | otherwise = withCString name act
+
+-- | Convert the Unicode string into a codepage string using the given
+-- converter.
+fromUnicode :: Converter -> Text -> IO ByteString
+fromUnicode cnv t =
+  useAsPtr t $ \tptr tlen ->
+    withConverter cnv $ \cptr -> do
+      let capacity = fromIntegral . max_bytes_for_string cptr . fromIntegral $
+                     lengthWord16 t
+      createAndTrim (fromIntegral capacity) $ \sptr ->
+        fmap fromIntegral . handleError $
+           ucnv_fromUChars cptr (castPtr sptr) capacity tptr (fromIntegral tlen)
+
+-- | Convert the codepage string into a Unicode string using the given
+-- converter.
+toUnicode :: Converter -> ByteString -> IO Text
+toUnicode cnv bs =
+  unsafeUseAsCStringLen bs $ \(sptr, slen) ->
+    withConverter cnv $ \cptr -> do
+      let capacity = slen * 2
+      allocaArray capacity $ \tptr -> 
+        fromPtr tptr =<< (fmap fromIntegral . handleError $
+                          ucnv_toUChars cptr tptr (fromIntegral capacity) sptr
+                                        (fromIntegral slen))
+
+-- | Determines whether the converter uses fallback mappings or not.
+-- This flag has restrictions; see 'setFallback'.
+usesFallback :: Converter -> IO Bool
+usesFallback cnv = asBool `fmap` withConverter cnv ucnv_usesFallback
+
+-- | Sets the converter to use fallback mappings or not.  Regardless
+-- of this flag, the converter will always use fallbacks from Unicode
+-- Private Use code points, as well as reverse fallbacks (to Unicode).
+-- For details see \".ucm File Format\" in the Conversion Data chapter
+-- of the ICU User Guide:
+-- <http://www.icu-project.org/userguide/conversion-data.html#ucmformat>
+setFallback :: Converter -> Bool -> IO ()
+setFallback cnv flag =
+  withConverter cnv $ \p -> (ucnv_setFallback p (fromIntegral (fromEnum flag)))
+
+-- | Returns the current default converter name. If you want to 'open'
+-- a default converter, you do not need to use this function.  It is
+-- faster to pass the empty string to 'open' the default converter.
+getDefaultName :: IO String
+getDefaultName = peekCString =<< ucnv_getDefaultName
+
+-- | Indicates whether the converter contains ambiguous mappings of
+-- the same character or not.
+isAmbiguous :: Converter -> Bool
+isAmbiguous cnv = asBool . unsafePerformIO $ withConverter cnv ucnv_isAmbiguous
+
+-- | Sets the current default converter name. If this function needs
+-- to be called, it should be called during application
+-- initialization. Most of the time, the results from 'getDefaultName'
+-- or 'open' with an empty string argument is sufficient for your
+-- application.
+--
+-- /Note/: this function is not thread safe. /Do not/ call this
+-- function when /any/ ICU function is being used from more than one
+-- thread!
+setDefaultName :: String -> IO ()
+setDefaultName s = withCString s $ ucnv_setDefaultName
+
+-- | A list of the canonical names of all available converters.
+converterNames :: [String]
+{-# NOINLINE converterNames #-}
+converterNames = unsafePerformIO $
+  mapM ((peekCString =<<) . ucnv_getAvailableName) [0..ucnv_countAvailable-1]
+
+-- | The list of supported standard names.
+standardNames :: [String]
+{-# NOINLINE standardNames #-}
+standardNames = filter (not . null) . unsafePerformIO $
+  mapM ((peekCString =<<) . handleError . ucnv_getStandard) [0..ucnv_countStandards-1]
+
+-- | Return the aliases for a given converter or alias name.
+aliases :: String -> [String]
+aliases name = unsafePerformIO . withCString name $ \ptr -> do
+  count <- ucnv_countAliases ptr
+  if count == 0
+    then return []
+    else mapM ((peekCString =<<) . handleError . ucnv_getAlias ptr) [0..count-1]
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_open_4_0" ucnv_open
+    :: CString -> Ptr UErrorCode -> IO (Ptr UConverter)
+
+foreign import ccall unsafe "unicode/ucnv.h &ucnv_close_4_0" ucnv_close
+    :: FunPtr (Ptr UConverter -> IO ())
+
+foreign import ccall unsafe "__get_max_bytes_for_string" max_bytes_for_string
+    :: Ptr UConverter -> CInt -> CInt
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_toUChars_4_0" ucnv_toUChars
+    :: Ptr UConverter -> Ptr UChar -> Int32 -> CString -> Int32
+    -> Ptr UErrorCode -> IO Int32
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_fromUChars_4_0" ucnv_fromUChars
+    :: Ptr UConverter -> CString -> Int32 -> Ptr UChar -> Int32
+    -> Ptr UErrorCode -> IO Int32
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_compareNames_4_0" ucnv_compareNames
+    :: CString -> CString -> IO CInt
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_getDefaultName_4_0" ucnv_getDefaultName
+    :: IO CString
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_setDefaultName_4_0" ucnv_setDefaultName
+    :: CString -> IO ()
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_countAvailable_4_0" ucnv_countAvailable
+    :: Int32
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_getAvailableName_4_0" ucnv_getAvailableName
+    :: Int32 -> IO CString
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_countAliases_4_0" ucnv_countAliases
+    :: CString -> IO Word16
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_getAlias_4_0" ucnv_getAlias
+    :: CString -> Word16 -> Ptr UErrorCode -> IO CString
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_countStandards_4_0" ucnv_countStandards
+    :: Word16
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_getStandard_4_0" ucnv_getStandard
+    :: Word16 -> Ptr UErrorCode -> IO CString
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_usesFallback_4_0" ucnv_usesFallback
+    :: Ptr UConverter -> IO UBool
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_setFallback_4_0" ucnv_setFallback
+    :: Ptr UConverter -> UBool -> IO ()
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_isAmbiguous_4_0" ucnv_isAmbiguous
+    :: Ptr UConverter -> IO UBool
diff --git a/Data/Text/ICU/Converter/Internal.hs b/Data/Text/ICU/Converter/Internal.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/ICU/Converter/Internal.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE DeriveDataTypeable, EmptyDataDecls, ForeignFunctionInterface #-}
+-- |
+-- Module      : Data.Text.ICU.Converter.Internal
+-- Copyright   : (c) Bryan O'Sullivan 2009
+--
+-- License     : BSD-style
+-- Maintainer  : bos@serpentine.com
+-- Stability   : experimental
+-- Portability : GHC
+--
+-- Low-level character set types and functions.
+
+module Data.Text.ICU.Converter.Internal
+    (
+      Converter(..)
+    , UConverter
+    , getName
+    , withConverter
+    ) where
+
+import Data.Text.ICU.Error.Internal (UErrorCode, handleError)
+import Data.Typeable (Typeable)
+import Foreign.C.String (CString, peekCString)
+import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
+import Foreign.Ptr (Ptr)
+import System.IO.Unsafe (unsafePerformIO)
+
+data UConverter
+
+-- | Character set converter type.  /Note/: this structure is not
+-- thread safe. It is /not/ safe to use value of this type
+-- simultaneously from multiple threads.
+data Converter = Converter {-# UNPACK #-} !(ForeignPtr UConverter)
+                 deriving (Eq, Typeable)
+
+instance Show Converter where
+    show c = "Converter " ++ show (getName c)
+
+withConverter :: Converter -> (Ptr UConverter -> IO a) -> IO a
+{-# INLINE withConverter #-}
+withConverter (Converter cnv) action = withForeignPtr cnv action
+
+-- | Gets the internal, canonical name of the converter.
+getName :: Converter -> String
+getName cnv = unsafePerformIO .
+  withConverter cnv $ \ptr ->
+    peekCString =<< handleError (ucnv_getName ptr)
+
+foreign import ccall unsafe "unicode/ucnv.h ucnv_getName_4_0" ucnv_getName
+    :: Ptr UConverter -> Ptr UErrorCode -> IO CString
diff --git a/Data/Text/ICU/Error.hsc b/Data/Text/ICU/Error.hsc
new file mode 100644
--- /dev/null
+++ b/Data/Text/ICU/Error.hsc
@@ -0,0 +1,275 @@
+module Data.Text.ICU.Error
+    (
+     -- * Types
+     ErrorCode,
+
+     -- * Functions
+     isSuccess,
+     isFailure,
+     errorName,
+
+     -- * Warnings
+     u_USING_FALLBACK_WARNING,
+     u_USING_DEFAULT_WARNING,
+     u_SAFECLONE_ALLOCATED_WARNING,
+     u_STATE_OLD_WARNING,
+     u_STRING_NOT_TERMINATED_WARNING,
+     u_SORT_KEY_TOO_SHORT_WARNING,
+     u_AMBIGUOUS_ALIAS_WARNING,
+     u_DIFFERENT_UCA_VERSION,
+
+     -- * Errors
+     u_ILLEGAL_ARGUMENT_ERROR,
+     u_MISSING_RESOURCE_ERROR,
+     u_INVALID_FORMAT_ERROR,
+     u_FILE_ACCESS_ERROR,
+     u_INTERNAL_PROGRAM_ERROR,
+     u_MESSAGE_PARSE_ERROR,
+     u_MEMORY_ALLOCATION_ERROR,
+     u_INDEX_OUTOFBOUNDS_ERROR,
+     u_PARSE_ERROR,
+     u_INVALID_CHAR_FOUND,
+     u_TRUNCATED_CHAR_FOUND,
+     u_ILLEGAL_CHAR_FOUND,
+     u_INVALID_TABLE_FORMAT,
+     u_INVALID_TABLE_FILE,
+     u_BUFFER_OVERFLOW_ERROR,
+     u_UNSUPPORTED_ERROR,
+     u_RESOURCE_TYPE_MISMATCH,
+     u_ILLEGAL_ESCAPE_SEQUENCE,
+     u_UNSUPPORTED_ESCAPE_SEQUENCE,
+     u_NO_SPACE_AVAILABLE,
+     u_CE_NOT_FOUND_ERROR,
+     u_PRIMARY_TOO_LONG_ERROR,
+     u_STATE_TOO_OLD_ERROR,
+     u_TOO_MANY_ALIASES_ERROR,
+     u_ENUM_OUT_OF_SYNC_ERROR,
+     u_INVARIANT_CONVERSION_ERROR,
+     u_INVALID_STATE_ERROR,
+     u_COLLATOR_VERSION_MISMATCH,
+     u_USELESS_COLLATOR_ERROR,
+     u_NO_WRITE_PERMISSION,
+
+     -- ** Transliterator errors
+     u_BAD_VARIABLE_DEFINITION,
+     u_MALFORMED_RULE,
+     u_MALFORMED_SET,
+     u_MALFORMED_UNICODE_ESCAPE,
+     u_MALFORMED_VARIABLE_DEFINITION,
+     u_MALFORMED_VARIABLE_REFERENCE,
+     u_MISPLACED_CURSOR_OFFSET,
+     u_MISPLACED_QUANTIFIER,
+     u_MISSING_OPERATOR,
+     u_MULTIPLE_ANTE_CONTEXTS,
+     u_MULTIPLE_CURSORS,
+     u_MULTIPLE_POST_CONTEXTS,
+     u_TRAILING_BACKSLASH,
+     u_UNDEFINED_SEGMENT_REFERENCE,
+     u_UNDEFINED_VARIABLE,
+     u_UNQUOTED_SPECIAL,
+     u_UNTERMINATED_QUOTE,
+     u_RULE_MASK_ERROR,
+     u_MISPLACED_COMPOUND_FILTER,
+     u_MULTIPLE_COMPOUND_FILTERS,
+     u_INVALID_RBT_SYNTAX,
+     u_MALFORMED_PRAGMA,
+     u_UNCLOSED_SEGMENT,
+     u_VARIABLE_RANGE_EXHAUSTED,
+     u_VARIABLE_RANGE_OVERLAP,
+     u_ILLEGAL_CHARACTER,
+     u_INTERNAL_TRANSLITERATOR_ERROR,
+     u_INVALID_ID,
+     u_INVALID_FUNCTION,
+
+     -- ** Formatting API parsing errors
+     u_UNEXPECTED_TOKEN,
+     u_MULTIPLE_DECIMAL_SEPARATORS,
+     u_MULTIPLE_EXPONENTIAL_SYMBOLS,
+     u_MALFORMED_EXPONENTIAL_PATTERN,
+     u_MULTIPLE_PERCENT_SYMBOLS,
+     u_MULTIPLE_PERMILL_SYMBOLS,
+     u_MULTIPLE_PAD_SPECIFIERS,
+     u_PATTERN_SYNTAX_ERROR,
+     u_ILLEGAL_PAD_POSITION,
+     u_UNMATCHED_BRACES,
+     u_ARGUMENT_TYPE_MISMATCH,
+     u_DUPLICATE_KEYWORD,
+     u_UNDEFINED_KEYWORD,
+     u_DEFAULT_KEYWORD_MISSING,
+
+     -- ** Break iterator errors
+     u_BRK_INTERNAL_ERROR,
+     u_BRK_HEX_DIGITS_EXPECTED,
+     u_BRK_SEMICOLON_EXPECTED,
+     u_BRK_RULE_SYNTAX,
+     u_BRK_UNCLOSED_SET,
+     u_BRK_ASSIGN_ERROR,
+     u_BRK_VARIABLE_REDFINITION,
+     u_BRK_MISMATCHED_PAREN,
+     u_BRK_NEW_LINE_IN_QUOTED_STRING,
+     u_BRK_UNDEFINED_VARIABLE,
+     u_BRK_INIT_ERROR,
+     u_BRK_RULE_EMPTY_SET,
+     u_BRK_UNRECOGNIZED_OPTION,
+     u_BRK_MALFORMED_RULE_TAG,
+
+     -- ** Regular expression errors
+     u_REGEX_INTERNAL_ERROR,
+     u_REGEX_RULE_SYNTAX,
+     u_REGEX_INVALID_STATE,
+     u_REGEX_BAD_ESCAPE_SEQUENCE,
+     u_REGEX_PROPERTY_SYNTAX,
+     u_REGEX_UNIMPLEMENTED,
+     u_REGEX_MISMATCHED_PAREN,
+     u_REGEX_NUMBER_TOO_BIG,
+     u_REGEX_BAD_INTERVAL,
+     u_REGEX_MAX_LT_MIN,
+     u_REGEX_INVALID_BACK_REF,
+     u_REGEX_INVALID_FLAG,
+     u_REGEX_SET_CONTAINS_STRING,
+     u_REGEX_OCTAL_TOO_BIG,
+     u_REGEX_INVALID_RANGE,
+     u_REGEX_STACK_OVERFLOW,
+     u_REGEX_TIME_OUT,
+     u_REGEX_STOPPED_BY_CALLER,
+
+     -- ** IDNA errors
+     u_IDNA_PROHIBITED_ERROR,
+     u_IDNA_UNASSIGNED_ERROR,
+     u_IDNA_CHECK_BIDI_ERROR,
+     u_IDNA_STD3_ASCII_RULES_ERROR,
+     u_IDNA_ACE_PREFIX_ERROR,
+     u_IDNA_VERIFICATION_ERROR,
+     u_IDNA_LABEL_TOO_LONG_ERROR,
+     u_IDNA_ZERO_LENGTH_LABEL_ERROR,
+     u_IDNA_DOMAIN_NAME_TOO_LONG_ERROR
+    ) where
+
+#include <unicode/utypes.h>
+
+import Data.Text.ICU.Error.Internal
+
+#{enum ErrorCode, ErrorCode,
+  u_USING_FALLBACK_WARNING = U_USING_FALLBACK_WARNING,
+  u_USING_DEFAULT_WARNING = U_USING_DEFAULT_WARNING,   
+  u_SAFECLONE_ALLOCATED_WARNING = U_SAFECLONE_ALLOCATED_WARNING, 
+  u_STATE_OLD_WARNING = U_STATE_OLD_WARNING,   
+  u_STRING_NOT_TERMINATED_WARNING = U_STRING_NOT_TERMINATED_WARNING,
+  u_SORT_KEY_TOO_SHORT_WARNING = U_SORT_KEY_TOO_SHORT_WARNING, 
+  u_AMBIGUOUS_ALIAS_WARNING = U_AMBIGUOUS_ALIAS_WARNING,   
+  u_DIFFERENT_UCA_VERSION = U_DIFFERENT_UCA_VERSION,     
+  u_ILLEGAL_ARGUMENT_ERROR = U_ILLEGAL_ARGUMENT_ERROR,     
+  u_MISSING_RESOURCE_ERROR = U_MISSING_RESOURCE_ERROR,     
+  u_INVALID_FORMAT_ERROR = U_INVALID_FORMAT_ERROR,     
+  u_FILE_ACCESS_ERROR = U_FILE_ACCESS_ERROR,     
+  u_INTERNAL_PROGRAM_ERROR = U_INTERNAL_PROGRAM_ERROR,     
+  u_MESSAGE_PARSE_ERROR = U_MESSAGE_PARSE_ERROR,     
+  u_MEMORY_ALLOCATION_ERROR = U_MEMORY_ALLOCATION_ERROR,     
+  u_INDEX_OUTOFBOUNDS_ERROR = U_INDEX_OUTOFBOUNDS_ERROR,     
+  u_PARSE_ERROR = U_PARSE_ERROR,     
+  u_INVALID_CHAR_FOUND = U_INVALID_CHAR_FOUND,     
+  u_TRUNCATED_CHAR_FOUND = U_TRUNCATED_CHAR_FOUND,     
+  u_ILLEGAL_CHAR_FOUND = U_ILLEGAL_CHAR_FOUND,     
+  u_INVALID_TABLE_FORMAT = U_INVALID_TABLE_FORMAT,     
+  u_INVALID_TABLE_FILE = U_INVALID_TABLE_FILE,     
+  u_BUFFER_OVERFLOW_ERROR = U_BUFFER_OVERFLOW_ERROR,     
+  u_UNSUPPORTED_ERROR = U_UNSUPPORTED_ERROR,     
+  u_RESOURCE_TYPE_MISMATCH = U_RESOURCE_TYPE_MISMATCH,     
+  u_ILLEGAL_ESCAPE_SEQUENCE = U_ILLEGAL_ESCAPE_SEQUENCE,     
+  u_UNSUPPORTED_ESCAPE_SEQUENCE = U_UNSUPPORTED_ESCAPE_SEQUENCE, 
+  u_NO_SPACE_AVAILABLE = U_NO_SPACE_AVAILABLE,     
+  u_CE_NOT_FOUND_ERROR = U_CE_NOT_FOUND_ERROR,     
+  u_PRIMARY_TOO_LONG_ERROR = U_PRIMARY_TOO_LONG_ERROR,     
+  u_STATE_TOO_OLD_ERROR = U_STATE_TOO_OLD_ERROR,     
+  u_TOO_MANY_ALIASES_ERROR = U_TOO_MANY_ALIASES_ERROR,     
+  u_ENUM_OUT_OF_SYNC_ERROR = U_ENUM_OUT_OF_SYNC_ERROR,     
+  u_INVARIANT_CONVERSION_ERROR = U_INVARIANT_CONVERSION_ERROR,  
+  u_INVALID_STATE_ERROR = U_INVALID_STATE_ERROR,     
+  u_COLLATOR_VERSION_MISMATCH = U_COLLATOR_VERSION_MISMATCH,   
+  u_USELESS_COLLATOR_ERROR = U_USELESS_COLLATOR_ERROR,     
+  u_NO_WRITE_PERMISSION = U_NO_WRITE_PERMISSION,     
+  u_BAD_VARIABLE_DEFINITION = U_BAD_VARIABLE_DEFINITION,
+  u_MALFORMED_RULE = U_MALFORMED_RULE,
+  u_MALFORMED_SET = U_MALFORMED_SET,
+  u_MALFORMED_UNICODE_ESCAPE = U_MALFORMED_UNICODE_ESCAPE,
+  u_MALFORMED_VARIABLE_DEFINITION = U_MALFORMED_VARIABLE_DEFINITION,
+  u_MALFORMED_VARIABLE_REFERENCE = U_MALFORMED_VARIABLE_REFERENCE,
+  u_MISPLACED_CURSOR_OFFSET = U_MISPLACED_CURSOR_OFFSET,
+  u_MISPLACED_QUANTIFIER = U_MISPLACED_QUANTIFIER,
+  u_MISSING_OPERATOR = U_MISSING_OPERATOR,
+  u_MULTIPLE_ANTE_CONTEXTS = U_MULTIPLE_ANTE_CONTEXTS,
+  u_MULTIPLE_CURSORS = U_MULTIPLE_CURSORS,
+  u_MULTIPLE_POST_CONTEXTS = U_MULTIPLE_POST_CONTEXTS,
+  u_TRAILING_BACKSLASH = U_TRAILING_BACKSLASH,
+  u_UNDEFINED_SEGMENT_REFERENCE = U_UNDEFINED_SEGMENT_REFERENCE,
+  u_UNDEFINED_VARIABLE = U_UNDEFINED_VARIABLE,
+  u_UNQUOTED_SPECIAL = U_UNQUOTED_SPECIAL,
+  u_UNTERMINATED_QUOTE = U_UNTERMINATED_QUOTE,
+  u_RULE_MASK_ERROR = U_RULE_MASK_ERROR,
+  u_MISPLACED_COMPOUND_FILTER = U_MISPLACED_COMPOUND_FILTER,
+  u_MULTIPLE_COMPOUND_FILTERS = U_MULTIPLE_COMPOUND_FILTERS,
+  u_INVALID_RBT_SYNTAX = U_INVALID_RBT_SYNTAX,
+  u_MALFORMED_PRAGMA = U_MALFORMED_PRAGMA,
+  u_UNCLOSED_SEGMENT = U_UNCLOSED_SEGMENT,
+  u_VARIABLE_RANGE_EXHAUSTED = U_VARIABLE_RANGE_EXHAUSTED,
+  u_VARIABLE_RANGE_OVERLAP = U_VARIABLE_RANGE_OVERLAP,
+  u_ILLEGAL_CHARACTER = U_ILLEGAL_CHARACTER,
+  u_INTERNAL_TRANSLITERATOR_ERROR = U_INTERNAL_TRANSLITERATOR_ERROR,
+  u_INVALID_ID = U_INVALID_ID,
+  u_INVALID_FUNCTION = U_INVALID_FUNCTION,
+  u_UNEXPECTED_TOKEN = U_UNEXPECTED_TOKEN,       
+  u_MULTIPLE_DECIMAL_SEPARATORS = U_MULTIPLE_DECIMAL_SEPARATORS,
+  u_MULTIPLE_EXPONENTIAL_SYMBOLS = U_MULTIPLE_EXPONENTIAL_SYMBOLS,
+  u_MALFORMED_EXPONENTIAL_PATTERN = U_MALFORMED_EXPONENTIAL_PATTERN,
+  u_MULTIPLE_PERCENT_SYMBOLS = U_MULTIPLE_PERCENT_SYMBOLS,
+  u_MULTIPLE_PERMILL_SYMBOLS = U_MULTIPLE_PERMILL_SYMBOLS,
+  u_MULTIPLE_PAD_SPECIFIERS = U_MULTIPLE_PAD_SPECIFIERS,
+  u_PATTERN_SYNTAX_ERROR = U_PATTERN_SYNTAX_ERROR,
+  u_ILLEGAL_PAD_POSITION = U_ILLEGAL_PAD_POSITION,
+  u_UNMATCHED_BRACES = U_UNMATCHED_BRACES,
+  u_ARGUMENT_TYPE_MISMATCH = U_ARGUMENT_TYPE_MISMATCH,
+  u_DUPLICATE_KEYWORD = U_DUPLICATE_KEYWORD,
+  u_UNDEFINED_KEYWORD = U_UNDEFINED_KEYWORD,
+  u_DEFAULT_KEYWORD_MISSING = U_DEFAULT_KEYWORD_MISSING,
+  u_BRK_INTERNAL_ERROR = U_BRK_INTERNAL_ERROR,          
+  u_BRK_HEX_DIGITS_EXPECTED = U_BRK_HEX_DIGITS_EXPECTED,
+  u_BRK_SEMICOLON_EXPECTED = U_BRK_SEMICOLON_EXPECTED,
+  u_BRK_RULE_SYNTAX = U_BRK_RULE_SYNTAX,
+  u_BRK_UNCLOSED_SET = U_BRK_UNCLOSED_SET,
+  u_BRK_ASSIGN_ERROR = U_BRK_ASSIGN_ERROR,
+  u_BRK_VARIABLE_REDFINITION = U_BRK_VARIABLE_REDFINITION,
+  u_BRK_MISMATCHED_PAREN = U_BRK_MISMATCHED_PAREN,
+  u_BRK_NEW_LINE_IN_QUOTED_STRING = U_BRK_NEW_LINE_IN_QUOTED_STRING,
+  u_BRK_UNDEFINED_VARIABLE = U_BRK_UNDEFINED_VARIABLE,
+  u_BRK_INIT_ERROR = U_BRK_INIT_ERROR,
+  u_BRK_RULE_EMPTY_SET = U_BRK_RULE_EMPTY_SET,
+  u_BRK_UNRECOGNIZED_OPTION = U_BRK_UNRECOGNIZED_OPTION,
+  u_BRK_MALFORMED_RULE_TAG = U_BRK_MALFORMED_RULE_TAG,
+  u_REGEX_INTERNAL_ERROR = U_REGEX_INTERNAL_ERROR,       
+  u_REGEX_RULE_SYNTAX = U_REGEX_RULE_SYNTAX,
+  u_REGEX_INVALID_STATE = U_REGEX_INVALID_STATE,
+  u_REGEX_BAD_ESCAPE_SEQUENCE = U_REGEX_BAD_ESCAPE_SEQUENCE,
+  u_REGEX_PROPERTY_SYNTAX = U_REGEX_PROPERTY_SYNTAX,
+  u_REGEX_UNIMPLEMENTED = U_REGEX_UNIMPLEMENTED,
+  u_REGEX_MISMATCHED_PAREN = U_REGEX_MISMATCHED_PAREN,
+  u_REGEX_NUMBER_TOO_BIG = U_REGEX_NUMBER_TOO_BIG,
+  u_REGEX_BAD_INTERVAL = U_REGEX_BAD_INTERVAL,
+  u_REGEX_MAX_LT_MIN = U_REGEX_MAX_LT_MIN,
+  u_REGEX_INVALID_BACK_REF = U_REGEX_INVALID_BACK_REF,
+  u_REGEX_INVALID_FLAG = U_REGEX_INVALID_FLAG,
+  u_REGEX_SET_CONTAINS_STRING = U_REGEX_SET_CONTAINS_STRING,
+  u_REGEX_OCTAL_TOO_BIG = U_REGEX_OCTAL_TOO_BIG,
+  u_REGEX_INVALID_RANGE = U_REGEX_INVALID_RANGE,
+  u_REGEX_STACK_OVERFLOW = U_REGEX_STACK_OVERFLOW,
+  u_REGEX_TIME_OUT = U_REGEX_TIME_OUT,
+  u_REGEX_STOPPED_BY_CALLER = U_REGEX_STOPPED_BY_CALLER,
+  u_IDNA_PROHIBITED_ERROR = U_IDNA_PROHIBITED_ERROR,
+  u_IDNA_UNASSIGNED_ERROR = U_IDNA_UNASSIGNED_ERROR,
+  u_IDNA_CHECK_BIDI_ERROR = U_IDNA_CHECK_BIDI_ERROR,
+  u_IDNA_STD3_ASCII_RULES_ERROR = U_IDNA_STD3_ASCII_RULES_ERROR,
+  u_IDNA_ACE_PREFIX_ERROR = U_IDNA_ACE_PREFIX_ERROR,
+  u_IDNA_VERIFICATION_ERROR = U_IDNA_VERIFICATION_ERROR,
+  u_IDNA_LABEL_TOO_LONG_ERROR = U_IDNA_LABEL_TOO_LONG_ERROR,
+  u_IDNA_ZERO_LENGTH_LABEL_ERROR = U_IDNA_ZERO_LENGTH_LABEL_ERROR,
+  u_IDNA_DOMAIN_NAME_TOO_LONG_ERROR = U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR
+}
diff --git a/Data/Text/ICU/Error/Internal.hs b/Data/Text/ICU/Error/Internal.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/ICU/Error/Internal.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE DeriveDataTypeable, ForeignFunctionInterface #-}
+
+module Data.Text.ICU.Error.Internal
+    (
+    -- * Types
+      ErrorCode(..)
+    -- ** Low-level types
+    , UErrorCode
+    -- * Functions
+    , isFailure
+    , isSuccess
+    , errorName
+    , handleError
+    , throwOnError
+    , withError
+    ) where
+
+import Control.Exception
+import Foreign.Ptr
+import Foreign.Marshal.Alloc
+import Data.Typeable (Typeable)
+import Foreign.C.String (CString, peekCString)
+import Foreign.C.Types (CInt)
+import Foreign.Storable
+import System.IO.Unsafe (unsafePerformIO)
+
+type UErrorCode = CInt
+
+-- | ICU error code.
+newtype ErrorCode = ErrorCode {
+      fromErrorCode :: UErrorCode
+    } deriving (Eq, Typeable)
+
+instance Show ErrorCode where
+    show code = "ErrorCode " ++ errorName code
+
+instance Exception ErrorCode
+
+-- | Indicate whether the given error code is a success.
+isSuccess :: ErrorCode -> Bool
+{-# INLINE isSuccess #-}
+isSuccess = (<= 0) . fromErrorCode
+
+-- | Indicate whether the given error code is a failure.
+isFailure :: ErrorCode -> Bool
+{-# INLINE isFailure #-}
+isFailure = (> 0) . fromErrorCode
+
+-- | Throw an exception if the given code is actually an error.
+throwOnError :: UErrorCode -> IO ()
+{-# INLINE throwOnError #-}
+throwOnError code = do
+  let err = (ErrorCode code)
+  if isFailure err
+    then throw err
+    else return ()
+
+withError :: (Ptr UErrorCode -> IO a) -> IO (ErrorCode, a)
+{-# INLINE withError #-}
+withError action = alloca $ \errPtr -> do
+                     poke errPtr 0
+                     ret <- action errPtr
+                     err <- peek errPtr
+                     return (ErrorCode err, ret)
+
+handleError :: (Ptr UErrorCode -> IO a) -> IO a
+{-# INLINE handleError #-}
+handleError action = alloca $ \errPtr -> do
+                       poke errPtr 0
+                       ret <- action errPtr
+                       throwOnError =<< peek errPtr
+                       return ret
+
+-- | Return a string representing the name of the given error code.
+errorName :: ErrorCode -> String
+errorName code = unsafePerformIO $
+                 peekCString (u_errorName (fromErrorCode code))
+
+foreign import ccall unsafe "unicode/utypes.h u_errorName_4_0" u_errorName
+    :: UErrorCode -> CString
diff --git a/Data/Text/ICU/Internal.hs b/Data/Text/ICU/Internal.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/ICU/Internal.hs
@@ -0,0 +1,24 @@
+module Data.Text.ICU.Internal
+    (
+      UBool
+    , UChar
+    , asBool
+    , asOrdering
+    ) where
+
+import Data.Int (Int8)
+import Data.Word (Word16)
+
+type UBool = Int8
+type UChar = Word16
+
+asBool :: Integral a => a -> Bool
+{-# INLINE asBool #-}
+asBool = (/=0)
+
+asOrdering :: Integral a => a -> Ordering
+{-# INLINE asOrdering #-}
+asOrdering i
+    | i < 0     = LT
+    | i == 0    = EQ
+    | otherwise = GT
diff --git a/Data/Text/ICU/Normalizer.hsc b/Data/Text/ICU/Normalizer.hsc
new file mode 100644
--- /dev/null
+++ b/Data/Text/ICU/Normalizer.hsc
@@ -0,0 +1,298 @@
+{-# LANGUAGE CPP, DeriveDataTypeable, ForeignFunctionInterface,
+    GeneralizedNewtypeDeriving #-}
+-- |
+-- Module      : Data.Text.ICU.Normalizer
+-- Copyright   : (c) Bryan O'Sullivan 2009
+--
+-- License     : BSD-style
+-- Maintainer  : bos@serpentine.com
+-- Stability   : experimental
+-- Portability : GHC
+--
+-- Character set normalization functions for Unicode, implemented as
+-- bindings to the International Components for Unicode (ICU)
+-- libraries.
+
+module Data.Text.ICU.Normalizer
+    (
+    -- * Unicode normalization API
+    -- $api
+      NormalizationMode(..)
+    -- * Normalization functions
+    , normalize
+    -- * Normalization checks
+    , NormalizationCheckResult(..)
+    , quickCheck
+    , isNormalized
+    -- * Normalization-sensitive comparison
+    , CompareOption
+    , u_INPUT_IS_FCD
+    , u_COMPARE_CODE_POINT_ORDER
+    , u_COMPARE_IGNORE_CASE
+    , compare
+    ) where
+
+#include <unicode/unorm.h>
+
+import Control.Exception (throw)
+import Control.Monad (when)
+import Data.Text (Text)
+import Data.Text.Foreign (fromPtr, useAsPtr)
+import Data.Text.ICU.Error (u_BUFFER_OVERFLOW_ERROR)
+import Data.Text.ICU.Error.Internal (UErrorCode, isFailure, handleError, withError)
+import Data.Text.ICU.Internal (UBool, UChar, asBool, asOrdering)
+import Data.Typeable (Typeable)
+import Data.Int (Int32)
+import Data.Word (Word32)
+import Foreign.C.Types (CInt)
+import Foreign.Marshal.Array (allocaArray)
+import Foreign.Ptr (Ptr)
+import System.IO.Unsafe (unsafePerformIO)
+import Prelude hiding (compare)
+import Data.List (foldl')
+import Data.Bits ((.|.))
+
+-- $api
+--
+-- 'normalize' transforms Unicode text into an equivalent composed or
+-- decomposed form, allowing for easier sorting and searching of text.
+-- 'normalize' supports the standard normalization forms described in
+-- <http://www.unicode.org/unicode/reports/tr15/>,
+-- Unicode Standard Annex #15: Unicode Normalization Forms.
+--
+-- Characters with accents or other adornments can be encoded in
+-- several different ways in Unicode.  For example, take the character A-acute.
+-- In Unicode, this can be encoded as a single character (the
+-- \"composed\" form):
+--
+-- @
+--      00C1    LATIN CAPITAL LETTER A WITH ACUTE
+-- @
+--
+-- or as two separate characters (the \"decomposed\" form):
+--
+-- @
+--      0041    LATIN CAPITAL LETTER A
+--      0301    COMBINING ACUTE ACCENT
+-- @
+--
+-- To a user of your program, however, both of these sequences should
+-- be treated as the same \"user-level\" character \"A with acute
+-- accent\".  When you are searching or comparing text, you must
+-- ensure that these two sequences are treated equivalently.  In
+-- addition, you must handle characters with more than one accent.
+-- Sometimes the order of a character's combining accents is
+-- significant, while in other cases accent sequences in different
+-- orders are really equivalent.
+--
+-- Similarly, the string \"ffi\" can be encoded as three separate letters:
+--
+-- @
+--      0066    LATIN SMALL LETTER F
+--      0066    LATIN SMALL LETTER F
+--      0069    LATIN SMALL LETTER I
+-- @
+--
+-- or as the single character
+--
+-- @
+--      FB03    LATIN SMALL LIGATURE FFI
+-- @
+--
+-- The \"ffi\" ligature is not a distinct semantic character, and
+-- strictly speaking it shouldn't be in Unicode at all, but it was
+-- included for compatibility with existing character sets that
+-- already provided it.  The Unicode standard identifies such
+-- characters by giving them \"compatibility\" decompositions into the
+-- corresponding semantic characters.  When sorting and searching, you
+-- will often want to use these mappings.
+--
+-- 'normalize' helps solve these problems by transforming text into
+-- the canonical composed and decomposed forms as shown in the first
+-- example above.  In addition, you can have it perform compatibility
+-- decompositions so that you can treat compatibility characters the
+-- same as their equivalents.  Finally, 'normalize' rearranges accents
+-- into the proper canonical order, so that you do not have to worry
+-- about accent rearrangement on your own.
+--
+-- Form 'FCD', \"Fast C or D\", is also designed for collation.  It
+-- allows to work on strings that are not necessarily normalized with
+-- an algorithm (like in collation) that works under \"canonical
+-- closure\", i.e., it treats precomposed characters and their
+-- decomposed equivalents the same.
+--
+-- It is not a normalization form because it does not provide for
+-- uniqueness of representation. Multiple strings may be canonically
+-- equivalent (their NFDs are identical) and may all conform to 'FCD'
+-- without being identical themselves.
+--
+-- The form is defined such that the \"raw decomposition\", the
+-- recursive canonical decomposition of each character, results in a
+-- string that is canonically ordered. This means that precomposed
+-- characters are allowed for as long as their decompositions do not
+-- need canonical reordering.
+--
+-- Its advantage for a process like collation is that all 'NFD' and
+-- most 'NFC' texts - and many unnormalized texts - already conform to
+-- 'FCD' and do not need to be normalized ('NFD') for such a
+-- process. The 'FCD' 'quickCheck' will return 'Yes' for most strings
+-- in practice.
+--
+-- @'normalize' 'FCD'@ may be implemented with 'NFD'.
+--
+-- For more details on 'FCD' see the collation design document:
+-- <http://source.icu-project.org/repos/icu/icuhtml/trunk/design/collation/ICU_collation_design.htm>
+--
+-- ICU collation performs either 'NFD' or 'FCD' normalization
+-- automatically if normalization is turned on for the collator
+-- object.  Beyond collation and string search, normalized strings may
+-- be useful for string equivalence comparisons,
+-- transliteration/transcription, unique representations, etc.
+--
+-- The W3C generally recommends to exchange texts in 'NFC'.  Note also
+-- that most legacy character encodings use only precomposed forms and
+-- often do not encode any combining marks by themselves. For
+-- conversion to such character encodings the Unicode text needs to be
+-- normalized to 'NFC'.  For more usage examples, see the Unicode
+-- Standard Annex.
+
+newtype CompareOption = CompareOption {
+      fromCompareOption :: Word32
+    } deriving (Eq, Typeable)
+
+foldCompareOptions :: [CompareOption] -> Word32
+foldCompareOptions = foldl' orO 0
+    where a `orO` b = a .|. fromCompareOption b
+
+#{enum CompareOption, CompareOption,
+  u_INPUT_IS_FCD = UNORM_INPUT_IS_FCD,
+  u_COMPARE_CODE_POINT_ORDER = U_COMPARE_CODE_POINT_ORDER,
+  u_COMPARE_IGNORE_CASE = U_COMPARE_IGNORE_CASE
+  }
+
+type UNormalizationMode = CInt
+
+data NormalizationMode
+    = None   -- ^ No decomposition/composition.
+    | NFD    -- ^ Canonical decomposition.
+    | NFKD   -- ^ Compatibility decomposition.
+    | NFC    -- ^ Canonical decomposition followed by canonical composition.
+    | NFKC   -- ^ Compatibility decomposition followed by canonical composition.
+    | FCD    -- ^ \"Fast C or D\" form.
+      deriving (Eq, Show, Enum, Typeable)
+                       
+toNM :: NormalizationMode -> UNormalizationMode
+toNM None = #const UNORM_NONE
+toNM NFD  = #const UNORM_NFD
+toNM NFKD = #const UNORM_NFKD
+toNM NFC  = #const UNORM_NFC
+toNM NFKC = #const UNORM_NFKC
+toNM FCD  = #const UNORM_FCD
+
+type UNormalizationCheckResult = CInt
+
+data NormalizationCheckResult
+    = No      -- ^ Text is not normalized.
+    | Perhaps -- ^ It cannot be determined whether text is in normalized 
+              -- form without further thorough checks.
+    | Yes     -- ^ Text is in normalized form.
+      deriving (Eq, Show, Enum, Typeable)
+                              
+toNCR :: UNormalizationCheckResult -> NormalizationCheckResult
+toNCR (#const UNORM_NO)    = No
+toNCR (#const UNORM_MAYBE) = Perhaps
+toNCR (#const UNORM_YES)   = Yes
+toNCR _                    = error "toNormalizationCheckResult"
+
+-- | Normalize a string according the specified normalization mode.
+normalize :: NormalizationMode -> Text -> Text
+normalize mode t = unsafePerformIO . useAsPtr t $ \sptr slen ->
+  let slen' = fromIntegral slen
+      mode' = toNM mode
+      loop dlen =
+        (either loop return =<<) .
+        allocaArray dlen $ \dptr -> do
+          (err, newLen) <- withError $
+              unorm_normalize sptr slen' mode' 0 dptr (fromIntegral dlen)
+          when (isFailure err && err /= u_BUFFER_OVERFLOW_ERROR) $
+            throw err
+          let newLen' = fromIntegral newLen
+          if newLen' > dlen
+            then return (Left newLen')
+            else Right `fmap` fromPtr dptr newLen'
+  in loop slen
+    
+      
+-- | Perform an efficient check on a string, to quickly determine if
+-- the string is in a particular normalization format.
+--
+-- A 'Perhaps' result indicates that a more thorough check is
+-- required, e.g. with 'isNormalized'.  The user may have to put the
+-- string in its normalized form and compare the results.
+quickCheck :: NormalizationMode -> Text -> NormalizationCheckResult
+quickCheck mode t =
+  unsafePerformIO . useAsPtr t $ \ptr len ->
+    fmap toNCR . handleError $ unorm_quickCheck ptr (fromIntegral len)
+                               (toNM mode)
+
+-- | Indicate whether a string is in a given normalization form.
+--
+-- Unlike 'quickCheck', this function returns a definitive result.
+-- For 'NFD', 'NFKD', and 'FCD' normalization forms, both functions
+-- work in exactly the same ways.  For 'NFC' and 'NFKC' forms, where
+-- 'quickCheck' may return 'Perhaps', this function will perform
+-- further tests to arrive at a definitive result.
+isNormalized :: NormalizationMode -> Text -> Bool
+isNormalized mode t =
+  unsafePerformIO . useAsPtr t $ \ptr len ->
+    fmap asBool . handleError $ unorm_isNormalized ptr (fromIntegral len)
+                                (toNM mode)
+
+-- | Compare two strings for canonical equivalence.
+-- Further options include case-insensitive comparison and
+-- code point order (as opposed to code unit order).
+--
+-- Canonical equivalence between two strings is defined as their normalized
+-- forms (NFD or NFC) being identical.
+-- This function compares strings incrementally instead of normalizing
+-- (and optionally case-folding) both strings entirely,
+-- improving performance significantly.
+--
+-- Bulk normalization is only necessary if the strings do not fulfill the FCD
+-- conditions. Only in this case, and only if the strings are relatively long,
+-- is memory allocated temporarily.
+-- For FCD strings and short non-FCD strings there is no memory allocation.
+--
+-- Options:
+--
+--  ['u_INPUT_IS_FCD'] Set if the caller knows that both strings fulfill the FCD conditions.
+--     If not set, the function will 'quickCheck' for FCD
+--     and normalize if necessary.
+--
+--  ['u_COMPARE_CODE_POINT_ORDER'] Set to choose code point order instead of code unit order.
+--
+--  ['u_COMPARE_IGNORE_CASE'] Set to compare strings case-insensitively using case folding,
+--     instead of case-sensitively.
+--     If set, then the following case folding options are used.
+compare :: [CompareOption] -> Text -> Text -> Ordering
+compare opts a b = unsafePerformIO .
+  useAsPtr a $ \aptr alen ->
+    useAsPtr b $ \bptr blen ->
+      fmap asOrdering . handleError $
+      unorm_compare aptr (fromIntegral alen) bptr (fromIntegral blen)
+                    (foldCompareOptions opts)
+
+foreign import ccall unsafe "unicode/unorm.h unorm_compare_4_0" unorm_compare
+    :: Ptr UChar -> Int32 -> Ptr UChar -> Int32 -> Word32
+    -> Ptr UErrorCode -> IO Int32
+
+foreign import ccall unsafe "unicode/unorm.h unorm_quickCheck_4_0" unorm_quickCheck
+    :: Ptr UChar -> Int32 -> UNormalizationMode -> Ptr UErrorCode
+    -> IO UNormalizationCheckResult
+
+foreign import ccall unsafe "unicode/unorm.h unorm_isNormalized_4_0" unorm_isNormalized
+    :: Ptr UChar -> Int32 -> UNormalizationMode -> Ptr UErrorCode -> IO UBool
+
+foreign import ccall unsafe "unicode/unorm.h unorm_normalize_4_0" unorm_normalize
+    :: Ptr UChar -> Int32 -> UNormalizationMode -> Int32
+    -> Ptr UChar -> Int32 -> Ptr UErrorCode -> IO Int32
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2009, Bryan O'Sullivan
+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.
+
+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.
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,31 @@
+Text-ICU: Comprehensive support for string manipulation
+-------------------------------------------------------
+
+This package provides the Data.Text.ICU library, for performing
+complex manipulation of Unicode text.  It provides features such as
+the following:
+
+- Unicode normalization
+- Conversion to and from many common and obscure encodings
+
+
+Prerequisites
+-------------
+
+This library is implemented as bindings to the well-respected ICU
+library, which is not included.  The version of ICU currently
+supported is 4.0.
+
+http://www.icu-project.org/
+
+
+Source code
+-----------
+
+darcs get http://darcs.serpentine.com/text-icu
+
+
+Authors
+-------
+
+This library was written by Bryan O'Sullivan.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain
diff --git a/cbits/text_icu.c b/cbits/text_icu.c
new file mode 100644
--- /dev/null
+++ b/cbits/text_icu.c
@@ -0,0 +1,8 @@
+#include "unicode/ucnv.h"
+
+#include <stdint.h>
+
+int __get_max_bytes_for_string(UConverter *cnv, int src_length)
+{
+    return UCNV_GET_MAX_BYTES_FOR_STRING(src_length, ucnv_getMaxCharSize(cnv));
+}
diff --git a/text-icu.cabal b/text-icu.cabal
new file mode 100644
--- /dev/null
+++ b/text-icu.cabal
@@ -0,0 +1,35 @@
+name:           text-icu
+version:        0.1
+synopsis:       Bindings to the ICU library
+description:    Haskell bindings to the International Components for
+                Unicode (ICU) libraries.  These libraries provide
+                robust and full-featured Unicode services on a wide
+                variety of platforms.
+maintainer:     Bryan O'Sullivan <bos@serpentine.com>
+copyright:      2009 Bryan O'Sullivan
+category:       Data, Text
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.2
+extra-source-files: README
+
+library
+  build-depends:     base < 5, bytestring, text
+  if impl(ghc >= 6.10)
+    build-depends:   base >= 4
+
+  exposed-modules:
+      Data.Text.ICU.Converter
+      Data.Text.ICU.Error
+      Data.Text.ICU.Normalizer
+  other-modules:
+      Data.Text.ICU.Converter.Internal
+      Data.Text.ICU.Error.Internal
+      Data.Text.ICU.Internal
+  c-sources: cbits/text_icu.c
+  extra-libraries: icui18n icuuc icudata
+
+  ghc-options: -Wall
+  if impl(ghc >= 6.8)
+    ghc-options: -fwarn-tabs
