4 > {-# LANGUAGE DataKinds #-} -- for type-level locales (EN, FR, …)
5 > {-# LANGUAGE LambdaCase #-} -- for less verbose case switching
6 > {-# LANGUAGE MultiParamTypeClasses #-} -- for messages as a type class (Msg2)
7 > {-# LANGUAGE OverloadedStrings #-} -- for Text encoding of locales
8 > {-# LANGUAGE FlexibleInstances #-} -- for localization instances
9 > module Data.Locale.Tutorial where
11 > import Data.Semigroup ((<>))
12 > import Data.Text (Text)
13 > import qualified Data.Map.Strict as Map
21 At the type-level, each locale is an independant type (eg. `data FR`), which does need inhabitant,
22 but a `FullLocale` data instance to list the full locales it gathers.
23 (eg. `data instance FullLocale FR = FR_BE | FR_CA | FR_CH | FR_FR | FR_LU`)
25 Supported locales must be gathered into a type-level list:
27 > type SupportedLocales = [EN, FR]
29 Here using two library-defined locales:
33 NOTE: To define custom locales: copy and adapt
34 the corresponding section of source code
35 defining one of the library-defined ones.
40 A specific term-level locale is an index (a so-called singleton using `GADTs`)
41 within the `SupportedLocales`, eg:
43 > fr_BE :: Locale SupportedLocales FR
44 > fr_BE = localeInj FR_BE
46 And a generic term-level locale hides the type-level locale
47 (using `ExistentialQuantification` in `LocaleIn`):
49 > selectedLocaleHardCoded :: LocaleIn SupportedLocales
50 > selectedLocaleHardCoded = LocaleIn fr_BE
52 The same can be achieved without hardcoding:
54 > selectedLocale :: LocaleIn SupportedLocales
55 > selectedLocale = case Map.lookup "fr_BE" locales of
56 > Nothing -> error "Unsupported locale"
59 First method: messages as a data type
60 -------------------------------------
62 Each message is a variant within a data type:
68 Each localization is an instance of `LocalizeIn`:
70 > instance LocalizeIn EN Text (Msg1 Text) where
71 > localizeIn _l = \case
72 > Msg1_Hello -> "Hello"
73 > Msg1_Bang n -> n<>"!"
75 > instance LocalizeIn FR Text (Msg1 Text) where
76 > localizeIn l = \case
79 > FR_BE -> "Bonjour une fois"
81 > Msg1_Bang n -> n<>" !"
83 A localization can be selected with `localize`,
84 which selects the `localizeIn` indexed by `selectedLocale`:
86 > l10n1 :: Msg1 Text -> Text
87 > l10n1 = localize selectedLocale
89 A message can then be localized by applying `l10n1` to a variant of `Msg1`:
92 > helloWorld1 = l10n1 Msg1_Hello <> " " <> l10n1 (Msg1_Bang "World")
94 Second method: messages as a type class
95 ---------------------------------------
97 Each message is a class method:
99 > class Msg2 out l where
100 > msg2_Hello :: FullLocale l -> out
101 > msg2_Bang :: out -> FullLocale l -> out
103 Each localization is an instance of `Msg2`:
105 > instance Msg2 Text EN where
106 > msg2_Hello _l = "Hello"
107 > msg2_Bang n _l = n<>"!"
109 > instance Msg2 Text FR where
111 > FR_BE -> "Bonjour une fois"
113 > msg2_Bang n _l = n<>" !"
115 A localization can be selected with `loqualize`,
116 which embeds the type class dictionnary (aka. type qualification)
117 indexed by `selectedLocale`
118 (using `ExistentialQuantification` in `Loqualization`):
120 > l10n2 :: Loqualization (Msg2 Text)
121 > l10n2 = loqualize selectedLocale
123 A message can then be localized
124 by bringing into scope the class dictionnary of `Msg2`
125 embedded by `l10n2`, and using its methods:
127 > helloWorld2 :: Text
129 > | Loqualization l <- l10n2
130 > = msg2_Hello l <> " " <> msg2_Bang "World" l