Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import intl from 'react-intl-universal';
import intl, { createIntlCache } from 'react-intl-universal';
import useForceUpdate from 'use-force-update';
import enUS from 'locales/en-US.json';
import zhCN from 'locales/zh-CN.json';
Expand Down Expand Up @@ -73,11 +73,18 @@ const ReactIntlUniversalExample: React.FC<any> = (props) => {
setInitDone(true);
}

// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()

const setCurrentLocale = (currentLocale: string) => {
intl.init({
currentLocale,
locales: LOCALE_DATA,
});
intl.init(
{
currentLocale,
locales: LOCALE_DATA,
},
cache
);
};

const onLocaleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
Expand Down
22 changes: 15 additions & 7 deletions packages/react-intl-universal/examples/node-js-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, { createIntlCache } from 'react-intl-universal';
import React, { Component } from "react";
import BasicComponent from "./Basic";
import PluralComponent from "./Plural";
Expand Down Expand Up @@ -44,12 +44,20 @@ class App extends Component {
constructor(props) {
super(props);
const currentLocale = SUPPORTED_LOCALES[0].value; // Determine user's locale here
intl.init({
currentLocale,
locales: {
[currentLocale]: require(`./locales/${currentLocale}`)
}
});

// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()

intl.init(
{
currentLocale,
locales: {
[currentLocale]: require(`./locales/${currentLocale}`)
}
},
cache
);
}

render() {
Expand Down
17 changes: 13 additions & 4 deletions packages/react-intl-universal/src/ReactIntlUniversal.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import "intl";
import React from "react";
import IntlMessageFormat from "intl-messageformat";
import escapeHtml from "escape-html";
import cookie from "cookie";
import queryParser from "querystring";
import invariant from "invariant";
import * as constants from "./constants";
import merge from "lodash.merge";
import { generateCacheKey, createFormattedMessage } from "./utils";

String.prototype.defaultMessage = String.prototype.d = function (msg) {
return this || msg || "";
Expand Down Expand Up @@ -84,8 +84,14 @@ class ReactIntlUniversal {
}

try {
const msgFormatter = new IntlMessageFormat(msg, currentLocale, formats);
return msgFormatter.format(variables);
if (this.cache) {
const cacheKey = generateCacheKey(key, variables, currentLocale);
if (!this.cache[cacheKey]) {
this.cache[cacheKey] = createFormattedMessage(msg, currentLocale, formats, variables);
}
return this.cache[cacheKey];
}
return createFormattedMessage(msg, currentLocale, formats, variables);
} catch (err) {
this.options.warningHandler(
`react-intl-universal format message failed for key='${key}'.`,
Expand Down Expand Up @@ -174,9 +180,10 @@ class ReactIntlUniversal {
* @param {Object} options
* @param {string} options.currentLocale Current locale such as 'en-US'
* @param {string} options.locales App locale data like {"en-US":{"key1":"value1"},"zh-CN":{"key1":"值1"}}
* @param {Object} [cache] explicit cache to prevent leaking memory, Initialize using createIntlCache
* @returns {Promise}
*/
init(options = {}) {
init(options = {}, cache) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

could you create cache object internally by adding a enableCache as one of options?

init(options) {
  if(options.enableCache) {
    // create cache object internally
  }
}

invariant(options.currentLocale, "options.currentLocale is required");
invariant(options.locales, "options.locales is required");

Expand All @@ -188,6 +195,8 @@ class ReactIntlUniversal {
constants.defaultFormats
);

this.cache = cache || null;

return new Promise((resolve, reject) => {
// init() will not load external common locale data anymore.
// But, it still return a Promise for abckward compatibility.
Expand Down
4 changes: 3 additions & 1 deletion packages/react-intl-universal/src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ReactIntlUniversal from './ReactIntlUniversal';
import { createIntlCache } from './utils';

const defaultInstance = new ReactIntlUniversal();
// resolved by CommonJS module loader
Expand Down Expand Up @@ -33,5 +34,6 @@ export {
getLocaleFromURL,
getDescendantProp,
getLocaleFromBrowser,
defaultInstance as default
defaultInstance as default,
createIntlCache
};
39 changes: 39 additions & 0 deletions packages/react-intl-universal/src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import IntlMessageFormat from "intl-messageformat";

/**
* Create an object that acts as a cache for internationalized messages.
* This function uses Object.create(null) to ensure that the returned object
* does not inherit any properties or methods from the global Object prototype.
*
* @returns {Object} An empty object that can be used as a cache.
*/
export function createIntlCache() {
return Object.create(null)
}

/**
* Get the formatted message by key
* @param {string} key The string representing key in locale data file
* @param {Object} variables Variables in message
* @param {string} currentLocale Current locale such as 'en-US'
* @returns {string} message
*/
export function generateCacheKey(key, variables, currentLocale) {
return key + JSON.stringify(variables) + currentLocale;
}

/**
* Create and return a formatted message based on provided parameters.
* This function takes in a message, a locale, optional formats, and variables,
* and returns the message formatted accordingly using IntlMessageFormat.
*
* @param {string} msg The message string to be formatted
* @param {string} currentLocale The locale in which the message should be formatted
* @param {Object} formats formats to be applied to the message
* @param {Object} variables Variables to replace placeholders in the message
* @returns {string} The formatted message string
*/
export function createFormattedMessage(msg, currentLocale, formats, variables) {
const msgFormatter = new IntlMessageFormat(msg, currentLocale, formats);
return msgFormatter.format(variables);
}
15 changes: 14 additions & 1 deletion packages/react-intl-universal/test/index.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import cookie from "cookie";
import intl from "../src/index";
import intl, { createIntlCache } from "../src/index";
import zhCN from "./locales/zh-CN";
import enUS from "./locales/en-US";
import enUSMore from "./locales/en-US-more";
Expand Down Expand Up @@ -368,3 +368,16 @@ test("Resolve directly if the environment is not browser", async () => {
});
expect(result).toBe(undefined);
});

test("cache", () => {
const key = "HELLO";
const variables = { name: 'World' };
const currentLocale = "en-US"
const cacheKey = key + JSON.stringify(variables) + currentLocale;
const cache = createIntlCache()

intl.init({ locales, currentLocale }, cache);

expect(intl.get(key, variables)).toBe("Hello, World");
expect(intl.cache[cacheKey]).toBe("Hello, World");
});
3 changes: 2 additions & 1 deletion packages/react-intl-universal/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ declare module "react-intl-universal" {
* @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 {boolean} options.escapeHtml To escape html. Default value is true.
* @param {Object} [cache] explicit cache to prevent leaking memory, Initialize using createIntlCache
* @returns {Promise}
*/
export function init(options: ReactIntlUniversalOptions): Promise<void>;
export function init(options: ReactIntlUniversalOptions, cache: { [key: string]: any }): Promise<void>;

/**
* Load more locales after init
Expand Down