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

No messages extracted outside of react #415

Closed diegolacarta closed 3 years ago

diegolacarta commented 5 years ago

No messages get extracted when using:

import i18n from './utils/i18n'
import {t} from '@lingui/macro';

i18n._(t`hello`)

They get extracted when using it like:

i18n._("hello")

They also get extracted as expected when using a react component:

import {Trans} from '@lingui/react';

<Trans>Hello</Trans>

any ideas?

tricoder42 commented 5 years ago

Hey @diegolacarta, what version of Lingui do you have?

diegolacarta commented 5 years ago

"@lingui/cli": "2.7.2", "@lingui/loader": "2.7.2", "@lingui/macro": "2.7.2", "@lingui/react": "2.7.2",

tricoder42 commented 5 years ago

Could you please also post your Babel config? Do you have babel-plugin-macros enabled?

diegolacarta commented 5 years ago
{
  "env": {
    "local": {
      "presets": ["@babel/preset-react", "@lingui/babel-preset-react"],
      "plugins": [
        ["@babel/plugin-syntax-dynamic-import"],
        ["macros"],
        [
          "emotion",
          {
            "sourceMap": true,
            "autoLabel": true
          }
        ],
        [
          "react-intl",
          {
            "messagesDir": "./i18n/messages/",
            "extractSourceLocation": true
          }
        ]
      ]
    }
  }
}

react-intl in there just while transitioning away from it

tricoder42 commented 5 years ago

That looks good. Could you also please show how babel transforms the file with i18n? babel src/file.with.i18n.js

Finally, does extract work for Trans imported from @lingui/macro package?

tricoder42 commented 5 years ago

There's also an example repo with js-macros (no React): https://github.com/lingui/js-lingui/tree/master/examples/vanilla-js Maybe it would help you find the root cause.

diegolacarta commented 5 years ago

That looks good. Could you also please show how babel transforms the file with i18n? babel src/file.with.i18n.js

Finally, does extract work for Trans imported from @lingui/macro package?

I'm using typescript, following the setup from https://github.com/lingui/js-lingui/blob/master/docs/guides/typescript.rst

I could debug at which point babel plugin is not doing what it should, or if you could point me into where to look that'd help

Trans doesn't work when imported from @lingui/macro: <Trans>random text</Trans> ...

> Missing message ID, skipping.
> <Trans>random text</Trans>
tricoder42 commented 5 years ago

Right, so now we now the macros doesn't work.

It would be great to see how transpiled source code looks like. Unfortunately I don't use Typescript, but maybe you run TS before Babel?

diegolacarta commented 5 years ago

Also not even this works:

import { setupI18n } from '@lingui/core';
import { t } from '@lingui/macro';
const i18n = setupI18n()
i18n._(t`Hello`)
diegolacarta commented 5 years ago

This is the final code after building with webpack:

Original:

import {setupI18n} from '@lingui/core'
import {t} from '@lingui/macro'

const i18n = setupI18n()
i18n._(t`Hello`)

Transpiled:

