Js-lingui: [Epic] Road to LinguiJS v3

Created on 22 Sep 2018  Β·  229Comments  Β·  Source: lingui/js-lingui

CircleCI
npm@next

Documentation
Migration guide

Examples:


Note: Original roadmap was deleted because it wasn't updated.

Breaking changes

  • [x] #657 Refactor pseudolocale - Migrate existing functionality to support multiple catalogs
  • [ ] Release RC πŸŽ‰

Documentation

  • [ ] Review tutorial
  • [x] Review API references, especially macros
  • [x] Review migration guide

Nice to have (might be released after v3)

  • [x] #656 Refactor catalog stats
  • [x] #577 Detect locale from URL, window.navigator or cookies
  • [ ] #406 Adds extractId option to conf
  • [x] #569 Feature: Merge compiled catalogs into one (feature is finished, it just requires merge latest next branch and resolve conflicts)

If you want to get involved

  • [x] #634 Add formatOptions.origin to PO format (copy existing feature from one catalog format to another)
  • [x] Review/proofread Configuration reference
⚠️breaking change

Most helpful comment

Hey everyone, 3.0.0 was just published to NPM πŸŽ‰

Thank you all for patience! Big thanks to @semoal who pushed the last features and fixes πŸ‘ I'm gonna cleanup the backlog of issues in next few days and see what's still relevant.

All 229 comments

I can't stop talking about how great this project is. It puts a sane workflow around localization (extract/compile), provides rich react integration and has a reasonable KB footprint. We've saved a lot of time using Lingui on our project.

Currently, the extract command takes all the messages in our src directory and outputs them into locales/{locale}/messages.json. When loading up the site, we are including the entire catalog for the current user's locale in the <head /> of our document. This means that for a given page, there are a lot of unused messages. Is there a way to make this more efficient in v3?

@disintegrator Yeah, this will be partially solved using new message catalogs. I already start the work (#326), but I want to publish pre-release with breaking changes before adding more features.

Automatic code-splitting isn't planned at the moment, but it's something I would like to solve as well. Just don't know how, yet.

Thank you for the support! πŸ‘

@tricoder42 I am curious as I don't see it on the checklist here, but are you planning on doing that callable object for i18n as we were talking about before? It should probably fit into V3 too since it's a bit of a breaking change (or not?).

@FredyC Yeah, sorry, I forgot, but I count with it (last, but definitely planned for v3):

img_2547

Would love to see this rolled out. I am scanning through the docs to create an internal presentation for developers to use jsLingui at my job and it is a bit confusing since there are examples of both babel-transform plugin vs macro for both js and react.
I might be wrong but it seems like clearing up the approaches is a goal of v3?

@dephiros Yes, exactly.

I would like to release v3 after React Conf. I expect some changes in React to be announced at the conference so I want to wait and see what they come up with.

Hi @tricoder42

First of all, thanks for your great work! I think this is a great lib, and I'm looking forward to using it within a product we're building.

I'd love to know... there's any plan / roadmap to follow up on the v3 release? Or it would be just enough to watch this issue? Thanks! πŸ˜„

@nuragic "Road to LinguiJS v3" doesn't sound like roadmap enough to you? πŸ˜„

If the question was "What's the timeline", then unfortunately I don't know at the moment. There're few breaking changes which I want to release in v3 and some of them aren't definitive (e.g. catalog loading API, macros).

Otherwise yes, keep watching this issue or PR #344, which is the next branch. v3 will be released when it's merged to master.

@tricoder42 Ok got it! Please make us know in case you need help to tackle some task to be done in order to shipping v3. Cheers

I would like to help to get this moving as well. I think it's rather a massive undertaking to do this alone. Perhaps, you don't need to ship a "perfect" version 3? Might be better to focus on breaking changes to have the stable API layer and other improvements can come in small batches later when there is no pressure to manage everything at once.

Can you please some more details what are you working on currently and if there is some room for help? Sometimes even a brainstorming over ideas can help?

The PR #344 looks rather scary, to be honest. Over 300 files changed. If I would be thinking about code review, I wouldn't be sure where to start :/

I'm in the middle of refactoring macros. There's ongoing discussion about the syntax, but the implementation should be following:

  1. Macro converts AST to tokens
  2. Tokens are converted to ICU MessageFormat message
  3. Macro is replaced with runtime call with ICU Message Format

Step 1 and 3 are different for JS and JSX. Step 2 is always the same. On top of that, this solution is more flexible for the future usecases, when we can generate locale specific bundles (in step 3), minify message ID in production (step 2) or replace message format (step 2).

The WIP is in rfc/001-macros-v2. I'm working on Step 1 for JS macros. Independent tasks are Step 1 for JSX or Step 2. Any feedback in the discussion is appreciated. Also, I don't know how macros work with Typescript. That's probably something I need to help with.

I'm planning to write up what are my plans for v3 based on feedback I collected in past weeks. I'll post it soon.

I'm in the middle of refactoring macros.

Sorry, but that still sounds pretty vague :) I am not trying to push you, just want to know if there some way to help to get into a process and help you. Do you have some actual stumbling blocks or do you have a clear idea and just lacking time and/or motivation?

Regarding the TypeScript, I am not entirely sure by myself. I don't particularly like the approach of running the code through two compilers. It creates an unhealthy ecosystem as you need to support various bundlers and tools and that's definitely head spinning (#407).

TypeScript has its own plugin system called transformers. It almost feels like some separate project similar to macros would need to be created, ideally with exactly same API as macros so any macro could be used as a TypeScript transform as well, but that's probably just a wet dream :)

Sorry, but that still sounds pretty vague :)

How so? The exact answer is few paragraphs below: The WIP is in rfc/001-macros-v2. I'm working on Step 1 for JS macros. There you can see the current status. The three steps described above is the whole picture of new macros.

I am not trying to push you, just want to know if there some way to help to get into a process and help you.

Independent tasks are Step 1 for JSX or Step 2. Any feedback in the discussion is appreciated. Also, I don't know how macros work with Typescript. That's probably something I need to help with.

TypeScript has its own plugin system called transformers. It almost feels like some separate project similar to macros would need to be created, ideally with exactly same API as macros so any macro could be used as a TypeScript transform as well, but that's probably just a wet dream :)

That would be great. I'm hoping that by splitting the macros logic, you have to rewrite only AST specific part, but the core should stay the same. There will be duplicate logic for babel/typescript, but maybe in the process we figure out how to generalize macros to run for both parsers.

Code splitting is very much needed for me to scale.

Update 2019-02-07

  • New macros are almost ready and I'm close to publish another pre-release.
  • I'm gradually converting codebase to Typescript (so far just Macros and part of Core)
  • Offline compilation of message catalogs will be most probably dropped in v3 and replace with runtime formatting. Alternative methods for size reduction will be used instead (ie. minification of IDs or per-locale bundles).

Could you elaborate on that last point?

@mbrevda Current solution is compile ICU MessageFormat messages into JS function. Thanks to that production bundle doesn't ship with message format parser (which is huge). On the other hand, it's not possible to load messages dynamically (e.g. from 3rd party source). Also, for large number of messages, the benefit of stripping message parser is lost because these JS functions are larger than strings.

Minification of message ID reduce sizes a lot. I don't have enough data, but what I measured on few message catalogs, there's about 40% size reduction. It depends on size of message IDs, number of messages, but if the project already uses macros, then it's for free. It also doesn't mess up with dynamically loaded messaged, because only known messages are minified.

Generating locale specific bundle gives the smallest bundle size because it strips most of i18n code. Source text is replaced with translation directly in the source code. The only parts which will remain are number/date localization, plurals and similar formatting. It'll be opt-in, because it requires specific setup.

Got it, thanks! Also, just watched the talk which already addressed additional concerns. Would love to see the docs on how integrated the build steps are.

@tricoder42 Coming at you to check on this v3, do you need any help?

Do you think you should split this big release into smaller chunks to release earlier then iterate more often on it do add features one by one?

I cannot help to think this is a too big release for one release & for one man's job.

How can we help you to solve that?

@JSteunou Currently the biggest hurdle is being solved in #479, come and join :)

@tricoder42 I suggest NOT to drop support for offline compiling.
In my current system, i use a 3rd party application to translate, then i pull translations for offline compilation to import into the application.
I think, a deprecation notice, combinated with a migration path is more useful for future release.

Oh damn, I read that update, upvoted it, but did not realize what the offline compilation means. Please keep that. We have a similar workflow setup as @revskill10 and it works just fine for us.

Also, runtime formatting sounds kinda more expensive than doing it as part of the build process.

I had a number of problems with core-js version conflicts when I tried to set up a mono repo using react-native and applied @lingui. I ended up nohoisting @lingui/loader to get rid of that. Since mono repos are more and more common, perhaps making that part of the overall release plan might be a good idea.

Dropping the compile time parser seems like a bad idea to me. Sounds to me like you're saying that you think that in certain scenario's the growth in package size for the parser will be offset by a reduction in other areas depending on message id size. Gut feeling is that if that's the case, it should be possible to compile the message ids to something small(er) and achieve an overall size reduction.

Latest release: 3.0.0-5 (@next tag)
Migration guide


Hey everyone!
I've finished first large part of refactoring for v3, new API for JS macros.

TLDR:

  • all macros are transformed into message descriptors, which are passed to i18n._ function
const static = i18n._(t`Static Message`)
// ↓ ↓ ↓ ↓ ↓ ↓
// const static = i18n._(/*i18n*/{
//   id: 'Static',
// })
  • comments and custom IDs can be add by wrapping the macro in defineMessage macro:
import { defineMessage, t } from "@lingui/macro"

const name = "Joe"

const message = defineMessage({
   comment: "Greetings on the welcome page",
   message: t`Welcome, ${name}!`
})
i18n._(message)

// ↓ ↓ ↓ ↓ ↓ ↓

const message = /*i18n*/{
   comment: "Greetings on the welcome page",
   message: "Welcome, {name}",
   values: {
     name
   }
}
i18n._(message)

It's more verbose than all variants which I considered before (e.g making macros callable). On the other hand, when you start adding comments and custom ids, the message gets verbose itself. I found this more readable than previous variations:

i18n._(t({
   id: "Welcome page / Greetings",
   comment: "Greetings on the welcome page",
})`Welcome, ${name}!`)
  • the defineMessages macro is just a helper to define many messages at once. In the end I removed the bind API, because it was confusing that messages from defineMessage and defineMessages behave differently (the former was a string, the latter a function).
import { defineMessages } from "@lingui/macro"

const messages = defineMessages({
   ok: "OK",
   about: {
      id: "Navigation / About",
      comment: "Link in navigation pointing to About page",
      message: "About us"
   }
})

// ↓ ↓ ↓ ↓ ↓ ↓
const messages = {
   ok: /*i18n*/"OK",
   about: /*i18n*/{
      id: "Navigation / About",
      comment: "Link in navigation pointing to About page",
      message: "About us"
   }
}
  • useLingui hook was added. i18n object must be imported explicitly, when required:
function App() {
  const { i18n } = useLingui()
  return i18n._(t`Hello App`)
}

There's no hidden magic, everything is explicit.

For more info, checkout the new version of documentation for macros and draft migration guide


@FredyC @JSteunou Are you guys still keen to help? After this release, all changes should be small and incremental ones. If you're interested, I would draft planned features in more detail.

If you don't have much time, any comments or suggestions are still very valuable πŸ™


Other points mentioned in this thread:

offline compilation

