1
0
Fork 0
metamask-extension/ui/helpers/utils/i18n-helper.js

132 lines
3.9 KiB
JavaScript

// cross-browser connection to extension i18n API
import React from 'react';
import log from 'loglevel';
import * as Sentry from '@sentry/browser';
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
const fetchWithTimeout = getFetchWithTimeout();
const warned = {};
const missingMessageErrors = {};
const missingSubstitutionErrors = {};
/**
* Returns a localized message for the given key
*
* @param {string} localeCode - The code for the current locale
* @param {object} localeMessages - The map of messages for the current locale
* @param {string} key - The message key
* @param {string[]} substitutions - A list of message substitution replacements
* @returns {null|string} The localized message
*/
export const getMessage = (localeCode, localeMessages, key, substitutions) => {
if (!localeMessages) {
return null;
}
if (!localeMessages[key]) {
if (localeCode === 'en') {
if (!missingMessageErrors[key]) {
missingMessageErrors[key] = new Error(
`Unable to find value of key "${key}" for locale "${localeCode}"`,
);
Sentry.captureException(missingMessageErrors[key]);
log.error(missingMessageErrors[key]);
if (process.env.IN_TEST) {
throw missingMessageErrors[key];
}
}
} else if (!warned[localeCode] || !warned[localeCode][key]) {
if (!warned[localeCode]) {
warned[localeCode] = {};
}
warned[localeCode][key] = true;
log.warn(
`Translator - Unable to find value of key "${key}" for locale "${localeCode}"`,
);
}
return null;
}
const entry = localeMessages[key];
let phrase = entry.message;
const hasSubstitutions = Boolean(substitutions && substitutions.length);
const hasReactSubstitutions =
hasSubstitutions &&
substitutions.some(
(element) =>
element !== null &&
(typeof element === 'function' || typeof element === 'object'),
);
// perform substitutions
if (hasSubstitutions) {
const parts = phrase.split(/(\$\d)/gu);
const substitutedParts = parts.map((part) => {
const subMatch = part.match(/\$(\d)/u);
if (!subMatch) {
return part;
}
const substituteIndex = Number(subMatch[1]) - 1;
if (
(substitutions[substituteIndex] === null ||
substitutions[substituteIndex] === undefined) &&
!missingSubstitutionErrors[localeCode]?.[key]
) {
if (!missingSubstitutionErrors[localeCode]) {
missingSubstitutionErrors[localeCode] = {};
}
missingSubstitutionErrors[localeCode][key] = true;
const error = new Error(
`Insufficient number of substitutions for key "${key}" with locale "${localeCode}"`,
);
log.error(error);
Sentry.captureException(error);
}
return substitutions[substituteIndex];
});
phrase = hasReactSubstitutions ? (
<span> {substitutedParts} </span>
) : (
substitutedParts.join('')
);
}
return phrase;
};
export async function fetchLocale(localeCode) {
try {
const response = await fetchWithTimeout(
`./_locales/${localeCode}/messages.json`,
);
return await response.json();
} catch (error) {
log.error(`failed to fetch ${localeCode} locale because of ${error}`);
return {};
}
}
const relativeTimeFormatLocaleData = new Set();
export async function loadRelativeTimeFormatLocaleData(localeCode) {
const languageTag = localeCode.split('_')[0];
if (
Intl.RelativeTimeFormat &&
typeof Intl.RelativeTimeFormat.__addLocaleData === 'function' &&
!relativeTimeFormatLocaleData.has(languageTag)
) {
const localeData = await fetchRelativeTimeFormatData(languageTag);
Intl.RelativeTimeFormat.__addLocaleData(localeData);
}
}
async function fetchRelativeTimeFormatData(languageTag) {
const response = await fetchWithTimeout(
`./intl/${languageTag}/relative-time-format-data.json`,
);
return await response.json();
}