! function(e) {
    function r(r) {
        for (var n, i, l = r[0], f = r[1], c = r[2], a = 0, s = []; a < l.length; a++) i = l[a], o[i] && s.push(o[i][0]), o[i] = 0;
        for (n in f) Object.prototype.hasOwnProperty.call(f, n) && (e[n] = f[n]);
        for (p && p(r); s.length;) s.shift()();
        return u.push.apply(u, c || []), t()
    }

    function t() {
        for (var e, r = 0; r < u.length; r++) {
            for (var t = u[r], n = !0, l = 1; l < t.length; l++) {
                var f = t[l];
                0 !== o[f] && (n = !1)
            }
            n && (u.splice(r--, 1), e = i(i.s = t[0]))
        }
        return e
    }
    var n = {},
        o = {
            0: 0
        },
        u = [];

    function i(r) {
        if (n[r]) return n[r].exports;
        var t = n[r] = {
            i: r,
            l: !1,
            exports: {}
        };
        return e[r].call(t.exports, t, t.exports, i), t.l = !0, t.exports
    }
    i.m = e, i.c = n, i.d = function(e, r, t) {
        i.o(e, r) || Object.defineProperty(e, r, {
            enumerable: !0,
            get: t
        })
    }, i.r = function(e) {
        "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
            value: "Module"
        }), Object.defineProperty(e, "__esModule", {
            value: !0
        })
    }, i.t = function(e, r) {
        if (1 & r && (e = i(e)), 8 & r) return e;
        if (4 & r && "object" == typeof e && e && e.__esModule) return e;
        var t = Object.create(null);
        if (i.r(t), Object.defineProperty(t, "default", {
                enumerable: !0,
                value: e
            }), 2 & r && "string" != typeof e)
            for (var n in e) i.d(t, n, function(r) {
                return e[r]
            }.bind(null, n));
        return t
    }, i.n = function(e) {
        var r = e && e.__esModule ? function() {
            return e.default
        } : function() {
            return e
        };
        return i.d(r, "a", r), r
    }, i.o = function(e, r) {
        return Object.prototype.hasOwnProperty.call(e, r)
    }, i.p = "\x3c!-- server-replace:PUBLIC_PATH --\x3e/";
    var l = window.webpackJsonp = window.webpackJsonp || [],
        f = l.push.bind(l);
    l.push = r, l = l.slice();
    for (var c = 0; c < l.length; c++) r(l[c]);
    var p = f;
    u.push([39, 1]), t()
}({
    39: function(e, r, t) {
        e.exports = t(40)
    },
    40: function(e, r, t) {
        "use strict";
        t.r(r);
        var n = t(38);
        Object(n.setupI18n)()._({
            id: "Hello"
        })
    }
});
diegolacarta commented 5 years ago

I just tried changing my .linguirc to:

{
   "localeDir": "src/locales/",
   "srcPathDirs": ["src/"],
   "format": "po",
   "extractBabelOptions": {
      "plugins": ["@babel/plugin-syntax-dynamic-import", "macros"]
   }
}

Adding "macros" it now extracts the message with both \<Trans/> from marcos, and i18n._(t`...`), but doesn't translate it

diegolacarta commented 5 years ago

and then finally adding: "presets": ["@babel/preset-react", "@lingui/babel-preset-react"],

to .linguirc seems to make everything work as expected.

I thought it would pick up my .babelrc from the project

tricoder42 commented 5 years ago

Interesting. What's your project structure? Do you have .babelrc right next to the package.json? And how do you run the lingui extract command?

diegolacarta commented 5 years ago

yes, .babelrc is at same level as package.json, and I run lingui extract via npm script: npm run extract-locales

tricoder42 commented 5 years ago

Could you please share minimal example so I can take a look? It should work as you described, pick the config from .babelrc

diegolacarta commented 5 years ago

will try to reproduce when I find some time

ianmartorell commented 5 years ago

I'm having the same issue with lingui extract not picking up strings from i18n._(t`...`). I'm importing t from @lingui/react, since my project is on React Native and macros don't work (babel-plugin-macros tries to import Node standard modules path, os and fs when I import Trans from @lingui/macros, should it actually work?). The docs don't mention anything about using lingui without macros though, is it possible?

Using Trans from @lingui/react works just fine though. Strings get extracted, catalogs are compiled and no errors are thrown during runtime.

Any help is much appreciated. πŸ™

EDIT: Actually, t is not part of @lingui/react is it? πŸ˜† It seems the whole problem is simply that macros aren't working, so of course I can't use t, and strings using it aren't being extracted.

Hotell commented 5 years ago

I'm facing similar issue. The whole extraction behaves very weird/doesn't work.

Versions:

$ yarn list --depth 0 --pattern @lingui
yarn list v1.13.0
β”œβ”€ @lingui/babel-plugin-extract-messages@2.8.3
β”œβ”€ @lingui/babel-plugin-transform-js@2.8.3
β”œβ”€ @lingui/babel-plugin-transform-react@2.8.3
β”œβ”€ @lingui/cli@2.8.3
β”œβ”€ @lingui/conf@2.8.3
β”œβ”€ @lingui/core@2.8.3
β”œβ”€ @lingui/macro@2.8.3
└─ @lingui/react@2.8.3

config:

const config = {
  compileNamespace: 'es',
  format: 'minimal',
  localeDir: 'public/locale',
  srcPathDirs: ['src'],
};