Let's make it optional. I would like to support more message parsers anyway, so if you don't compile messages at build time, you need to use message parser at runtime:

const i18n = setupI18n()
i18n.useFormat(icuMessageFormat)  // icuMessageFormat parser

babel 6

I saw that lot of people are still using Babel 6, so I'm not sure if we should drop it. I would rather resolve issues metnioned by @mschipperheyn and moved on using babel bridge. What do you think?


Thank you all for the patience. I should have more time in following weeks, so I would like to finalize the relese. Any help welcome πŸ™

@tricoder42 the next tag on npm is still pointing to version 3.0.0-3

@lucasecdb Ah, my bad! I have a bug in release script, which I need to fix. Re-deployed again as 3.0.0-5

@tricoder42 Thank you for finally moving this forward. I will definitely try it out later, but I am still a bit annoyed by that i18n._(t'message') syntax to be honest. I know we have been all over it and there is no easy path out, but still, it feels that by transitioning to macros we gained some and lost some.

I am actually thinking about writing my own macro, that upon seeing import from @lingui/macro would add an import for my global i18n. However, that means the i18n cannot be actually used in such file explicitly. The macro would need to go deeper and actually modify all t, plural, etc... calls and wrap them with i18n._... Yea, I am that lazy 😎 I am not sure if it's possible like that, I hope it is.

Edit: https://github.com/lingui/js-lingui/issues/508 - ticket per locale bundles

We might actually end up with i18n.t, i18n.plural etc macros where i18n object itself will be a macro.

I've just finished a draft of RFC003 - Per-locale bundles. Relevant example is in the i18n context section:

// src/LinkWithTitle.js
import * as React from "react"
import { i18n } from "@lingui/macro"

const LinkWithTitle = () => {
   i18n.useLingui()

   return (
      <a href="" aria-title={i18n.t`Link to documentation`}>
         <img />
      </a>
   )
}

The point of wrapping everything in macro (even the hooks) is build time translation and generating per-locale bundles. If we generate per-locale bundle, the code above would be transformed into:

// build/LinkWithTitle.<locale>.js
import * as React from "react"

const LinkWithTitle = () => {
   return (
      <a href="" aria-title="Link to documentation">
         <img />
      </a>
   )
}

In locale agnostic bundle, where localization runs at runtime, the same code would be transformed into this:

// build/LinkWithTitle.js
import * as React from "react"
import { useLingui } from "@lingui/react"

const LinkWithTitle = () => {
   const { i18n } = useLingui()

   return (
      <a href="" aria-title={i18n._("Link to documentation")}>
         <img />
      </a>
   )
}

Still very early RFC, but the bottom line is: If we want to do this (the best size/performance), then everything related to i18n, must be wrapped to macro.

Despite not being a fan of per-locale bundles, I do like the i18n.useLingui() approach and other stuff around it. It certainly looks a lot let verbose and useful. Are you going to make an RFC for that as well?

Hi,
I was curious to see how the new linguijs would behave with next and SSR as I am currently shopping for an I18n solution for my project.

I created a demo app at https://github.com/ywg-jean/linguijs3-next
I was able to make it work in what is probably not a very optimal way; but at least:

  • The initial SSR rendered html can be rehydrated
  • Toggling language on the frontend works

PS: I am fairly new to all the frontend subjects, I am interested in comments/review (preferably in the repo itself I don't want to pollute this thread any more than I just did)

Hi @ywg-jean,
I actually want to test v3 with Next.js as well, because:

  • it covers both SSR and client-side localization
  • I have a Next.js project which I need to translate ;)

I'll keep you posted about updates. I just skimmed through your repo and I'll post some notes later today.

Thanks for bringing this up πŸ‘

I'm a fan of per-language bundles. After working on a website with very complex and long pages, I benchmarked it and it actually improved the performance after I converted react-intl messages into static strings. Not sure if the gains would be the same with LinguiJS, but it definitely makes sense.

Most of codebase is already written in Typescript and build is green after a very long time. Further progress should be faster from now πŸš€

@tricoder42 bundles feature looking great, looking forward for it but I'm concerned the DX is dropping down a little because of the new API. Looks like double function call to do i18n._(t`my message`)

I dont know how it could be possible but in a idealistic world a simple i18n.t('my message') would be the best for DX

@JSteunou It's annoying, but if we want to simplify it, we need to turn whole i18n object into macro. I posted an example few comments above:

// src/LinkWithTitle.js
import * as React from "react"
import { i18n } from "@lingui/macro"

const LinkWithTitle = () => {
   i18n.useLingui()

   return (
      <a href="" aria-title={i18n.t`Link to documentation`}>
         <img />
      </a>
   )
}

The API would be the same for per-locale bundles (new feature) and locale agnostic bundles with runtime loading of catalogs (as it's now).

i18n.t looks great, but at the moment there's lot of confusion how it works which leads to several bugs in production.

I think the i18n.useLingui() is also confusing, because it looks like it's doing some side effects, as it doesn't "return" anything (although I know it returns after the macro is applied), but it's necessary for the macro to work and may break in obscure ways if you forget to include it.

another thing I think is a good argument against this is that, by constraining the macro to only be applied with the hook, you'll drop all support for class components right? Before you could write your own HOC or render props component and pass the i18n object from the hook down, since the library doesn't have them anymore, but right now we won't even be able to use the macros in a class because you're relying on the hook.

What is the i18n.useLingui() going to do anyway? I mean macro is imported, why the extra hook call? This approach looks slightly better, but not sure how that works. Is it actually possible to return a macro function from another function call? Or why cannot the useLingui be a macro actually? :)

import { useLingui } from "@lingui/react"

const LinkWithTitle = () => {
   const { i18n } = useLingui()
   ...
}

@lucasecdb Btw, you can make your own HOC that has useLingui inside and then easily wrap any class component with it. As long as you can use React 16.8+, there is no problem.

you can make your own HOC that has useLingui inside and then easily wrap any class component with it

exactly, that's what I meant by "before you could write your own HOC or render props component and pass the i18n object from the hook down", but if the i18n is in a macro that won't be possible, right?

Macros are always transformed into runtime code, so as @FredyC says: i18n.useLingui() macro is simply transformed into const { i18n } = useLingui() hook call. You can write your own HOC as required.

What is the i18n.useLingui() going to do anyway? I mean macro is imported, why the extra hook call?

To avoid duplicate identifiers:

// src/LinkWithTitle.js
import * as React from "react"
improt { useLingui } from "@lingui/react"
import { i18n } from "@lingui/macro"

const LinkWithTitle = () => {
   const { i18n } = useLingui() // oops, I can't override/shadow i18n identifier

   return (
      <a href="" aria-title={i18n.t`Link to documentation`}>
         <img />
      </a>
   )
}

But we're getting back to question if we really need to pass i18n through context...

But if you pass it from a HOC, there will also have shadowing:

// src/LinkWithTitle.js
import * as React from "react"
import { i18n } from "@lingui/macro"

const injectI18n = (WrappedComponent: React.ComponentType) => {
  const Component = props => {
    i18n.useLingui()
    return <WrappedComponent {...props} i18n={i18n} />
  }

  return Component
}

class LinkWithTitle extends React.Component {
  render() {
    const { i18n } = this.props // same shadowing issue

    return (
       <a href="" aria-title={i18n.t`Link to documentation`}>
         <img />
       </a>
    )
  }
}

export default injectI18n(LinkWithTitle)

But we're getting back to question if we really need to pass i18n through context...

Funny, that's what I was promoting in earlier days that global instance should be enough. It's unlikely you want to have different locales or catalogs per component tree.

To avoid duplicate identifiers:

I am not sure what you mean by that. I was referring to i18n.useLingui version which is seemingly doing nothing.

But if you pass it from a HOC, there will also have shadowing:

Good point! I don't have an answer yet, feel free to suggest different API.

it sounds like having a global i18n object would solve a lot of problems πŸ˜„

We just need an escape hatch for SSR in multi-threaded environments.

I assume that SSR is not really using prebuilt bundles as the browser does? Never really tried it. If it runs the code from the source, why it should be a problem to have a global variable?

The multi-threaded environments are exactly what in Javascript terms? Separate child processes or is there some shiny new approach I've missed? Each of those processes can have its own global variable, or not? I am really fishing here, probably best to leave this to people that actually know how these things work :)

Hey everyone,
new prerelease (3.0.0-6) has just been published to NPM.

Documentation Migration guide

See updated Create React App example or React example.


Catalogs

Configuration allows multiple catalogs:

module.exports = {
  locales: ["en", "cs"], // required, must be added manually in config, lingui add-locale is deprecated
  catalogs: [
    {
      path: "src/locales/{locale}",
      include: ["src"]
    }
  ],
}

Events

Events were refactored, there're three:

  • activate - triggered when locale change is requested with i18n.activate
  • load - triggered when catalog is about to load with i18n.load
  • change - triggered after either active locale or catalog were updated

The activate event is useful for automatic loading of catalogs:

function loadCatalog(locale) {
  return import(`@lingui/loader!./locales/${locale}.po`).then(catalog =>
    i18n.load(locale, catalog)
  )
}

export const i18n = setupI18n()
i18n.on("activate", loadCatalog)
i18n.activate("en") // loads ./locales/en.po

The change event is used internally in I18nProvider. We need to know that i18n data has changed to force re-render.

Finally, load even was added just as a symmetry. I wanted originally use it inside I18nProvider, but in the end I needed extra even called after locale is changed or catalog is loaded.


I'm also playing with automatic message catalog splitting based on chunks (#503). Unfortunatelly, the webpack API required for this will be released in webpack 5.0. It'll allow register runtime hook, so when e.g. chunk 0.js is requests, locale file 0-en.js will start loading at the same time. I think all we can do right now is start loading 0-en.js only after 0.js is loaded. It's suboptimal, but should be possible.

I think you should seriously consider dropping babel 6 for version 3. Most libraries have with their major release upgrades and it will remove a lot of problems. On a large scale project with React, node, Material-ui, Razzle, React Native Web etc, @lingui is one of an ever shrinking few that still works with Babel 6. Supporting babel 6 in version 3 makes no sense at all.

Dude, just dump it.I struggled enormously with core-js implementing @lingui 2.x on a monorepo. And that shit would be just poof gone.

I wish! There's an open PR which add extractBabelVersion to config #517. Since babel is just peer dependency, it's up to you what you install. Using this config option you could skip installing babel-core bridge and use @babel/core (ver 7) directly.

If all this effort fails, we'll have to drop it, but I'm afraid it'll cause more problems :/

Ok, my understanding is that the babel 7 bridge is only needed if the library is based on babel 6, with its own dependencies such as core-js. That is was is causing the problems. My suggestion was you base on babel 7, and just say: "sorry babel 6 users, time to upgrade". If it can work without the bridge out of the box, I'm good.

I'll give it a go against our platform with the 3.0.0 pre release and report back

@mschipperheyn Just to clarify - the PR wasn't merged yet and it's not part of pre-release either. I'll let you know when I merge it.

Hey everyone,
new prerelease (3.0.0-7) has just been published to NPM.

Documentation | Migration guide

See updated Create React App example or React example.


Macros

Macros are transformed into i18n._ call:

import * as React from "react"
import { t } from "@lingui/macro"

function App() {
  return <img alt={t`Translated image alternative text`} />
}

is the same as:

import * as React from "react"
import { i18n } from "@lingui/core"

