Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop deprecated formatter libraries #247

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- Display numbers, currency, dates and times for different locales.
- Pluralize labels in strings.
- Support variables in message.
- Support HTML in message.
- Support HTML in message ([read more](#html-message)).
- Support for 150+ languages.
- Runs in the browser and Node.js.
- Message format is strictly implemented by [ICU standards](http://userguide.icu-project.org/formatparse/messages).
Expand Down Expand Up @@ -216,6 +216,23 @@ JS code:
intl.getHTML('TIP'); // {React.Element}
```

Before using HTML tags inside the messages, you need to define how the HTML/XML tags you intend to use should be parsed, during the library initializing.

Defining the XML parser:

```js
intl.init({
// ...
xmlParser: {
div: children => '<div>' + children + '</div>',
span: children => '<span>' + children + '</span>',
// ...
}
})
```

Use this feature with caution as there are a set of restrictions. [Click here to learn more](https://formatjs.io/docs/intl-messageformat/#rich-text-support).

### Helper
[react-intl-universal](https://www.npmjs.com/package/react-intl-universal) provides a utility helping developer determine the user's `currentLocale`. As the running examples, when user select a new locale, it redirect user new location like `http://localhost:3000?lang=en-US`. Then, we can use `intl.determineLocale` to get the locale from URL. It can also support determine user's locale via cookie, localStorage, and browser default language. Refer to the APIs section for more detail.

Expand Down
79 changes: 52 additions & 27 deletions packages/react-intl-universal/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/react-intl-universal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"dependencies": {
"cookie": "^0.3.1",
"escape-html": "^1.0.3",
"intl-messageformat": "^7.8.4",
"intl-messageformat": "^10.5.11",
"invariant": "^2.2.2",
"lodash.merge": "^4.6.2",
"object-keys": "^1.0.11"
Expand Down
14 changes: 8 additions & 6 deletions packages/react-intl-universal/src/ReactIntlUniversal.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class ReactIntlUniversal {
fallbackLocale: null, // Locale to use if a key is not found in the current locale
debug: false, // If debugger mode is on, the message will be wrapped by a span
dataKey: 'data-i18n-key', // If debugger mode is on, the message will be wrapped by a span with this data key
xmlParser: { span: children => `<span>${children}</span>` }, // If there are XML tags present in the message, parsers should be added per tag. https://formatjs.io/docs/intl-messageformat/#rich-text-support
};
}

Expand All @@ -38,7 +39,7 @@ class ReactIntlUniversal {
}
}
invariant(key, "key is required");
const { locales, currentLocale, formats } = this.options;
const { locales, currentLocale, formats, xmlParser } = this.options;

// 1. check if the locale data and key exists
if (!locales || !locales[currentLocale]) {
Expand Down Expand Up @@ -89,7 +90,7 @@ class ReactIntlUniversal {
let finalMsg;
if (variables) { // format message with variables
const msgFormatter = new IntlMessageFormat(msg, currentLocale, formats);
finalMsg = msgFormatter.format(variables);
finalMsg = msgFormatter.format(Object.assign(xmlParser, variables));
} else { // no variables, just return the message
finalMsg = msg;
}
Expand Down Expand Up @@ -173,10 +174,11 @@ class ReactIntlUniversal {

/**
* Initialize properties and load CLDR locale data according to currentLocale
* @param {Object} options
* @param {string} options.currentLocale Current locale such as 'en-US'
* @param {any} options.locales App locale data like {"en-US":{"key1":"value1"},"zh-CN":{"key1":"值1"}}
* @param {boolean} [options.debug] debug mode
* @param {Object} options Initialization options
* @param {string} options.currentLocale Current locale (eg. `'en-US'`)
* @param {any} options.locales App locale data (eg. `{ "en-US": { "key1": "value1" }, "zh-CN": { "key1": "值1" }}`)
* @param {boolean} [options.debug] Enable debug mode
* @param {{[tag: string]: (children: any) => string}} [options.xmlParser] Indicates how to parse XML tags inside the messages. (eg. `{ span: children => '<span>' + children + '</span>' }`)
* @returns {Promise}
*/
init(options = {}) {
Expand Down
23 changes: 20 additions & 3 deletions packages/react-intl-universal/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,30 @@ test("HTML Message with variables", () => {
);
});

test("HTML Message with variables and custom XML parser", () => {
intl.init({ locales, currentLocale: "en-US", xmlParser: { div: children => '<div>' + children + '</div>' } });
let reactEl = intl.getHTML("TIP_VAR_DIV", {
message: "your message"
});
expect(reactEl.props.dangerouslySetInnerHTML.__html).toBe(
"This is<div>your message</div>"
);
});

test("HTML Message with XSS attack", () => {
intl.init({ locales, currentLocale: "en-US" });
intl.init({
locales,
currentLocale: "en-US",
xmlParser: {
span: children => `<span>${children}</span>`,
script: children => `<script>${children}</script>`
}
});
let reactEl = intl.getHTML("TIP_VAR", {
message: "<sctipt>alert(1)</script>"
message: "<script>alert(1)</script>"
});
expect(reactEl.props.dangerouslySetInnerHTML.__html).toBe(
"This is<span>&lt;sctipt&gt;alert(1)&lt;/script&gt;</span>"
"This is<span>&lt;script&gt;alert(1)&lt;/script&gt;</span>"
);
});

Expand Down
1 change: 1 addition & 0 deletions packages/react-intl-universal/test/locales/en-US.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = ({
"HELLO": "Hello, {name}",
"TIP": "This is <span>HTML</span>",
"TIP_VAR": "This is<span>{message}</span>",
"TIP_VAR_DIV": "This is<div>{message}</div>",
"SALE_START": "Sale begins {start, date}",
"SALE_END": "Sale begins {start, date, long}",
"COUPON": "Coupon expires at {expires, time, medium}",
Expand Down
2 changes: 2 additions & 0 deletions packages/react-intl-universal/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ declare module "react-intl-universal" {
* @param {string} options.fallbackLocale Fallback locale such as 'zh-CN' to use if a key is not found in the current locale
* @param {boolean} options.escapeHtml To escape html. Default value is true.
* @param {boolean} options.debug debug mode
* @param {Object} options.xmlParser Parser for the XML tags used in messages (eg: `{ span: children => '<span>' + children + '</span>' }`)
* @returns {Promise}
*/
export function init(options: ReactIntlUniversalOptions): Promise<void>;
Expand All @@ -89,6 +90,7 @@ declare module "react-intl-universal" {
escapeHtml?: boolean;
debug?: boolean;
dataKey?: string;
xmlParser?: { [tag: string]: (children: string) => string };
}

export interface ReactIntlUniversalMessageDescriptor {
Expand Down