module.exports = config;
import React from 'react'
import {Trans} from '@lingui/macro'

// Missing message ID, skipping. <Trans>Continue</Trans>
const App = () => <div><h1>hello</h2><button><Trans>Continue</Trans></button></div>

// Works -> {
  "continue": ""
}
const App = () => <div><h1>hello</h2><button><Trans id="continue">Continue</Trans></button></div>

Additional bugs:

import React from 'react';
import { Plural } from '@lingui/macro';

const App = () => {
  return (
    <div>
      /* WORKS ->  
        "{count, plural, one {At least # lower-case letter} other {At least # lower-case letters}}": "{count, plural, one {At least # lower-case letter} other {At least # lower-case letters}}"
      */
      <Plural
        one="At least # lower-case letter"
        other="At least # lower-case letters"
        value={count}
      />
      /* doesn't! 
        it even removes valid translation when run with --clean flag
      */
      <Plural
        id="validator.lowerCase"
        one="At least # lower-case letter"
        other="At least # lower-case letters"
        value={count}
      />
    </div>
  );
};

What's super weird is that once I add id to Plural, then I run lignui extract --clean,

->

{
-{count, plural, one {At least # lower-case letter} other {At least # lower-case letters}}": "{count, plural, one {At least # lower-case letter} other {At least # lower-case letters}}
+
}

after that, once I remove id from plural and run lignui extract again, it won't work anymore and generates nothing within messages.json.

tricoder42 commented 5 years ago

Hey @Hotell, what's your babel config? It seems you're mixing macros with plugins. You should use either one and not mix them together.

tricoder42 commented 5 years ago

Ideally please create a repository with minimal example so we can debug the issue quickly.

Hotell commented 5 years ago

what's your babel config?

 const presets = [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'entry',
        corejs: 2,
        // Do not transform modules to CJS
        modules: IS_TEST ? 'commonjs' : false,
        loose: true,
      },
    ],
    '@babel/preset-react',
    '@babel/preset-typescript'
  ]

  /** @type {import('babel').BabelConfigOptions['plugins']} */
  const plugins = [
    '@babel/plugin-syntax-dynamic-import',
    ['@babel/plugin-proposal-class-properties', { loose: true }],
    ['@babel/plugin-proposal-object-rest-spread', { loose: true }],
    'macros',
  ]

It seems you're mixing macros with plugins. You should use either one and not mix them together.

I'm not sure I follow

Hotell commented 5 years ago

Also

Hotell commented 5 years ago

Ideally please create a repository with minimal example so we can debug the issue quickly.

I wished I'd have time for that hah. That's a non trivial request when taking into consideration the size of the project...

tricoder42 commented 5 years ago

Also Plural gets caught when used from @lingui/react

If you use macros, definitely don't import Plural from @lingui/react.

In LinguiJS 2.x (current version), there're two ways how to transform components into ICU MessageFormat - plugins and macros:

Plugins:

Macros:

It seems you're using the second option, macros. I just misinterpreted the dependencies.

I wished I'd have time for that hah. That's a non trivial request when taking into consideration the size of the project...

Well, the problem is somewhere in your configuration. If you look into examples, you'll find working projects. The only way to figure out the problem is create a repository with one file, your babel config and install LinguiJS dependencies. If you don't have time, then I can't help you, sorry.

Hotell commented 5 years ago

I'm using @lingui/macro with v2 solely. It doesn't work with Plural -> strangely enough id does when Trans "low level" ICU is used directly <Trans id={ '{count, plural, one {At least # lower-case letter} other {At least # lower-case letters}}' } />

I'm gonna try to use v3 and get back here with results ( if there will be any )

In the meantime I appreciate your time and help ✌️.

Cheers

tricoder42 commented 5 years ago

Unfortunately, the v3 isn't production ready yet :/ See #334

I'll try to setup the repo this evening. I'm suspicious that it's either Typescript related (LinguiJS uses TypeScript 2.x, not Babel preset) or related to some babel plugins.

Hotell commented 5 years ago

Unfortunately, the v3 isn't production ready yet :/ See #334

