@@ -30,6 +30,251 @@ you're a tool author you may be interested in the formal [EBNF grammar][].
30
30
[ EBNF grammar ] : https://github.com/projectfluent/fluent/tree/master/spec
31
31
32
32
33
+ Installation
34
+ ------------
35
+
36
+ python-fluent consists of two packages:
37
+
38
+ * ` fluent.syntax ` - includes AST classes and parser. Most end users will not
39
+ need this directly. Documentation coming soon!
40
+
41
+ To install:
42
+
43
+ pip install fluent.syntax
44
+
45
+
46
+ * ` fluent.runtime ` - methods for generating translations from FTL files.
47
+ Documentation below.
48
+
49
+ To install:
50
+
51
+ pip install fluent.runtime
52
+
53
+ (The correct version of `` fluent.syntax `` will be installed automatically)
54
+
55
+
56
+ PyPI also contains an old ` fluent ` package which is an older version of just
57
+ ` fluent.syntax ` .
58
+
59
+ Usage
60
+ -----
61
+
62
+ To generate translations using the `` fluent.runtime `` package, you start with
63
+ the ` FluentBundle ` class:
64
+
65
+ >>> from fluent.runtime import FluentBundle
66
+
67
+ You pass a list of locales to the constructor - the first being the desired
68
+ locale, with fallbacks after that:
69
+
70
+ >>> bundle = FluentBundle(["en-US"])
71
+
72
+
73
+ You must then add messages. These would normally come from a ` .ftl ` file stored
74
+ on disk, here we will just add them directly:
75
+
76
+ >>> bundle.add_messages("""
77
+ ... welcome = Welcome to this great app!
78
+ ... greet-by-name = Hello, { $name }!
79
+ ... """)
80
+
81
+ To generate translations, use the ` format ` method, passing a message ID and an
82
+ optional dictionary of substitution parameters. If the the message ID is not
83
+ found, a ` LookupError ` is raised. Otherwise, as per the Fluent philosophy, the
84
+ implementation tries hard to recover from any formatting errors and generate the
85
+ most human readable representation of the value. The ` format ` method therefore
86
+ returns a tuple containing ` (translated string, errors) ` , as below.
87
+
88
+ >>> translated, errs = bundle.format('welcome')
89
+ >>> translated
90
+ "Welcome to this great app!"
91
+ >>> errs
92
+ []
93
+
94
+ >>> translated, errs = bundle.format('greet-by-name', {'name': 'Jane'})
95
+ >>> translated
96
+ 'Hello, \u2068Jane\u2069!'
97
+
98
+ >>> translated, errs = bundle.format('greet-by-name', {})
99
+ >>> translated
100
+ 'Hello, \u2068name\u2069!'
101
+ >>> errs
102
+ [FluentReferenceError('Unknown external: name')]
103
+
104
+ You will notice the extra characters ` \u2068 ` and ` \u2069 ` in the output. These
105
+ are Unicode bidi isolation characters that help to ensure that the interpolated
106
+ strings are handled correctly in the situation where the text direction of the
107
+ substitution might not match the text direction of the localized text. These
108
+ characters can be disabled if you are sure that is not possible for your app by
109
+ passing ` use_isolating=False ` to the ` FluentBundle ` constructor.
110
+
111
+ Python 2
112
+ --------
113
+
114
+ The above examples assume Python 3. Since Fluent uses unicode everywhere
115
+ internally (and doesn't accept bytestrings), if you are using Python 2 you will
116
+ need to make adjustments to the above example code. Either add ` u ` unicode
117
+ literal markers to strings or add this at the top of the module or the start of
118
+ your repl session:
119
+
120
+ from __future__ import unicode_literals
121
+
122
+
123
+ Numbers
124
+ -------
125
+
126
+ When rendering translations, Fluent passes any numeric arguments (int or float)
127
+ through locale-aware formatting functions:
128
+
129
+ >>> bundle.add_messages("show-total-points = You have { $points } points.")
130
+ >>> val, errs = bundle.format("show-total-points", {'points': 1234567})
131
+ >>> val
132
+ 'You have 1,234,567 points.'
133
+
134
+
135
+ You can specify your own formatting options on the arguments passed in by
136
+ wrapping your numeric arguments with ` fluent.runtime.types.fluent_number ` :
137
+
138
+ >>> from fluent.runtime.types import fluent_number
139
+ >>> points = fluent_number(1234567, useGrouping=False)
140
+ >>> bundle.format("show-total-points", {'points': points})[0]
141
+ 'You have 1234567 points.'
142
+
143
+ >>> amount = fluent_number(1234.56, style="currency", currency="USD")
144
+ >>> bundle.add_messages("your-balance = Your balance is { $amount }")
145
+ >>> bundle.format("your-balance", {'amount': amount})[0]
146
+ 'Your balance is $1,234.56'
147
+
148
+ Thee options available are defined in the Fluent spec for
149
+ [ NUMBER] ( https://projectfluent.org/fluent/guide/functions.html#number ) . Some of
150
+ these options can also be defined in the FTL files, as described in the Fluent
151
+ spec, and the options will be merged.
152
+
153
+ Date and time
154
+ -------------
155
+
156
+ Python ` datetime.datetime ` and ` datetime.date ` objects are also passed through
157
+ locale aware functions:
158
+
159
+ >>> from datetime import date
160
+ >>> bundle.add_messages("today-is = Today is { $today }")
161
+ >>> val, errs = bundle.format("today-is", {"today": date.today() })
162
+ >>> val
163
+ 'Today is Jun 16, 2018'
164
+
165
+ You can explicitly call the ` DATETIME ` builtin to specify options:
166
+
167
+ >>> bundle.add_messages('today-is = Today is { DATETIME($today, dateStyle: "short") }')
168
+
169
+ See the [ DATETIME
170
+ docs] ( https://projectfluent.org/fluent/guide/functions.html#datetime ) . However,
171
+ currently the only supported options to ` DATETIME ` are:
172
+
173
+ * ` timeZone `
174
+ * ` dateStyle ` and ` timeStyle ` which are [ proposed
175
+ additions] ( https://github.com/tc39/proposal-ecma402-datetime-style ) to the ECMA i18n spec.
176
+
177
+ To specify options from Python code, use ` fluent.runtime.types.fluent_date ` :
178
+
179
+ >>> from fluent.runtime.types import fluent_date
180
+ >>> today = date.today()
181
+ >>> short_today = fluent_date(today, dateStyle='short')
182
+ >>> val, errs = bundle.format("today-is", {"today": short_today })
183
+ >>> val
184
+ 'Today is 6/17/18'
185
+
186
+ You can also specify timezone for displaying ` datetime ` objects in two ways:
187
+
188
+ * Create timezone aware ` datetime ` objects, and pass these to the ` format ` call
189
+ e.g.:
190
+
191
+ >>> import pytz
192
+ >>> from datetime import datetime
193
+ >>> utcnow = datime.utcnow().replace(tzinfo=pytz.utc)
194
+ >>> moscow_timezone = pytz.timezone('Europe/Moscow')
195
+ >>> now_in_moscow = utcnow.astimezone(moscow_timezone)
196
+
197
+ * Or, use timezone naive ` datetime ` objects, or ones with a UTC timezone, and
198
+ pass the ` timeZone ` argument to ` fluent_date ` as a string:
199
+
200
+ >>> utcnow = datetime.utcnow()
201
+ >>> utcnow
202
+ datetime.datetime(2018, 6, 17, 12, 15, 5, 677597)
203
+
204
+ >>> bundle.add_messages("now-is = Now is { $now }")
205
+ >>> val, errs = bundle.format("now-is",
206
+ ... {"now": fluent_date(utcnow,
207
+ ... timeZone="Europe/Moscow",
208
+ ... dateStyle="medium",
209
+ ... timeStyle="medium")})
210
+ >>> val
211
+ 'Now is Jun 17, 2018, 3:15:05 PM'
212
+
213
+
214
+ Custom functions
215
+ ----------------
216
+
217
+ You can add functions to the ones available to FTL authors by passing
218
+ a ` functions ` dictionary to the ` FluentBundle ` constructor:
219
+
220
+
221
+ >>> import platform
222
+ >>> def os_name():
223
+ ... """Returns linux/mac/windows/other"""
224
+ ... return {'Linux': 'linux',
225
+ ... 'Darwin': 'mac',
226
+ ... 'Windows': 'windows'}.get(platform.system(), 'other')
227
+
228
+ >>> bundle = FluentBundle(['en-US'], functions={'OS': os_name})
229
+ >>> bundle.add_messages("""
230
+ ... welcome = { OS() ->
231
+ ... [linux] Welcome to Linux
232
+ ... [mac] Welcome to Mac
233
+ ... [windows] Welcome to Windows
234
+ ... *[other] Welcome
235
+ ... }
236
+ ... """)
237
+ >>> print(bundle.format('welcome')[0]
238
+ Welcome to Linux
239
+
240
+ These functions can accept positioal and keyword arguments (like the ` NUMBER `
241
+ and ` DATETIME ` builtins), and in this case must accept the following types of
242
+ arguments:
243
+
244
+ * unicode strings (i.e. ` unicode ` on Python 2, ` str ` on Python 3)
245
+ * ` fluent.runtime.types.FluentType ` subclasses, namely:
246
+ * ` FluentNumber ` - ` int ` , ` float ` or ` Decimal ` objects passed in externally,
247
+ or expressed as literals, are wrapped in these. Note that these objects also
248
+ subclass builtin ` int ` , ` float ` or ` Decimal ` , so can be used as numbers in
249
+ the normal way.
250
+ * ` FluentDateType ` - ` date ` or ` datetime ` objects passed in are wrapped in
251
+ these. Again, these classes also subclass ` date ` or ` datetime ` , and can be
252
+ used as such.
253
+ * ` FluentNone ` - in error conditions, such as a message referring to an argument
254
+ that hasn't been passed in, objects of this type are passed in.
255
+
256
+ Custom functions should not throw errors, but return ` FluentNone ` instances to
257
+ indicate an error or missing data. Otherwise they should return unicode strings,
258
+ or instances of a ` FluentType ` subclass as above.
259
+
260
+
261
+ Known limitations and bugs
262
+ --------------------------
263
+
264
+ * We do not yet support ` NUMBER(..., currencyDisplay="name") ` - see [ this python-babel
265
+ pull request] ( https://github.com/python-babel/babel/pull/585 ) which needs to
266
+ be merged and released.
267
+
268
+ * Most options to ` DATETIME ` are not yet supported. See the [ MDN docs for
269
+ Intl.DateTimeFormat] ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat ) ,
270
+ the [ ECMA spec for
271
+ BasicFormatMatcher] ( http://www.ecma-international.org/ecma-402/1.0/#BasicFormatMatcher )
272
+ and the [ Intl.js
273
+ polyfill] ( https://github.com/andyearnshaw/Intl.js/blob/master/src/12.datetimeformat.js ) .
274
+
275
+ Help with the above would be welcome!
276
+
277
+
33
278
Discuss
34
279
-------
35
280
0 commit comments