skyline/src/utils/translate.js
Jingwei.Zhang a9370ef261 feat: update zh-cn to zh-hans
Update zh-cn to zh-hans, and update the icon when switch language

Change-Id: I3e6d3e8aebec4584f863282082a11b36d7cee193
2023-03-28 10:15:57 +08:00

261 lines
8.0 KiB
JavaScript

// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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 { getLocalStorageItem } from 'utils/local-storage';
// import * as constants from './constants';
import { merge } from 'lodash';
// eslint-disable-next-line no-extend-native
String.prototype.defaultMessage = function (msg) {
return this || msg || '';
};
class SLI18n {
constructor() {
this.options = {
currentLocale: null, // Current locale such as 'en'
locales: {}, // app locale data like {"en":{"key1":"value1"},"zh-hans":{"key1":"值1"}}
// eslint-disable-next-line no-console
warningHandler: function warn(...msg) {
console.warn(...msg);
}, // ability to accumulate missing messages using third party services
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
};
}
/**
* Get the formatted message by key
* @param {string} key The string representing key in locale data file
* @param {Object} variables Variables in message
* @returns {string} message
*/
get(key, variables) {
invariant(key, 'key is required');
const { locales, currentLocale, formats } = this.options;
if (!locales || !locales[currentLocale]) {
this.options.warningHandler(
`translate locales data "${currentLocale}" not exists.`
);
return '';
}
let msg = this.getDescendantProp(locales[currentLocale], key);
if (msg == null || msg === '') {
if (this.options.fallbackLocale) {
msg = this.getDescendantProp(locales[this.options.fallbackLocale], key);
if (msg == null) {
this.options.warningHandler(
`translate key "${key}" not defined in ${currentLocale}`
);
// this.options.warningHandler(
// `translate key "${key}" not defined in ${currentLocale} or the fallback locale, ${this.options.fallbackLocale}`
// );
msg = key;
}
} else {
this.options.warningHandler(
`translate key "${key}" not defined in ${currentLocale}`
);
msg = key;
}
}
if (variables) {
variables = { ...variables };
// HTML message with variables. Escape it to avoid XSS attack.
Object.keys(variables).forEach((i) => {
let value = variables[i];
if (
this.options.escapeHtml === true &&
(typeof value === 'string' || value instanceof String) &&
value.indexOf('<') >= 0 &&
value.indexOf('>') >= 0
) {
value = escapeHtml(value);
}
variables[i] = value;
});
}
try {
const msgFormatter = new IntlMessageFormat(msg, currentLocale, formats);
return msgFormatter.format(variables);
} catch (err) {
this.options.warningHandler(
`translate format message failed for key='${key}'.`,
err.message
);
return msg;
}
}
/**
* Get the formatted html message by key.
* @param {string} key The string representing key in locale data file
* @param {Object} variables Variables in message
* @returns {React.Element} message
*/
getHTML(key, variables) {
const msg = this.get(key, variables);
if (msg) {
const el = React.createElement('span', {
dangerouslySetInnerHTML: {
__html: msg,
},
});
// when key exists, it should still return element if there's defaultMessage() after getHTML()
const defaultMessage = () => el;
return {
defaultMessage,
d: defaultMessage,
...el,
};
}
return '';
}
/**
* As same as get(...) API
* @param {Object} options
* @param {string} options.id
* @param {string} options.defaultMessage
* @param {Object} variables Variables in message
* @returns {string} message
*/
formatMessage(messageDescriptor, variables) {
const { id, defaultMessage } = messageDescriptor;
return this.get(id, variables).defaultMessage(defaultMessage);
}
/**
* As same as getHTML(...) API
* @param {Object} options
* @param {string} options.id
* @param {React.Element} options.defaultMessage
* @param {Object} variables Variables in message
* @returns {React.Element} message
*/
formatHTMLMessage(messageDescriptor, variables) {
const { id, defaultMessage } = messageDescriptor;
return this.getHTML(id, variables).defaultMessage(defaultMessage);
}
/**
* Helper: determine user's locale via URL, cookie, localStorage, and browser's language.
* You may not this API, if you have other rules to determine user's locale.
* @param {string} options.urlLocaleKey URL's query Key to determine locale. Example: if URL=http://localhost?lang=en, then set it 'lang'
* @param {string} options.cookieLocaleKey Cookie's Key to determine locale. Example: if cookie=lang:en, then set it 'lang'
* @param {string} options.localStorageLocaleKey LocalStorage's Key to determine locale such as 'lang'
* @returns {string} determined locale such as 'en'
*/
determineLocale(options = {}) {
return (
this.getLocaleFromLocalStorage(options) ||
this.getLocaleFromURL(options) ||
this.getLocaleFromCookie(options) ||
this.getLocaleFromBrowser()
);
}
/**
* Initialize properties and load CLDR locale data according to currentLocale
* @param {Object} options
* @param {string} options.currentLocale Current locale such as 'en'
* @param {string} options.locales App locale data like {"en":{"key1":"value1"},"zh-hans":{"key1":"值1"}}
* @returns {Promise}
*/
init(options = {}) {
invariant(options.currentLocale, 'options.currentLocale is required');
invariant(options.locales, 'options.locales is required');
Object.assign(this.options, options);
this.options.formats = {
...this.options.formats,
// constants.defaultFormats
};
return new Promise((resolve) => {
// init() will not load external common locale data anymore.
// But, it still return a Promise for backward compatibility.
resolve();
});
}
/**
* Get the initial options
*/
getInitOptions() {
return this.options;
}
/**
* Load more locales after init
*/
load(locales) {
merge(this.options.locales, locales);
}
getLocaleFromCookie(options) {
const { cookieLocaleKey } = options;
if (cookieLocaleKey) {
const params = cookie.parse(document.cookie);
return params && params[cookieLocaleKey];
}
}
getLocaleFromLocalStorage(options) {
const { localStorageLocaleKey } = options;
if (localStorageLocaleKey && window.localStorage) {
return getLocalStorageItem(localStorageLocaleKey);
}
}
getLocaleFromURL(options) {
const { urlLocaleKey } = options;
if (urlLocaleKey) {
const query = window.location.search.split('?');
if (query.length >= 2) {
const params = queryParser.parse(query[1]);
return params && params[urlLocaleKey];
}
}
}
getDescendantProp(locale, key) {
if (locale[key]) {
return locale[key];
}
const msg = key
.split('.')
.reduce((a, b) => (a !== undefined ? a[b] : a), locale);
return msg;
}
getLocaleFromBrowser() {
return navigator.language || navigator.userLanguage;
}
}
export default SLI18n;