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:
Michael Telatynski 2023-08-22 17:15:16 +01:00 committed by GitHub
parent ac70f7ac9b
commit 0d8b58cdd7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 48 additions and 523 deletions

View file

@ -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 }>;

View file

@ -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 dIvoire": "Côte dIvoire",
"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

View file

@ -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();
});
});