lingui / js-lingui

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

help wanted: strings not interpolated (displaying {var}) in "production" builds #759

Closed davakos closed 3 years ago

davakos commented 3 years ago

Describe the bug I am experiencing an issue in my TypeScript application (bootstrapped by Create-React-App) where translation strings aren't properly interpolated when built for "production" (minified, etc.).

For example:

I also see this same interpolation behavior in the following cases:

I haven't seen a duplicate bug to this in existing issues (open or closed).

To Reproduce

  1. Bootstrap a React app via create-react-app with Redux+Typescript:
    • npx create-react-app linguiv2 --template redux-typescript
  2. Add lingui components to project:
    • npm install @lingui/react @lingui/cli @lingui/macro
  3. Modify project tsconfig.json, change:
  4. Add minimal lingui to the project:

    • In index.tsx, add import { I18nProvider } from '@lingui/react' and wrap <Provider store={store}> with <Provider store={store}> in ReactDOM.render(
    • In App.tsx, add import { Trans } from "@lingui/macro"; and in App functional component, add some variables to be interpolated and a translation element, such as:
      
      function App() {
      const item = 1;
      const total = 5;

    return (

    logo

    Displaying {item} of {total}

    ```
  5. View in "developer mode"
    • npm build and viewing page displays: Displaying 1 of 5 (expected behavior)
  6. Build+view in "production mode"
    • npm run build and serve -s build and viewing page displays: Displaying {item} of {total} (not expected behavior)

Expected behavior Expected that variables would be interpolated in messages in production builds.

Additional context Please note that I followed the guidance for configuration of my tsconfig.json file here: https://lingui.js.org/guides/typescript.html

But I am unsure how to complete the additional steps to configure babel and webpack as I am using a project bootstrapped by create-react-app that I have not ejected (and I would prefer not to eject unless it's necessary to make this work). As such, I do not have separate .babelrc or webpack.config.js files and when I create these for the project they do not appear to do anything (because this config is "owned" by cra). Note that I did also try ejecting my app and following this documentation, but it seems like it may be out-of-date at this point because it appears babel-preset-env and babel-preset-react have been replaced by @babel/preset-env and @babel/preset-react for a while now and the preferred way way use TS and Babel is to run TS through Babel (via @babel/preset-typescript), so there is no morets-loader in webpack.

I also tried following these 2.x to 3.x migration directions: https://github.com/lingui/js-lingui/blob/81937f9c05368459d201c6b5069ed06307b988f0/docs/releases/migration-3.rst#pluginspresets by installing babel-plugin-macros and adding the following macro to my package.json file, but this also yielded the same results:

  "babel": {
    "plugins": [
      "macros"
    ]
  }

I'm sure there's a step I'm missing here for how (or in what order) the code is getting transpiled + minified, but I can't figure out how to fix this.

Dev screenshot (see highlighted text)

Production screenshot (see highlighted text)

Versions

davakos commented 3 years ago

In case it helps, this is my full tsconfig.json. This was all created by create-react-app and I only changed target from es5 => es2016 and jsx from react => preserve

{
  "compilerOptions": {
    "target": "es2016",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve"
  },
  "include": [
    "src"
  ]
}
semoal commented 3 years ago

Alright, i saw you executed extract and compile.

For me the problem is that babel config is not getting applied. If you're using cra you could use https://github.com/gsoft-inc/craco, to override babel with that macro

semoal commented 3 years ago

I've created a sample repo for you https://github.com/semoal/lingui-v3-ts-redux

Also probably you're issue was not loading the catalogs correctly on v2 you need to require the messages.js (compiled catalogs) and load them in i18nProvider, on v3 you can do it directly with i18n.load

semoal commented 3 years ago

Also i saw create-react-app alreadyy contains babel-plugin-macros, so probably you don't need to use craco.

https://github.com/facebook/create-react-app/blob/master/packages/babel-preset-react-app/create.js#L121

davakos commented 3 years ago

Thank you very much for the quick response and sample! I am adjusting my project now to see if I can resolve the issue. I probably should have noted that I first saw this in an application I am creating using TypeScript and Lingui v3.0.0-13 and in this app I am importing the message catalog. I just didn't import the message catalogs in the create-react-app sample app I created to reproduce the issue.

In my app I am bootstrapping my message catalog as:

import { setupI18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { en } from "make-plural/plurals";
...
const locale = "en";
const i18n = setupI18n();
// locales must be loaded AND loaded before load() to prevent the exception: "plurals is not a function"
// see:
// https://github.com/lingui/js-lingui/issues/683#issuecomment-620424893
// note: using cmj "require" instead of es6 "import" here because despite setting "compileNamespace": "es" in .linguirc
// I am getting a typescript "messages has no default export" warning using "import lang from 'catalogPath'"
i18n.loadLocaleData(locale, { plurals: en });
i18n.load(locale, require("locale/en/messages"));
i18n.activate(locale);

I haven't tried adjusting this yet, but perhaps it has something to do with me using CJS vs ES6 imports here. I also note that our .linguirc files differ. The notable differences seem to be:

Will update once I adjust my project and test, I just wanted to make note of these updates.

davakos commented 3 years ago

Fixed. The issue was my import of the message catalogs, specifically this line: i18n.load(locale, require("locale/en/messages"));

I was loading the messages, but not loading the actual exported messages object, so I adjusted to an es6 import:

import {messages as enMessages} from 'locale/en/messages'
...
// @ts-ignore
i18n.load(locale, enMessages);

and this resolved it. Or I supposed if I wanted to continue to use CJS require in order to conditionally load catalogs based on supported language(s), I could use:

const enMessages = require("locale/en/messages");
i18n.load(locale, enMessages.messages);

The main thing is that I missed importing the actual .messages object. Thank you very much for your help, please feel free to close/resolve this issue. Best regards,