lingui / js-lingui

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

Allow escaping placeholders #457

Closed SilentImp closed 5 years ago

SilentImp commented 5 years ago

Describe the bug

Both react FilePond and lingui.js use {} chars to mark variables. When you try to add FilePonf label for translations you get an error because lingui expect you will pass a value of the variable, but it actually FilePond's variable.

To Reproduce Steps to reproduce the behavior, possibly with minimal code sample, e.g:

import { t } from '@lingui/macro';
import { I18n } from '@lingui/react';
import { FilePond } from 'react-filepond';

export default function App() {
   return (<I18n>
        {({ i18n }) => (<FilePond
                  …
                  labelMaxFileSize={i18n._(t`Maximum file size is {filesize}`)} />))}
        </I18n>);
}

You will get TypeError: Cannot read property 'filesize' of undefined — it came from /node_modules/@lingui/core/cjs/core.development.js:134. I suppose that lingui.js think that filesize is lingui.js variable and expect filesize value should be passed. But it's should be passed by FilePhond.

Expected behavior It would be nice to escape {} somehow. I have tried / and // but I either get an error, either it just doesn't translate it.

Additional context Add any other context about the problem here.

$ npm list babel-core
instapro-client@1.0.0 /Users/silentimp/Work/instapro-client
└─┬ next@7.0.3
  └── babel-core@7.0.0-bridge.0 
tricoder42 commented 5 years ago

LinguiJS uses ICU MessageFormat. Could you please try escape argument using apostrophes?

Maximum file size is '{filesize}'
SilentImp commented 5 years ago

@tricoder42 I have tried to do it this way:

import { t } from '@lingui/macro';
import { I18n } from '@lingui/react';
import { FilePond } from 'react-filepond';

export default function App() {
   return (<I18n>
        {({ i18n }) => (<FilePond
                  …
                  labelMaxFileSize={i18n._(t`Maximum file size is '{filesize}'`)} />))}
        </I18n>);
}

After extractions I get inside message.json:

…
  "Maximum file size is '{filesize}'": "Maximum file size is '{filesize}'",
  "Maximum total file size is '{filesize}'": "Maximum total file size is '{filesize}'",
…

But I still get an error:

×
TypeError: Cannot read property 'filesize' of undefined
ctx
./node_modules/@lingui/core/cjs/core.development.js:134
  131 | var formatters = defaultFormats(language, locales, languageData, formats);
  132 | 
  133 | var ctx = function ctx(name, type, format) {
> 134 |   var value = values[name];
  135 |   var formatted = formatters[type](value, format);
  136 |   var message = isFunction(formatted) ? formatted(ctx) : formatted;
  137 |   return Array.isArray(message) ? message.join("") : message;

…
hjylewis commented 5 years ago

Looks like this is present in the newer version of messageformat-parser but not in version 2.0.0 that @lingui/core uses.

Here's the messageformat-parser changelog

hjylewis commented 5 years ago

Actually, I think this is an actual non-production bug with how the lingui compiler parses escaping characters that would exist even after upgrading to messageformat-parser@4.0.0.

In messageformat-parser@2.0.0, using \\ instead of ' should escape the curly braces (i.e. Maximum file size is \\{filesize\\}).

In non-production env, messages that are compiled to strings are compiled twice, once on I18n.load and again on I18n._.

Since the messageformat-parser removes the escape characters, the redundant parsing causes the escape characters to be ignored.

parse(parse('Maximum file size is \\{filesize\\}'))
↓↓↓↓↓↓↓↓
parse('Maximum file size is {filesize}')
↓↓↓↓↓↓↓↓
compiled message function that treats 'filesize' as an argument

A solution would be to try to avoid compiling messages twice, either by always compiling to a functiion instead of sometimes a string or avoiding compiling on I18n._ if this.messages[id] exists.

hjylewis commented 5 years ago

In fact, I was wrong earlier; messageformat-parser@2.0.0 actually contains support for ' as an escape character already which might explain the behavior in #421.

hjylewis commented 5 years ago

@tricoder42 This is fixed in v3. Should this be closed?

tricoder42 commented 5 years ago

@hjylewis Yeah, good point! 👍