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
messageFormatconfiguration. 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.