I'll try to setup the repo this evening. I'm suspicious that it's either Typescript related (LinguiJS uses TypeScript 2.x, not Babel preset) or related to some babel plugins.

I appreciate your efforts Tomas, but it's open source... In my experience it should be consumer responsibility to provide demo which simulates described "bug", not the authors. Our time as maintainers is very precious. So please don't waste your time on creating repro. I'll do it, when I find some time today...

cheers πŸ‘

tricoder42 commented 5 years ago

I know, you’re right. I’m just open-source junkie - when I see a problem I can’t resist solving it. If I weren’t super busy at work right now, I would already start working on it ;)

Let me know if I can further help or clarify something.

Cheers On 26 Jun 2019, 16:01 +0200, Martin Hochel notifications@github.com, wrote:

Unfortunately, the v3 isn't production ready yet :/ See #334 I'll try to setup the repo this evening. I'm suspicious that it's either Typescript related (LinguiJS uses TypeScript 2.x, not Babel preset) or related to some babel plugins. I appreciate your efforts Tomas, but it's open source... In my experience it should be consumer responsibility to provide demo which simulates described "bug", not the authors. Our time as maintainers is very precious. So please don't waste your time on creating repro. I'll do it, when I find some time today... cheers πŸ‘ β€” You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

Hotell commented 5 years ago

Hmm so, I've converted weback js/flow/react example to typescript with latest versions of deps. Looks like works there (attached) Check Form within Children.tsx.

Everything works, hmm. Looks like I don't have @lingui/babel-plugin-transform-{js,react} within dependencies at work (as it's not properly stated in the docs ?)

webpack-react-typescript.tar.gz

I’m just open-source junkie - when I see a problem I can’t resist solving it I know, been there, don't that. don't do that :D

Hotell commented 5 years ago

so after adding "@lingui/babel-preset-react": "2.8.3", Plural macro works.

Hotell commented 5 years ago

Ups so I found the real culprit. lingui CLI doesn't work within monorepo...

It won't find root babel.config.js. That's why it was behaving so strange all along...

No idea how to solve this situation...

Hotell commented 5 years ago

What lingui cli needs to support is:

babelOptions: {
    rootMode: 'upward',
  },
Hotell commented 5 years ago

HA! so actually that's supported already :D πŸ‘

const config = {
extractBabelOptions: {
    rootMode: 'upward',
}
}
tricoder42 commented 5 years ago

Great, I'm glad you've figured it out.

So what's the summary? You have a monorepo, each package has it's own .babelrc or they all use the root babel.config.js? I'm just asking so I can include it in docs.

Cheers πŸ‘

Hotell commented 5 years ago

Yes, one babel.config.js within root.

-> lingui.config.js within root -> lingui.config.js per package(app) which extends/overrides from root

✌️

image

image

maktouch commented 4 years ago

Hey @Hotell,

Just to confirm, did you make this work in a monorepo, in which Components are being translated?

I'd love to replicate what you did and when I get it working, I'll help @tricoder42 with the documentation.

I'm currently facing a similar problem with monorepos, I can get everything to work except Lingui, and it's crucial to the developer's experience.

gpolanco commented 4 years ago

Hey @Hotell,

Just to confirm, did you make this work in a monorepo, in which Components are being translated?

I'd love to replicate what you did and when I get it working, I'll help @tricoder42 with the documentation.

I'm currently facing a similar problem with monorepos, I can get everything to work except Lingui, and it's crucial to the developer's experience.

I have the same problem, any solution for monorepos?

clehene commented 4 years ago

I was able to get the extraction working in a monorepo setup. We have multiple applications and shared common-ui component library built with rollup.js. In our case the problem was that we were configuring babel plugins directly through rollup rather than .babelrc

I was puzzled about how lignui actually works and then realized it uses .babelrc so once I had that (using macros plugin) the extraction worked.

Now there's a bigger / more interesting problem remaining. The component library will have its own catalog and it's unclear what's the right way to get that loaded.

Each app has a global provider:

          <I18nProvider
            language={i18nStore.language}
            catalogs={i18nStore.catalogs}>
            <MainContainer />
          </I18nProvider>

But then somewhere deeper in MainContainer we use a component that comes with its own catalog.