function App() {
  return <img alt={i18n._("Translated image alternative text")} />
}
  • module with exported i18n object can be configured using new runtimeConfigModule configuration. By default the app uses i18n object exported from core package so you don't need to create your own with setupI18n unless you need to.
  • defineMessages macro was removed. If you want this functionality, you need to call multiple defineMessage macros
  • lazyness of macros was removed as well. It would be possible to add lazy macro which would transform wrapped macro into () => i18n._(...), but I don't have usecase to work on it personally.
  • useLingui macro isn't needed, because the app reads messages from global source. It should be possible to use bare useLingui if you want to force component re-render on locale changes, but my main focus are apps which require full page reload for locale changes. There're lot of optimization which can be made with this assumption and there's really very little usecases when you need runtime locale switching without reload.

Format of compiled messages

Messages are compiled into arrays instead of functions.

const message = t`Hello ${name}`

const before = function (a) { return ["Hello ", a("name")] }

const after = ["Hello ", ["name"]]

This change significanctly reduces sizes of compiled catalogs while the runtime processing is about the same. Also, such format is serializable so there's no problem with Next.js which serializes getInitialProps into json.

This is internal change, doesn't affect public API at all.

πŸ† Inspiration taken from https://github.com/format-message/format-message

What's next

  • Cleanup event emitters. Right now they all return promises, which is kinda unfortunate when you want to run i18n.activate or i18n.load synchronously.
  • Migrate project to Babel 7 - I really don't have resources to support both versions and I want to remove as much clutter as possible. Unless there's someone who needs it badly and who's willing to sponsor the maintainence, LinguiJS v3 will require Babel 7.

I guess that's all breaking changes for now. Everything else can be added in minor releases. It would be great to cut this releases asap, because it's way overdue.

Any feedback welcome! Cheers πŸ‘

Simply wow! This is the update I was waiting for and was holding off from trying previous versions. I definitely have to try this out soon as it sounds almost miraculous :) Well done!

About the laziness, so it means we cannot simply declare messages outside the component scope? I mean your migration guide does not mention what to use instead of i18nMark. Or is that what's defineMessage is for?

You still have the defineMessages at the https://lingui-docs-git-next.lingui-js.now.sh/ref/macro.html, I suppose oversight? I suppose it's not really needed, so not a big loss.

I wonder what are the actual use cases for calling setupI18n. Since the one included in the core can be configured in all aspects, then what is the benefit of having own one?

PS: Little typo that made me swallow in fear for a moment :)

Migrate project to Babel 6

I definitely have to try this out soon as it sounds almost miraculous :)

Actually, feel free to wait a day or two. I want to simplify the events which might be a bit breaking change.

About the laziness, so it means we cannot simply declare messages outside the component scope?

Well, you can, but with extra work:

// src/common.js

export const yes = () => t`Yes`

export const no = () => t`No`

export const greetings = ({ name }) => t`Hello ${name}`

You need to wrap each macro into function call to make it lazy. Otherwise the message might be translated before the translations are actually loaded. When you're inside your component you most probably have the messages loaded.

It also means it's not possible to translate defaultProps - the prop would be function and not a string. There're limitations, but nothing what couldn't be solved.

I mean your migration guide does not mention what to use instead of i18nMark.

Ah, my bad, I need to review it and update it.

Or is that what's defineMessage is for?

defineMessage is the only way how to add additional metadata to js macros:

plural(value, { one: "Book", other: "Books" })
// i18n._("{value, plural, one {Book} other {Books}}", { value })

defineMessage({
  id: "Pluralized books",
  comment: "The work book, but pluralized",
  message: plural(value, { one: "Book", other: "Books" })
})
// In development (e.g. for extraction):
// i18n._("Pluralized books", { value }, { 
//   message: "{value, plural, one {Book} other {Books}}",
//   comment: "The work book, but pluralized",
// })
//
// In production:
// i18n._("Pluralized books", { value })

I wonder what are the actual use cases for calling setupI18n. Since the one included in the core can be configured in all aspects, then what is the benefit of having own one?

I asked myself the same question and I don't know. I didn't want to use global object because it should be avoided in general, but yolo. I'm gonna try it and if I find any issues I can fallback to own instance using setupI18n.

PS: Little typo that made me swallow in fear for a moment :)

Ups, sorry for that :D

It also means it's not possible to translate defaultProps - the prop would be function and not a string. There're limitations, but nothing what couldn't be solved.

Luckily, the defaultProps are going to be deprecated (cannot find the source right now, sorry) and default args in function should be used, so there shouldn't be any problem considering these are assigned during a function call. Of course, class components still need that, but let's kick into that dying beast :D

However, it's definitely more rare to have translations in default props than having a bunch of them declared outside the scope of a component which I doing fairly often. The workaround with a function will work and I doing it in most of the newer code, but it's not that optimal because it has to always evaluate again and look up the translation. Might be better to actually useMemo in there to at least optimize slightly, but that's a lot of typing...

The defineMessage is somewhat confusing. Does it mean it has to be called inside another function as well? Since macro translates it to i18n._ call, it won't have the translation available at the moment of the call. Is that correct? Shouldn't the macro wrap it into function automatically?

The workaround with a function will work and I doing it in most of the newer code, but it's not that optimal because it has to always evaluate again and look up the translation.

If it's a performance issue, then you could cache it. Or write a lazy macro which wraps the function for you:

const yes = lazy(t`Yes`)
// const yes = memoize(() => i18n._("Yes"))

In that case you need to either clear the cache somehow on locale change or reload the page completely. Either way, suggestions and PRs are welcome :)

Edit: Anyway, I would suggest: measure first. Unless it causes performance issues, then ignore it. Simple object lookup (which is more or less i18n._ call) shoud be fast enough in most cases.

The defineMessage is somewhat confusing. Does it mean it has to be called inside another function as well?

Yes. If you use it outside component, you need to wrap it in function as described above. defineMessage is really just a wrapper around t, plural and other macros to add additional metadata. Anything inside message prop transforms as standalone macro, but the whole defineMessage macro is replaced with i18n._ call.

Thanks for taking a pragmatic approach with this library. So many optimisations are lost because of runtime language switchers but 99.99% of the websites don't get any value out of it.

Sorry if this was already mentioned, but will the macro compile static messages down to simple strings (per-language bundles)? For ex, this:

<Heading>{t`Hello World`}</Heading>

into this:

<Heading>{"Hello World"}</Heading>

will the macro compiled static messages down to simple strings (per-language bundles)?

Yes, this is planned, see #508 . It won't be shipped in 3.0, but it's one of reasons why js macros wraps entire message, instead of passing output to i18n._ call. I have a small prototype where I test webpack 5 plugin for better message loading and I tried this feature there, although just simplified. In the end it should require just toggling a switch in configuration.

Few examples of JSX macros:

<h1><Trans>Hello World</Trans></h1>
// becomes
<h1>Hello world</h1>

<p><Trans>Read the <a href="/docs">documentation</a></Trans></p>
// becomes
<p>Read the <a href="/docs">documentation</a></p>

<Plural value={count} one="Book" other="Books" />
// becomes in Czech
<Plural value={count} one="Kniha" few="Knihy" other="Knih" />

and so on. JS macros will be pretty much the same.

So far it's just planned feature, so if you want to jump aboard and work on it, you're more than welcome πŸ‘

:clap: :clap: @tricoder42 this is terrific! I was thinking like @FredyC when I saw your message, this is the real v3 I was waiting for : macros are no brain to use and yay to Babel 7, finally !!

I'm wondering what the use of useLingui vis-a-vis using the created i18n instance directly. The i18n instance seems to be used by I18nProvider directly. Is there any advantage using useLingui?

That's a good question, do we still need I18nProvider in case we use the global i18n instance? Looking at the code we do...

https://github.com/lingui/js-lingui/blob/6fc169aebbe0a8eb8d29e0305480d2acd76d9c35/packages/react/src/I18nProvider.tsx#L21

The hook is used in Trans, but technically it could be as simple as return context || core.i18n without that check, right? Or set that global instance as a default value of LinguiContext might be even better.

Context makes sense in cases someone needs a custom i18n for a subtree.

Getting the following error trying to compile with 3.0.0-7

Cannot find module '@babel/runtime/helpers/objectSpread2'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:582:15)
    at Function.Module._load (internal/modules/cjs/loader.js:508:25)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
    at Object.<anonymous> (/Users/mschipperheyn/development/projects/funcional/social-front/node_modules/@lingui/macro/macroJs.js:12:45)

Context

  • multi workspace yarn based setup
  • @babel/core 7.4.5

The only available objectSpread in objectSpread.js

@mschipperheyn Migration to Babel7 is still on TODO list, so you might hold with that, otherwise, you probably need to follow same Babel setup as for V2.

I'm wondering what the use of useLingui vis-a-vis using the created i18n instance directly. The i18n instance seems to be used by I18nProvider directly. Is there any advantage using useLingui?

useLingui will most probably be used only by Trans. You'll need to use it manually only if your component needs to re-render on locale change.

The hook is used in Trans, but technically it could be as simple as return context || core.i18n without that check, right?

Yeah, that should work.

Getting the following error trying to compile with 3.0.0-7

Babel 7 was merged (#537) but haven't shipped yet. It'll be in next pre-release.

You'll need to use it manually only if your component needs to re-render on locale change.

How would that work? :) As long as there is the same i18n instance in the Context, it won't re-render on language change. The actual value in the Provider needs to change, but then a whole tree gets re-rendered no matter if you consume context or not. Yea it would be brilliant if only components consuming context would be affected, but sadly it's going from Provider all the way down.

@FredyC Well, I'm running this against our production which does have a working v2 setup. We have a 7.0.0-bridge. The one thing that broke before which lead to me downgrade @lingui/cli was the implementation of that open source payment service which you guys have (I forget the name) which forced a specific version of core-js (I think babel 6 uses core-js 2 and babel 7 core-js 3) which caused all sorts of havoc in my monorepo. But this seems to harken back to @babel/preset-env so it may or may not be related.

In any case, I'll be here, when you guys support babel 7, to give it a whirl against our setup.

I'm not sure where to post this as I don't have time right now to make a reproducible demo, but I still think it's worth reporting.

After trying to update a current project to get that precious useLingui hook, I was able to set up the project, but the extract cli would not detect the messages with the t macro.

<Trans> components would get extracted though.

This error occurs on the v3.0.0-7 version of all the lingui packages

Another thing worth noting is that the docs and the migration guide is not up-to-date and the current state is a bit different than the one described in the docs.

For example, setupI18n should be imported from @lingui/core, but the example in the docs imports it from @lingui/react.

The configuration format has also been changed and I couldn't find any description on it. Happily the validation errors do a really good job in describing the correct format, so I was able to set it all up.

@NSLS Thanks for the report, but it's really hard to guess what's wrong without example code and your configuration.

Anyway, if you had a bit of time, please share what needs to be updated in migration guide and/or send a PR. Thanks a lot πŸ™

I've spent some time gathering a reproducible demo based on our project boilerplate.

You can have a look at it here:
https://github.com/NSLS/lingui-3-example

I couldn't make it to work properly even though I have tried setting up lingui from scratch.

I would really appreciate if you could tell me what I'm doing wrong

Sorry to ask but is this project still under active development? The project seems great and fits my needs, and I'm considering adding this into my project but I'm concerned to see this v3 has been worked on for almost 1 year, and commits seem to be sparse recently.

I only work on this project in my free time, hence the slow development.

V3 is really close, I can feel it :) Seeing that latest update, it looks really hopeful. I haven't tried it yet though based on the instructions :)

Actually, feel free to wait a day or two. I want to simplify the events which might be a bit breaking change.

