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

Enhance locale fallback mechanism #104

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion examples/browser-example/public/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
3 changes: 2 additions & 1 deletion examples/browser-example/public/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "文案兜底测试"
}
36 changes: 27 additions & 9 deletions examples/browser-example/src/App.js
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 = [
Expand Down Expand Up @@ -56,6 +57,7 @@ class App extends Component {
<DateComponent />
<CurrencyComponent />
<MessageNotInComponent />
<FallbackComponent />
</div>
);
}
Expand All @@ -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<allLocales.length; i++) {
Object.assign(locales, {
[allLocales[i]]: results[i].data
})
}

return intl.init({
currentLocale,
locales: {
[currentLocale]: res.data
}
});
})
fallbackLocale,
locales
})
}))
.then(() => {
// After loading CLDR locale data, start to render
this.setState({ initDone: true });
Expand Down
16 changes: 16 additions & 0 deletions examples/browser-example/src/Fallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, { Component } from 'react'
import intl from 'react-intl-universal';

class FallbackComponent extends Component {
render () {
return (
<div>
<div className="title">Language Fallback:</div>
<div>{intl.get('FALLBACK_NOT_EXIST_IN_ZH_TW')}</div>
<div>{intl.get('FALLBACK_ONLY_EXIST_IN_EN')}</div>
</div>
)
}
}

export default FallbackComponent;
2 changes: 2 additions & 0 deletions examples/node-js-example/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -61,6 +62,7 @@ class App extends Component {
<DateComponent />
<CurrencyComponent />
<MessageNotInComponent />
<FallbackComponent />
</div>
);
}
Expand Down
16 changes: 16 additions & 0 deletions examples/node-js-example/src/Fallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, { Component } from 'react'
import intl from 'react-intl-universal';

class FallbackComponent extends Component {
render () {
return (
<div>
<div className="title">Language Fallback:</div>
<div>{intl.get('FALLBACK_NOT_EXIST_IN_ZH_TW')}</div>
<div>{intl.get('FALLBACK_ONLY_EXIST_IN_EN')}</div>
</div>
)
}
}

export default FallbackComponent;
3 changes: 2 additions & 1 deletion examples/node-js-example/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
3 changes: 2 additions & 1 deletion examples/node-js-example/src/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "文案兜底测试"
}
20 changes: 15 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @cwtuan
Thanks a lot for your great advice. Here attached the modification according to your suggestion, please have a kind review again.

};
}

Expand All @@ -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 {
Expand All @@ -85,6 +94,7 @@ class ReactIntlUniversal {
return "";
}
}

if (variables) {
variables = Object.assign({}, variables);
// HTML message with variables. Escape it to avoid XSS attack.
Expand Down
9 changes: 8 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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");
})
3 changes: 2 additions & 1 deletion test/locales/zh-CN.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
});
12 changes: 12 additions & 0 deletions test/locales/zh-TW.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = ({
"HELLO": "你好, {name}。歡迎來到{where}!",
"TIP": "這是<span style='color:red'>HTML</span>",
"TIP_VAR": "這是<span style='color:red'>{message}</span>",
"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文件進行國際化"
})