ihp
ihp copied to clipboard
I18n
haskell-gettext is a pure-Haskell library to parse gettext binary catalog (.mo / .gmo) files and provides functions like gettext and ngettext. I've got it running in a toy IHP example (using unsafePerformIO for now but that's just because I'm lazy).
The "usual" gettext workflow is to use xgettext to extract the to-be-translated strings from the source files. This doesn't support Haskell unfortunately. There's an alternative called hgettext which can parse Haskell syntax and create the .pot files, though it fails to work with files where HSX is present.
We should probably copy over / fork this tool from https://github.com/haskell-hvr/hgettext/blob/master/src-exe/hgettext.hs and make it work with IHP and HSX.
@hendi is your toy example code online anywhere?
Related to i18n in routing: https://github.com/digitallyinduced/ihp/pull/1486
Here's some real world code of how we build a i18n in an IHP app:
{-# LANGUAGE DeriveGeneric #-}
module Application.Localization
( Localizable(..)
, Locale(..)
, l10n
, l10nText
, localeFromHeader
, defaultLocale
, htmlLang
, LLocation(..)
, localizedPath
, localizedPathTo
, localizedRedirectTo
) where
import IHP.ViewPrelude
import Generated.Types
import IHP.ControllerPrelude
import qualified Data.Attoparsec.Text as A
import Control.Applicative (optional)
import Data.Char (isAlpha, isDigit)
import Data.Serialize (Serialize)
import GHC.Generics (Generic)
data Locale
= DE_DE
| EN_UK
deriving (Eq, Show, Generic)
instance Serialize Locale
type AcceptLanguage = [(Text, [Text], Double)]
data Localizable
= LFindYourDreamProperty
| LApartment
| LHouse
| LSwitchToLanguage
| LFrequentlyAskedQuestions
defaultLocale :: Locale
defaultLocale = DE_DE
currentLocale :: _ => Locale
currentLocale = fromMaybe defaultLocale $ maybeFromFrozenContext @Locale
l10nText :: _ => Localizable -> Text
l10nText l = l10nText' l currentLocale
-- In the view you can then do e.g. this:
[hsx|
{(textField #firstname) { fieldLabel = l10nText LFirstName <> ":"}}
|]
TODO: Add example to documentation.
Thanks for this example. I got basic i18n working thanks to it. Do you have an example of how you handle setting the locale automatically from the HTTP header? I presume that is what localeFromHeader is for.
How would you go about using currentLocale within a controller? I have separate database records for each language and so I would like to query the database records appropriate for the current locale before rendering the view. Trying to use currentLocale within a controller results in the error:
maybeFromFrozenContext called on a non frozen context while trying to access Locale
which I assume means that the locale is not set until a view is loaded. My FrontController is set up like in #1486
Inside the controller you can use fromContext instead of maybeFromFrozenContext to access the locale
Thank you, that indeed was the solution. However, now I'm struggling to figure out how to build a language toggle link. Do you have examples of how to do that? Specifically, I know how to prepend the locale (such as 'en') to the page path but I'm not sure how I would include a link to remove it to reverse the process.
Here's some example code:
footer :: Html
footer = [hsx|
<footer class="site-footer-container container mt-auto">
<div class="site-footer mt-4 py-4">
<a href={switchLanguageHref}>{l10n LSwitchToLanguage}</a>
</div>
</footer>
|]
where
req = request $ requestContext ?context
currentPath = rawPathInfo req <> rawQueryString req
switchLanguageHref = if B.isPrefixOf "/en" currentPath
then case B.drop 3 currentPath of
"" -> "/"
p -> p
else "/en" <> currentPath
Thanks so much!
@stephenbenedict Care to add it as a recipe on the docs? :pray: