001 - Macros for JavaScript API¶
Proposal for macros transforming template literals and function calls into message format.
Background¶
Macros are used for generating messages in MessageFormat syntax. The advantages over writing format by hand are following:
API of macros is much simpler than API of core i18n method:
import { t, date } from `@lingui/macro` const name = "Joe" const today = new Date() // With macro i18n._(t`Hello ${name}, today is ${date(today)}`) // Without macro i18n._('Hello {name}, today is {today, date}', { name, today }) // For the sake of completeness, macro is transformed into this code: // i18n._({ // id: 'Hello {name}, today is {today, date}', // values: { name, today } // })
Messages are validated and type-checked. Generated message is always syntactically valid. This is especially important for nested formatters:
import { t, plural } from `@lingui/macro` const name = "Joe" const value = 42 // With macro i18n._(t`Hello ${name}, you have ${plural(value, { one: '# unread message', other: '# unread messages' })}`) // Without macro i18n._( 'Hello {name}, you have {value, plural, one {# unread message} other {# unread messages}}', { name, date } )
Using abstraction over message syntax allow easy replacement of message syntax. For example, without rewriting code it’s possible to switch from ICU MessageFormat to Fluent:
import { t, date } from `@lingui/macro` const name = "Joe" const today = new Date() i18n._(t`Hello ${name}, today is ${date(today)}`) // Lingui configration { messageFormat: 'icu' } // ↓ ↓ ↓ ↓ ↓ ↓ // i18n._({ // id: 'Hello {name}, today is {today, date}', // values: { name, date } // }) // Lingui configration { messageFormat: 'fluent' } // ↓ ↓ ↓ ↓ ↓ ↓ // i18n._({ // id: 'Hello { $name }, today is { DATETIME($today) }', // values: { name, date } // })
Warning
Fluent format isn’t supported at the moment, nor the
messageFormat
configuration. Both will be added in the future.
Message definitions¶
Tagged template literals¶
t
macro itself is used as a template literal tag:
import { t } from `@lingui/macro`
t`Hello ${name}`
Plural, select and selectOrdinal formatters¶
plural
, select
, selectOrdinal
macros are used as functions.
All of them must be called with an object containing value
key and corresponding
plural forms (plural
, selectOrdinal
) or categories (select
):
import { plural, select } from '@lingui/macro'
plural(value, {
one: "# book",
other: "# books"
})
select(value, {
male: "he",
female: "she",
other: "they"
})
It’s possible to arbitrary nest formatters. t
macro isn’t required
for nested template literals:
import { t, plural } from '@lingui/macro'
plural(value, {
one: `${name} has # book`,
other: `${name} has # books`
})
Date and number formatters¶
Finally, date
and number
macros are also used as a functions.
First argument is value to be formatted, the second is optional format:
import { t, date, number } from `@lingui/macro`
// default format
t`Today is ${date(today)}`
// custom format
t`Interest rate is ${number(rate, 'percent')}`
Custom ID and comments for translators¶
All macros above can be wrapped inside defineMessage
macro
to provide comment
for translators or to override the message id
:
import { defineMessage } from '@lingui/macro'
// Message is used as an ID
defineMessage({
message: "Default message",
comment: "Comment for translators"
})
// Custom ID
defineMessage({
id: "msg.id",
comment: "Comment for translators",
message: "Default message"
})
Lazy translations¶
Lazy translations are useful when we need to define a message, but translate it later.
This was previously achieved using i18Mark
. Now we can use the same macros:
// The API of `t` and `lazy` are the same.
import { t } from `@lingui/macro`
// define the message
const msg = t`Default message`
// translate it
const msg = i18n._(msg)`
Lazy translations are usually defined in different scope than evaluated. Parameters
are therefore unknown, but we still need to know their names, so we can create placeholders
in MessageFormat. arg
macro is used exactly for that:
import { plural, arg } from `@lingui/macro`
// Macro
const books = plural(arg('count'), {
one: '# book',
other: '# books'
})
i18n._(books, { count: 42 })
Extracting messages¶
Messages are extracted from code already transformed by macros. This makes macros completely optional and extraction will work also with message descriptors created manually.
Extract script will look for a i18n
comments, which are automatically added by macros:
t`Message`
// ↓ ↓ ↓ ↓ ↓ ↓
/*i18n*/{
id: 'Message'
}
An object after such comment is considered as message descriptor and extracted.