Ah, it has already been 13 days since I wrote it... I've just started working full-time on one project to rush MVP before I go on vacation, so my time is limited.

On top of that, migration guide needs to be updated.

Hi!
In the example repo, alt={t`logo`} property is not changed along with language.

Can be fixed by adding useLingui() React hook, but this wasn't obvious at first

is there an eta on a final 3.x release @tricoder42 ?

@benmonro Unfortunately there isn't, at the moment. I guess the earliest realistic estimate is September.

i would love to be able to get .po files to do this for plurals:

msgid "%1$s Jahr"
msgid_plural "%1$s Jahre"
msgstr[0] "%1$s Jahr"
msgstr[1] "%1$s Jahre"

this makes it easy for translators to toggle between the plural form when translating. below interface from transifex when the above format is present.

Screenshot 2019-08-21 at 10 17 04

@cezarneaga That's how gettext-style message format works (i.e. Gettext, Polyglot), but not ICU MessageFormat or Mozilla's Fluent. It assumes that the source locale has 2 plural forms, which isn't true for all locales. Your usecase is also specific to Transifex (and possible other services) which doesn't support ICU MessageFormat, otherwise the editor could explode {0, plural, one {Jarh} other {Jahre}} into multiple sentences and allow translator to edit them separately.

ICU MessageFormat gives you ultimate freedom between source and translation, so in one locale you might have a plural, but in other locale you might have just a one sentence and vice versa. It's more flexible but the raw format is more complex.

