From 7a6b106d82b4beb31a8041b58677605251bb78c0 Mon Sep 17 00:00:00 2001 From: icnbrave Date: Wed, 28 Nov 2018 13:29:35 +0800 Subject: [PATCH 1/3] Enhance locale fallback mechanism --- .../browser-example/public/locales/en-US.json | 3 +- .../browser-example/public/locales/zh-CN.json | 3 +- examples/browser-example/src/App.js | 36 ++++++++++++++----- examples/browser-example/src/Fallback.js | 16 +++++++++ examples/node-js-example/src/App.js | 2 ++ examples/node-js-example/src/Fallback.js | 16 +++++++++ .../node-js-example/src/locales/en-US.json | 3 +- .../node-js-example/src/locales/zh-CN.json | 3 +- src/index.js | 20 ++++++++--- test/index.js | 9 ++++- test/locales/zh-CN.js | 3 +- test/locales/zh-TW.js | 12 +++++++ 12 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 examples/browser-example/src/Fallback.js create mode 100644 examples/node-js-example/src/Fallback.js create mode 100644 test/locales/zh-TW.js diff --git a/examples/browser-example/public/locales/en-US.json b/examples/browser-example/public/locales/en-US.json index cbe2c9f..9471132 100644 --- a/examples/browser-example/public/locales/en-US.json +++ b/examples/browser-example/public/locales/en-US.json @@ -8,5 +8,6 @@ "COUPON": "Coupon expires at {expires, time, medium}", "SALE_PRICE": "The price is {price, number, USD}", "PHOTO": "You have {photoNum, plural, =0 {no photos.} =1 {one photo.} other {# photos.}}", - "MESSAGE_NOT_IN_COMPONENT": "react-intl-universal is able to internationalize message not in React.Component" + "MESSAGE_NOT_IN_COMPONENT": "react-intl-universal is able to internationalize message not in React.Component", + "FALLBACK_ONLY_EXIST_IN_EN": "Fallback Test, Only exist in English." } \ No newline at end of file diff --git a/examples/browser-example/public/locales/zh-CN.json b/examples/browser-example/public/locales/zh-CN.json index 34fb02a..3c6d357 100644 --- a/examples/browser-example/public/locales/zh-CN.json +++ b/examples/browser-example/public/locales/zh-CN.json @@ -9,5 +9,6 @@ "TIME": "时间是{theTime, time}", "SALE_PRICE": "售价{price, number, CNY}", "PHOTO": "你有{photoNum, number}张照片", - "MESSAGE_NOT_IN_COMPONENT": "react-intl-universal可以在非React.Component的js文件进行国际化" + "MESSAGE_NOT_IN_COMPONENT": "react-intl-universal可以在非React.Component的js文件进行国际化", + "FALLBACK_NOT_EXIST_IN_ZH_TW": "文案兜底测试" } \ No newline at end of file diff --git a/examples/browser-example/src/App.js b/examples/browser-example/src/App.js index 5a8e962..b718a0c 100644 --- a/examples/browser-example/src/App.js +++ b/examples/browser-example/src/App.js @@ -1,4 +1,4 @@ -import intl from "react-intl-universal"; +import intl from "react-intl-universal" import _ from "lodash"; import http from "axios"; import React, { Component } from "react"; @@ -8,6 +8,7 @@ import HtmlComponent from "./Html"; import DateComponent from "./Date"; import CurrencyComponent from "./Currency"; import MessageNotInComponent from "./MessageNotInComponent"; +import FallbackComponent from "./Fallback"; import "./app.css"; const SUPPOER_LOCALES = [ @@ -56,6 +57,7 @@ class App extends Component { + ); } @@ -69,18 +71,34 @@ class App extends Component { currentLocale = "en-US"; } + let fallbackLocale = currentLocale === 'zh-TW' ? 'zh-CN; en-US' : 'en-US'; + + let fallbackLocales = fallbackLocale.split(';').map(item => item.trim()); + let allLocales = [currentLocale, ...fallbackLocales] + + function getMessagesByLocale(locale) { + return http.get(`locales/${locale}.json`) + } + + let promises = allLocales.map(item => getMessagesByLocale(item)); + http - .get(`locales/${currentLocale}.json`) - .then(res => { - console.log("App locale data", res.data); + .all(promises) + .then(http.spread((...results) => { // init method will load CLDR locale data according to currentLocale + let locales = {} + for (let i=0; i { // After loading CLDR locale data, start to render this.setState({ initDone: true }); diff --git a/examples/browser-example/src/Fallback.js b/examples/browser-example/src/Fallback.js new file mode 100644 index 0000000..ec41bdd --- /dev/null +++ b/examples/browser-example/src/Fallback.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react' +import intl from 'react-intl-universal'; + +class FallbackComponent extends Component { + render () { + return ( +
+
Language Fallback:
+
{intl.get('FALLBACK_NOT_EXIST_IN_ZH_TW')}
+
{intl.get('FALLBACK_ONLY_EXIST_IN_EN')}
+
+ ) + } +} + +export default FallbackComponent; \ No newline at end of file diff --git a/examples/node-js-example/src/App.js b/examples/node-js-example/src/App.js index d9cb5db..39a05e5 100644 --- a/examples/node-js-example/src/App.js +++ b/examples/node-js-example/src/App.js @@ -6,6 +6,7 @@ import HtmlComponent from "./Html"; import DateComponent from "./Date"; import CurrencyComponent from "./Currency"; import MessageNotInComponent from "./MessageNotInComponent"; +import FallbackComponent from "./Fallback"; import IntlPolyfill from "intl"; // For Node.js, common locales should be added in the application @@ -61,6 +62,7 @@ class App extends Component { + ); } diff --git a/examples/node-js-example/src/Fallback.js b/examples/node-js-example/src/Fallback.js new file mode 100644 index 0000000..ec41bdd --- /dev/null +++ b/examples/node-js-example/src/Fallback.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react' +import intl from 'react-intl-universal'; + +class FallbackComponent extends Component { + render () { + return ( +
+
Language Fallback:
+
{intl.get('FALLBACK_NOT_EXIST_IN_ZH_TW')}
+
{intl.get('FALLBACK_ONLY_EXIST_IN_EN')}
+
+ ) + } +} + +export default FallbackComponent; \ No newline at end of file diff --git a/examples/node-js-example/src/locales/en-US.json b/examples/node-js-example/src/locales/en-US.json index cbe2c9f..9471132 100644 --- a/examples/node-js-example/src/locales/en-US.json +++ b/examples/node-js-example/src/locales/en-US.json @@ -8,5 +8,6 @@ "COUPON": "Coupon expires at {expires, time, medium}", "SALE_PRICE": "The price is {price, number, USD}", "PHOTO": "You have {photoNum, plural, =0 {no photos.} =1 {one photo.} other {# photos.}}", - "MESSAGE_NOT_IN_COMPONENT": "react-intl-universal is able to internationalize message not in React.Component" + "MESSAGE_NOT_IN_COMPONENT": "react-intl-universal is able to internationalize message not in React.Component", + "FALLBACK_ONLY_EXIST_IN_EN": "Fallback Test, Only exist in English." } \ No newline at end of file diff --git a/examples/node-js-example/src/locales/zh-CN.json b/examples/node-js-example/src/locales/zh-CN.json index 34fb02a..3c6d357 100644 --- a/examples/node-js-example/src/locales/zh-CN.json +++ b/examples/node-js-example/src/locales/zh-CN.json @@ -9,5 +9,6 @@ "TIME": "时间是{theTime, time}", "SALE_PRICE": "售价{price, number, CNY}", "PHOTO": "你有{photoNum, number}张照片", - "MESSAGE_NOT_IN_COMPONENT": "react-intl-universal可以在非React.Component的js文件进行国际化" + "MESSAGE_NOT_IN_COMPONENT": "react-intl-universal可以在非React.Component的js文件进行国际化", + "FALLBACK_NOT_EXIST_IN_ZH_TW": "文案兜底测试" } \ No newline at end of file diff --git a/src/index.js b/src/index.js index dbd50ff..aed2953 100644 --- a/src/index.js +++ b/src/index.js @@ -48,7 +48,7 @@ class ReactIntlUniversal { warningHandler: console.warn, // ability to accumulate missing messages using third party services like Sentry escapeHtml: true, // disable escape html in variable mode commonLocaleDataUrls: COMMON_LOCALE_DATA_URLS, - fallbackLocale: null, // Locale to use if a key is not found in the current locale + fallbackLocale: null, // Locales to use if a key is not found in the current locale, such as 'zh-CN;en' will use the key in locale 'zh-CN', if the specific key not exist in 'zh-CN', will fallback to 'en' }; } @@ -69,13 +69,22 @@ class ReactIntlUniversal { return ""; } let msg = this.getDescendantProp(locales[currentLocale], key); + if (msg == null) { if (this.options.fallbackLocale) { - msg = this.getDescendantProp(locales[this.options.fallbackLocale], key); + let fallbackLocales = this.options.fallbackLocale.split(';').map(locale => locale.trim()); + for (let locale of fallbackLocales) { + msg = this.getDescendantProp(locales[locale], key); + if (msg == null) { + this.options.warningHandler( + `react-intl-universal key "${key}" not defined in ${currentLocale} or the fallback locale, ${locale}` + ); + } else { + break; + } + } + if (msg == null) { - this.options.warningHandler( - `react-intl-universal key "${key}" not defined in ${currentLocale} or the fallback locale, ${this.options.fallbackLocale}` - ); return ""; } } else { @@ -85,6 +94,7 @@ class ReactIntlUniversal { return ""; } } + if (variables) { variables = Object.assign({}, variables); // HTML message with variables. Escape it to avoid XSS attack. diff --git a/test/index.js b/test/index.js index 41f4677..3eeb860 100644 --- a/test/index.js +++ b/test/index.js @@ -4,10 +4,12 @@ import intl from "../src/index"; import zhCN from "./locales/zh-CN"; import enUS from "./locales/en-US"; import enUSMore from "./locales/en-US-more"; +import zhTW from "./locales/zh-TW"; const locales = { "en-US": enUS, - "zh-CN": zhCN + "zh-CN": zhCN, + "zh-TW": zhTW }; test("Set specific locale", () => { @@ -305,3 +307,8 @@ test("Uses default message if key not found in fallbackLocale", () => { expect(intl.get("not-exist-key").defaultMessage("this is default msg")).toBe("this is default msg"); }); +test("Test for enhanced fallback mechnanism", () => { + intl.init({ locales, currentLocale: "zh-TW", fallbackLocale: "zh-CN; en-US" }); + expect(intl.get("NOT_IN_zh-TW")).toBe("NOT_IN_zh-TW"); + expect(intl.get("ONLY_IN_ENGLISH")).toBe("ONLY_IN_ENGLISH"); +}) \ No newline at end of file diff --git a/test/locales/zh-CN.js b/test/locales/zh-CN.js index 76cc519..56a4f94 100644 --- a/test/locales/zh-CN.js +++ b/test/locales/zh-CN.js @@ -8,5 +8,6 @@ module.exports = ({ "COUPON": "优惠卷将在{expires, time, medium}过期", "TIME": "时间是{theTime, time}", "SALE_PRICE": "售价{price, number, CNY}", - "PHOTO": "你有{num}张照片" + "PHOTO": "你有{num}张照片", + "NOT_IN_zh-TW": "NOT_IN_zh-TW" }); diff --git a/test/locales/zh-TW.js b/test/locales/zh-TW.js new file mode 100644 index 0000000..6f921b4 --- /dev/null +++ b/test/locales/zh-TW.js @@ -0,0 +1,12 @@ +module.exports = ({ + "HELLO": "你好, {name}。歡迎來到{where}!", + "TIP": "這是HTML", + "TIP_VAR": "這是{message}", + "SALE_START": "拍賣將在{start, date}開始", + "SALE_END": "拍賣將在{end, date, full}結束", + "COUPON": "優惠卷將在{expires, time, medium}過期", + "TIME": "時間是{theTime, time}", + "SALE_PRICE": "售價{price, number, TWD}", + "PHOTO": "你有{photoNum, number}張照片", + "MESSAGE_NOT_IN_COMPONENT": "react-intl-universal可以在非React.Component的js文件進行國際化" +}) \ No newline at end of file From d5312df403e50a5f6ddd7e8ec73e31c08e1c7850 Mon Sep 17 00:00:00 2001 From: icnbrave Date: Mon, 28 Jan 2019 15:19:21 +0800 Subject: [PATCH 2/3] add another parameters fallbackLocales in order to comply with alicloud globalization standart. --- examples/browser-example/src/App.js | 11 +++++------ src/index.js | 17 ++++++++++++----- test/index.js | 2 +- typings/index.d.ts | 2 ++ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/examples/browser-example/src/App.js b/examples/browser-example/src/App.js index b718a0c..cbb0d4c 100644 --- a/examples/browser-example/src/App.js +++ b/examples/browser-example/src/App.js @@ -71,13 +71,12 @@ class App extends Component { currentLocale = "en-US"; } - let fallbackLocale = currentLocale === 'zh-TW' ? 'zh-CN; en-US' : 'en-US'; - - let fallbackLocales = fallbackLocale.split(';').map(item => item.trim()); - let allLocales = [currentLocale, ...fallbackLocales] + let fallbackLocales = currentLocale === 'zh-TW' ? ['zh-CN', 'en-US'] : ['en-US']; + + let allLocales = [currentLocale, ...fallbackLocales]; function getMessagesByLocale(locale) { - return http.get(`locales/${locale}.json`) + return http.get(`locales/${locale}.json`); } let promises = allLocales.map(item => getMessagesByLocale(item)); @@ -95,7 +94,7 @@ class App extends Component { return intl.init({ currentLocale, - fallbackLocale, + fallbackLocales, locales }) })) diff --git a/src/index.js b/src/index.js index aed2953..7b5333b 100644 --- a/src/index.js +++ b/src/index.js @@ -48,7 +48,8 @@ class ReactIntlUniversal { warningHandler: console.warn, // ability to accumulate missing messages using third party services like Sentry escapeHtml: true, // disable escape html in variable mode commonLocaleDataUrls: COMMON_LOCALE_DATA_URLS, - fallbackLocale: null, // Locales to use if a key is not found in the current locale, such as 'zh-CN;en' will use the key in locale 'zh-CN', if the specific key not exist in 'zh-CN', will fallback to 'en' + fallbackLocale: '', // Locale to use if a key is not found in the current locale + fallbackLocales: [], // Locales to use if a key is not found in the current locale, such as ['zh-CN', 'en-US'] will use the key in locale 'zh-CN' first, if the specific key not exist in 'zh-CN', will fallback to locale 'en-US' }; } @@ -71,9 +72,8 @@ class ReactIntlUniversal { let msg = this.getDescendantProp(locales[currentLocale], key); if (msg == null) { - if (this.options.fallbackLocale) { - let fallbackLocales = this.options.fallbackLocale.split(';').map(locale => locale.trim()); - for (let locale of fallbackLocales) { + if (Array.isArray(this.options.fallbackLocales) && this.options.fallbackLocales.length > 0) { + for (let locale of this.options.fallbackLocales) { msg = this.getDescendantProp(locales[locale], key); if (msg == null) { this.options.warningHandler( @@ -83,10 +83,17 @@ class ReactIntlUniversal { break; } } - if (msg == null) { return ""; } + } else if (this.options.fallbackLocale) { + msg = this.getDescendantProp(locales[this.options.fallbackLocale], key); + if (msg == null) { + this.options.warningHandler( + `react-intl-universal key "${key}" not defined in ${currentLocale} or the fallback locale, ${this.options.fallbackLocale}` + ); + return ""; + } } else { this.options.warningHandler( `react-intl-universal key "${key}" not defined in ${currentLocale}` diff --git a/test/index.js b/test/index.js index 3eeb860..60013e9 100644 --- a/test/index.js +++ b/test/index.js @@ -308,7 +308,7 @@ test("Uses default message if key not found in fallbackLocale", () => { }); test("Test for enhanced fallback mechnanism", () => { - intl.init({ locales, currentLocale: "zh-TW", fallbackLocale: "zh-CN; en-US" }); + intl.init({ locales, currentLocale: "zh-TW", fallbackLocales: ["zh-CN", "en-US"] }); expect(intl.get("NOT_IN_zh-TW")).toBe("NOT_IN_zh-TW"); expect(intl.get("ONLY_IN_ENGLISH")).toBe("ONLY_IN_ENGLISH"); }) \ No newline at end of file diff --git a/typings/index.d.ts b/typings/index.d.ts index 3371228..727c3b1 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -71,6 +71,7 @@ declare module "react-intl-universal" { * @param {Object} options.locales App locale data like {"en-US":{"key1":"value1"},"zh-CN":{"key1":"值1"}} * @param {Object} options.warningHandler Ability to accumulate missing messages using third party services like Sentry * @param {string} options.fallbackLocale Fallback locale such as 'zh-CN' to use if a key is not found in the current locale + * @param {string[]} options.fallbackLocales Locales to use if a key is not found in the current locale, such as ['zh-CN', 'en-US'] will use the key in locale 'zh-CN' first, if the specific key not exist in 'zh-CN', will fallback to locale 'en-US * @param {boolean} options.escapeHtml To escape html. Default value is true. * @returns {Promise} */ @@ -86,6 +87,7 @@ declare module "react-intl-universal" { currentLocale?: string; locales?: { [key: string]: any }; fallbackLocale?: string; + fallbackLocales?: string[]; commonLocaleDataUrls?: { [key: string]: string }; cookieLocaleKey?: string; urlLocaleKey?: string; From effa62d86a6c23f2c362ffad1c8fecd13aee0722 Mon Sep 17 00:00:00 2001 From: icnbrave Date: Mon, 28 Jan 2019 15:31:21 +0800 Subject: [PATCH 3/3] deprecate fallbackLocale. --- src/index.js | 2 +- typings/index.d.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 7b5333b..4f0dbc8 100644 --- a/src/index.js +++ b/src/index.js @@ -48,7 +48,7 @@ class ReactIntlUniversal { warningHandler: console.warn, // ability to accumulate missing messages using third party services like Sentry escapeHtml: true, // disable escape html in variable mode commonLocaleDataUrls: COMMON_LOCALE_DATA_URLS, - fallbackLocale: '', // Locale to use if a key is not found in the current locale + fallbackLocale: '', /** @deprecated Locale to use if a key is not found in the current locale */ fallbackLocales: [], // Locales to use if a key is not found in the current locale, such as ['zh-CN', 'en-US'] will use the key in locale 'zh-CN' first, if the specific key not exist in 'zh-CN', will fallback to locale 'en-US' }; } diff --git a/typings/index.d.ts b/typings/index.d.ts index 727c3b1..0469a0a 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -70,7 +70,7 @@ declare module "react-intl-universal" { * @param {string} options.currentLocale Current locale such as 'en-US' * @param {Object} options.locales App locale data like {"en-US":{"key1":"value1"},"zh-CN":{"key1":"值1"}} * @param {Object} options.warningHandler Ability to accumulate missing messages using third party services like Sentry - * @param {string} options.fallbackLocale Fallback locale such as 'zh-CN' to use if a key is not found in the current locale + * @param {string} options.fallbackLocale @deprecated Fallback locale such as 'zh-CN' to use if a key is not found in the current locale * @param {string[]} options.fallbackLocales Locales to use if a key is not found in the current locale, such as ['zh-CN', 'en-US'] will use the key in locale 'zh-CN' first, if the specific key not exist in 'zh-CN', will fallback to locale 'en-US * @param {boolean} options.escapeHtml To escape html. Default value is true. * @returns {Promise} @@ -86,7 +86,7 @@ declare module "react-intl-universal" { export interface ReactIntlUniversalOptions { currentLocale?: string; locales?: { [key: string]: any }; - fallbackLocale?: string; + fallbackLocale?: string; /** @deprecated Please use fallbackLocales instead **/ fallbackLocales?: string[]; commonLocaleDataUrls?: { [key: string]: string }; cookieLocaleKey?: string;