feat: add country selection translations and update country name retrieval

This commit is contained in:
estifanos
2026-07-31 07:31:17 +00:00
parent 53b7b3f6f7
commit fca732e399
10 changed files with 77 additions and 95 deletions

View File

@@ -1,65 +1,24 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Group, Select, Text, type ComboboxItem, type SelectProps } from '@mantine/core';
import { getData, getName, overwrite } from 'country-list';
import { registerLocale, getNames, getName } from 'i18n-iso-countries';
import * as Flags from 'country-flag-icons/react/3x2';
import en from 'i18n-iso-countries/langs/en.json';
import am from 'i18n-iso-countries/langs/am.json';
// country-list ships raw ISO 3166 register names, which invert qualifiers into
// a trailing "(the)" / "(Qualifier of)" suffix — e.g. "Iran (Islamic Republic of)".
// Reorder every such entry into natural reading order ("Islamic Republic of Iran").
// Full list + codes still come from country-list; this only rewrites display text.
overwrite([
{ code: 'AE', name: 'United Arab Emirates' },
{ code: 'BO', name: 'Plurinational State of Bolivia' },
{ code: 'BS', name: 'Bahamas' },
{ code: 'CC', name: 'Cocos (Keeling) Islands' },
{ code: 'CD', name: 'Democratic Republic of the Congo' },
{ code: 'CF', name: 'Central African Republic' },
{ code: 'CG', name: 'Congo' },
{ code: 'CK', name: 'Cook Islands' },
{ code: 'DO', name: 'Dominican Republic' },
{ code: 'FK', name: 'Falkland Islands (Malvinas)' },
{ code: 'FM', name: 'Federated States of Micronesia' },
{ code: 'FO', name: 'Faroe Islands' },
{ code: 'GB', name: 'United Kingdom of Great Britain and Northern Ireland' },
{ code: 'GM', name: 'Gambia' },
{ code: 'IO', name: 'British Indian Ocean Territory' },
{ code: 'IR', name: 'Islamic Republic of Iran' },
{ code: 'KM', name: 'Comoros' },
{ code: 'KP', name: "Democratic People's Republic of Korea" },
{ code: 'KR', name: 'Republic of Korea' },
{ code: 'KY', name: 'Cayman Islands' },
{ code: 'LA', name: "Lao People's Democratic Republic" },
{ code: 'MD', name: 'Republic of Moldova' },
{ code: 'MF', name: 'Saint Martin' },
{ code: 'MH', name: 'Marshall Islands' },
{ code: 'MP', name: 'Northern Mariana Islands' },
{ code: 'NE', name: 'Niger' },
{ code: 'NL', name: 'Kingdom of the Netherlands' },
{ code: 'PH', name: 'Philippines' },
{ code: 'PS', name: 'State of Palestine' },
{ code: 'RU', name: 'Russian Federation' },
{ code: 'SD', name: 'Sudan' },
{ code: 'SX', name: 'Sint Maarten' },
{ code: 'SY', name: 'Syrian Arab Republic' },
{ code: 'TC', name: 'Turks and Caicos Islands' },
{ code: 'TF', name: 'French Southern Territories' },
{ code: 'TW', name: 'Taiwan' },
{ code: 'TZ', name: 'United Republic of Tanzania' },
{ code: 'UM', name: 'United States Minor Outlying Islands' },
{ code: 'US', name: 'United States of America' },
{ code: 'VA', name: 'Holy See' },
{ code: 'VE', name: 'Bolivarian Republic of Venezuela' },
{ code: 'VG', name: 'British Virgin Islands' },
{ code: 'VI', name: 'United States Virgin Islands' },
]);
// Registered once at module load — locale data is static.
registerLocale(en);
registerLocale(am);
// Built once at module load — static data, no hook memoization needed.
const COUNTRIES = getData()
.map(({ code, name }) => ({ value: code, label: name }))
.sort((a, b) => a.label.localeCompare(b.label));
type CountryLang = 'en' | 'am';
/** English country name for an alpha-2 code; '' when unset/unknown. */
export function getCountryName(code: string | null | undefined): string {
return code ? (getName(code) ?? '') : '';
function resolveLang(lng: string): CountryLang {
return lng === 'am' ? 'am' : 'en';
}
/** Localized country name for an alpha-2 code; '' when unset/unknown. */
export function getCountryName(code: string | null | undefined, lang: CountryLang = 'en'): string {
return code ? (getName(code, lang) ?? '') : '';
}
function CountryFlag({ code }: { code: string }) {
@@ -98,19 +57,30 @@ export interface CountrySelectProps {
export function CountrySelect({
value,
onChange,
placeholder = 'Select a country',
placeholder,
...rest
}: CountrySelectProps) {
const { t, i18n } = useTranslation();
const lang = resolveLang(i18n.language);
const countries = useMemo(
() =>
Object.entries(getNames(lang))
.map(([code, label]) => ({ value: code, label }))
.sort((a, b) => a.label.localeCompare(b.label, lang)),
[lang],
);
return (
<Select
data={COUNTRIES}
data={countries}
value={value}
onChange={onChange}
placeholder={placeholder}
placeholder={placeholder ?? t('country.select')}
leftSection={value ? <CountryFlag code={value} /> : undefined}
renderOption={renderCountryOption}
filter={filterCountries}
nothingFoundMessage="No countries found"
nothingFoundMessage={t('country.notFound')}
searchable
clearable
maxDropdownHeight={320}