Get country names from the browser instead of manual i18n (#11428)
* Get country names from the browser instead of manual i18n * Make getUserLanguage more resilient to bad inputs * Improve coverage
This commit is contained in:
parent
ac70f7ac9b
commit
0d8b58cdd7
4 changed files with 48 additions and 523 deletions
|
@ -18,16 +18,15 @@ import React, { ReactElement } from "react";
|
|||
|
||||
import { COUNTRIES, getEmojiFlag, PhoneNumberCountryDefinition } from "../../../phonenumber";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { _t, getUserLanguage } from "../../../languageHandler";
|
||||
import Dropdown from "../elements/Dropdown";
|
||||
import { NonEmptyArray } from "../../../@types/common";
|
||||
|
||||
const COUNTRIES_BY_ISO2: Record<string, PhoneNumberCountryDefinition> = {};
|
||||
for (const c of COUNTRIES) {
|
||||
COUNTRIES_BY_ISO2[c.iso2] = c;
|
||||
interface InternationalisedCountry extends PhoneNumberCountryDefinition {
|
||||
name: string; // already translated to the user's locale
|
||||
}
|
||||
|
||||
function countryMatchesSearchQuery(query: string, country: PhoneNumberCountryDefinition): boolean {
|
||||
function countryMatchesSearchQuery(query: string, country: InternationalisedCountry): boolean {
|
||||
// Remove '+' if present (when searching for a prefix)
|
||||
if (query[0] === "+") {
|
||||
query = query.slice(1);
|
||||
|
@ -41,7 +40,7 @@ function countryMatchesSearchQuery(query: string, country: PhoneNumberCountryDef
|
|||
|
||||
interface IProps {
|
||||
value?: string;
|
||||
onOptionChange: (country: PhoneNumberCountryDefinition) => void;
|
||||
onOptionChange: (country: InternationalisedCountry) => void;
|
||||
isSmall: boolean; // if isSmall, show +44 in the selected value
|
||||
showPrefix: boolean;
|
||||
className?: string;
|
||||
|
@ -53,15 +52,25 @@ interface IState {
|
|||
}
|
||||
|
||||
export default class CountryDropdown extends React.Component<IProps, IState> {
|
||||
private readonly defaultCountry: PhoneNumberCountryDefinition;
|
||||
private readonly defaultCountry: InternationalisedCountry;
|
||||
private readonly countries: InternationalisedCountry[];
|
||||
private readonly countryMap: Map<string, InternationalisedCountry>;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
let defaultCountry: PhoneNumberCountryDefinition | undefined;
|
||||
const displayNames = new Intl.DisplayNames([getUserLanguage()], { type: "region" });
|
||||
|
||||
this.countries = COUNTRIES.map((c) => ({
|
||||
name: displayNames.of(c.iso2) ?? c.iso2,
|
||||
...c,
|
||||
}));
|
||||
this.countryMap = new Map(this.countries.map((c) => [c.iso2, c]));
|
||||
|
||||
let defaultCountry: InternationalisedCountry | undefined;
|
||||
const defaultCountryCode = SdkConfig.get("default_country_code");
|
||||
if (defaultCountryCode) {
|
||||
const country = COUNTRIES.find((c) => c.iso2 === defaultCountryCode.toUpperCase());
|
||||
const country = this.countries.find((c) => c.iso2 === defaultCountryCode.toUpperCase());
|
||||
if (country) defaultCountry = country;
|
||||
}
|
||||
|
||||
|
@ -69,9 +78,8 @@ export default class CountryDropdown extends React.Component<IProps, IState> {
|
|||
try {
|
||||
const locale = new Intl.Locale(navigator.language ?? navigator.languages[0]);
|
||||
const code = locale.region ?? locale.language ?? locale.baseName;
|
||||
const displayNames = new Intl.DisplayNames(["en"], { type: "region" });
|
||||
const displayName = displayNames.of(code)!.toUpperCase();
|
||||
defaultCountry = COUNTRIES.find(
|
||||
defaultCountry = this.countries.find(
|
||||
(c) => c.iso2 === code.toUpperCase() || c.name.toUpperCase() === displayName,
|
||||
);
|
||||
} catch (e) {
|
||||
|
@ -79,7 +87,7 @@ export default class CountryDropdown extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
this.defaultCountry = defaultCountry ?? COUNTRIES[0];
|
||||
this.defaultCountry = defaultCountry ?? this.countries[0];
|
||||
this.state = {
|
||||
searchQuery: "",
|
||||
};
|
||||
|
@ -101,7 +109,7 @@ export default class CountryDropdown extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private onOptionChange = (iso2: string): void => {
|
||||
this.props.onOptionChange(COUNTRIES_BY_ISO2[iso2]);
|
||||
this.props.onOptionChange(this.countryMap.get(iso2)!);
|
||||
};
|
||||
|
||||
private flagImgForIso2(iso2: string): React.ReactNode {
|
||||
|
@ -112,9 +120,9 @@ export default class CountryDropdown extends React.Component<IProps, IState> {
|
|||
if (!this.props.isSmall) {
|
||||
return undefined;
|
||||
}
|
||||
let countryPrefix;
|
||||
let countryPrefix: string | undefined;
|
||||
if (this.props.showPrefix) {
|
||||
countryPrefix = "+" + COUNTRIES_BY_ISO2[iso2].prefix;
|
||||
countryPrefix = "+" + this.countryMap.get(iso2)!.prefix;
|
||||
}
|
||||
return (
|
||||
<span className="mx_CountryDropdown_shortOption">
|
||||
|
@ -125,26 +133,28 @@ export default class CountryDropdown extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
let displayedCountries;
|
||||
let displayedCountries: InternationalisedCountry[];
|
||||
if (this.state.searchQuery) {
|
||||
displayedCountries = COUNTRIES.filter(countryMatchesSearchQuery.bind(this, this.state.searchQuery));
|
||||
if (this.state.searchQuery.length == 2 && COUNTRIES_BY_ISO2[this.state.searchQuery.toUpperCase()]) {
|
||||
displayedCountries = this.countries.filter((country) =>
|
||||
countryMatchesSearchQuery(this.state.searchQuery, country),
|
||||
);
|
||||
if (this.state.searchQuery.length == 2 && this.countryMap.has(this.state.searchQuery.toUpperCase())) {
|
||||
// exact ISO2 country name match: make the first result the matches ISO2
|
||||
const matched = COUNTRIES_BY_ISO2[this.state.searchQuery.toUpperCase()];
|
||||
const matched = this.countryMap.get(this.state.searchQuery.toUpperCase())!;
|
||||
displayedCountries = displayedCountries.filter((c) => {
|
||||
return c.iso2 != matched.iso2;
|
||||
});
|
||||
displayedCountries.unshift(matched);
|
||||
}
|
||||
} else {
|
||||
displayedCountries = COUNTRIES;
|
||||
displayedCountries = this.countries;
|
||||
}
|
||||
|
||||
const options = displayedCountries.map((country) => {
|
||||
return (
|
||||
<div className="mx_CountryDropdown_option" key={country.iso2}>
|
||||
{this.flagImgForIso2(country.iso2)}
|
||||
{_t(country.name)} (+{country.prefix})
|
||||
{country.name} (+{country.prefix})
|
||||
</div>
|
||||
);
|
||||
}) as NonEmptyArray<ReactElement & { key: string }>;
|
||||
|
|
|
@ -106,255 +106,6 @@
|
|||
"Unable to enable Notifications": "Unable to enable Notifications",
|
||||
"This email address was not found": "This email address was not found",
|
||||
"Your email address does not appear to be associated with a Matrix ID on this homeserver.": "Your email address does not appear to be associated with a Matrix ID on this homeserver.",
|
||||
"United Kingdom": "United Kingdom",
|
||||
"United States": "United States",
|
||||
"Afghanistan": "Afghanistan",
|
||||
"Åland Islands": "Åland Islands",
|
||||
"Albania": "Albania",
|
||||
"Algeria": "Algeria",
|
||||
"American Samoa": "American Samoa",
|
||||
"Andorra": "Andorra",
|
||||
"Angola": "Angola",
|
||||
"Anguilla": "Anguilla",
|
||||
"Antarctica": "Antarctica",
|
||||
"Antigua & Barbuda": "Antigua & Barbuda",
|
||||
"Argentina": "Argentina",
|
||||
"Armenia": "Armenia",
|
||||
"Aruba": "Aruba",
|
||||
"Australia": "Australia",
|
||||
"Austria": "Austria",
|
||||
"Azerbaijan": "Azerbaijan",
|
||||
"Bahamas": "Bahamas",
|
||||
"Bahrain": "Bahrain",
|
||||
"Bangladesh": "Bangladesh",
|
||||
"Barbados": "Barbados",
|
||||
"Belarus": "Belarus",
|
||||
"Belgium": "Belgium",
|
||||
"Belize": "Belize",
|
||||
"Benin": "Benin",
|
||||
"Bermuda": "Bermuda",
|
||||
"Bhutan": "Bhutan",
|
||||
"Bolivia": "Bolivia",
|
||||
"Bosnia": "Bosnia",
|
||||
"Botswana": "Botswana",
|
||||
"Bouvet Island": "Bouvet Island",
|
||||
"Brazil": "Brazil",
|
||||
"British Indian Ocean Territory": "British Indian Ocean Territory",
|
||||
"British Virgin Islands": "British Virgin Islands",
|
||||
"Brunei": "Brunei",
|
||||
"Bulgaria": "Bulgaria",
|
||||
"Burkina Faso": "Burkina Faso",
|
||||
"Burundi": "Burundi",
|
||||
"Cambodia": "Cambodia",
|
||||
"Cameroon": "Cameroon",
|
||||
"Canada": "Canada",
|
||||
"Cape Verde": "Cape Verde",
|
||||
"Caribbean Netherlands": "Caribbean Netherlands",
|
||||
"Cayman Islands": "Cayman Islands",
|
||||
"Central African Republic": "Central African Republic",
|
||||
"Chad": "Chad",
|
||||
"Chile": "Chile",
|
||||
"China": "China",
|
||||
"Christmas Island": "Christmas Island",
|
||||
"Cocos (Keeling) Islands": "Cocos (Keeling) Islands",
|
||||
"Colombia": "Colombia",
|
||||
"Comoros": "Comoros",
|
||||
"Congo - Brazzaville": "Congo - Brazzaville",
|
||||
"Congo - Kinshasa": "Congo - Kinshasa",
|
||||
"Cook Islands": "Cook Islands",
|
||||
"Costa Rica": "Costa Rica",
|
||||
"Croatia": "Croatia",
|
||||
"Cuba": "Cuba",
|
||||
"Curaçao": "Curaçao",
|
||||
"Cyprus": "Cyprus",
|
||||
"Czech Republic": "Czech Republic",
|
||||
"Côte d’Ivoire": "Côte d’Ivoire",
|
||||
"Denmark": "Denmark",
|
||||
"Djibouti": "Djibouti",
|
||||
"Dominica": "Dominica",
|
||||
"Dominican Republic": "Dominican Republic",
|
||||
"Ecuador": "Ecuador",
|
||||
"Egypt": "Egypt",
|
||||
"El Salvador": "El Salvador",
|
||||
"Equatorial Guinea": "Equatorial Guinea",
|
||||
"Eritrea": "Eritrea",
|
||||
"Estonia": "Estonia",
|
||||
"Ethiopia": "Ethiopia",
|
||||
"Falkland Islands": "Falkland Islands",
|
||||
"Faroe Islands": "Faroe Islands",
|
||||
"Fiji": "Fiji",
|
||||
"Finland": "Finland",
|
||||
"France": "France",
|
||||
"French Guiana": "French Guiana",
|
||||
"French Polynesia": "French Polynesia",
|
||||
"French Southern Territories": "French Southern Territories",
|
||||
"Gabon": "Gabon",
|
||||
"Gambia": "Gambia",
|
||||
"Georgia": "Georgia",
|
||||
"Germany": "Germany",
|
||||
"Ghana": "Ghana",
|
||||
"Gibraltar": "Gibraltar",
|
||||
"Greece": "Greece",
|
||||
"Greenland": "Greenland",
|
||||
"Grenada": "Grenada",
|
||||
"Guadeloupe": "Guadeloupe",
|
||||
"Guam": "Guam",
|
||||
"Guatemala": "Guatemala",
|
||||
"Guernsey": "Guernsey",
|
||||
"Guinea": "Guinea",
|
||||
"Guinea-Bissau": "Guinea-Bissau",
|
||||
"Guyana": "Guyana",
|
||||
"Haiti": "Haiti",
|
||||
"Heard & McDonald Islands": "Heard & McDonald Islands",
|
||||
"Honduras": "Honduras",
|
||||
"Hong Kong": "Hong Kong",
|
||||
"Hungary": "Hungary",
|
||||
"Iceland": "Iceland",
|
||||
"India": "India",
|
||||
"Indonesia": "Indonesia",
|
||||
"Iran": "Iran",
|
||||
"Iraq": "Iraq",
|
||||
"Ireland": "Ireland",
|
||||
"Isle of Man": "Isle of Man",
|
||||
"Israel": "Israel",
|
||||
"Italy": "Italy",
|
||||
"Jamaica": "Jamaica",
|
||||
"Japan": "Japan",
|
||||
"Jersey": "Jersey",
|
||||
"Jordan": "Jordan",
|
||||
"Kazakhstan": "Kazakhstan",
|
||||
"Kenya": "Kenya",
|
||||
"Kiribati": "Kiribati",
|
||||
"Kosovo": "Kosovo",
|
||||
"Kuwait": "Kuwait",
|
||||
"Kyrgyzstan": "Kyrgyzstan",
|
||||
"Laos": "Laos",
|
||||
"Latvia": "Latvia",
|
||||
"Lebanon": "Lebanon",
|
||||
"Lesotho": "Lesotho",
|
||||
"Liberia": "Liberia",
|
||||
"Libya": "Libya",
|
||||
"Liechtenstein": "Liechtenstein",
|
||||
"Lithuania": "Lithuania",
|
||||
"Luxembourg": "Luxembourg",
|
||||
"Macau": "Macau",
|
||||
"Macedonia": "Macedonia",
|
||||
"Madagascar": "Madagascar",
|
||||
"Malawi": "Malawi",
|
||||
"Malaysia": "Malaysia",
|
||||
"Maldives": "Maldives",
|
||||
"Mali": "Mali",
|
||||
"Malta": "Malta",
|
||||
"Marshall Islands": "Marshall Islands",
|
||||
"Martinique": "Martinique",
|
||||
"Mauritania": "Mauritania",
|
||||
"Mauritius": "Mauritius",
|
||||
"Mayotte": "Mayotte",
|
||||
"Mexico": "Mexico",
|
||||
"Micronesia": "Micronesia",
|
||||
"Moldova": "Moldova",
|
||||
"Monaco": "Monaco",
|
||||
"Mongolia": "Mongolia",
|
||||
"Montenegro": "Montenegro",
|
||||
"Montserrat": "Montserrat",
|
||||
"Morocco": "Morocco",
|
||||
"Mozambique": "Mozambique",
|
||||
"Myanmar": "Myanmar",
|
||||
"Namibia": "Namibia",
|
||||
"Nauru": "Nauru",
|
||||
"Nepal": "Nepal",
|
||||
"Netherlands": "Netherlands",
|
||||
"New Caledonia": "New Caledonia",
|
||||
"New Zealand": "New Zealand",
|
||||
"Nicaragua": "Nicaragua",
|
||||
"Niger": "Niger",
|
||||
"Nigeria": "Nigeria",
|
||||
"Niue": "Niue",
|
||||
"Norfolk Island": "Norfolk Island",
|
||||
"North Korea": "North Korea",
|
||||
"Northern Mariana Islands": "Northern Mariana Islands",
|
||||
"Norway": "Norway",
|
||||
"Oman": "Oman",
|
||||
"Pakistan": "Pakistan",
|
||||
"Palau": "Palau",
|
||||
"Palestine": "Palestine",
|
||||
"Panama": "Panama",
|
||||
"Papua New Guinea": "Papua New Guinea",
|
||||
"Paraguay": "Paraguay",
|
||||
"Peru": "Peru",
|
||||
"Philippines": "Philippines",
|
||||
"Pitcairn Islands": "Pitcairn Islands",
|
||||
"Poland": "Poland",
|
||||
"Portugal": "Portugal",
|
||||
"Puerto Rico": "Puerto Rico",
|
||||
"Qatar": "Qatar",
|
||||
"Romania": "Romania",
|
||||
"Russia": "Russia",
|
||||
"Rwanda": "Rwanda",
|
||||
"Réunion": "Réunion",
|
||||
"Samoa": "Samoa",
|
||||
"San Marino": "San Marino",
|
||||
"Saudi Arabia": "Saudi Arabia",
|
||||
"Senegal": "Senegal",
|
||||
"Serbia": "Serbia",
|
||||
"Seychelles": "Seychelles",
|
||||
"Sierra Leone": "Sierra Leone",
|
||||
"Singapore": "Singapore",
|
||||
"Sint Maarten": "Sint Maarten",
|
||||
"Slovakia": "Slovakia",
|
||||
"Slovenia": "Slovenia",
|
||||
"Solomon Islands": "Solomon Islands",
|
||||
"Somalia": "Somalia",
|
||||
"South Africa": "South Africa",
|
||||
"South Georgia & South Sandwich Islands": "South Georgia & South Sandwich Islands",
|
||||
"South Korea": "South Korea",
|
||||
"South Sudan": "South Sudan",
|
||||
"Spain": "Spain",
|
||||
"Sri Lanka": "Sri Lanka",
|
||||
"St. Barthélemy": "St. Barthélemy",
|
||||
"St. Helena": "St. Helena",
|
||||
"St. Kitts & Nevis": "St. Kitts & Nevis",
|
||||
"St. Lucia": "St. Lucia",
|
||||
"St. Martin": "St. Martin",
|
||||
"St. Pierre & Miquelon": "St. Pierre & Miquelon",
|
||||
"St. Vincent & Grenadines": "St. Vincent & Grenadines",
|
||||
"Sudan": "Sudan",
|
||||
"Suriname": "Suriname",
|
||||
"Svalbard & Jan Mayen": "Svalbard & Jan Mayen",
|
||||
"Swaziland": "Swaziland",
|
||||
"Sweden": "Sweden",
|
||||
"Switzerland": "Switzerland",
|
||||
"Syria": "Syria",
|
||||
"São Tomé & Príncipe": "São Tomé & Príncipe",
|
||||
"Taiwan": "Taiwan",
|
||||
"Tajikistan": "Tajikistan",
|
||||
"Tanzania": "Tanzania",
|
||||
"Thailand": "Thailand",
|
||||
"Timor-Leste": "Timor-Leste",
|
||||
"Togo": "Togo",
|
||||
"Tokelau": "Tokelau",
|
||||
"Tonga": "Tonga",
|
||||
"Trinidad & Tobago": "Trinidad & Tobago",
|
||||
"Tunisia": "Tunisia",
|
||||
"Turkey": "Turkey",
|
||||
"Turkmenistan": "Turkmenistan",
|
||||
"Turks & Caicos Islands": "Turks & Caicos Islands",
|
||||
"Tuvalu": "Tuvalu",
|
||||
"U.S. Virgin Islands": "U.S. Virgin Islands",
|
||||
"Uganda": "Uganda",
|
||||
"Ukraine": "Ukraine",
|
||||
"United Arab Emirates": "United Arab Emirates",
|
||||
"Uruguay": "Uruguay",
|
||||
"Uzbekistan": "Uzbekistan",
|
||||
"Vanuatu": "Vanuatu",
|
||||
"Vatican City": "Vatican City",
|
||||
"Venezuela": "Venezuela",
|
||||
"Vietnam": "Vietnam",
|
||||
"Wallis & Futuna": "Wallis & Futuna",
|
||||
"Western Sahara": "Western Sahara",
|
||||
"Yemen": "Yemen",
|
||||
"Zambia": "Zambia",
|
||||
"Zimbabwe": "Zimbabwe",
|
||||
"Sign In or Create Account": "Sign In or Create Account",
|
||||
"Sign In": "Sign In",
|
||||
"Use your account or create a new one to continue.": "Use your account or create a new one to continue.",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,7 +15,8 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "@testing-library/react";
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import CountryDropdown from "../../../../src/components/views/auth/CountryDropdown";
|
||||
import SdkConfig from "../../../../src/SdkConfig";
|
||||
|
@ -65,4 +66,18 @@ describe("CountryDropdown", () => {
|
|||
expect(fn).toHaveBeenCalledWith(expect.objectContaining({ prefix: defaultCountryCode.toString() }));
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow filtering", async () => {
|
||||
const fn = jest.fn();
|
||||
const { getByRole, findByText } = render(
|
||||
<CountryDropdown onOptionChange={fn} isSmall={false} showPrefix={false} />,
|
||||
);
|
||||
|
||||
const dropdown = getByRole("button");
|
||||
fireEvent.click(dropdown);
|
||||
|
||||
await userEvent.keyboard("Al");
|
||||
|
||||
await expect(findByText("Albania (+355)")).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue