]> Git — Sourcephile - haskell/localization.git/blob - Data/Locale/Tutorial.lhs
Add a tutorial.
[haskell/localization.git] / Data / Locale / Tutorial.lhs
1 localization tutorial
2 =====================
3
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
10 > import Data.Locale
11 > import Data.Semigroup ((<>))
12 > import Data.Text (Text)
13 > import qualified Data.Map.Strict as Map
14
15 Locales
16 -------
17
18 Type-level locale
19 ~~~~~~~~~~~~~~~~~
20
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`)
24
25 Supported locales must be gathered into a type-level list:
26
27 > type SupportedLocales = [EN, FR]
28
29 Here using two library-defined locales:
30 - `EN` for english,
31 - `FR` for french.
32
33 NOTE: To define custom locales: copy and adapt
34 the corresponding section of source code
35 defining one of the library-defined ones.
36
37 Term-level locale
38 ~~~~~~~~~~~~~~~~~
39
40 A specific term-level locale is an index (a so-called singleton using `GADTs`)
41 within the `SupportedLocales`, eg:
42
43 > fr_BE :: Locale SupportedLocales FR
44 > fr_BE = localeInj FR_BE
45
46 And a generic term-level locale hides the type-level locale
47 (using `ExistentialQuantification` in `LocaleIn`):
48
49 > selectedLocaleHardCoded :: LocaleIn SupportedLocales
50 > selectedLocaleHardCoded = LocaleIn fr_BE
51
52 The same can be achieved without hardcoding:
53
54 > selectedLocale :: LocaleIn SupportedLocales
55 > selectedLocale = case Map.lookup "fr_BE" locales of
56 > Nothing -> error "Unsupported locale"
57 > Just l -> l
58
59 First method: messages as a data type
60 -------------------------------------
61
62 Each message is a variant within a data type:
63
64 > data Msg1 out
65 > = Msg1_Hello
66 > | Msg1_Bang out
67
68 Each localization is an instance of `LocalizeIn`:
69
70 > instance LocalizeIn EN Text (Msg1 Text) where
71 > localizeIn _l = \case
72 > Msg1_Hello -> "Hello"
73 > Msg1_Bang n -> n<>"!"
74 >
75 > instance LocalizeIn FR Text (Msg1 Text) where
76 > localizeIn l = \case
77 > Msg1_Hello ->
78 > case l of
79 > FR_BE -> "Bonjour une fois"
80 > _ -> "Bonjour"
81 > Msg1_Bang n -> n<>" !"
82
83 A localization can be selected with `localize`,
84 which selects the `localizeIn` indexed by `selectedLocale`:
85
86 > l10n1 :: Msg1 Text -> Text
87 > l10n1 = localize selectedLocale
88
89 A message can then be localized by applying `l10n1` to a variant of `Msg1`:
90
91 > helloWorld1 :: Text
92 > helloWorld1 = l10n1 Msg1_Hello <> " " <> l10n1 (Msg1_Bang "World")
93
94 Second method: messages as a type class
95 ---------------------------------------
96
97 Each message is a class method:
98
99 > class Msg2 out l where
100 > msg2_Hello :: FullLocale l -> out
101 > msg2_Bang :: out -> FullLocale l -> out
102
103 Each localization is an instance of `Msg2`:
104
105 > instance Msg2 Text EN where
106 > msg2_Hello _l = "Hello"
107 > msg2_Bang n _l = n<>"!"
108 >
109 > instance Msg2 Text FR where
110 > msg2_Hello = \case
111 > FR_BE -> "Bonjour une fois"
112 > _ -> "Bonjour"
113 > msg2_Bang n _l = n<>" !"
114
115 A localization can be selected with `localization`,
116 which embeds the type class dictionnary indexed by `selectedLocale`
117 (using `ExistentialQuantification` in `Localization`):
118
119 > l10n2 :: Localization (Msg2 Text)
120 > l10n2 = localization selectedLocale
121
122 A message can then be localized
123 by bringing into scope the class dictionnary of `Msg2`
124 embedded by `l10n2`, and using its methods:
125
126 > helloWorld2 :: Text
127 > helloWorld2
128 > | Localization l <- l10n2
129 > = msg2_Hello l <> " " <> msg2_Bang "World" l