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

Improve translations - flatten translation objects #25846

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
78 changes: 78 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,29 @@ const CONST = {
DEFAULT: 'en',
},

LANGUAGES: ['en', 'es'],
BeeMargarida marked this conversation as resolved.
Show resolved Hide resolved

PRONOUNS_LIST: [
'coCos',
'eEyEmEir',
'heHimHis',
'heHimHisTheyThemTheirs',
'sheHerHers',
'sheHerHersTheyThemTheirs',
'merMers',
'neNirNirs',
'neeNerNers',
'perPers',
'theyThemTheirs',
'thonThons',
'veVerVis',
'viVir',
'xeXemXyr',
'zeZieZirHir',
'zeHirHirs',
'callMeByMyName',
],

POLICY: {
TYPE: {
FREE: 'free',
Expand Down Expand Up @@ -1641,6 +1664,61 @@ const CONST = {
ZW: 'Zimbabwe',
},

ALL_STATES: {
BeeMargarida marked this conversation as resolved.
Show resolved Hide resolved
AK: 'Alaska',
AL: 'Alabama',
AR: 'Arkansas',
AZ: 'Arizona',
CA: 'California',
CO: 'Colorado',
CT: 'Connecticut',
DE: 'Delaware',
FL: 'Florida',
GA: 'Georgia',
HI: 'Hawaii',
IA: 'Iowa',
ID: 'Idaho',
IL: 'Illinois',
IN: 'Indiana',
KS: 'Kansas',
KY: 'Kentucky',
LA: 'Louisiana',
MA: 'Massachusetts',
MD: 'Maryland',
ME: 'Maine',
MI: 'Michigan',
MN: 'Minnesota',
MO: 'Missouri',
MS: 'Mississippi',
MT: 'Montana',
NC: 'North Carolina',
ND: 'North Dakota',
NE: 'Nebraska',
NH: 'New Hampshire',
NJ: 'New Jersey',
NM: 'New Mexico',
NV: 'Nevada',
NY: 'New York',
OH: 'Ohio',
OK: 'Oklahoma',
OR: 'Oregon',
PA: 'Pennsylvania',
PR: 'Puerto Rico',
RI: 'Rhode Island',
SC: 'South Carolina',
SD: 'South Dakota',
TN: 'Tennessee',
TX: 'Texas',
UT: 'Utah',
VA: 'Virginia',
VT: 'Vermont',
WA: 'Washington',
WI: 'Wisconsin',
WV: 'West Virginia',
WY: 'Wyoming',
DC: 'District Of Columbia',
},

// Sources: https://github.com/Expensify/App/issues/14958#issuecomment-1442138427
// https://github.com/Expensify/App/issues/14958#issuecomment-1456026810
COUNTRY_ZIP_REGEX_DATA: {
Expand Down
17 changes: 10 additions & 7 deletions src/components/CountryPicker/CountrySelectorModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ function CountrySelectorModal({currentCountry, isVisible, onClose, onCountrySele

const countries = useMemo(
() =>
_.map(translate('allCountries'), (countryName, countryISO) => ({
value: countryISO,
keyForList: countryISO,
text: countryName,
isSelected: currentCountry === countryISO,
searchValue: StringUtils.sanitizeString(`${countryISO}${countryName}`),
})),
_.map(_.keys(CONST.ALL_COUNTRIES), (countryISO) => {
const countryName = translate(`allCountries.${countryISO}`);
return {
value: countryISO,
keyForList: countryISO,
text: countryName,
isSelected: currentCountry === countryISO,
searchValue: StringUtils.sanitizeString(`${countryISO}${countryName}`),
};
}),
[translate, currentCountry],
);

Expand Down
12 changes: 5 additions & 7 deletions src/components/CountryPicker/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, {useEffect, useState} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import styles from '../../styles/styles';
import MenuItemWithTopDescription from '../MenuItemWithTopDescription';
import useLocalize from '../../hooks/useLocalize';
Expand Down Expand Up @@ -31,16 +30,15 @@ const defaultProps = {

function CountryPicker({value, errorText, onInputChange, forwardedRef}) {
const {translate} = useLocalize();
const allCountries = translate('allCountries');
const [isPickerVisible, setIsPickerVisible] = useState(false);
const [searchValue, setSearchValue] = useState(lodashGet(allCountries, value, ''));
const [searchValue, setSearchValue] = useState(value ? translate(`allCountries.${value}`) : '');

useEffect(() => {
setSearchValue(lodashGet(allCountries, value, ''));
}, [value, allCountries]);
setSearchValue(value ? translate(`allCountries.${value}`) : '');
}, [value, translate]);

const showPickerModal = () => {
setSearchValue(lodashGet(allCountries, value, ''));
setSearchValue(value ? translate(`allCountries.${value}`) : '');
setIsPickerVisible(true);
};

Expand All @@ -53,7 +51,7 @@ function CountryPicker({value, errorText, onInputChange, forwardedRef}) {
hidePickerModal();
};

const title = allCountries[value] || '';
const title = value ? translate(`allCountries.${value}`) : '';
const descStyle = title.length === 0 ? styles.textNormal : null;

return (
Expand Down
8 changes: 5 additions & 3 deletions src/components/LocalePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ const defaultProps = {
};

function LocalePicker(props) {
const localesToLanguages = _.map(props.translate('languagePage.languages'), (language, key) => ({
value: key,
label: language.label,
const localesToLanguages = _.map(CONST.LANGUAGES, (language) => ({
value: language,
label: props.translate(`languagePage.languages.${language}.label`),
keyForList: language,
isSelected: props.preferredLocale === language,
}));
return (
<Picker
Expand Down
18 changes: 11 additions & 7 deletions src/components/StatePicker/StateSelectorModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,17 @@ function StateSelectorModal({currentState, isVisible, onClose, onStateSelected,

const countryStates = useMemo(
() =>
_.map(translate('allStates'), (state) => ({
value: state.stateISO,
keyForList: state.stateISO,
text: state.stateName,
isSelected: currentState === state.stateISO,
searchValue: StringUtils.sanitizeString(`${state.stateISO}${state.stateName}`),
})),
_.map(_.keys(CONST.ALL_STATES), (state) => {
const stateName = translate(`allStates.${state}.stateName`);
const stateISO = translate(`allStates.${state}.stateISO`);
return {
value: stateISO,
keyForList: stateISO,
text: stateName,
isSelected: currentState === stateISO,
searchValue: StringUtils.sanitizeString(`${stateISO}${stateName}`),
};
}),
[translate, currentState],
);

Expand Down
12 changes: 5 additions & 7 deletions src/components/StatePicker/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, {useEffect, useState} from 'react';
import {View} from 'react-native';
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import styles from '../../styles/styles';
import MenuItemWithTopDescription from '../MenuItemWithTopDescription';
Expand Down Expand Up @@ -35,16 +34,15 @@ const defaultProps = {

function StatePicker({value, errorText, onInputChange, forwardedRef, label}) {
const {translate} = useLocalize();
const allStates = translate('allStates');
const [isPickerVisible, setIsPickerVisible] = useState(false);
const [searchValue, setSearchValue] = useState(lodashGet(allStates, `${value}.stateName`, ''));
const [searchValue, setSearchValue] = useState(value ? translate(`allStates.${value}.stateName`) : '');

useEffect(() => {
setSearchValue(lodashGet(allStates, `${value}.stateName`, ''));
}, [value, allStates]);
setSearchValue(value ? translate(`allStates.${value}.stateName`) : '');
}, [translate, value]);

const showPickerModal = () => {
setSearchValue(lodashGet(allStates, `${value}.stateName`, ''));
setSearchValue(value ? translate(`allStates.${value}.stateName`) : '');
setIsPickerVisible(true);
};

Expand All @@ -57,7 +55,7 @@ function StatePicker({value, errorText, onInputChange, forwardedRef, label}) {
hidePickerModal();
};

const title = allStates[value] ? allStates[value].stateName : '';
const title = value ? translate(`allStates.${value}.stateName`) : '';
const descStyle = title.length === 0 ? styles.textNormal : null;

return (
Expand Down
46 changes: 44 additions & 2 deletions src/languages/translations.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,51 @@
import _ from 'underscore';
import en from './en';
import es from './es';
import esES from './es-ES';

/**
* Converts an object to it's flattened version.
*
* Ex:
* Input: { common: { yes: "Yes", no: "No" }}
* Output: { "common.yes": "Yes", "common.no": "No" }
*
* @param {Object} obj
* @returns {Object}
*/
// Necessary to export so that it is accessible to the unit tests
// eslint-disable-next-line rulesdir/no-inline-named-export
export function flattenObject(obj) {
const result = {};

const recursive = (data, key) => {
// If the data is a function or not a object (eg. a string), it's
// the value of the key being built and no need for more recursion
if (_.isFunction(data) || _.isArray(data) || !_.isObject(data)) {
result[key] = data;
} else {
let isEmpty = true;

// Recursive call to the keys and connect to the respective data
_.keys(data).forEach((k) => {
isEmpty = false;
recursive(data[k], key ? `${key}.${k}` : k);
});

// Check for when the object is empty but a key exists, so that
// it defaults to an empty object
if (isEmpty && key) {
result[key] = {};
}
}
};

recursive(obj, '');
return result;
}

export default {
en,
es,
en: flattenObject(en),
es: flattenObject(es),
'es-ES': esES,
};
14 changes: 7 additions & 7 deletions src/libs/Localize/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function init() {
* Return translated string for given locale and phrase
*
* @param {String} [desiredLanguage] eg 'en', 'es-ES'
* @param {String|Array} phraseKey
* @param {String} phraseKey
* @param {Object} [phraseParameters] Parameters to supply if the phrase is a template literal.
* @returns {String}
*/
Expand All @@ -47,15 +47,15 @@ function translate(desiredLanguage = CONST.LOCALES.DEFAULT, phraseKey, phrasePar
let translatedPhrase;

// Search phrase in full locale e.g. es-ES
const desiredLanguageDictionary = lodashGet(translations, desiredLanguage);
translatedPhrase = lodashGet(desiredLanguageDictionary, phraseKey);
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 = lodashGet(translations, languageAbbreviation);
translatedPhrase = lodashGet(fallbackLanguageDictionary, phraseKey);
const fallbackLanguageDictionary = translations[languageAbbreviation] || {};
translatedPhrase = fallbackLanguageDictionary[phraseKey];
if (translatedPhrase) {
return Str.result(translatedPhrase, phraseParameters);
}
Expand All @@ -64,8 +64,8 @@ function translate(desiredLanguage = CONST.LOCALES.DEFAULT, phraseKey, phrasePar
}

// Phrase is not translated, search it in default language (en)
const defaultLanguageDictionary = lodashGet(translations, CONST.LOCALES.DEFAULT, {});
translatedPhrase = lodashGet(defaultLanguageDictionary, phraseKey);
const defaultLanguageDictionary = translations[CONST.LOCALES.DEFAULT] || {};
translatedPhrase = defaultLanguageDictionary[phraseKey];
if (translatedPhrase) {
return Str.result(translatedPhrase, phraseParameters);
}
Expand Down
11 changes: 6 additions & 5 deletions src/pages/settings/Preferences/LanguagePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import withLocalize, {withLocalizePropTypes} from '../../../components/withLocal
import * as App from '../../../libs/actions/App';
import Navigation from '../../../libs/Navigation/Navigation';
import ROUTES from '../../../ROUTES';
import CONST from '../../../CONST';
import SelectionList from '../../../components/SelectionList';

const propTypes = {
Expand All @@ -17,11 +18,11 @@ const propTypes = {
};

function LanguagePage(props) {
const localesToLanguages = _.map(props.translate('languagePage.languages'), (language, key) => ({
value: key,
text: language.label,
keyForList: key,
isSelected: props.preferredLocale === key,
const localesToLanguages = _.map(CONST.LANGUAGES, (language) => ({
value: language,
text: props.translate(`languagePage.languages.${language}.label`),
keyForList: language,
isSelected: props.preferredLocale === language,
}));

return (
Expand Down
7 changes: 2 additions & 5 deletions src/pages/settings/Preferences/PreferencesPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ function PreferencesPage(props) {
const {isProduction} = useEnvironment();
const {translate, preferredLocale} = useLocalize();

const priorityModes = translate('priorityModePage.priorityModes');
const languages = translate('languagePage.languages');

return (
<IllustratedHeaderPageLayout
title={translate('common.preferences')}
Expand Down Expand Up @@ -71,13 +68,13 @@ function PreferencesPage(props) {
</View>
<MenuItemWithTopDescription
shouldShowRightIcon
title={priorityModes[props.priorityMode].label}
title={translate(`priorityModePage.priorityModes.${props.priorityMode}.label`)}
description={translate('priorityModePage.priorityMode')}
onPress={() => Navigation.navigate(ROUTES.SETTINGS_PRIORITY_MODE)}
/>
<MenuItemWithTopDescription
shouldShowRightIcon
title={languages[preferredLocale].label}
title={translate(`languagePage.languages.${preferredLocale}.label`)}
description={translate('languagePage.language')}
onPress={() => Navigation.navigate(ROUTES.SETTINGS_LANGUAGE)}
/>
Expand Down
6 changes: 5 additions & 1 deletion src/pages/settings/Profile/ProfilePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ function ProfilePage(props) {
if (pronounsKey.startsWith(CONST.PRONOUNS.PREFIX)) {
pronounsKey = pronounsKey.slice(CONST.PRONOUNS.PREFIX.length);
}
return lodashGet(props.translate('pronouns'), pronounsKey, props.translate('profilePage.selectYourPronouns'));

if (!pronounsKey) {
return props.translate('profilePage.selectYourPronouns');
}
return props.translate(`pronouns.${pronounsKey}`);
};
const currentUserDetails = props.currentUserPersonalDetails || {};
const contactMethodBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(props.loginList);
Expand Down
Loading
Loading