This adapte picks up the language provided by the upper provider and replaces the catalog with the internal one. Perhaps merging the catalog (or a way to provide fallback catalogs) would be better, just in case the shared components are wrapping components from outside (which get their catalog overriden)

export const withI18nAdapter = <P extends any>(
  Component: React.ComponentType<P>
) =>
  withI18n()((_props: P & withI18nProps) => {
    const language = _props.i18n?.language ?? 'en';
    return (
      <I18nProvider language={language} catalogs={catalogs}>
        <Component {...(_props as P)} />
      </I18nProvider>
    );
  })

Then, when exporting the components

const ComponentI18n = withI18nAdapter(Component);
export { ComponentI18n as Component };
cezarneaga commented 3 years ago

Hi Cosmin @clehene ,

did you get this to work somehow? i have the same situation. component library in monorepo. not sure how to get it to work. thanks, Cezar ps: @tricoder42 any input on this?

clehene commented 3 years ago

Hey @cezarneaga. Yes we built an adapter. Likely suboptimal, but it works. We can contribute it as a helper to lingui-js. Meanwhile I'm posting the code here

import { I18nProvider, withI18n, withI18nProps } from '@lingui/react';
import * as React from 'react';

import catalogRo from '../locales/ro/messages.js';
import catalogEn from '../locales/en/messages.js';

/**
 *
 * Similar to Lingui JS `withI18n` but wraps component with an i18n adapter
 * that:
 *  * picks the `language` parameter from the provided `i18n` instance
 *  * changes the i18n catalog to the wrapped component's (internal) one
 *
 * embed-ui        provider(lang1, catalog1) ->
 *  adapter => new provider(lang1, catalog2)
 *
 * TODO use defaults if instance not provided (we'll fail with undefined otherwise
 * TODO load catalog dynamically
 * TODO merge catalog message instead of override?
 *
 * @param Component to wrap
 */

export const withI18nAdapter: <P extends object>(
  Component: React.ComponentType<P>
) => React.ComponentClass<
  Pick<P & withI18nProps, Exclude<keyof P, keyof withI18nProps>>
> = <P extends object>(Component: React.ComponentType<P>) =>
  withI18n()((props: P & withI18nProps) => {
    const language = props.i18n?.language ?? 'en';
    return (
      <I18nProvider language={language} catalogs={catalogs}>
        <Component {...(props as P)} />
      </I18nProvider>
    );
  });
const catalogs = { ro: catalogRo, en: catalogEn };

And then in index.tsx

import { ActionSet } from './components/ActionSet';
const ActionSetI18n = withI18nAdapter(ActionSet);
export { ActionSetI18n as ActionSet };
tricoder42 commented 3 years ago

New version has been released. Please check if the problem persist in the new version.

cezarneaga commented 3 years ago

Hey @cezarneaga. Yes we built an adapter. Likely suboptimal, but it works. We can contribute it as a helper to lingui-js. Meanwhile I'm posting the code here

Thanks! would like to try the v3 and see if this is still needed.

clehene commented 3 years ago

Is the new behavior documented somewhere? I'd like to track that and change our code if that works.

clehene commented 3 years ago

@cezarneaga does 3 work properly? Can we remove the adapter?

semoal commented 3 years ago

@cezarneaga does 3 work properly? Can we remove the adapter?

On v3.x, practically you don't need withI18n for anything, macros work out of the box for example:

t`some translate`

and if you need i18n instance, you can directly import it from core

import { i18n } from "@lingui/core"

I'll try to find some time tomorrow to build a sample mono-repo with lingui 3 and multiple catalogs πŸ‘πŸ»

clehene commented 3 years ago

That's great. How does it work with shared components libraries that contain catalogs?

In our case we have

There are going to be different instances / catalogs. How do these get reconciled?

semoal commented 3 years ago

That's great. How does it work with shared components libraries that contain catalogs?

In our case we have

  • ui-1 - catalog-ui-1
  • ui-2 - catalog-ui-2
  • shared-components - catalog-shared-components

There are going to be different instances / catalogs. How do these get reconciled?

They get merged by locale internally, you have more information in lingui website on configuration section.

Tomorrow I'll expand this a lit