Closed vonovak closed 6 years ago
Hello @vonovak,
yeah, this is one missing piece in the puzzle. Right now there's no way to set default message using i18n.t`component.title`
syntax.
i18n._
is low-level API. It's not documented, but definitely stable and won't go anyway. All tagged template literals (i18n.t
, i18n.plural
and so on) are transformed to low-level i18n._
call:
type MessageParams = {
values?: Object
defaults?: string
}
i18n._(message: string, params: MessageParams?)
The point is, that tagged template literals are transformed into this call, where message
is in ICU MessageFormat. If you want to use custom keys, message
is the key
and params.defaults
is MessageFormat:
// Auto-generated key
i18n._('Hello {world}', { values: { world } })
// Custom key
i18n._('component.title', { values: { world }, defaults: 'Hello {world}' })
So, you can use i18n._
directly, but then you have to write message by yourself. That should be enough as a temporary workaround.
I need to come up with API which supports custom keys even in template literals.
i18n.id('component.title', i18n.t`Hello ${world}`)
// Auto-generated key
i18n.t`Hello ${world}`
// Custom key
i18n.t('component.title')`Hello ${world}`
// Custom keys for other i18n methods (plural, select, selectOrdinal, date, number)
i18n.plural('component.plural', {
value: 42,
one: 'Book',
other: 'Books'
})
What do you think? Which option seems to be more readable/logical to you?
Also, I need to publish reference documentation for lingui-i18n
package!
Thanks @tricoder42 I don't have a strong opinion on the two options you gave - option one seems to be slightly more readable at the first sight, option two is more compact and makes it easier to add the custom key, plus it's not creating a new api (/ uses the existing one). I probably like nr. 2 better.
I'm just wondering: say I have a product whose name is "Great Product"
so the plan is: I do this once (or use the Trans
component in a similar fashion):
i18n.t('product.name')`Great Product`
and then in other places I use
i18n.t('product.name') // prints "Great Product"
?
Then when I have a dozen terms like this, it would be nice to have one place where the defaults are configured (eg. in some JSON file). There could be a config option for this - so js-lingui automatically goes looking for the defaults into that file. Just an idea.
Yes, this is actually supported right now, but only in combination with Trans
tag:
<a title={i18n.t`msg.title`})
<Trans id="msg.title">Hello World</Trans>
</a>
The only problem would be if you define different defaults for the same key - this would raise an error.
The API maybe needs a bit of polishing. i18n.t(`id`)
returns a function (template tag), so type-checkers will be confused if you use const productName = i18n.t('product.name')
.
Also, the situation is more complicated if message uses params:
i18n.t('key')`Hello ${world}`
// in different scope, where `world` isn't available
i18n.t('key') // missing params?
I think if you want to have your messages as DRY as possible and define defaults in one place only, you should use i18n.t
where you define default and i18n._
everywhere else:
i18n.t('product')`Product Name`
i18n.t('key')`Hello ${world}`
i18n._('product')
i18n._('key', { values: { world: "Fred" })
Now the return types are consistent and it works in all cases.
Then when I have a dozen terms like this, it would be nice to have one place where the defaults are configured (eg. in some JSON file). There could be a config option for this - so js-lingui automatically goes looking for the defaults into that file. Just an idea.
How would you create this JSON file and kept it up-to-date? Seems very error-prone to write it manually. Keys should be extracted automatically, but that's already job of lingui extract
command. You could simply extract messages and fill in translation field in your fallbackLocale
catalog.
Other possible solutions: You can create file default.js
with bunch of i18n.t
calls:
// defaults.js
i18n.t('name')`Product Name`
i18n.t('variable')`Hello ${world}` // we need to define `world` variable in this scope
and then use i18n._
in the code, but again: What about messages with variables? We need to define dummy value for each variable in our messages. This seems to be more consistent:
// defaults.js
i18n._('name', { defaults: 'Product Name' })
i18n._('variable', { defaults: 'Hello {world}' })
but you have to write MessageFormat manually, which is something I'm trying to avoid in this project.
We could probably just add this functionality to existing i18nMark function:
i18nMark('name', 'Product Name')
i18nMark('variable', 'Hello {world}')
but it doesn't solve the problem with manually writing messages.
What do you think?
Thanks for the comments. I use react (native), so I have the option of using the Trans component as well as the i18n.t function, I just wanted to get a better picture of the options.
I'll probably have a file with some very common translations, eg:
export const commonTranslations = {
done: i18n.t`Done`,
edit: i18n.t`Edit`,
cancel: i18n.t`Cancel`,
yes: i18n.t`Yes`,
no: i18n.t`No`,
ok: i18n.t`OK`,
};
Since I work with React Native, I sometimes can use the trans component and other times I cannot - sometimes I need to be able to get a plain string translated.
I will leave the api decisions to you, this is the first time I'm working with i18n and react, so I don't yet have the necessary amount of experience.
@tricoder42 Is there currently a method which allows to use variables with the low-level API?
const message = i18n._(
'It\'s only allowed to download max {max} documents at the same time',
{ values: { max: 50} },
);
This doesn't work
@akkie Yes, that's exactly how it's supposed to work (see this test case).
This doesn't work
What's the content of message
variable?
{max}
placeholder isn't replaced with 50?
{max}
placeholder is missing completely in final message?
Message isn't translated at all?
@tricoder42 Ahh, sorry!
This is the message:
It's only allowed to download max {max} documents at the same time
The placeholder will not be replaced.
Same with this example:
const name = "Fred";
console.log(i18n.t`Hello ${name}`);
#> Hello {name}
No need to apologize! This library has a huge documentation dept, I'm gonna work on it during upcoming holidays (BTW, there're old tutorial for lingui-i18n
).
Do you use lingui-i18n
only or in combination with lingui-react
? If solo, could you please try this minimum example:
import { i18n } from 'lingui-i18n'
// Required for development only
i18n.development(require('lingui-i18n/dev'))
// load messages
i18n.load({
en: {
messages: {
"It's only allowed to download max {max} documents at the same time": "It's only allowed to download max {max} documents at the same time",
}
}
})
// set active language
i18n.use('en')
I think the important part is i18n.development(require('lingui-i18n/dev'))
when you're running in development environment.
I'll try to look at it this afternoon, but it seems that either development
package isn't loaded (in dev env) or compiled message catalog isn't loaded (in production env)
I use it in combination with lingui-react
and the message is contained in my catalog. I use the code within a Redux Saga which is completely decoupled from the React related code. I store the language and the catalog in my Redux store. So I'm able to fetch this data in my Saga and set it with the i18n.load
and i18n.use
functions, as you've it suggested. This works now with my English messages which I use as keys. But it doesn't work for German messages.
const language = 'de';
i18n.load({ [language]: { messages: { Documents: 'Dokumente' } } });
i18n.use(language);
i18n.t`Documents`
#> "Documents" instead of "Dokumente"
Will print always the English message.
Use .activate(language)
instead of .use(language)
. use
is for local language switching and creates a copy of i18n object, activate
changes global language:
i18n.use('de')._('Documents') // Dokumente
i18n._('Documents') // Documents
i18n.activate('de')
i18n._('Documents') // Dokumente
Do you use babel-preset-lingui-react
? If not, you need to load babel-plugin-lingui-transform-js
explicitly.
Also, js plugin is looking for i18n.t
calls, so these examples won't work:
// Bad
const { t } = i18n
t`Documents`
// Also bad
this.props.i18n.t`Documents`
// Good
i18n.t`Documents`
Does it solve your usecase?
I'm working on lingui-i18n
reference guide and tutorial, should be ready in next few weeks. Sorry for confusion!
@tricoder42 activate
works as expected. Thanks for the help and the great library :+1:
Fixed in @lingui/core@2.0.0
. See release docs
How should we handle the case when id
has not been found in catalog? I mean not just rendering it but also doing logic like if (translated === 'NOT_FOUND') {...}
If I do const translated = i18n._("bad_id", {defaults: "NOT_FOUND"})
, translated
will equal to "bad_id"
instead of "NOT_FOUND"
And const translated = <Trans id={"bad_id"}>NOT_FOUND</Trans>
also won't work because in this case translated
will equal to an object instead of translated string. (But if you put it in render it will correctly output "NOT_FOUND")
I note that this idea of the message key with defaults is not part of the reference documentation on i18n.t
hi! I'm wondering - in https://github.com/lingui/js-lingui/issues/15 you mention how to use key with the i18n.t function:
I'd like to ask - how do I provide a default message for the key? Thanks!
I did notice there is the
_
function in https://github.com/lingui/js-lingui/blob/master/packages/lingui-i18n/src/i18n.js#L158 but I haven't seen it documented so not sure if I should use it.A related question (perhaps more important than the first): I have a set of terms that keep popping up in many places across the app. I would like to have the ability to refer to those terms by a key. Is there a way to have one place where all these keys (and their values) are defined, and then refer those terms from the
i18n.t
function and from the<Trans>
component?Thank you!