|
| 1 | +This page covers i18n and l10n filter and tag configuration, and how we support translations and translation message catalogs. See the [filter reference](optional_filters.md) and [tag reference](optional_tags.md) for usage examples. |
| 2 | + |
| 3 | +## Currency |
| 4 | + |
| 5 | +The `currency` filter returns the input number formatted as currency for the current locale. For usage examples see [`currency`](optional_filters.md#currency) in the filter reference. |
| 6 | + |
| 7 | +### Options |
| 8 | + |
| 9 | +`currency` defaults to looking for a locale in a render context variable called `locale`, and a currency code in a render context variable called `currency_code`. It outputs in the locale's standard format and falls back to `en_US` and `USD` if those context variables don't exist. |
| 10 | + |
| 11 | +```python |
| 12 | +from liquid import parse |
| 13 | + |
| 14 | +template = parse("{{ 100457.99 | currency }}") |
| 15 | + |
| 16 | +print(template.render()) |
| 17 | +print(template.render(currency_code="GBP")) |
| 18 | +print(template.render(locale="de", currency_code="CAD")) |
| 19 | +``` |
| 20 | + |
| 21 | +```plain title="output" |
| 22 | +$100,457.99 |
| 23 | +£100,457.99 |
| 24 | +100.457,99 CA$ |
| 25 | +``` |
| 26 | + |
| 27 | +To configure `currency`, register a new instance of [`Currency`](api/extra.md#liquid.extra.Currency) with an [`Environment`](environment.md#managing-tags-and-filters), then render your templates from that. See [the API reference](api/extra.md#liquid.extra.Currency) for details of all arguments accepted by `Currency`. |
| 28 | + |
| 29 | +```python |
| 30 | +from liquid.extra import Currency |
| 31 | +from liquid import Environment |
| 32 | + |
| 33 | +env = Environment() |
| 34 | +env.filters["currency"] = Currency(default_locale="de") |
| 35 | +``` |
| 36 | + |
| 37 | +### Money |
| 38 | + |
| 39 | +For convenience, some "money" filters are defined that mimic Shopify's money filter behavior. These are instances of [`Currency()`](api/extra.md#liquid.extra.Currency) with specific default formats. All other currency options are set to their defaults. |
| 40 | + |
| 41 | +```python |
| 42 | +from liquid import parse |
| 43 | + |
| 44 | +template = parse("""\ |
| 45 | +{% assign amount = 10 %} |
| 46 | +{{ amount | money }} |
| 47 | +{{ amount | money_with_currency }} |
| 48 | +{{ amount | money_without_currency }} |
| 49 | +{{ amount | money_without_trailing_zeros }}""") |
| 50 | + |
| 51 | +print(template.render(currency_code="CAD", locale="en_CA")) |
| 52 | +``` |
| 53 | + |
| 54 | +```plain title="output" |
| 55 | +$10.00 |
| 56 | +$10.00 CAD |
| 57 | +10.00 |
| 58 | +$10 |
| 59 | +``` |
| 60 | + |
| 61 | +## DateTime |
| 62 | + |
| 63 | +The `datetime` filter returns the input _datetime_ formatted for the current locale. For usage examples see [`datetime`](optional_filters.md#datetime) in the filter reference. |
| 64 | + |
| 65 | +### Options |
| 66 | + |
| 67 | +`datetime` defaults to looking for a timezone in a render context variable called `timezone`, a locale in a render context variable called `locale` and a datetime format in a render context variable called `datetime_format`. |
| 68 | + |
| 69 | +```python |
| 70 | +from liquid import parse |
| 71 | + |
| 72 | +template = parse("{{ 'Apr 1, 2007, 3:30:00 PM' | datetime }}") |
| 73 | + |
| 74 | +print(template.render()) |
| 75 | +print(template.render(locale="de", datetime_format="long")) |
| 76 | +print(template.render(locale="de", timezone="CET", datetime_format="short")) |
| 77 | +``` |
| 78 | + |
| 79 | +```plain title="output" |
| 80 | +Apr 1, 2007, 3:30:00 PM |
| 81 | +1. April 2007 um 15:30:00 UTC |
| 82 | +01.04.07, 17:30 |
| 83 | +``` |
| 84 | + |
| 85 | +To configure `datetime`, register a new instance of [`DateTime`](api/extra.md#liquid.extra.DateTime) with an [`Environment`](environment.md#managing-tags-and-filters), then render your templates from that. See [the API reference](api/extra.md#liquid.extra.DateTime) for details of all arguments accepted by `DateTime`. |
| 86 | + |
| 87 | +```python |
| 88 | +from liquid.extra import DateTime |
| 89 | +from liquid import Environment |
| 90 | + |
| 91 | +env = Environment() |
| 92 | +env.filters["datetime"] = DateTime(timezone_var="tz") |
| 93 | +``` |
| 94 | + |
| 95 | +## Decimal |
| 96 | + |
| 97 | +The `decimal` filter returns the input number formatted as a decimal for the current locale. For usage examples see [`decimal`](optional_filters.md#decimal) in the filter reference. |
| 98 | + |
| 99 | +### Options |
| 100 | + |
| 101 | +`decimal` defaults to looking for a locale in a render context variable called `locale`. It uses the locale's standard format and falls back to `en_US` if that variable does not exist. |
| 102 | + |
| 103 | +```python |
| 104 | +from liquid import parse |
| 105 | + |
| 106 | +# Parse a number from a string in the default (en_US) input locale. |
| 107 | +template = parse("""\ |
| 108 | +{{ '10,000.23' | decimal }} |
| 109 | +{{ '10,000.23' | decimal: group_separator: false }} |
| 110 | +""") |
| 111 | + |
| 112 | +print(template.render(locale="de")) |
| 113 | +print(template.render(locale="en_GB")) |
| 114 | +``` |
| 115 | + |
| 116 | +```plain title="output" |
| 117 | +10.000,23 |
| 118 | +10000,23 |
| 119 | +
|
| 120 | +10,000.23 |
| 121 | +10000.23 |
| 122 | +``` |
| 123 | + |
| 124 | +To configure `decimal`, register a new instance of [`Number`](api/extra.md#liquid.extra.Number) with an [`Environment`](environment.md#managing-tags-and-filters), then render your templates from that. |
| 125 | + |
| 126 | +```python |
| 127 | +from liquid.extra import Number |
| 128 | +from liquid import Environment |
| 129 | + |
| 130 | +env = Environment() |
| 131 | +env.filters["decimal"] = Number(default_locale="en_GB") |
| 132 | +``` |
| 133 | + |
| 134 | +## Unit |
| 135 | + |
| 136 | +The `unit` filter returns he input number formatted with the given units according to the current locale. For usage examples see [`unit`](optional_filters.md#unit) in the filter reference. |
| 137 | + |
| 138 | +### Options |
| 139 | + |
| 140 | +`unit` defaults to looking for a locale in a render context variable called `locale`, a length in a render context variable called `unit_length`, and a decimal format in a render context variable called `unit_format`. |
| 141 | + |
| 142 | +```python |
| 143 | +from liquid import parse |
| 144 | + |
| 145 | +template = parse("""\ |
| 146 | +{{ 12 | unit: 'length-meter', format: '#,##0.00' }} |
| 147 | +{{ 150 | unit: 'kilowatt', denominator_unit: 'hour' }} |
| 148 | +""") |
| 149 | + |
| 150 | +print(template.render(unit_length="long")) |
| 151 | +print(template.render(locale="de", unit_length="long")) |
| 152 | +``` |
| 153 | + |
| 154 | +```plain title="output" |
| 155 | +12.00 meters |
| 156 | +150 kilowatts per hour |
| 157 | +
|
| 158 | +12,00 Meter |
| 159 | +150 Kilowatt pro Stunde |
| 160 | +``` |
| 161 | + |
| 162 | +To configure `unit`, register a new instance of [`Unit`](api/extra.md#liquid.extra.Unit) with an [`Environment`](environment.md#managing-tags-and-filters), then render your templates from that. |
| 163 | + |
| 164 | +```python |
| 165 | +from liquid.extra import Unit |
| 166 | +from liquid import Environment |
| 167 | + |
| 168 | +env = Environment() |
| 169 | +env.filters["unit"] = Unit(locale_var="_locale") |
| 170 | +``` |
| 171 | + |
| 172 | +## Translations |
| 173 | + |
| 174 | +Liquid Babel includes [`gettext`](optional_filters.md#gettext), [`ngettext`](optional_filters.md#ngettext), [`pgettext`](optional_filters.md#pgettext) and [`npgettext`](optional_filters.md#npgettext) filter equivalents to the functions found in [Python's gettext module](https://docs.python.org/3.10/library/gettext.html#gnu-gettext-api). Application developers can choose to use any of these filters, possibly using more user friendly filter names, and/or the more general [`t (translate)`](optional_filters.md#t) filter. |
| 175 | + |
| 176 | +The [`t`](optional_filters.md#t) filter can behave like any of the \*gettext filters, depending on the arguments it is given. Where the \*gettext filters require positional arguments for `context`, `count` and `plural`, `t` reserves optional `count` and `plural` keyword arguments. |
| 177 | + |
| 178 | +Liquid Babel also offers a [`{% translate %}`](optional_tags.md#translate) tag. This is similar to the [`{% trans %}`](https://jinja.palletsprojects.com/en/3.1.x/templates/#i18n) tag found in Jinja or the [`{% blocktranslate %}`](https://docs.djangoproject.com/en/4.1/topics/i18n/translation/#blocktranslate-template-tag) tag found in Django's template language. Again, application developers can configure and customize the included `translate` tag to suit an application's needs. |
| 179 | + |
| 180 | +### Filters |
| 181 | + |
| 182 | +[`gettext`](optional_filters.md#gettext), [`ngettext`](optional_filters.md#ngettext), [`npgettext`](optional_filters.md#npgettext), [`pgettext`](optional_filters.md#pgettext) and [`t`](optional_filters.md#t) filters all default to looking for [translations](#message-catalogs) in a render context variable called `translations`, falling back to an instance of [`NullTranslations`](https://docs.python.org/3.10/library/gettext.html#the-nulltranslations-class) if `translations` can not be resolved. |
| 183 | + |
| 184 | +```python |
| 185 | +from liquid import parse |
| 186 | + |
| 187 | +# You'll need to load an appropriate Translations object. |
| 188 | +# `get_translations()` is defined elsewhere. |
| 189 | +translations = get_translations(locale="de") |
| 190 | + |
| 191 | +template = parse("{{ 'Hello, World!' | t }}") |
| 192 | +print(template.render(translations=translations)) # Hallo Welt! |
| 193 | +``` |
| 194 | + |
| 195 | +To configure [`gettext`](optional_filters.md#gettext), [`ngettext`](optional_filters.md#ngettext), [`npgettext`](optional_filters.md#npgettext), [`pgettext`](optional_filters.md#pgettext) or [`t`](optional_filters.md#t), register a new instance of [`GetText`](api/extra.md#liquid.extra.GetText), [`NGetText`](api/extra.md#liquid.extra.NGetText), [`NPGetText`](api/extra.md#liquid.extra.NPGetText), [`PGetText`](api/extra.md#liquid.extra.PGetText) or [`Translate`](api/extra.md#liquid.extra.Translate) with an [`Environment`](environment.md#managing-tags-and-filters), then render your templates from that. All of these classes inherit from [`BaseTranslateFilter`](api/extra.md#liquid.extra.BaseTranslateFilter) and accept the same constructor arguments. |
| 196 | + |
| 197 | +### Message catalogs |
| 198 | + |
| 199 | +By default, all translation filters and tags will look for a render context variable called `translations`, which must be an object implementing the `Translations` protocol. It is the application developer's responsibility to provide a `Translations` object, being the interface between Liquid and a message catalog. |
| 200 | + |
| 201 | +The `Translations` protocol is defined as follows. It is simply a subset of the [`NullTranslations`](https://docs.python.org/3.10/library/gettext.html#gettext.NullTranslations) class found in the [gettext module](https://docs.python.org/3.10/library/gettext.html#gnu-gettext-api). |
| 202 | + |
| 203 | +```python |
| 204 | +class Translations(Protocol): |
| 205 | + def gettext(self, message: str) -> str: |
| 206 | + ... |
| 207 | + |
| 208 | + def ngettext(self, singular: str, plural: str, n: int) -> str: |
| 209 | + ... |
| 210 | + |
| 211 | + def pgettext(self, context: str, message: str) -> str: |
| 212 | + ... |
| 213 | + |
| 214 | + def npgettext(self, context: str, singular: str, plural: str, n: int) -> str: |
| 215 | + ... |
| 216 | +``` |
| 217 | + |
| 218 | +It could be a [`GNUTranslations`](https://docs.python.org/3.10/library/gettext.html#the-gnutranslations-class) instance, a [Babel `Translations`](https://babel.pocoo.org/en/latest/support.html#extended-translations-class) instance, or any object implementing `gettext`, `ngettext`, `pgettext` and `npgettext` methods. |
| 219 | + |
| 220 | +### Message variables |
| 221 | + |
| 222 | +Translatable message text can contain placeholders for variables. When using variables in strings to be translated by filters, variables are defined using percent-style formatting. Only the `s` modifier is supported and every variable must have a name. In this example `you` is the variable name. |
| 223 | + |
| 224 | +```liquid |
| 225 | +{{ "Hello, %(you)s!" | t }} |
| 226 | +``` |
| 227 | + |
| 228 | +Filter keyword arguments are merged with the current render context before being used to replace variables in message text. All variables are converted to their string representation before substitution. Dotted property/attribute access is not supported inside message variables. |
| 229 | + |
| 230 | +```liquid |
| 231 | +{{ "Hello, %(you)s!" | t: you: user.name }} |
| 232 | +``` |
| 233 | + |
| 234 | +The [`translate`](optional_tags.md#translate) block tag recognizes simplified Liquid output statements as translation message variables. These variables must be valid identifiers without dotted or bracketed property/attribute access, and no filters. |
| 235 | + |
| 236 | +```liquid |
| 237 | +{% translate %} |
| 238 | + Hello, {{ you }}! |
| 239 | +{% endtranslate %} |
| 240 | +``` |
| 241 | + |
| 242 | +Keyword arguments passed to the [`translate`](optional_tags.md#translate) tag will be merged with the current render context before being used to replace variables in message text. |
| 243 | + |
| 244 | +```liquid |
| 245 | +{% translate you: user.name, count: users.size %} |
| 246 | + Hello, {{ you }}! |
| 247 | +{% plural %} |
| 248 | + Hello, {{ you }}s! |
| 249 | +{% endtranslate %} |
| 250 | +``` |
| 251 | + |
| 252 | +### Message Extraction |
| 253 | + |
| 254 | +Use the [`extract_from_templates()`](api/messages.md#liquid.extract_from_template) function to build a message [catalog](https://babel.pocoo.org/en/latest/api/messages/catalog.html#catalogs) from one or more templates. You are then free to make use of [Babel's PO file features](https://babel.pocoo.org/en/latest/api/messages/pofile.html), or convert the catalog to a more convenient internal representation. |
| 255 | + |
| 256 | +```python |
| 257 | +import io |
| 258 | + |
| 259 | +from babel.messages.pofile import write_po |
| 260 | + |
| 261 | +from liquid import parse |
| 262 | +from liquid.messages import extract_from_templates |
| 263 | + |
| 264 | +source = """ |
| 265 | +{% # Translators: some comment %} |
| 266 | +{{ 'Hello, World!' | t }} |
| 267 | +{% comment %}Translators: other comment{% endcomment %} |
| 268 | +{% translate count: 2 %} |
| 269 | + Hello, {{ you }}! |
| 270 | +{% plural %} |
| 271 | + Hello, all! |
| 272 | +{% endtranslate %} |
| 273 | +""" |
| 274 | + |
| 275 | +template = parse(source, name="something.liquid") |
| 276 | +catalog = extract_from_templates(template) |
| 277 | + |
| 278 | +buf = io.BytesIO() |
| 279 | +write_po(buf, catalog, omit_header=True) |
| 280 | +print(buf.getvalue().decode("utf-8")) |
| 281 | +``` |
| 282 | + |
| 283 | +```plain title="output" |
| 284 | +#. Translators: some comment |
| 285 | +#: something.liquid:3 |
| 286 | +msgid "Hello, World!" |
| 287 | +msgstr "" |
| 288 | +
|
| 289 | +#. Translators: other comment |
| 290 | +#: something.liquid:5 |
| 291 | +#, python-format |
| 292 | +msgid "Hello, %(you)s!" |
| 293 | +msgid_plural "Hello, all!" |
| 294 | +msgstr[0] "" |
| 295 | +msgstr[1] "" |
| 296 | +``` |
| 297 | + |
| 298 | +### Translator Comments |
| 299 | + |
| 300 | +When a Liquid comment tag immediately precedes a translatable filter or tag, and the comment starts with a string in `comment_tags`, that comment will be included as a translator comment with the message. Use the `comment_tags` argument to [`extract_liquid()`](api/convenience.md#liquid.extract_liquid), or [`extract_from_templates()`](api/messages.md#liquid.extract_from_template) to change translator comment prefixes. The default is `["Translators:"]`. |
| 301 | + |
| 302 | +```python |
| 303 | +from liquid import parse |
| 304 | +from liquid.messages import extract_from_templates |
| 305 | + |
| 306 | +source = """ |
| 307 | +{% # Translators: some comment %} |
| 308 | +{{ 'Hello, World!' | t }} |
| 309 | +{% comment %}Translators: other comment{% endcomment %} |
| 310 | +{% translate count: 2 %} |
| 311 | + Hello, {{ you }}! |
| 312 | +{% plural %} |
| 313 | + Hello, all! |
| 314 | +{% endtranslate %} |
| 315 | +""" |
| 316 | + |
| 317 | +template = parse(source, name="something.liquid") |
| 318 | +catalog = extract_from_templates(template, strip_comment_tags=True) |
| 319 | + |
| 320 | +message = catalog.get("Hello, World!") |
| 321 | +print(message.auto_comments[0]) # some comment |
| 322 | +``` |
0 commit comments