lingui / js-lingui

🌍 📖 A readable, automated, and optimized (3 kb) internationalization for JavaScript
https://lingui.dev
MIT License
4.6k stars 382 forks source link

Make components work without I18nProvider #114

Closed kirillku closed 4 years ago

kirillku commented 7 years ago

I'm not sure is it bug or current behavior, but when there are no catalog passed to I18nProvider or no translation specified in messages, instead of showing default values, it shows translations strings inside the application.

example:

<I18nProvider>
  <Trans>Hello, <a href='#'>{name}</a></Trans>
  <Plural value={count} one='# book' other='# books' />
</I18nProvider>

=>

<span>Hello, <a href="#">{name}</a></span>
<span>{count, plural, one {# book} other {# books}}</span>

should be something like:

<span>Hello, <a href="#">Someone</a></span>
<span>1 book</span>

Seems like it works properly only when <I18nProvider development={dev} catalogs={{}} />.

tricoder42 commented 7 years ago

At least development={dev} is required, because I18nProvider doesn't have bundled message parser.

In production parser isn't required, because messages in catalogs are parsed and compiled to pure functions. However, in development it's necessary to use development prop and pass development data (plural rules and message parser).

tricoder42 commented 7 years ago

Feel free to reopen if you have more questions.

kirillku commented 7 years ago

I have added <I18nProvider development={dev} catalogs={{}} />, but it still shows translations strings instead of default values in production (in dev it works fine). If there are any way to generate empty catalog, so it will include only those helper functions?

And as a possible feature, what do you think about making components work even without I18nProvider or with empty I18nProvider. Then components will still work with default values. It can be used when different projects share same common components and only one require translations (my case). Also, there will be no need to compile and include default language. For example, right now I'm just compiling english messages.json where all values are empty strings, which looks strange.

tricoder42 commented 7 years ago

In production you should use compiled message catalogs always. There're guards all over the code, which checks that NODE_ENV !== 'production' and only then development code is executed. So it won't work even if you had some sort of empty catalog. It's similar to how prop-types work. In production, all unnecessary code is removed, minimizing the bundle size.

The easiest way is simply run lingui extract; lingui compile and use compiled message catalog. I understand it looks weird if you have just one messages.json, but it works in general case.

Btw, if you're using English in your source code (e.g. Inbox Title instead of message IDs like Inbox.title), you should set "sourceLocale": "en" in your lingui config. That way you should never bother about filling translation in en/messages.json.


About your feature request: Sounds interesting. It would be really great if we could use components even outside i18n context. It might be possible by compiling source code without lingui babel plugins. Then we could update Trans component to work even without I18nProvider. My only concern are all other components, like Plural, Select, DateFormat, NumberFormat, because these components actually need language data (plurals, formats, etc).

kirillku commented 7 years ago

I have "sourceLocale": "en" and "fallbackLocale": "en" in my package.json, but if I will remove en: unpackCatalog(en) from I18nProvider's catalogs, it will not work and also will show warning.

I think if it would be possible for components to work without I18nProvider, all components could decide just by props, what to output, anyway it would be better than translation string. For example <Plural value={count} one="# book" other="# books" /> is already enough to show something readable. It will be really cool feature to have. As for me, it is the only missing part to make lingui the most userfriendly intl lib for react. I can also help if you will give me some kind of starting point.

tricoder42 commented 7 years ago

Yes, in production you always need to load compiled message catalog. In development you either need source catalog and development data or compiled catalog as well.


Let's try to figure out how it should work.

Translations

Babel plugins transfrom all i18n components to Trans and convert children to ICU MessageFormat string. For example:

<Trans>Hello <strong>{world}</strong></Trans>

becomes

<Trans id="Hello <0>{world}</0>" values={{ world }} components={[<strong />]} />

Trans component renders Hello <0>{world}</0> instead of original children. That's why we need to disable babel plugin, when we plan to use it without I18nProvider.

Plurals

The situation is similar to singular translations, but there's one more problem: I18nProvider provides also plural data, which is basically a function number -> string which maps a number of items to plural rule (e.g: 1 -> one, 2 -> other, 3 -> other, for English).

To avoid loading plural rules for all languages, there should be some way to define what's the source language and then inject language data.

kirillku commented 7 years ago

How it seems to me, if Plural is used without I18nProvider, then it should decide based on props, which text to show, it shouldn't be exact right form, just something acceptable. other prop is required and other props will be used if specified. And if the user want to show exact right messages, then he needs to connect I18nProvider with language catalog.

<Plural value={count} other="# books" /> 
0 books, 1 books, 2 books, 5 books

<Plural value={count} one="# book" other="# books" />
0 books, 1 book, 2 books, 5 books

<Plural value={count} zero="no books" one="# book" few="# bookz" other="# books" />
no books, 1 book, 2 bookz, 5 books

<I18nProvider language='en' catalogs={{ en }}>
  <Plural value={count} zero="no books" one="# book" few="# bookz" other="# books" />
</I18nProvider>
0 books, 1 book, 2 books, 5 books
tricoder42 commented 7 years ago

@kirillku I see, so basically we would have default plural rules:

0: zero 1: one 2: two 3: few 4: many 5+: other

when language specific plural rules aren't available, right?

kirillku commented 7 years ago

@tricoder42 this is how i see general rules for plurals:

plural(props) {
  switch(props.count) {
    case 0:
      return props.zero || props.other
    case 1:
      return props.one || props.other
    case 2:
      return props.two || props.few || props.other
    case 3:
      return props.few || props.other
    case 4:
      return props.few || props.other
    // 5+
    default:
      return props.other
  }
}

Also, didn't get what the diffirence between many and other, so didn't put it there.

tricoder42 commented 7 years ago

The difference depends on language, but if we do it this way, we should probably define all plural rules.

I'm still deciding between default plural rules or using other form when no plural rules are loaded. We want to make the component work without I18nProvider. However, when component returns incorrect text because of default plural form, you are forced to use I18nProvider anyway.

kirillku commented 7 years ago

@tricoder42 From my point of view, it is ok if the wrong text is returned when no I18nProvider is used. The only point is that the return value should be readable, because it is better to get 1 books instead of {count, plural, other {# books}}.

What do you think about this default behavior:

plural(props) {
  if (props.count === 1) {
    return props.one || props.other
  }
  return props.other
}

It uses only other and one. So it will be already enough for English. And other languages will get wrong plural forms, but it will be still readable.

tricoder42 commented 4 years ago

Closing as a stale issue. Feel free to reopen with updates