-
Notifications
You must be signed in to change notification settings - Fork 2.9k
/
index.js
162 lines (144 loc) · 5.66 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import _ from 'underscore';
import lodashGet from 'lodash/get';
import Str from 'expensify-common/lib/str';
import * as RNLocalize from 'react-native-localize';
import Onyx from 'react-native-onyx';
import Log from '../Log';
import Config from '../../CONFIG';
import translations from '../../languages/translations';
import CONST from '../../CONST';
import LocaleListener from './LocaleListener';
import BaseLocaleListener from './LocaleListener/BaseLocaleListener';
import ONYXKEYS from '../../ONYXKEYS';
// Current user mail is needed for handling missing translations
let userEmail = '';
Onyx.connect({
key: ONYXKEYS.SESSION,
waitForCollectionCallback: true,
callback: (val) => {
if (!val) {
return;
}
userEmail = val.email;
},
});
// Listener when an update in Onyx happens so we use the updated locale when translating/localizing items.
LocaleListener.connect();
// Note: This has to be initialized inside a function and not at the top level of the file, because Intl is polyfilled,
// and if React Native executes this code upon import, then the polyfill will not be available yet and it will barf
let CONJUNCTION_LIST_FORMATS_FOR_LOCALES;
function init() {
CONJUNCTION_LIST_FORMATS_FOR_LOCALES = _.reduce(
CONST.LOCALES,
(memo, locale) => {
// This is not a supported locale, so we'll use ES_ES instead
if (locale === CONST.LOCALES.ES_ES_ONFIDO) {
// eslint-disable-next-line no-param-reassign
memo[locale] = new Intl.ListFormat(CONST.LOCALES.ES_ES, {style: 'long', type: 'conjunction'});
return memo;
}
// eslint-disable-next-line no-param-reassign
memo[locale] = new Intl.ListFormat(locale, {style: 'long', type: 'conjunction'});
return memo;
},
{},
);
}
/**
* Return translated string for given locale and phrase
*
* @param {String} [desiredLanguage] eg 'en', 'es-ES'
* @param {String} phraseKey
* @param {Object} [phraseParameters] Parameters to supply if the phrase is a template literal.
* @returns {String}
*/
function translate(desiredLanguage = CONST.LOCALES.DEFAULT, phraseKey, phraseParameters = {}) {
const languageAbbreviation = desiredLanguage.substring(0, 2);
let translatedPhrase;
// Search phrase in full locale e.g. es-ES
const desiredLanguageDictionary = translations[desiredLanguage] || {};
translatedPhrase = desiredLanguageDictionary[phraseKey];
if (translatedPhrase) {
return Str.result(translatedPhrase, phraseParameters);
}
// Phrase is not found in full locale, search it in fallback language e.g. es
const fallbackLanguageDictionary = translations[languageAbbreviation] || {};
translatedPhrase = fallbackLanguageDictionary[phraseKey];
if (translatedPhrase) {
return Str.result(translatedPhrase, phraseParameters);
}
if (languageAbbreviation !== CONST.LOCALES.DEFAULT) {
Log.alert(`${phraseKey} was not found in the ${languageAbbreviation} locale`);
}
// Phrase is not translated, search it in default language (en)
const defaultLanguageDictionary = translations[CONST.LOCALES.DEFAULT] || {};
translatedPhrase = defaultLanguageDictionary[phraseKey];
if (translatedPhrase) {
return Str.result(translatedPhrase, phraseParameters);
}
// Phrase is not found in default language, on production and staging log an alert to server
// on development throw an error
if (Config.IS_IN_PRODUCTION || Config.IS_IN_STAGING) {
const phraseString = _.isArray(phraseKey) ? phraseKey.join('.') : phraseKey;
Log.alert(`${phraseString} was not found in the en locale`);
if (userEmail.includes(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN)) {
return CONST.MISSING_TRANSLATION;
}
return phraseString;
}
throw new Error(`${phraseKey} was not found in the default language`);
}
/**
* Uses the locale in this file updated by the Onyx subscriber.
*
* @param {String|Array} phrase
* @param {Object} [variables]
* @returns {String}
*/
function translateLocal(phrase, variables) {
return translate(BaseLocaleListener.getPreferredLocale(), phrase, variables);
}
/**
* Return translated string for given error.
*
* @param {String|Array} message
* @returns {String}
*/
function translateIfPhraseKey(message) {
if (_.isEmpty(message)) {
return '';
}
try {
// check if error message has a variable parameter
const [phrase, variables] = _.isArray(message) ? message : [message];
// This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys.
if (variables && variables.isTranslated) {
return phrase;
}
return translateLocal(phrase, variables);
} catch (error) {
return message;
}
}
/**
* Format an array into a string with comma and "and" ("a dog, a cat and a chicken")
*
* @param {Array} anArray
* @return {String}
*/
function arrayToString(anArray) {
if (!CONJUNCTION_LIST_FORMATS_FOR_LOCALES) {
init();
}
const listFormat = CONJUNCTION_LIST_FORMATS_FOR_LOCALES[BaseLocaleListener.getPreferredLocale()];
return listFormat.format(anArray);
}
/**
* Returns the user device's preferred language.
*
* @return {String}
*/
function getDevicePreferredLocale() {
return lodashGet(RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES]), 'languageTag', CONST.LOCALES.DEFAULT);
}
export {translate, translateLocal, translateIfPhraseKey, arrayToString, getDevicePreferredLocale};