Right now the library uses ICU MessageFormat and I would like to add Fluent (see #489), but I don't plan to support other message formats. However, once we add support for Fluent, you should be able to write your own implementation of message format. In the end, the library should be completely message-agnostic as the macros are transformed at compile time anyway.

@tricoder42 I dont really understand why you wont support full po / gettext format whereas your doc says it is the preferred catalog format to choose https://lingui.js.org/ref/catalog-formats.html

@JSteunou I use PO file with ICU MessageFormat instead of Gettext format. I understand that Gettext and PO file format and closely related and I don't use all features of this file format, but I definitely prefer ICU MessageFormat because it's more flexible. Even newest i18n format, Mozilla's Fluent, is based on ICU.

TLDR: I'm not planning support it because I don't use it. On the other hand, once the lib supports ICU and Fluent, you should be able to write a plugin to support any format you like. As always - PRs welcome.

@tricoder42 thanks for all the great work!

I would like to help with shipping version 3. Let me know what I can do, either here or on kenneth.skovhus (at) gmail.com

@tricoder42 do you intend to invest into future development of the package, or do you think you will not have time to push it further? Please note that I'm looking for an honest answer whatever it's going to be. I'm asking because the project has not been very active and even CI seems to be broken (see #572).

If you intent do continue, perhaps you could try marking some issue and "good first issue" / hacktoberfest to motivate people to take them, and add more people as collaborators? (how many people can push to repo and publish the package?). Thanks.

Hey @vonovak, thanks for bringing this up! I want to work on it during hacktober fest, but I'm constantly busy or not motivated enough to squeeze extra time from my day.

I'll do my best to focus on it this week and at least get the green build.

I was also thinking about splitting the work, but the project is in unfinished state when half of things are still in my head or broken. I need to give it the final push to get over v3 and then it'll be more friendly to contributing.

@vonovak The build in next is green πŸ’š

@tricoder42 is the list in the op still up to date on what needs to be done for v3? I'd love to help out to get the new release ready :)

@rovansteen I've just update it. Once we have working example of SSR and CSR and there're no obvious bugs or obstacles, I believe we can ship it.

I've just added one issue which needs to be done #577 if you wanted to dig into something specific. It might be as easy as finding existing library and documenting how to use it with Lingui.

Thanks for helping!

Have we tested the compatibility with Concurrent React(already is in a experimental branch, so we can start trying if our projects are working nice)?

If we find some bugs, will be a nice task to implement to v3? or we should add them to v2?

Let's not delay V3 further by worrying about Concurrent mode. I don't even think it's going to be breaking change, so it can surely be done later. Besides, Linqui is not that complex to utilize Concurrent imo.

@semoal I haven't tested it yet, but if you find any bug feel free to create PR against next (v3).

I'm having a go at migrating to v3 on our system. One thing I noticed is that withI18n was removed. While I support the focus on hooks, how do you use the programmatic i18n._ api inside a React class? Should I use @lingui/core and expect it to reach into the global scope or should I place the i18n into an Context and access it that way?

I know everybody is tight on time, but wouldn't it make more sense to keep withI18n and use useLingui under the hood. I guess talking about it, I'll just do that myself as a quick migrate :-D

Another question I have is regarding monorepos. The new catalogs options seems a great way to deal with the various different catalogs generated in the various packages. But would you then store this in lingui.config.js at the root of the monorepo or at the package specific lingui.config.js that includes the root lingui?

I guess storing it at the root feels more natural.

@mschipperheyn Yeah, I don't plan to ship withI18n. Maybe we could ship separate package @lingui/hoc as Apollo does, but it doesn't seem to be a big deal at the moment.

About the monorepos, right now I assume one global lingui.config.js in the root.

@tricoder42 it makes it more difficult for any project using class components from using 3.x. I would definitely support having some type of backward compatibility for teams with legacy codebases....

@benmonro The core idea of V3 is that i18n can be simply imported from @lingui/core. I hope it hasn't changed again, @tricoder42's mind is kinda volatile πŸ™Š

@tricoder42 speaking of SSR: I think it would be useful to add a warning here:
https://github.com/lingui/js-lingui/blob/da35f25f6927f30d2ac3aa58e7a9df9464346ca4/packages/react/src/I18nProvider.tsx#L61
, in case there's no locale set. I've spent some time trying to figure out why there's no SSR code generated, turned out it's because of async nature of i18n object.

In V2, it was possible to use a default value in the Trans component with id. It wasn't overriding it. For example:

<Trans id="homepage.banner.description">
       This is a placeholder for translation
</Trans

Now in V3, the id generated and overridden. The above example is rendered as below:

<Trans id="This is a placeholder for translation">
</Trans

Is there any way to not overriding the id if it exists?

@meysamizadmehr I think this deserves a separate issue, this thread should stay focused on v3 release.

@tricoder42 any news on when v3 could be ready so that the project becomes "more friendly to contributing" (as you said in https://github.com/lingui/js-lingui/issues/334#issuecomment-539018284) Thanks!

the two links (documentation and migration guide) are 404ing

@tricoder42 Please check the SSG improvements RFC of https://github.com/zeit/next.js/issues/9524

is there an ETA on a final v3 release @tricoder42, or has been abandoned?

There's no ETA unfortunatelly, but I'm working on it these days during lockdown... I'm trying to figure out what's the current status. I'm working on a Next.js example app, which I want to use to test SSR and SSG. I'll post few updates once I get another pre-release and I'll try to figure out how to split the workload.

@tricoder42 I can imagine it's hard to work on that, you are probably not that motivated anymore, but to be honest we keep hearing from you pretty much the same on the loop. Just promises :/

I am currently standing in front of a new project that I know will require i18n on a rather big scale and even though I love the simplicity of Lingui and what features are planned for it, I will probably have to look for another solution. I don't want to vendor-lock myself and regret the decision after 1 year when you finally declare a project as a dead weight.

It would be nice if you can provide some practical roadmap. I think it would help you as well when you set some _deadlines_ for yourself. Put down some list of what exactly is missing from rolling out V3 out. It doesn't need to be a perfect version. Just stabilize it enough to be usable. It's not that big deal if after releasing you realize you need to do another breaking change. An increasing major version is just a number. Even if you end up with v10 in the end, it's perfectly fine.

I need to revert to previous pre-release, because this one is unfortunatelly deadend. i18n instance can't be global. Maybe we'll find a better way in the future...

  1. Revert macros to previous format, i.e. i18n._(t...)
  2. Create working example of Next.js project. Doesn't have to be perfect, just make sure it can work with proposed API. I wasn't able to get it working with global i18n instance for example.

Once this is done and API is sealed, we can proceed to upgrade docs. Anyone has an idea how to improve tutorial? What should be add to quickstart guide to onboard new users quickly? Feel free to share.

Another nice to have task is: Move file extractors in cli to separate packages, e.g. @lingui/cli-extract-babel, @lingui/cli-extract-typescript. I'm not going to do @lingui/cli-extract-typescript because I believe most people use TS with Babel anyway.

This is a nice small and independent task, so if anyone wants to jump in, feel free to ask for details.

Can you elaborate more on why this change? Why cannot it be global? Also, tutorials/examples sound like something that can be solved after the V3 release. I can imagine more people have kinda moved away from Lingui already (I am kinda thinking about it too πŸ™Š) due to this uncertainty.

For "nice to have" tasks you should perhaps open issues with detail for each one so it's not just in your head and other people can help when you have stabilized essentials.

I think it's very important to get all ideas out of your head and into separate issues to avoid this blank period in the future where nobody can help.

Hey everyone, few updates:

  • i18n instance remains global. I managed to get Next.js app working. It's not perfect, but good enough for now. It's n examples/ directory if anyone were intesrested.
  • The only macro which returns a message descriptor instead of translation is defineMessage. It has the same functionality as i18nMark in Lingui v2 - to mark strings for translation, but don't translate them immediately (i.e. lazy translations)

One thing I've noticed while working on Next.js example: I'm gonna probably split localeData and messages. Right now lingui compile generates JS file which contains both and i18n.load(locale, catalog) expects catalog to contain object { localeData, messages }. This is problematic if you want to rehydrate i18n instance from SSR or load messages gradually (localeData contains plural function -> can't be serialized for rehydration and when you loading messages gradually, you only need localeData once).

So to sum up:

  1. I'm gonna probably re-export plurals to @lingui/core/data/[locale] which anyone can import in their project depending on locales they use. This might be optimized in the future.

  2. lingui compile will create a plain JSON with id -> translation mapping. This will be always a plain JSON no matter what catalog format you use to store translations (e.g PO)

  3. API of i18n.load will change. There's gonna be probably two methods, i18n.load(locale, messages) and i18n.loadLocaleData(locale, data)

 

  1. and 3. are breaking changes, so I want them finish before the release.

Latest pre-release is 3.0.0-9, there're working examples of plain JS project, create-react-app and Next.js projects. Documentation isn't updated.

Wonderful news. Thanks @tricoder42!

Hello and happy Easter holiday if you celebrate it 🐣

  • New pre-release: 3.0.0-10
  • New task if you want to get involved #634 (this should be easy-moderately difficult)
  • Configuration reference was reviewed and updated. They should reflect config in latest pre-release. If you're native english speaker or english level 9000, feel free to review/proofread this section and send PR with edits.
  • Link to documentation in first comment were fixed

The biggest change is #631, localeData were removed from compiled catalogs and must be loaded separately. The reason is that localeData only needs to be loaded once, while it should be possible to load message catalogs gradually. Also, localeData contains a plural function, which can't be serialized in Next.js and prevents re-hydratation of i18n object. While I haven't implementing better SSR support, the API now supports it and it can be implemented and documented later. Checkout examples for more info (link in the first comment).

Also if anyone has a spare time for coding, #406 implements a new feature. The PR is finished, with updated docs. It just requires a rebase against next branch and copy/migrate changes.

So I see that localeData is not exported from @lingui/core. In examples you are importing make-plural module. I wonder why is it as a dependency of the core if it's not used there? I see it more like a recommendation since custom implementation is possible.

I wonder how to approach the dynamic loading of make-plural to have it tree shaken. Probably best would be to have a generated file per locale which would just do export { en as plurals } from 'make-plural/plurals'. The best would be to include that in @lingui/core for sure. I would be willing to work on PR for that.

Btw, In examples the yarn.lock contains http://0.0.0.0:4873 as a resolution address. I assume it's some private registry? Probably best not to commit yarn.lock in these folders if it's problem getting rid of it.

Anyway, the CRA example is broken for me and the same for my app (#642). I will dive into it since it's kinda blocker for me right now.

make-plural package has a CLI, which you can use to create your own bundle of locale data. I though about porting it to Lingui, but it's not a breaking change, just an improvement, so it can be implemented anytime later. As you say, make-plural is just one library. You can use any other which provides plural rules from CLDR data.

If you with, you could make a command locale-data, which generates project specific bundle of locale data based on locales config. It could be a new command, because it needs to be run only when locales changes.

The only reason why I don't want to use make-plural CLI directly is that it returns plural functions named as locales, i.e. const en = function (index) { ...english plural function }. I would like to keep locale data in format type LocaleData = { plurals: (index: number) => string }, for example const en = { plurals: function (index) { ... english plural function } } so the data are easily extended in the future e.g. by common date formats.

Btw, In examples the yarn.lock contains http://0.0.0.0:4873 as a resolution address. I assume it's some private registry? Probably best not to commit yarn.lock in these folders if it's problem getting rid of it.

That's a mistake obviously. I use verdaccio for local testing, as described in contributing docs.

Well, I don't think we need to add the command to generate those into a project. I am thinking about approach of providing default locale data pre-generated as a set of files inside the core package. Each file would export data for a single locale in a format you've described. The benefit is that such an approach can be tree-shaken and dynamically imported.

Btw, what about ordinals/cardinals from that package? How come we don't need those?

Plurals contains both ordinals/cardinals.

I though about re-exporting individual packages as well, but again: it's not a breaking change, just an improvement. It can wait after v3 release. Feel free to tackle it if you have spare time. @lingui/core/locales/{code} would be good place, but you gonna need to change the build script as well.

Plurals contains both ordinals/cardinals.

Are you sure about that? When I look at the plural definition for en it's much different than ordinals or cardinals. Or do you mean pluginCategories? In the example you are importing plurals not pluralCategories.

In plurals, second argument is ord which tells the function whether you want cardinal or ordinal plural form. It’s two functions merged into one. I’ve just checked english plural functions and it holds.
On 11 Apr 2020, 22:33 +0200, Daniel K. notifications@github.com, wrote:

Plurals contains both ordinals/cardinals.
Are you sure about that? When I look at the plural definition for en it's much different than ordinals or cardinals. Or do you mean pluginCategories? In the example you are importing plurals not pluralCategories.
β€”
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.

Also if anyone has a spare time for coding, #406 implements a new feature. The PR is finished, with updated docs. It just requires a rebase against next branch and copy/migrate changes.

I'll take a stab at this.

@tricoder42 I keep wondering if i18n is meant to be global, why is there a hard dependency on using I18nProvider? I mean using <Trans> fails because useLingui throws. It would be much nicer if it could just use i18n from a core in case there is nothing in the context. I can do PR if you agree.

I mean using fails because useLingui throws

What do you mean? In what cases <Trans> fails?

i18n object may be global, but it doesn't have to be. You can configure macros to use your own instance using runtimeConfigModule. If we hardcode it to Trans, we loose this ability.

Using context is also required when you want to listen for changes in catalog/active locale.

If you figure out how to implement your changes and without breaking any examples, I'll all for it. I just still don't understand why is I18nProvider problem.

Consider the simplest of cases for a small app where I don't need multiple catalogs, no listening for changes. I simply want to import Trans and use it with default i18n instance exported from core.

image

I am merely suggesting that instead of useLingui throwing, it could use that exported i18n instance directly. If you think that's a breaking change and you want some warning, it could be configurable. However, I feel it's the most straightforward way of using Lingui without any hassle. If someone needs to customize it, it will still work just fine.

I'm just afraid this is going to be very difficult to explain and document.

Default approach:

  • Don't use I18nProvider
  • Don't use useLingui
  • Always import i18n instance directly from @lingui/core
  • When new catalogs are loaded or locale has chaged, force page refresh

Advanced approach (custom i18n instance, switch locale without refresh):

  • Use I18nProvider
  • Use useLingui when you need to access i18n instance
  • Import i18n instance only once for I18nProvider

Page refresh only makes sense in browser environment. In SSR, you would have to 1. trigger loading of messages in active locale, 2. postpone rendering until all messages and locales are loaded

I would like to have one and preferably only one obvious way how to do it, but this might be worth exploring. Important question right now is - Does it require any breaking changes in API or can it wait until v3? Even if we decide to return default i18n instance from useLingui when context isn't defined, that isn't a breaking change.

You make the default approach sound too difficult with those negatives there :) On the contrary, it would be implicit behavior not to use those things and just import i18n.

But you are right it's not a breaking change. I will open a new issue with thoughts and we can get back to it when V3 is stable.

I wasn't really following along, but what "page refresh" by default? Why would anybody want that in their JS app?

@stovmascript Switching locale without page refresh requires a little bit more complex setup. Even react-intl simply re-renders the whole app (without refresh) when locale changes.

On the other hand, this isn't very common user interaction. In 95% cases the correct locale should be detected. If it's not, you switch it, it's stored in cookies/localStorage and from that point you won't touch it again. This isn't a common interaction, which would require "smooth" UX (i.e. without page refresh).

By removing this requirement, you can simplify your app a lot and it also opens door for future optimizations (i.e per-locale bundles #508)

@tricoder42 Sorry, last time I used react-intl was v2.3.0 and I don't even remember there being an option. Yeah, setup might be a little hurdle, but it's something you do once and then maybe some maintenance as you upgrade the lib. Nothing really changes throughout the app.

About it not being a common interaction, from my experience, the need to reload a JS app usually means there is some other problem elsewhere. We currently also have this behaviour in our production app and it annoys the ** out of me. It's a loose end that just introduces a whole slew of weird conditions you have to think about.

@stovmascript It's a big difference if your app reloads because of bug and when it reloads after a specific user action (language change) that is under your control. If you fear that the user won't understand reload, you can always explain in before that action happens.

@tricoder42 consider in v3 that one of the advantages of having global i18n was that of not importing it every time when use the macro t

@FredyC You've made my point about the loose end - you have to do more work to explain it to your users. Also as @tricoder42 noted, it's not isomorphic and the list will continue to grow as your app gets more complex.

I just don't think it's that big of a deal to configure your app to work without having to reload. If it seems it's not possible, then there's most likely some other issue in the app that needs to be dealt with.

@stovmascript You don't have to explain, most users won't care that refresh happens. Unless it would reset form or something for them, that would be bad. Considering they will literally do it once per device if auto-detection fails, it's not such a problem.

You may think it's easy to make a dynamic switch of locale, but I tried in the past and there are so many small things you need to consider that it's not worth it. Especially if you want to use other libs to eg. format dates or numbers (when Intl is not enough). So many times I've run into a random issue where something was correctly translated and something else wasn't and it was a mess.

Also, it's not that uncommon to present a user with an initial screen to pick a language, store that value and it's done. That's an approach without any refresh because you can load data after this initial selection happens. Giving an option to change the language after is just hassle that 1% of users will care about.

In my webapp I already have this feature (switching language/locale without page refresh or application re-render) I spent some time to achieve it with linguijs. I don't expect to lose a feature after upgrading a library to a new version.

@sassanh Yea exactly, it's not easy to set up and I am sure if you would run some numbers you would realize it was a pure waste of time :) Nonetheless, nobody is saying it won't be possible to have dynamic locale switching further on. We are just talking here that it's generally easier not to bother about it and how it can simplify the use of the library if the developer decides to follow that path.

Nonetheless, nobody is saying it won't be possible to have dynamic locale switching further on.

I'm glad nobody is saying that because dropping a feature in the new version of a library without going with deprecation cycle is a strong signal for many developers to migrate from it. A good library shouldn't be opinionated about how it is used and I'm glad it's the case for lingui. There are cases in which switching language is not something that happens only once per device.

There are cases in which switching language is not something that happens only once per device.

Can you elaborate on that? I am curious. Are you talking about enterprise software? Not sure they would actually use an open-source library maintained by one person (no offense).

Just to sum up, in v3 you'be able to:

  • switch locale without refresh using combination of I18nProvider and useLingui
  • switch locale with forced refresh which allows you to drop I18nProvider and useLingui completely.

Please keep the dicussion focused on features which might require breaking change of API or missing pieces. Everything else might be discussed and released later.

Just to sum up, in v3 you'be able to:
[...]

@tricoder42 what about the macro t?
you confirm that in v3, with i18n global, there is no more need to write:

i18n._(t`text to translate, ${var}`);

but just

t`text to translate, ${var}`

to have the translated text

@bitlab-code That is correct, it works just fine, I am using it with the latest version.

Hey everyone, I've updated roadmap in first comment ☝️

Once all breaking changes are implemented we can ship RC and work on documentation, bug fixes and final polishing. There isn't much stuff left, so we could see this land in ~2 weeks 🀞

Thanks for a great job! Really waiting for this to be released. I'm working on one project now and we are about to switch to from custom localization solution to some proper one. I want to proceed with lingui, but need v3 changes to convince others. Is there anything I can help you with ? Or some good first contribution issues?

@kirillku There is a list in the first comment and also some open issues with Help wanted label.

@kirillku It would be really helpful if you try to follow migration guide and update your project. I've just updated this document, so it should reflect latest changes. It definitely needs a review and proofreading. That would help me a lot πŸ‘

If you want to get more involved, there're few issues where you either need to migrate existing feature to v3 or rebase finish PR agains latest changes in next and resolve conflicts (e.g. #569, #634) . I would say they're good first issues.

Hey everyone, new pre-release 3.0.0-11 was just published to NPM.

Unfortunately I didn't have much time last week, but there're several merged PRs - compatability with Windows #643, improved typings #651 and other minor fixes. Lot of PRs are ready, just waiting for review. More improvements hopefully next week πŸ‘

With regards to that "refresh" requirement. I hope this is not a hard page refresh. We use @lingui in react-native, and the concept of a page refresh doesn't exist there. Personally, I wouldn't mind a full app rerender for a language change.

Hi!

Today I upgraded our app to 3.0.

I have some minor feedback on the migration guide which I will detail once I've completed the migration.

I have a question regarding the next.js example. How do I best detect the language and load the catalog on the server?


in v2 I was using a (pretty complicated) approach like this:

import * as React from 'react';
import Head from 'next/head';
import { NextPage } from 'next';
import { getDataFromTree } from '@apollo/react-ssr';
import { I18nProvider } from '@lingui/react';
import { client } from 'f';

type Options = {
  defaultLanguage?: string;
  ssr?: boolean;
  render?: (props: any) => React.ReactElement;
  getDataFromTree?: any;
};

const withLang = (options: Options = { defaultLanguage: 'en', ssr: false }) => (
  Page: NextPage<any>,
  pageOptions: Options = { defaultLanguage: 'en' },
): any => {
  const { getInitialProps } = Page;
  const render = pageOptions.render || options.render;
  function WithLanguage({ language, catalogs, ...props }) {
    if (render) {
      return render({
        Page,
        props,
      });
    }

    return (
      <I18nProvider language={language} catalogs={catalogs}>
        <Page {...props} />
      </I18nProvider>
    );
  }
  if (getInitialProps || options.ssr) {
    WithLanguage.getInitialProps = async pageCtx => {
      const ctx = 'Component' in pageCtx ? pageCtx.ctx : pageCtx;
      const { AppTree } = pageCtx;
      const headers = ctx?.req?.headers;
      const languageFromQuery = ctx?.query?.language;
      const languageFromHeaders =
        (headers && headers['accept-language']) || 'en';
      let language =
        languageFromQuery ||
        languageFromHeaders ||
        options.defaultLanguage ||
        'en';

      if (language.toLowerCase().match(/en-us/)) {
        language = 'en';
      }

      const catalog = await import(
        `../../__generated__/locales/${language}/messages.po`
      )
        .then(m => m.default)
        .catch(() =>
          import(
            `../../__generated__/locales/${options.defaultLanguage ||
              'en'}/messages.po`
          ).then(m => m.default),
        );

      let pageProps = {};

      if (getInitialProps) {
        pageProps = await getInitialProps(pageCtx);
      }

      if (!client) {
        if (ctx.res && (ctx.res.headersSent || ctx.res.finished)) {
          return pageProps;
        }

        if (options.ssr) {
          try {
            const props = { ...pageProps, catalog, language };
            const appTreeProps =
              'Component' in pageCtx ? props : { pageProps: props };
            await getDataFromTree(<AppTree {...appTreeProps} />);
          } catch (error) {
            if (process.env.NODE_ENV !== 'production') {
              console.error('Internationalization error', error);
            }
          }
        }
        Head.rewind();
      }

      return {
        ...pageProps,
        language,
        catalogs: {
          [String(language)]: catalog,
        },
      };
    };
  }
  return WithLanguage;
};

export default withLang;

I also always see this message on the server:

Messages for locale "en-PL" not loaded.
Locale data for locale "en-PL" not loaded. Plurals won't work correctly

But plurals do work correctly. Confusing.

I tried following the next.js example in the next branch and got it working on the client side, but how do I approach SSR?

Thanks! Great work on v3, we're really excited to get it into production on TED.com.

I have one question about something I didn't see mentioned in the migration guide: from what I gather, v2 supported message IDs with t:

t(`foo.new`) `Create something`

However this doesn't seem to be the case anymore (the types seem to prevent it, and the code doesn't seem to support it anymore). Are IDs discouraged?

@arcanis AFAIK for the custom ID you have to use defineMessage or there was some way with magic comments for the t but I am not exactly sure how that looks like.

@mschipperheyn

With regards to that "refresh" requirement. I hope this is not a hard page refresh. We use @lingui in react-native, and the concept of a page refresh doesn't exist there. Personally, I wouldn't mind a full app rerender for a language change.

As it was said, Lingui 3.0 won't prevent changing locale in runtime, it's just a bit more involved. I think that for a native app the equivalent of "page refresh" is a restart of the app. It may sound nasty, but then again it's not expected the user would be changing language every day :)

[...] for a native app the equivalent of "page refresh" is a restart of the app. It may sound nasty, but then again it's not expected the user would be changing language every day :)

I haven't tried the beta yet, but if you really need to restart the app to change the translation it seems an almost ridiculous solution

[edit]
I just read Migration guide from 2.x to 3.x, if it's up to date it seem you not need to restart the app

@bitlab-code For the last time, there is NO LIMITATION from the Lingui side that would force you to reload/restart the app. It's merely about that it's usually not worth the developer effort to figure out all edge cases for "dynamic locale change". I was on this boat in the past too, but @tricoder42 helped me to realize (#508) it's unlikely to hinder user experience.

I find it almost too ridiculous when a site shows language selection in some prominent place (especially with country flags - offensive). It's a clutter that 99% of users don't care about because the auto-detection of language worked for them.

@VinSpee Thank you for you report!

I have a question regarding the next.js example. How do I best detect the language and load the catalog on the server?

That's not included in LinguiJS right now, but I would love to see that. There's an open issue if you had a capacity to tackle it (#577). An existing tool is also mentioned in that issue.

I tried following the next.js example in the next branch and got it working on the client side, but how do I approach SSR?

What do you mean? Preloading catalog on server and passing them to client? I haven't figured out this part yet, but it should be possible with current API.

@tricoder42 I think I've found a minimally viable implementation of, as you aptly described:

Preloading catalog on server and passing them to client

see below:

import * as React from 'react';
import { I18nProvider, useLingui } from '@lingui/react';
import * as plurals from 'make-plural/plurals';
import { setupI18n } from '@lingui/core';
import Head from 'next/head';

const i18n = setupI18n();

export async function activate(locale: string): Promise<void> {
  try {
    const { messages } = (
      await import(`../../__generated__/locales/${locale}/messages.js`)
    ).default;
    i18n.load(locale, messages);
    i18n.activate(locale);
  } catch (err) {
    throw new Error(err);
  }
}

function I18nWatchLocale({ children }): React.ReactElement {
  const { i18n: currentI18n } = useLingui();

  // Skip render when locale isn't loaded
  if (!currentI18n.locale) return null;

  // Force re-render when locale changes. Otherwise string translations (e.g.
  // t`Macro`) won't be updated.
  return <React.Fragment key={i18n.locale}>{children}</React.Fragment>;
}

type Options = {
  defaultLanguage?: string;
  ssr?: boolean;
  render?: (props: any) => React.ReactElement;
};

const withLang = (options: Options = { defaultLanguage: 'en', ssr: false }) => (
  Page: any,
  pageOptions: Options = { defaultLanguage: 'en' },
): any => {
  i18n.loadLocaleData(options.defaultLanguage, {
    plurals: plurals[options.defaultLanguage],
  });
  const { getInitialProps } = Page;
  const render = pageOptions.render || options.render;
  function WithLanguage({ language, ...props }): React.ReactElement {
    React.useEffect(() => {
      activate(language);
    }, [language]);

    if (render) {
      return render({
        Page,
        props,
      });
    }

    return (
      <I18nProvider i18n={i18n}>
        <I18nWatchLocale>
          <Page {...props} />
        </I18nWatchLocale>
      </I18nProvider>
    );
  }
  if (getInitialProps || options.ssr) {
    WithLanguage.getInitialProps = async function getInitialPropsWithLang(
      pageCtx,
    ): Promise<typeof pageProps & { language?: string }> {
      const ctx = 'Component' in pageCtx ? pageCtx.ctx : pageCtx;
      const headers = ctx?.req?.headers;
      const languageFromQuery = ctx?.query?.language;
      const languageFromHeaders =
        (headers && headers['accept-language']) || 'en';
      let language =
        languageFromQuery ||
        languageFromHeaders ||
        options.defaultLanguage;

      activate(language);

      let pageProps = {};

      if (getInitialProps) {
        pageProps = await getInitialProps(pageCtx);
      }

      if (!process.browser) {
        if (ctx.res && (ctx.res.headersSent || ctx.res.finished)) {
          return {
            ...pageProps,
            language,
          };
        }

        Head.rewind();
      }

      return {
        ...pageProps,
        language,
      };
    };
  }
  return WithLanguage;
};

export default withLang;

happy to move this to another issue or PR if it looks relevant to your vision.

@VinSpee Looks pretty good πŸ‘ Just a small nitpick - could you please replace language with locale so it's consistent with the library?

Thank you for working on this πŸ™

@mschipperheyn

With regards to that "refresh" requirement. I hope this is not a hard page refresh. We use @lingui in react-native, and the concept of a page refresh doesn't exist there. Personally, I wouldn't mind a full app rerender for a language change.

As it was said, Lingui 3.0 won't prevent changing locale in runtime, it's just a bit more involved. I think that for a native app the equivalent of "page refresh" is a restart of the app. It may sound nasty, but then again it's not expected the user would be changing language every day :)

@FredyC I would say that restarting an app would be totally unworkable and an unacceptable hack. This should not be even considered as a scenario for a shipping product (at least, not the only one). Phones save state,etc. There's no way to explain to a user to do that cleanly and be guaranteed of a good result. Rerendering the app from a provider down is another matter. Anything that can be done using code works for me.

@mschipperheyn Seriously? You're still reviving this topic even it was said many times that v3 will support hard/soft change of locales? You're picking scenarios which support your thesis but omiting cases from other side of spectrum, where hard refresh is acceptable. Try changing locale on Wikipedia. Open your phone and change the default locale. We could continue, but the bottom line is:

Lingui v3 allows reloading locales without page refresh or restarting the app

Well, @FredyC emphasized that this scenario would be the one. And if it's not the applicable scenario I'll take it back and be happy.

I totally understand @mschipperheyn there's a confusion here: The message that linguiJS v3 will allow changing locale without refreshing the app is always mixed with the message that "something is wrong with you if you have this requirement in your app, and you better reload your app because of this and that".

I think it is pointless and doesn't make sense to tell others how they should manage their requirements, so let's stop it and just stick with this clear message: "LinguiJS v3 will allow changing locale without refreshing the application in case you need it period"

I'd like to second @arcanis concern about t macro not supporting message id anymore.
Does this mean we should use the full form from core like

i18n._("msg.id", { name: "Tom" }, { message: "My name is {name}" })

?

Edit: I found out this way of defining messages is not not being processed during the extract phase :(

Not really, you can use defineMessage macro:

const msg = defineMessage({
  id: "msg.id",
  message: `My name is ${name}`,
  comment: "My name label in profile header"
})

i18n._(msg)

I understand it's a more verbose as you had to pass the message descriptor to i18n._.

I guess we could extend the t macro that it would either stand as a template tag or it would be a function accepting object (as defineMessage does)?

// Autogenerated ID

t`My name is ${name}`
// ↓ ↓ ↓ ↓ ↓ ↓
// i18n._("My name is {name}", { name })

// Manual ID with metadata 

t({
  id: "msg.id",
  message: `My name is ${name}`,
  comment: "My name label in profile header"
})
// ↓ ↓ ↓ ↓ ↓ ↓
// i18n._("msg.id", { name }, { message: "My name is {name}", comment: "My name label in profile header" })

The message attribute is processed as it would be a t macro, so you can actually use other macros like plural:

t({
  id: "msg.plural",
  message: plural(count, { one: "Book", other: "Books" }),
})
// ↓ ↓ ↓ ↓ ↓ ↓
// i18n._("msg.plural", { count }, { message: "{count, plural, one {Book} other {Books}}" })

There would be only these two invocations, nothing in between to keep it simple. Also, this format would be possible only in the top most t macro (you shouldn't be able to add any metadata to nested messages). We would keep defineMessage for lazy translations.

What do you think? Second question, would you like to send the PR? The implementation is already done in defineMessage macro. You only need to check for objectExpression in t macro.

Yea, this has been my concern as well. I run into it when I was attempting to work on docs update where I saw the old syntax and I wasn't able to find the answer how it works now so I halted my work on that.

The extension to t macro would definitely make sense. Alternatively what about even less verbose version?

t(`My name is ${name}`, { id: "msg.plural", comment: "My name label in profile header" })

The same could be applied to defineMessage I suppose.

How it would look like with plurals?

Nothing surprising there? Point is to avoid repeating message and make it implicit argument.

t(
  plural(count, { one: "Book", other: "Books" }),
  { id: "msg.plural" }
)

I think it's an interesting approach because you can then opt to use the t(message) syntax everywhere and it wouldn't require changing it too much if you want to add id or comment later.

Yeah, I like how it's similar to t(message), I just don't find the verbose variant as readable as the objectExpression variant:

t(
  plural(count, { one: "Book", other: "Books" }),
  { id: "msg.plural" }
)
// vs
t({
  id: "msg.plural",
  message: plural(count, { one: "Book", other: "Books" }),
})

Edit: But that's just my opinion, of course :) YMMV

@tricoder42 missed the defineMessage. Thanks!

What about keeping all three variants?

t`message`
t(message)
t({ message })

Although, it can mean people start questioning when to use which approach and things might be rather wild on bigger teams without rules set in the stone.

What about keeping all three variants?

Yeah, that's what I would like to avoid. There should be one-- and preferably only one --obvious way to do it.

It's nice to be zen, but good DX is sometimes more important. I certainly wouldn't like to declare verbose t({ message }) everywhere. It's a clutter that makes code less readable.

On the other hand current defineMessage & i18n._ is a clutter of its own. I mean, why is the i18n call necessary? Isn't it possible in macro to see the usage of variable returned from defineMessage and wrap it automatically to i18n._?

@tricoder42 another thing i find hard when working with the new *.po files and message ids is not having the default (source lang) string in the comments.
Native(legacy?) lingui format was very nice in this regard

"airline.allowance.businessServiceClass": {
  "translation": "",
  "defaults": "Business"
},

You can see the source string right there and you don't have to jump back and forth to the source lang file, searching for the message id, figuring out the original message to translate it.

It would be really nice to have the original string from defaults in *.po files in the comments above the msgid and msgstr.

It's nice to be zen, but good DX is sometimes more important. I certainly wouldn't like to declare verbose t({ message }) everywhere. It's a clutter that makes code less readable.

I don't think that duplicating API would improve DX. It might be easier now, because we can't decide which one is better, but down the line it'll bring just confusion and problems when moving between codebases (or when introducing other team members to existing codebase).

On the other hand current defineMessage & i18n._ is a clutter of its own. I mean, why is the i18n call necessary? Isn't it possible in macro to see the usage of variable returned from defineMessage and wrap it automatically to i18n._?

Macro should control only the AST node it wraps. Although you can take a look above, I'm not sure you would catch all usecases. On top of that, I don't know how it solve the situation with t(message) vs t({ message }).

defineMessage deals with lazy messages. We might improve it (i.e. add i18n to closure), but it's still a separate problem.


One more point to the discussion t(message) vs t({ message }): We could easily add support to t({ message }) with minimal effort (just copy it from defineMessages). t(message) would require more effort.

defineMessage deals with lazy messages. We might improve it (i.e. add i18n to closure), but it's still a separate problem.

I don't follow. Right above you have declared defineMessage as the only (sane) way to add ID to message. That's not about lazy messages :) If it's possible to avoid i18n._ call then it's a good solution too without hindering the use of t'message' when someone doesn't need to use IDs.

Alternatively, if you want to inline variant of defineMessage, what about something like tx({ message }) or any different name so it's clear it does work differently than regular t for simple messages.

I don't follow. Right above you have declared defineMessage as the only (sane) way to add ID to message. That's not about lazy messages :)

It is. Right now the only (sane) way to add ID to message is defineMessage which doesn't perform translation, but rather return a message descriptor. You need to pass it to i18n._ call to get final translation.

I though that defineMessage is clear enough that you're just defining, not translating the message, but I'm open to any suggestion. It's similar name which is used in react-intl I guess.

Can we have t`message` and t(id, message)? I feel it's important to have first class support for ids. I'm not sure about statistics, but I feel ids are used even more widely.

Or maybe have something like tId(id, message).

I used to explore these two variations earlier, but the problem is that you still need a way to add at least comments.

Also, there should be a way to define ids for other macros as well, e.g. plurals.

Moving further into v3 without DateFormat and trying to import date from core.

VSCode says that

Module '"../../../../../node_modules/@lingui/core/cjs"' has no exported member 'date'.ts(2305)

Any hints on formatting dates in v3? :)

here's what I've been doing @gothy

export default function PriorityInfo({
  video,
}: {
  video: Partial<Video>;
}): React.ReactElement {
  const { i18n } = useLingui();
  return (
    <View>
      <Text variant="-2u">
        <Trans>
          {i18n.date(video.recordedOn, {
            month: 'long',
            year: 'numeric',
          })}
        </Trans>
      </Text>
    </View>
  );
}

@VinSpee got it. Thanks!

@gothy @VinSpee You don't need to wrap a single variable into <Trans> tag:

export default function PriorityInfo({
  video,
}: {
  video: Partial<Video>;
}): React.ReactElement {
  const { i18n } = useLingui();
  return (
    <View>
      <Text variant="-2u">
-       <Trans>
          {i18n.date(video.recordedOn, {
            month: 'long',
            year: 'numeric',
          })}
-       </Trans>
      </Text>
    </View>
  );
}

Originally, DateFormat, NumberFormat, i18n.date and i18n.number were transformed into ICU formatters, e.g. Today is {now, date}. I've found it unnecessary overhead and since I didn't have enough time to migrate it, I omit it in favor of simple function call.

<Trans>
  Today is <DateFormat value={now} />
</Trans>

becomes

<Trans>
   Today is {i18n.date(now)}
</Trans>

which is transformed into Today is {now} (instead of former Today is {now, date}).

date and number macros were removed in favor of i18n.date and i18n.number:

// before
t`Today is {date(now)}`

// after
t`Today is {i18n.date(now)}`

Hey everyone, sorry for (yet another) delay. I've just finished another project so in next few weeks I'll focus on LinguiJS again. I'm just collecting feedback from issues and PRs from last months, so if you had any major problem with pre-release, please let me know.

@tricoder42 another issue i've stumbled upon.
If you've an id for your translation and change the source lang string in the code, it doesn't get updated in the /locale/<source lang>/messages.po file after extract.

Edit: I've made a separate issue of it as @FredyC suggested https://github.com/lingui/js-lingui/issues/688

@gothy It would be probably best to set up some minimal reproduction. I haven't noticed any similar issues myself. Maybe even open a separate issue so it's not lost in here.

this issue has become extremely long, I'd perhaps suggest to lock the discussion and instruct folks to open new issues instead of posting here. This thread could then be used to post important news regarding v3 from maintainers.

This is a project that I'd like to start using, and I'd like to help along the v3 release so that we can start fresh with a stable API. Where is help most needed? I'm comfortable working on both code and documentation.

@tstirrat15 Check the first comment on the list of what's missing.

Hey everyone, new prerelease (3.0.0-13) has just been published to NPM.

@tstirrat15 Thanks for the help! Documentation would be really helpful as the docs are outdated which makes testing v3 really hard. Configuration reference is the most accurate, but it still requires a review and proofreading. After that, if you go one by one from reference docs to tutorial, it would be really helpful. Feel free to reach me anytime if you need clarify anything.

@tricoder42 When I try to migrate from v2.9.1 to v3.0.0-13 based on this guide the app breaks and there is no setupI18n exported from @lingui/react, seems like the guide is outdated
https://lingui.js.org/releases/migration-3.html#removed-i18nprovider-declarative-api
import { setupI18n, I18nProvider } from '@lingui/react'
Is there any place where I can see the updated changelog/migration guide to v3?

@tienpham94 Try this one https://js-lingui-git-next.lingui-js.now.sh/releases/migration-3.html (it's in the first post in this thread)

Thank you @tricoder42 , I have another issue with Plural from '@lingui/macro' with lingui v3.0.0-13,
It throws Uncaught TypeError: plurals is not a function when I try to use Plural

<Plural 
value={messagesCount} 
one="There is # comment"
other="There are # comments"
/>

image

@tienpham94 Have a look at the "next" branch, there are examples for different frameworks that work.

@tricoder42 I opened a bug https://github.com/lingui/js-lingui/issues/705 and a demo with the examples/create-react-app template https://github.com/lingui/js-lingui/pull/704 for the issue with <Plural> and plural macro from '@lingui/macro' with lingui v3.0.0-13/next
It crashes the app and show error plurals is not a function when I try to use Plural and plural macro
I added localeData manually, as in bug demo https://github.com/lingui/js-lingui/pull/704

import {i18n} from '@lingui/core';
import { en, ru } from 'make-plural/plurals'
i18n.loadLocaleData('en', { plurals: en })
i18n.loadLocaleData('ru', { plurals: ru })

Could you please check when have time?
image

Hi, thank you for your work. I'm working on a test migration from 2.X to 3.X and the enzyme test examples doesn't work anymore, since I can't new I18nProvider(). Is there any direction I could go with those?

This is the current code

import React from 'react';
import { shape, object } from 'prop-types';
import { mount, shallow } from 'enzyme';
import { I18nProvider } from '@lingui/react';

// Create the I18nProvider to retrieve context for wrapping around.
const language = 'en-US';
const intlProvider = new I18nProvider({
  language,
  catalogs: {
    [language]: {},
  },
}, {});

const {
  linguiPublisher: {
    i18n: originalI18n,
  },
} = intlProvider.getChildContext();

// You customize the i18n object here:
const i18n = {
  ...originalI18n,
  _: key => key, // provide _ macro, for just passing down the key
};

/**
 * When using Lingui `withI18n` on components, props.i18n is required.
 */
function nodeWithI18nProp(node) {
  return React.cloneElement(node, { i18n });
}

/*
 * Methods to use
 */
export function shallowWithIntl(node, { context } = {}) {
  return shallow(
    nodeWithI18nProp(node),
    {
      context: Object.assign({}, context, { i18n }),
    },
  );
}

export function mountWithIntl(node, { context, childContextTypes } = {}) {
  const newContext = Object.assign({}, context, { linguiPublisher: { i18n } });
  /*
   * I18nProvider sets the linguiPublisher in the context for withI18n to get
   * the i18n object from.
   */
  const newChildContextTypes = Object.assign({},
    {
      linguiPublisher: shape({
        i18n: object.isRequired,
      }).isRequired,
    },
    childContextTypes);
  return mount(
    nodeWithI18nProp(node),
    {
      context: newContext,
      childContextTypes: newChildContextTypes,
    },
  );
}

Hi,

I want to add comments to the messages. I know, that I have to use defineMessage macro for that.

Hope I am wrong on this, but it seems that Lingui by design leaves the comments, meant for the translators, inside the bundle.

const strawberry = defineMessage({
  message: "Strawberry",
  comment: "Strawberries are reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaly delicous"
})

console.log(i18n._(strawberry))

After the transpile step the long comment is still there:

const strawberry = 
/*i18n*/
{
  message: "Strawberry",
  comment: "Strawberries are reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaly delicous"
}

console.log(i18n._(strawberry))

Am I missing something here?

@laszbalo This is a bug. JSX macros correctly remove comments, as you can see here: https://github.com/lingui/js-lingui/blob/next/packages/macro/src/macroJsx.ts#L99-L108 but JS macros don't. The if-clause is missing here https://github.com/lingui/js-lingui/blob/next/packages/macro/src/macroJs.ts#L55-L62

@tricoder42 Thanks for your answer. It's a great relief. I only read the tests for defineMessage, that's why I mistakenly believed that it meant to preserve the comments after transpilation.

Would you recommend a new project to start with the 3.0.0-13 prerelease or the latest 2.x version?

Would you recommend a new project to start with the 3.0.0-13 prerelease or the latest 2.x version?

@ThiefMaster, i'm no authority but I've asked the same question and was recommended to use 3.0.0-13

@ThiefMaster: https://github.com/lingui/js-lingui/issues/707 could be a blocker.

@ThiefMaster Well, at this point I would recommend to wait until the end of October. I'm hoping to finish the release by the end of Hacktoberfest. Either way I'll be looking for new maintainers to gradually take over this project, but if I manage to finish the v3 release there's much higher chance this project will survive in longterm.

That said, I'll take a look at #707 as it really looks like a real blocker.

Either way I'll be looking for new maintainers to gradually take over this project

Do you see there's a risk of the project being abandoned any time soon? I know how hard it can be to find maintainers...

FWIW, I wrote a somewhat similar library some time ago (gettext-based, less powerful integration since there's no babel macro/plugin involved except for string extraction) and I was actually thinking whether it doesn't make more sense to just use Lingui instead since the syntax is actually not too different. But with a chance of ending up unmaintained that might be a bit risky! :/

@ThiefMaster One could argue that this project has been abandoned for the last two years. I don't think it's fair to keep it forever in the state which it is right now β€” having a feature freeze on a master branch but leaving the development branch unfinished in pre-release. I'm just trying to be as transparent as possible about the current state and future plans.

I'm gonna provide support and review PRs even if I don't find maintainers, but I believe this project has much better chance for survival if the v3 is finished and released.

I fully agree! And I think if there are open issues people are much more open to help (both during and outside of Hacktoberfest).

In case we decide to move our main application to lingui and thus discontinue our own library in favor of this one we'd certainly be open to contribute! We are currently trying it out in a smaller side project to see if it works well there :)

Perfect! Already lot of options how to move this project forward! Thanks for bringing up that uneasy but important question.

For me, V3 is already in very good shape. It's true I don't use much of advanced features, but those can be always polished later imo.

@tricoder42 Btw, check that todo for pseudolocale refactor in the first comment, that's a sure win :)

@ThiefMaster Well, at this point I would recommend to wait until the end of October. I'm hoping to finish the release by the end of Hacktoberfest. Either way I'll be looking for new maintainers to gradually take over this project, but if I manage to finish the v3 release there's much higher chance this project will survive in longterm.

That said, I'll take a look at #707 as it really looks like a real blocker.

I just added a comment to #707 with how i worked around the issue presented. Hope it helps. I don't think it is a blocker if one watches the locale changes acts when it happens.

While it did take looking at documentation, code and trying things out. I did reach a point in the POC where I feel Lingui works quite well... I certainly have the best wishes for the project and will contribute if we find issues as we've picked it for our next app. Thanks a lot, it's pretty cool!

Hi there, I'd like to just quickly share my experience with migrating to v3 – I would vouch for keeping the withI18n hook around. Other solutions (such as i18n-next) offer both the hook and the HOC, which is a good compromise IMHO. If withI18n is removed, migration of existing projects with a lot of class components is PITA.

  • The problem is mainly with places where <Trans> cannot be used, i.e. outside component trees

Hey everyone,
new version 3.0.0-18 was just published today. Mostly bug fixes. I also setup a next.js project to play with (in examples) and it seems to work well. I went through open PRs and the top-most issue and I believe there're only few blocking issues:

Some issue can be closed later:

I've added help needed label to issues which could be picked by volunteers. If you'd like to contribute, please check the details inside each issue and let me know if you had any questions.

@sladek-jan Good point. We could add withI18n to make the transition easier. After all, such HOC are just few lines of code. Would you like to add it?

Hey everyone,
new version 3.0.0-18 was just published today. Mostly bug fixes. I also setup a next.js project to play with (in examples) and it seems to work well. I went through open PRs and the top-most issue and I believe there're only few blocking issues:

  • #747
  • Update migration docs

Some issue can be closed later:

  • #748
  • #658

I've added help needed label to issues which could be picked by volunteers. If you'd like to contribute, please check the details inside each issue and let me know if you had any questions.

@sladek-jan Good point. We could add withI18n to make the transition easier. After all, such HOC are just few lines of code. Would you like to add it?

Done the HOC mate, feel free to request if some changes are needed about passing refs like master version... or we just release it as minimal as possible
https://github.com/lingui/js-lingui/pull/756

Hey everyone,
new version 3.0.0-20 was just published now. First of all I would like to thank to @semoal for his incredible support. He did the most work on the latest release cleaning many outstanding issues: #756 #760 #762 #763 #765

I believe we just need to update the documentation and the release is ready to go πŸš€

Getting #770 and #766 (the second one is already merged anyway) in the release would be great :)

Hey everyone,
new version 3.0.0-21 was just published. Mostly small bug fixes, but I wanted to get it out. @semoal just did another incredible feat and updated the docs πŸ‘ They should be deployed by now.

Please take a look and raise an issue if you find any problems.

I don't want to throw a wrench into release plans, this seems somewhat minor (and I'm trying to familiarize myself more with the source / to start working on a patch), but FYI it looks like i18nMark no longer exists in v3. Is there a different mark-type function that should be used?

Edit: is t'label' the recommended alternative? (having markdown issues typing that, but you get the idea)

I don't want to throw a wrench into release plans, this seems somewhat minor (and I'm trying to familiarize myself more with the source / to start working on a patch), but FYI it looks like i18nMark no longer exists in v3. Is there a different mark-type function that should be used?

We're going to write a codemod for helping users migrate from v2 to v3, on the related issue you have all the things that changed or are the recommended way to work.

I am working on upgrading a quite large app to Lingui 3.

So far I did this:

  • rewrote the lazy loading & initialisation to use the new v3 functions
  • replaced every usage of <I18n> with useLingui
  • updated my .linguirc (this should be in the upgrade guide IMO)

I am now working on the places where I am using the t macro to get a MessageDescriptor, which is then displayed using i18n._(descriptor). I noticed that the t macro now returns a string and no longer a MessageDescriptor. Is this intended?
I am getting TS errors due to this and I think it makes it a lot more error-prone.

@renchap Yes, to get a MessageDescriptor you should use defineMessage. But if you don't need a lazy translations or set specific message IDs, then you can use t directly and remove i18n._ call.

updated my .linguirc (this should be in the upgrade guide IMO)

What changes have you made?

@renchap Yes, to get a MessageDescriptor you should use defineMessage. But if you don't need a lazy translations or set specific message IDs, then you can use t directly and remove i18n._ call.

Mmh, I was getting errors about i18n being undefined, but now it works correctly when just using t. Now it seems to work fine, I will do more testing

updated my .linguirc (this should be in the upgrade guide IMO)

What changes have you made?

Changed from

{
   "localeDir": "locale/client/lingui/",
   "srcPathDirs": ["app/client/"],
   "format": "po",
   "sourceLocale": "en"
}

to

{
 "catalogs": [{
   "path": "<rootDir>/locale/client/lingui/{locale}/messages",
   "include": ["<rootDir>/app/client/"],
   "exclude": ["**/node_modules/**"]
 }],
 "compileNamespace": "cjs",
 "extractBabelOptions": {},
 "fallbackLocale": "en",
 "format": "po",
 "locales": ["en", "fr"],
 "orderBy": "messageId",
 "pseudoLocale": "",
 "rootDir": ".",
 "runtimeConfigModule": ["@lingui/core", "i18n"],
 "sourceLocale": "en",
}

It looks like it was previously trying to extract messages from my node_modules (took a long time and got warnings from files in node_modules), it works fine with the new config (copied from the docs).

I am also seeing an issue with HTML entities:

<Trans>Let&apos;s go!</Trans>

is rendered as:
image

My english messages.po (generated by Lingui) contains:

msgid "Let&apos;s go!"
msgstr "Let&apos;s go!"

I read the change about HTML entities in the upgrade guide, but I am not sure what I am missing here.

This is the last issue I am seeing. I got weird errors where t returned a descriptior ({id: "something"}), but if I edited the file the macro was called in, it disappeared. I ended up wiping Babel's cache and it fixed it.

I believe you need to use UTF-8 character directly, e.g. Let's go!. Otherwise the macro see & and treats it as a special character, ignoring the rest. I couldn't figure out how to handle this properly :/

Ok. I was using https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unescaped-entities.md which forces you to escape these characters. I should be able to use the UTF-8 character, but I feel this should be considered as a bug in Lingui.
Unfortunately I dont know macros well enough to help there :(

Yeah, I don't like it either. Let me check.

Hey everyone, not sure if you've noticed, but @semoal wrote a codemod (#774) which makes migration from v2 to v3 very easy. Please check and let us know how it works for you: https://github.com/lingui/codemods/

At the moment I'm working on the documentation β€” from the feedback I've noticed there're lot of obsolete sections. If you're interested, here's the preview of macro section. Feel free to add any comments. Also, proofreading from native english speakers would be much appreciated πŸ™

Thanks to @semoal we're on schedule and I believe we'll release v3 by the end of month.

Hi all, I've updated all sections of reference documentation: https://js-lingui-git-next.lingui-js.now.sh/

I think we might need to update the testing guide. Does anyone has an experince with testing components with LinguiJS? Do you need any specific setup?

Otherwise it all seems to be good. Once we manage to fix the @renchap's html entity issue, we're ready to go.

I would refer to react-testing-library and the wrapper-usage: https://testing-library.com/docs/react-testing-library/setup#custom-render
Since also CRA refers to this project, I think it should be fine to stick only to this.

@PutziSan Perfect, I was hoping we could utilize react-testing-library. Could you please write a short example to guides/testing.rst and send a PR? Feel free to remove anything else in that document, I don't think we need to preserve any of it.

Hi all, new pre-release published β€” 3.0.0-23. Also, the codemod package is now available as @lingui/codemods (thanks @semoal). Please try and let us know how everything works.

I believe we're ready for final release. Let's sink it for few days to catch any outstanding bugs.

There is still #777 (even if I worked around it), I am not sure how to troubleshoot this further and it also impacts v2, so probably not a blocker.
I will resume my v3 conversion branch on my big project and see if -23 works in staging.

Updated my app with 3.0.0-23 and all seems fine.

You really need to clean up all caches when upgrading, otherwise the macro is updated but not the source file, so the cached one is reused and you end up with t macros still compiled to {id: "…"} from the cache, which will cause errors where you do not use _ anymore. (this is a known babel-macros issue).

Hi all, new pre-release is out 3.0.0-24. Fixed #777 and new package was written by @semoal, @lingui/detect-locale. You can use it to detect default locale from several sources. Here's the documentation

Edit: Just published a fix in 3.0.0-25

Hey everyone, 3.0.0 was just published to NPM πŸŽ‰

Thank you all for patience! Big thanks to @semoal who pushed the last features and fixes πŸ‘ I'm gonna cleanup the backlog of issues in next few days and see what's still relevant.

https://github.com/marketplace/stale
You could add stale @tricoder42 , will close automatically issues with no activity, you have 90 issues but some of them are really old

@semoal Yeah, I want to install something like that! Thanks for the tip πŸ‘

Btw, I'm gonna re-publish LinguiJS 2.x docs to alternative domain soon. I'm just migrating lingui.js.org from GitHub Pages to Vercel.com. I'll post the new link when it's available.

EDIT: The old documentation is available at https://js-lingui-git-stable-2x.lingui-js.vercel.app/

Was this page helpful?
0 / 5 - 0 ratings

Related issues

LFDMR picture LFDMR  Β·  4Comments

gitschwifty picture gitschwifty  Β·  3Comments

MartinCerny-awin picture MartinCerny-awin  Β·  6Comments

vitexikora picture vitexikora  Β·  6Comments

LFDMR picture LFDMR  Β·  7Comments