Safe Haskell | None |
---|---|
Language | Haskell2010 |
Bindings to the ReactIntl library, which allows easy formatting of numbers, dates, times, relative times, pluralization, and translated messages. This library can be used for formatting and pluralization even if you intend to present your application in a single language and locale.
These bindings are for the 2.0 version of ReactIntl which is currently just a pre-release, and due to how react-flux works, it also requires React 0.14. For temporary documentation, see issue62. Because this is a binding to a pre-release, the API below might change! I consider it unlikely, but if a change is required I will violate the PVP and not increment the major number, just the minor number.
To use these bindings, you need to provide the ReactIntl
variable. In the browser you can just
load the react-intl.min.js
script onto the page so that window.ReactIntl
exists. If you are
running in node, execute ReactIntl = require("ReactIntl");
so that global.ReactIntl
exists. When compiling with closure, protect the ReactIntl variable as follows:
(function(global, React, ReactDOM, ReactIntl) { contents of all.js })(window, window['React'], window['ReactDOM'], window['ReactIntl']);
Using with a single locale and no translations. If you intend to present your application in
a single language, you can still use this module for formatting. Add a call to intlProvider_
to the top of your app with a hard-coded locale and Nothing
for the messages. You can then use
anything in the Formatting section like int_
, relativeTo_
, and message
, where message
will just always use the default message provided in the source code (helpful for templating).
If you want to specify the locale so dates and numbers are formatted in the user's locale, it is
strongly recommended to set the locale from the server based on the Accept-Language
header
and/or a user setting so that the page as a whole is consistint. I have the server set a
variable on window
for the locale to use, and then pass that locale into intlProvider_
.
Translations. The react-intl philosophy is that messages should be defined in the source code instead of kept in a separate file. To support translations, a tool (in this case Template Haskell) is used to extract the messages from the source code into a file given to the translators. The result of the translation is then used to replace the default message given in the source code.
- Use the functions in the Formatting section like
int_
,relativeTo_
, andmessage
inside your rendering functions. - At the bottom of each file which contains messages, add a call to
writeIntlMessages
. This is a template haskell function which during compilation will produce a file containing all the messages found within the haskell module. Give these message files to your translators. The translation results will then need to be converted into javascript files in the format expected by ReactIntl, which is a javascript object with keys the
MessageId
s and value the translated message. For example, each translation could result in a javascript file such as the following:window.myMessages = window.myMessages || {}; window.myMessages["fr-FR"] = { "num_photos": "{name} {numPhotos, plural, =0 {n'a pas pris de photographie.} =1 {a pris une photographie.} other {a pris # photographies.}", ... };
Based on the
Accept-Language
header and/or a user setting, the server includes the appropriate translation javascript file and sets a variable on window containing the locale to use. Note that no translation javascript file is needed if the default messages from the source code should be used.<script type="text/javascript">window.myIntialConfig = { "locale": "fr-FR" };</script> <script src="path/to/translations.fr-FR.js"></script>
Add a call to
intlProvider_
at the top of your application, passing the locale and the messages.foreign import javascript unsafe "$r = window['myInitialConfig']['locale']" js_initialLocale :: JSString foreign import javascript unsafe "window['myMessages'] ? window['myMessages'][$1] : null" js_myMessages :: JSString -> JSRef myApp :: ReactView () myApp = defineView "my application" $ () -> do intlProvider_ (JSString.unpack js_initialLocale) (Just $ js_myMessages js_initialLocale) $ ...
If you want to allow changing the locale without a page refresh, just load the initial locale into a store and use a controller-view to pass the locale and lookup the messages for
intlProvider_
.
- intlProvider_ :: String -> Maybe JSRef -> Maybe Object -> ReactElementM eventHandler a -> ReactElementM eventHandler a
- int_ :: Int -> ReactElementM eventHandler ()
- double_ :: Double -> ReactElementM eventHandler ()
- formattedNumber_ :: [PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()
- data DayFormat = DayFormat {}
- shortDate :: DayFormat
- day_ :: DayFormat -> Day -> ReactElementM eventHandler ()
- data TimeFormat = TimeFormat {}
- shortDateTime :: (DayFormat, TimeFormat)
- utcTime_ :: (DayFormat, TimeFormat) -> UTCTime -> ReactElementM eventHandler ()
- formattedDate_ :: Either Day UTCTime -> [PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()
- relativeTo_ :: UTCTime -> ReactElementM eventHandler ()
- formattedRelative_ :: UTCTime -> [PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()
- plural_ :: [PropertyOrHandler eventHandler] -> ReactElementM eventHandler ()
- type MessageId = Text
- message :: MessageId -> Text -> ExpQ
- message' :: MessageId -> Text -> Text -> ExpQ
- htmlMsg :: MessageId -> Text -> ExpQ
- htmlMsg' :: MessageId -> Text -> Text -> ExpQ
- data Message = Message {
- msgDescription :: Text
- msgDefaultMsg :: Text
- writeIntlMessages :: (HashMap MessageId Message -> IO ()) -> Q [Dec]
- intlFormatJson :: FilePath -> HashMap MessageId Message -> IO ()
- intlFormatJsonWithoutDescription :: FilePath -> HashMap MessageId Message -> IO ()
- intlFormatAndroidXML :: FilePath -> HashMap MessageId Message -> IO ()
Documentation
:: String | the locale to use |
-> Maybe JSRef | A reference to translated messages, which must be an object with keys
|
-> Maybe Object | An object to use for the |
-> ReactElementM eventHandler a | The children of this element. All descendents will use the given locale and messages. |
-> ReactElementM eventHandler a |
Use the IntlProvider to set the locale
, formats
, and messages
property.
Formatting
Numbers
int_ :: Int -> ReactElementM eventHandler () Source
Format an integer using formattedNumber_
and the default style.
double_ :: Double -> ReactElementM eventHandler () Source
Format a double using formattedNumber_
and the default style.
formattedNumber_ :: [PropertyOrHandler eventHandler] -> ReactElementM eventHandler () Source
A FormattedNumber which allows arbitrary properties and therefore allows control over the style and format of the number. The accepted properties are any options supported by Intl.NumberFormat.
Dates and Times
How to display a date. Each non-Nothing component will be displayed while the Nothing components will be ommitted. If everything is nothing, then it is assumed that year, month, and day are each numeric.
These properties coorespond directly the options accepted by Intl.DateTimeFormat.
DayFormat | |
|
A short day format, where month is "short" and year and day are "numeric".
day_ :: DayFormat -> Day -> ReactElementM eventHandler () Source
data TimeFormat Source
How to display a time. Each non-Nothing component will be displayed while Nothing components will be ommitted.
These properties coorespond directly the options accepted by Intl.DateTimeFormat.
shortDateTime :: (DayFormat, TimeFormat) Source
A default date and time format, using shortDate
and then numeric for hour, minute, and
second.
utcTime_ :: (DayFormat, TimeFormat) -> UTCTime -> ReactElementM eventHandler () Source
formattedDate_ :: Either Day UTCTime -> [PropertyOrHandler eventHandler] -> ReactElementM eventHandler () Source
A raw FormattedDate class which allows custom
properties to be passed. The given Day
or UTCTime
will be converted to a javascript Date
object and passed in the value
property. The remaining properties can be any properties that
Intl.DateTimeFormat
accepts. For example, you could pass in "timeZone" to specify a specific timezone to display.
Relative Times
relativeTo_ :: UTCTime -> ReactElementM eventHandler () Source
formattedRelative_ :: UTCTime -> [PropertyOrHandler eventHandler] -> ReactElementM eventHandler () Source
Format the given UTCTime using the FormattedRelative
class to display a relative time to now. The given UTCTime
is passed in the value property.
The supported style/formatting properties are "units" which can be one of second, minute, hour,
day, month, or year and "style" which if given must be numeric.
Plural
plural_ :: [PropertyOrHandler eventHandler] -> ReactElementM eventHandler () Source
A simple plural formatter useful if you do not want the full machinery of messages. This does
not support translation, for that you must use messages which via the ICU message syntax support
pluralization. The properties passed to plural_
must be value
, and then at least one of the
properties from other
, zero
, one
, two
, few
, many
.
Messages
:: MessageId | |
-> Text | The default message written in ICU message syntax. This message is used if no translation is found, and is also the message given to the translators. |
-> ExpQ |
Render a message and also record it during compilation. This template haskell
splice produces an expression of type [PropertyOrHandler eventHandler] -> ReactElementM eventHandler
()
, which should be passed the values for the message. For example,
li_ ["id" $= "some-id"] $ $(message "num_photos" "{name} took {numPhotos, plural, =0 {no photos} =1 {one photo} other {# photos}} {takenAgo}.") [ "name" $= "Neil Armstrong" , "numPhotos" @= (100 :: Int) , elementProperty "takenAgo" $ relativeTo_ (UTCTime (fromGregorian 1969 7 20) (2*60*60 + 56*60)) ]
This will first lookup the MessageId
(in this case num_photos
) in the messages
parameter passed to intlProvider_
.
If no messages were passed, intlProvider_
was not called, or the MessageId
was not found, the default message is used.
In my project, I create a wrapper around message
which sets the MessageId
as the sha1 hash of
the message. I did not implement it in react-flux because I did not want to add cryptohash as a
dependency. For example,
import Crypto.Hash (hash, SHA1) import qualified Data.Text as T import qualified Data.Text.Encoding as T msg :: T.Text -> ExpQ msg txt = message (T.pack $ show (hash (T.encodeUtf8 txt) :: Digest SHA1)) txt
:: MessageId | |
-> Text | A description indented to provide context for translators |
-> Text | The default message written in ICU message syntax |
-> ExpQ |
A variant of message
which allows you to specify some context for translators.
Similar to message
but use a FormattedHTMLMessage
which allows HTML inside the message. It
is recomended that you instead use message
together with elementProperty
to include rich text
inside the message. This splice produces a value of type [PropertyOrHandler
eventHandler] -> ReactElementM eventHandler ()
, the same as message
.
:: MessageId | |
-> Text | A description intended to provide context for translators |
-> Text | The default message written in ICU message syntax |
-> ExpQ |
A variant of htmlMsg
that allows you to specify some context for translators.
Translation
A message.
Message | |
|
writeIntlMessages :: (HashMap MessageId Message -> IO ()) -> Q [Dec] Source
Perform an arbitrary IO action on the accumulated messages at compile time, which usually
should be to write the messages to a file. Despite producing a value of type Q [Dec]
,
no declarations are produced. Instead, this is purly to allow IO to happen. A call to this
function should be placed at the bottom of the file, since it only will output messages that
appear above the call. Also, to provide consistency, I suggest you create a utility wrapper
around this function. For example,
{-# LANGUAGE TemplateHaskell #-} module MessageUtil where import Language.Haskell.TH import Language.Haskell.TH.Syntax import React.Flux.Addons.Intl writeMessages :: String -> Q [Dec] writeMessages name = writeIntlMessages (intlFormatJson $ "some/diretory/" ++ name ++ ".json")
Note that all paths in template haskell are relative to the directory containing the .cabal
file. You can then use this as follows:
{-# LANGUAGE TemplateHaskell #-} module SomeViews where import React.Flux import React.Flux.Addons.Intl import MessageUtil someView :: ReactView () someView = defineView .... use $(message) in render ... anotherView :: ReactView () anotherView = defineView ... use $(message) in render ... writeMessages "some-views"
intlFormatJson :: FilePath -> HashMap MessageId Message -> IO () Source
Format messages as json. The format is an object where keys are the MessageId
s, and the
value is an object with two properties, message
and optionally description
. This happens to
the the same format as chrome, although
the syntax of placeholders uses ICU message syntax instead of chrome's syntax. This does not
pretty-print the JSON, but I suggest before adding these messages in source control you pretty
print and sort by MessageIds so diffs are easy to read. This can be done with the
aeson-pretty
package, but I did not want to add it as a dependency.
intlFormatJsonWithoutDescription :: FilePath -> HashMap MessageId Message -> IO () Source
Format messages as json, ignoring the description. The format is an object where the keys are
the MessageId
s and the value is the message string. This format is used by many javascript
libraries, so many translation tools exist.
intlFormatAndroidXML :: FilePath -> HashMap MessageId Message -> IO () Source
Format messages in Android XML
format, but just using strings. String arrays and plurals are handled in the ICU message,
instead of in the XML. There are many utilities to translate these XML messages, and the format has the
advantage that it can include the descriptions as well as the messages. Also, the messages are
sorted by MessageId
so that if the output is placed in source control the diffs are easy to
review.