Change performer country value to be ISO code (#1922)

* Change performer country value to be ISO code
* Localize country names
* Use country select for filter

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
InfiniteTF 2022-10-28 07:37:57 +02:00 committed by GitHub
parent 1c0042c4c2
commit 7b7d6758ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1103 additions and 58 deletions

296
pkg/scraper/country.go Normal file
View file

@ -0,0 +1,296 @@
package scraper
import (
"strings"
"github.com/stashapp/stash/pkg/logger"
)
var countryNameMapping = map[string]string{
"afghanistan": "AF",
"albania": "AL",
"algeria": "DZ",
"america": "US",
"american": "US",
"american samoa": "AS",
"andorra": "AD",
"angola": "AO",
"anguilla": "AI",
"antarctica": "AQ",
"antigua and barbuda": "AG",
"argentina": "AR",
"armenia": "AM",
"aruba": "AW",
"australia": "AU",
"austria": "AT",
"azerbaijan": "AZ",
"bahamas": "BS",
"bahrain": "BH",
"bangladesh": "BD",
"barbados": "BB",
"belarus": "BY",
"belgium": "BE",
"belize": "BZ",
"benin": "BJ",
"bermuda": "BM",
"bhutan": "BT",
"bolivia": "BO",
"bosnia and herzegovina": "BA",
"botswana": "BW",
"bouvet island": "BV",
"brazil": "BR",
"british indian ocean territory": "IO",
"brunei darussalam": "BN",
"bulgaria": "BG",
"burkina faso": "BF",
"burundi": "BI",
"cambodia": "KH",
"cameroon": "CM",
"canada": "CA",
"cape verde": "CV",
"cayman islands": "KY",
"central african republic": "CF",
"chad": "TD",
"chile": "CL",
"china": "CN",
"christmas island": "CX",
"cocos (keeling) islands": "CC",
"colombia": "CO",
"comoros": "KM",
"congo": "CG",
"congo the democratic republic of the": "CD",
"cook islands": "CK",
"costa rica": "CR",
"cote d'ivoire": "CI",
"croatia": "HR",
"cuba": "CU",
"cyprus": "CY",
"czech republic": "CZ",
"czechia": "CZ",
"denmark": "DK",
"djibouti": "DJ",
"dominica": "DM",
"dominican republic": "DO",
"ecuador": "EC",
"egypt": "EG",
"el salvador": "SV",
"equatorial guinea": "GQ",
"eritrea": "ER",
"estonia": "EE",
"ethiopia": "ET",
"falkland islands (malvinas)": "FK",
"faroe islands": "FO",
"fiji": "FJ",
"finland": "FI",
"france": "FR",
"french guiana": "GF",
"french polynesia": "PF",
"french southern territories": "TF",
"gabon": "GA",
"gambia": "GM",
"georgia": "GE",
"germany": "DE",
"ghana": "GH",
"gibraltar": "GI",
"greece": "GR",
"greenland": "GL",
"grenada": "GD",
"guadeloupe": "GP",
"guam": "GU",
"guatemala": "GT",
"guinea": "GN",
"guinea-bissau": "GW",
"guyana": "GY",
"haiti": "HT",
"heard island and mcdonald islands": "HM",
"holy see (vatican city state)": "VA",
"honduras": "HN",
"hong kong": "HK",
"hungary": "HU",
"iceland": "IS",
"india": "IN",
"indonesia": "ID",
"iran": "IR",
"iran islamic republic of": "IR",
"iraq": "IQ",
"ireland": "IE",
"israel": "IL",
"italy": "IT",
"jamaica": "JM",
"japan": "JP",
"jordan": "JO",
"kazakhstan": "KZ",
"kenya": "KE",
"kiribati": "KI",
"north korea": "KP",
"south korea": "KR",
"kuwait": "KW",
"kyrgyzstan": "KG",
"lao people's democratic republic": "LA",
"latvia": "LV",
"lebanon": "LB",
"lesotho": "LS",
"liberia": "LR",
"libya": "LY",
"liechtenstein": "LI",
"lithuania": "LT",
"luxembourg": "LU",
"macao": "MO",
"madagascar": "MG",
"malawi": "MW",
"malaysia": "MY",
"maldives": "MV",
"mali": "ML",
"malta": "MT",
"marshall islands": "MH",
"martinique": "MQ",
"mauritania": "MR",
"mauritius": "MU",
"mayotte": "YT",
"mexico": "MX",
"micronesia federated states of": "FM",
"moldova": "MD",
"moldova republic of": "MD",
"moldova, republic of": "MD",
"monaco": "MC",
"mongolia": "MN",
"montserrat": "MS",
"morocco": "MA",
"mozambique": "MZ",
"myanmar": "MM",
"namibia": "NA",
"nauru": "NR",
"nepal": "NP",
"netherlands": "NL",
"new caledonia": "NC",
"new zealand": "NZ",
"nicaragua": "NI",
"niger": "NE",
"nigeria": "NG",
"niue": "NU",
"norfolk island": "NF",
"north macedonia republic of": "MK",
"northern mariana islands": "MP",
"norway": "NO",
"oman": "OM",
"pakistan": "PK",
"palau": "PW",
"palestinian territory occupied": "PS",
"panama": "PA",
"papua new guinea": "PG",
"paraguay": "PY",
"peru": "PE",
"philippines": "PH",
"pitcairn": "PN",
"poland": "PL",
"portugal": "PT",
"puerto rico": "PR",
"qatar": "QA",
"reunion": "RE",
"romania": "RO",
"russia": "RU",
"russian federation": "RU",
"rwanda": "RW",
"saint helena": "SH",
"saint kitts and nevis": "KN",
"saint lucia": "LC",
"saint pierre and miquelon": "PM",
"saint vincent and the grenadines": "VC",
"samoa": "WS",
"san marino": "SM",
"sao tome and principe": "ST",
"saudi arabia": "SA",
"senegal": "SN",
"seychelles": "SC",
"sierra leone": "SL",
"singapore": "SG",
"slovakia": "SK",
"slovak republic": "SK",
"slovenia": "SI",
"solomon islands": "SB",
"somalia": "SO",
"south africa": "ZA",
"south georgia and the south sandwich islands": "GS",
"spain": "ES",
"sri lanka": "LK",
"sudan": "SD",
"suriname": "SR",
"svalbard and jan mayen": "SJ",
"eswatini": "SZ",
"sweden": "SE",
"switzerland": "CH",
"syrian arab republic": "SY",
"taiwan": "TW",
"tajikistan": "TJ",
"tanzania united republic of": "TZ",
"thailand": "TH",
"timor-leste": "TL",
"togo": "TG",
"tokelau": "TK",
"tonga": "TO",
"trinidad and tobago": "TT",
"tunisia": "TN",
"turkey": "TR",
"turkmenistan": "TM",
"turks and caicos islands": "TC",
"tuvalu": "TV",
"uganda": "UG",
"ukraine": "UA",
"united arab emirates": "AE",
"england": "GB",
"great britain": "GB",
"united kingdom": "GB",
"usa": "US",
"united states": "US",
"united states of america": "US",
"united states minor outlying islands": "UM",
"uruguay": "UY",
"uzbekistan": "UZ",
"vanuatu": "VU",
"venezuela": "VE",
"vietnam": "VN",
"virgin islands british": "VG",
"virgin islands u.s.": "VI",
"wallis and futuna": "WF",
"western sahara": "EH",
"yemen": "YE",
"zambia": "ZM",
"zimbabwe": "ZW",
"åland islands": "AX",
"bonaire sint eustatius and saba": "BQ",
"curaçao": "CW",
"guernsey": "GG",
"isle of man": "IM",
"jersey": "JE",
"montenegro": "ME",
"saint barthélemy": "BL",
"saint martin (french part)": "MF",
"serbia": "RS",
"sint maarten (dutch part)": "SX",
"south sudan": "SS",
"kosovo": "XK",
}
func resolveCountryName(name *string) *string {
if name == nil {
return nil
}
trimmedName := strings.TrimSpace(*name)
if len(trimmedName) == 2 {
// If name is two characters it's likely already an ISO value
return &trimmedName
} else if len(trimmedName) == 0 {
return nil
}
v, exists := countryNameMapping[strings.ToLower(trimmedName)]
if exists {
return &v
}
logger.Debugf("Scraped country was not recognized: %s", trimmedName)
// return original name
return &trimmedName
}

View file

@ -66,6 +66,8 @@ func (c Cache) postScrapePerformer(ctx context.Context, p models.ScrapedPerforme
logger.Warnf("Could not set image using URL %s: %s", *p.Image, err.Error())
}
p.Country = resolveCountryName(p.Country)
return p, nil
}
@ -98,6 +100,8 @@ func (c Cache) postScrapeScenePerformer(ctx context.Context, p models.ScrapedPer
}
p.Tags = tags
p.Country = resolveCountryName(p.Country)
return nil
}

View file

@ -22,7 +22,7 @@ import (
"github.com/stashapp/stash/pkg/logger"
)
var appSchemaVersion uint = 36
var appSchemaVersion uint = 37
//go:embed migrations/*.sql
var migrationsBox embed.FS

View file

@ -0,0 +1,269 @@
UPDATE `performers`
SET `country` = CASE
WHEN LENGTH(TRIM(`country`)) == 2 THEN TRIM(`country`)
ELSE CASE `country`
WHEN 'Afghanistan' THEN 'AF'
WHEN 'Albania' THEN 'AL'
WHEN 'Algeria' THEN 'DZ'
WHEN 'America' THEN 'US'
WHEN 'American' THEN 'US'
WHEN 'American Samoa' THEN 'AS'
WHEN 'Andorra' THEN 'AD'
WHEN 'Angola' THEN 'AO'
WHEN 'Anguilla' THEN 'AI'
WHEN 'Antarctica' THEN 'AQ'
WHEN 'Antigua and Barbuda' THEN 'AG'
WHEN 'Argentina' THEN 'AR'
WHEN 'Armenia' THEN 'AM'
WHEN 'Aruba' THEN 'AW'
WHEN 'Australia' THEN 'AU'
WHEN 'Austria' THEN 'AT'
WHEN 'Azerbaijan' THEN 'AZ'
WHEN 'Bahamas' THEN 'BS'
WHEN 'Bahrain' THEN 'BH'
WHEN 'Bangladesh' THEN 'BD'
WHEN 'Barbados' THEN 'BB'
WHEN 'Belarus' THEN 'BY'
WHEN 'Belgium' THEN 'BE'
WHEN 'Belize' THEN 'BZ'
WHEN 'Benin' THEN 'BJ'
WHEN 'Bermuda' THEN 'BM'
WHEN 'Bhutan' THEN 'BT'
WHEN 'Bolivia' THEN 'BO'
WHEN 'Bosnia and Herzegovina' THEN 'BA'
WHEN 'Botswana' THEN 'BW'
WHEN 'Bouvet Island' THEN 'BV'
WHEN 'Brazil' THEN 'BR'
WHEN 'British Indian Ocean Territory' THEN 'IO'
WHEN 'Brunei Darussalam' THEN 'BN'
WHEN 'Bulgaria' THEN 'BG'
WHEN 'Burkina Faso' THEN 'BF'
WHEN 'Burundi' THEN 'BI'
WHEN 'Cambodia' THEN 'KH'
WHEN 'Cameroon' THEN 'CM'
WHEN 'Canada' THEN 'CA'
WHEN 'Cape Verde' THEN 'CV'
WHEN 'Cayman Islands' THEN 'KY'
WHEN 'Central African Republic' THEN 'CF'
WHEN 'Chad' THEN 'TD'
WHEN 'Chile' THEN 'CL'
WHEN 'China' THEN 'CN'
WHEN 'Christmas Island' THEN 'CX'
WHEN 'Cocos (Keeling) Islands' THEN 'CC'
WHEN 'Colombia' THEN 'CO'
WHEN 'Comoros' THEN 'KM'
WHEN 'Congo' THEN 'CG'
WHEN 'Congo the Democratic Republic of the' THEN 'CD'
WHEN 'Cook Islands' THEN 'CK'
WHEN 'Costa Rica' THEN 'CR'
WHEN 'Cote D''Ivoire' THEN 'CI'
WHEN 'Croatia' THEN 'HR'
WHEN 'Cuba' THEN 'CU'
WHEN 'Cyprus' THEN 'CY'
WHEN 'Czech Republic' THEN 'CZ'
WHEN 'Czechia' THEN 'CZ'
WHEN 'Denmark' THEN 'DK'
WHEN 'Djibouti' THEN 'DJ'
WHEN 'Dominica' THEN 'DM'
WHEN 'Dominican Republic' THEN 'DO'
WHEN 'Ecuador' THEN 'EC'
WHEN 'Egypt' THEN 'EG'
WHEN 'El Salvador' THEN 'SV'
WHEN 'Equatorial Guinea' THEN 'GQ'
WHEN 'Eritrea' THEN 'ER'
WHEN 'Estonia' THEN 'EE'
WHEN 'Ethiopia' THEN 'ET'
WHEN 'Falkland Islands (Malvinas)' THEN 'FK'
WHEN 'Faroe Islands' THEN 'FO'
WHEN 'Fiji' THEN 'FJ'
WHEN 'Finland' THEN 'FI'
WHEN 'France' THEN 'FR'
WHEN 'French Guiana' THEN 'GF'
WHEN 'French Polynesia' THEN 'PF'
WHEN 'French Southern Territories' THEN 'TF'
WHEN 'Gabon' THEN 'GA'
WHEN 'Gambia' THEN 'GM'
WHEN 'Georgia' THEN 'GE'
WHEN 'Germany' THEN 'DE'
WHEN 'Ghana' THEN 'GH'
WHEN 'Gibraltar' THEN 'GI'
WHEN 'Greece' THEN 'GR'
WHEN 'Greenland' THEN 'GL'
WHEN 'Grenada' THEN 'GD'
WHEN 'Guadeloupe' THEN 'GP'
WHEN 'Guam' THEN 'GU'
WHEN 'Guatemala' THEN 'GT'
WHEN 'Guinea' THEN 'GN'
WHEN 'Guinea-Bissau' THEN 'GW'
WHEN 'Guyana' THEN 'GY'
WHEN 'Haiti' THEN 'HT'
WHEN 'Heard Island and McDonald Islands' THEN 'HM'
WHEN 'Holy See (Vatican City State)' THEN 'VA'
WHEN 'Honduras' THEN 'HN'
WHEN 'Hong Kong' THEN 'HK'
WHEN 'Hungary' THEN 'HU'
WHEN 'Iceland' THEN 'IS'
WHEN 'India' THEN 'IN'
WHEN 'Indonesia' THEN 'ID'
WHEN 'Iran' THEN 'IR'
WHEN 'Iran Islamic Republic of' THEN 'IR'
WHEN 'Iraq' THEN 'IQ'
WHEN 'Ireland' THEN 'IE'
WHEN 'Israel' THEN 'IL'
WHEN 'Italy' THEN 'IT'
WHEN 'Jamaica' THEN 'JM'
WHEN 'Japan' THEN 'JP'
WHEN 'Jordan' THEN 'JO'
WHEN 'Kazakhstan' THEN 'KZ'
WHEN 'Kenya' THEN 'KE'
WHEN 'Kiribati' THEN 'KI'
WHEN 'North Korea' THEN 'KP'
WHEN 'South Korea' THEN 'KR'
WHEN 'Kuwait' THEN 'KW'
WHEN 'Kyrgyzstan' THEN 'KG'
WHEN 'Lao People''s Democratic Republic' THEN 'LA'
WHEN 'Latvia' THEN 'LV'
WHEN 'Lebanon' THEN 'LB'
WHEN 'Lesotho' THEN 'LS'
WHEN 'Liberia' THEN 'LR'
WHEN 'Libya' THEN 'LY'
WHEN 'Liechtenstein' THEN 'LI'
WHEN 'Lithuania' THEN 'LT'
WHEN 'Luxembourg' THEN 'LU'
WHEN 'Macao' THEN 'MO'
WHEN 'Madagascar' THEN 'MG'
WHEN 'Malawi' THEN 'MW'
WHEN 'Malaysia' THEN 'MY'
WHEN 'Maldives' THEN 'MV'
WHEN 'Mali' THEN 'ML'
WHEN 'Malta' THEN 'MT'
WHEN 'Marshall Islands' THEN 'MH'
WHEN 'Martinique' THEN 'MQ'
WHEN 'Mauritania' THEN 'MR'
WHEN 'Mauritius' THEN 'MU'
WHEN 'Mayotte' THEN 'YT'
WHEN 'Mexico' THEN 'MX'
WHEN 'Micronesia Federated States of' THEN 'FM'
WHEN 'Moldova' THEN 'MD'
WHEN 'Moldova Republic of' THEN 'MD'
WHEN 'Moldova, Republic of' THEN 'MD'
WHEN 'Monaco' THEN 'MC'
WHEN 'Mongolia' THEN 'MN'
WHEN 'Montserrat' THEN 'MS'
WHEN 'Morocco' THEN 'MA'
WHEN 'Mozambique' THEN 'MZ'
WHEN 'Myanmar' THEN 'MM'
WHEN 'Namibia' THEN 'NA'
WHEN 'Nauru' THEN 'NR'
WHEN 'Nepal' THEN 'NP'
WHEN 'Netherlands' THEN 'NL'
WHEN 'New Caledonia' THEN 'NC'
WHEN 'New Zealand' THEN 'NZ'
WHEN 'Nicaragua' THEN 'NI'
WHEN 'Niger' THEN 'NE'
WHEN 'Nigeria' THEN 'NG'
WHEN 'Niue' THEN 'NU'
WHEN 'Norfolk Island' THEN 'NF'
WHEN 'North Macedonia Republic of' THEN 'MK'
WHEN 'Northern Mariana Islands' THEN 'MP'
WHEN 'Norway' THEN 'NO'
WHEN 'Oman' THEN 'OM'
WHEN 'Pakistan' THEN 'PK'
WHEN 'Palau' THEN 'PW'
WHEN 'Palestinian Territory Occupied' THEN 'PS'
WHEN 'Panama' THEN 'PA'
WHEN 'Papua New Guinea' THEN 'PG'
WHEN 'Paraguay' THEN 'PY'
WHEN 'Peru' THEN 'PE'
WHEN 'Philippines' THEN 'PH'
WHEN 'Pitcairn' THEN 'PN'
WHEN 'Poland' THEN 'PL'
WHEN 'Portugal' THEN 'PT'
WHEN 'Puerto Rico' THEN 'PR'
WHEN 'Qatar' THEN 'QA'
WHEN 'Reunion' THEN 'RE'
WHEN 'Romania' THEN 'RO'
WHEN 'Russia' THEN 'RU'
WHEN 'Russian Federation' THEN 'RU'
WHEN 'Rwanda' THEN 'RW'
WHEN 'Saint Helena' THEN 'SH'
WHEN 'Saint Kitts and Nevis' THEN 'KN'
WHEN 'Saint Lucia' THEN 'LC'
WHEN 'Saint Pierre and Miquelon' THEN 'PM'
WHEN 'Saint Vincent and the Grenadines' THEN 'VC'
WHEN 'Samoa' THEN 'WS'
WHEN 'San Marino' THEN 'SM'
WHEN 'Sao Tome and Principe' THEN 'ST'
WHEN 'Saudi Arabia' THEN 'SA'
WHEN 'Senegal' THEN 'SN'
WHEN 'Seychelles' THEN 'SC'
WHEN 'Sierra Leone' THEN 'SL'
WHEN 'Singapore' THEN 'SG'
WHEN 'Slovakia' THEN 'SK'
WHEN 'Slovak Republic' THEN 'SK'
WHEN 'Slovenia' THEN 'SI'
WHEN 'Solomon Islands' THEN 'SB'
WHEN 'Somalia' THEN 'SO'
WHEN 'South Africa' THEN 'ZA'
WHEN 'South Georgia and the South Sandwich Islands' THEN 'GS'
WHEN 'Spain' THEN 'ES'
WHEN 'Sri Lanka' THEN 'LK'
WHEN 'Sudan' THEN 'SD'
WHEN 'Suriname' THEN 'SR'
WHEN 'Svalbard and Jan Mayen' THEN 'SJ'
WHEN 'Eswatini' THEN 'SZ'
WHEN 'Sweden' THEN 'SE'
WHEN 'Switzerland' THEN 'CH'
WHEN 'Syrian Arab Republic' THEN 'SY'
WHEN 'Taiwan' THEN 'TW'
WHEN 'Tajikistan' THEN 'TJ'
WHEN 'Tanzania United Republic of' THEN 'TZ'
WHEN 'Thailand' THEN 'TH'
WHEN 'Timor-Leste' THEN 'TL'
WHEN 'Togo' THEN 'TG'
WHEN 'Tokelau' THEN 'TK'
WHEN 'Tonga' THEN 'TO'
WHEN 'Trinidad and Tobago' THEN 'TT'
WHEN 'Tunisia' THEN 'TN'
WHEN 'Turkey' THEN 'TR'
WHEN 'Turkmenistan' THEN 'TM'
WHEN 'Turks and Caicos Islands' THEN 'TC'
WHEN 'Tuvalu' THEN 'TV'
WHEN 'Uganda' THEN 'UG'
WHEN 'Ukraine' THEN 'UA'
WHEN 'United Arab Emirates' THEN 'AE'
WHEN 'England' THEN 'GB'
WHEN 'Great Britain' THEN 'GB'
WHEN 'United Kingdom' THEN 'GB'
WHEN 'USA' THEN 'US'
WHEN 'United States' THEN 'US'
WHEN 'United States of America' THEN 'US'
WHEN 'United States Minor Outlying Islands' THEN 'UM'
WHEN 'Uruguay' THEN 'UY'
WHEN 'Uzbekistan' THEN 'UZ'
WHEN 'Vanuatu' THEN 'VU'
WHEN 'Venezuela' THEN 'VE'
WHEN 'Vietnam' THEN 'VN'
WHEN 'Virgin Islands British' THEN 'VG'
WHEN 'Virgin Islands U.S.' THEN 'VI'
WHEN 'Wallis and Futuna' THEN 'WF'
WHEN 'Western Sahara' THEN 'EH'
WHEN 'Yemen' THEN 'YE'
WHEN 'Zambia' THEN 'ZM'
WHEN 'Zimbabwe' THEN 'ZW'
WHEN 'Åland Islands' THEN 'AX'
WHEN 'Bonaire Sint Eustatius and Saba' THEN 'BQ'
WHEN 'Curaçao' THEN 'CW'
WHEN 'Guernsey' THEN 'GG'
WHEN 'Isle of Man' THEN 'IM'
WHEN 'Jersey' THEN 'JE'
WHEN 'Montenegro' THEN 'ME'
WHEN 'Saint Barthélemy' THEN 'BL'
WHEN 'Saint Martin (French part)' THEN 'MF'
WHEN 'Serbia' THEN 'RS'
WHEN 'Sint Maarten (Dutch part)' THEN 'SX'
WHEN 'South Sudan' THEN 'SS'
WHEN 'Kosovo' THEN 'XK'
ELSE `country`
END
END;

View file

@ -8,7 +8,7 @@ import { ToastProvider } from "src/hooks/Toast";
import LightboxProvider from "src/hooks/Lightbox/context";
import { initPolyfills } from "src/polyfills";
import locales from "src/locales";
import locales, { registerCountry } from "src/locales";
import {
useConfiguration,
useConfigureUI,
@ -85,6 +85,9 @@ export const App: React.FC = () => {
const defaultMessageLanguage = languageMessageString(defaultLocale);
const messageLanguage = languageMessageString(language);
// register countries for the chosen language
await registerCountry(language);
const defaultMessages = (await locales[defaultMessageLanguage]()).default;
const mergedMessages = cloneDeep(Object.assign({}, defaultMessages));
const chosenMessages = (await locales[messageLanguage]()).default;

View file

@ -28,6 +28,8 @@ import { LabeledIdFilter } from "./Filters/LabeledIdFilter";
import { HierarchicalLabelValueFilter } from "./Filters/HierarchicalLabelValueFilter";
import { OptionsFilter } from "./Filters/OptionsFilter";
import { InputFilter } from "./Filters/InputFilter";
import { CountryCriterion } from "src/models/list-filter/criteria/country";
import { CountrySelect } from "../Shared";
interface IAddFilterProps {
onAddCriterion: (
@ -173,6 +175,18 @@ export const AddFilterDialog: React.FC<IAddFilterProps> = ({
<NumberFilter criterion={criterion} onValueChanged={onValueChanged} />
);
}
if (
criterion instanceof CountryCriterion &&
(criterion.modifier === CriterionModifier.Equals ||
criterion.modifier === CriterionModifier.NotEquals)
) {
return (
<CountrySelect
value={criterion.value}
onChange={(v) => onValueChanged(v)}
/>
);
}
return (
<InputFilter criterion={criterion} onValueChanged={onValueChanged} />
);

View file

@ -2,7 +2,7 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { TagLink } from "src/components/Shared";
import * as GQL from "src/core/generated-graphql";
import { TextUtils, getStashboxBase } from "src/utils";
import { TextUtils, getStashboxBase, getCountryByISO } from "src/utils";
import { TextField, URLField } from "src/utils/field";
interface IPerformerDetails {
@ -114,7 +114,12 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
<TextField id="ethnicity" value={performer.ethnicity} />
<TextField id="hair_color" value={performer.hair_color} />
<TextField id="eye_color" value={performer.eye_color} />
<TextField id="country" value={performer.country} />
<TextField
id="country"
value={
getCountryByISO(performer.country, intl.locale) ?? performer.country
}
/>
<TextField id="height" value={formatHeight(performer.height)} />
<TextField id="weight" value={formatWeight(performer.weight)} />
<TextField id="measurements" value={performer.measurements} />

View file

@ -20,9 +20,9 @@ import {
CollapseButton,
TagSelect,
URLField,
CountrySelect,
} from "src/components/Shared";
import { ImageUtils, getStashIDs } from "src/utils";
import { getCountryByISO } from "src/utils/country";
import { useToast } from "src/hooks";
import { Prompt, useHistory } from "react-router-dom";
import { useFormik } from "formik";
@ -545,7 +545,6 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
const result: GQL.ScrapedPerformerDataFragment = {
...performerResult,
images: performerResult.images ?? undefined,
country: getCountryByISO(performerResult.country),
__typename: "ScrapedPerformer",
};
@ -880,7 +879,19 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
{renderTextField("birthdate", "Birthdate", "YYYY-MM-DD")}
{renderTextField("death_date", "Death Date", "YYYY-MM-DD")}
{renderTextField("country", "Country")}
<Form.Group as={Row}>
<Form.Label column xs={labelXS} xl={labelXL}>
<FormattedMessage id="country" />
</Form.Label>
<Col xs={fieldXS} xl={fieldXL}>
<CountrySelect
value={formik.getFieldProps("country").value}
onChange={(value) => formik.setFieldValue("country", value)}
/>
</Col>
</Form.Group>
{renderTextField("ethnicity", "Ethnicity")}
{renderTextField("hair_color", "Hair Color")}
{renderTextField("eye_color", "Eye Color")}

View file

@ -8,6 +8,7 @@ import {
ScrapedImageRow,
ScrapeDialogRow,
ScrapedTextAreaRow,
ScrapedCountryRow,
} from "src/components/Shared/ScrapeDialog";
import { useTagCreate } from "src/core/StashService";
import { Form } from "react-bootstrap";
@ -448,7 +449,7 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
result={ethnicity}
onChange={(value) => setEthnicity(value)}
/>
<ScrapedInputGroupRow
<ScrapedCountryRow
title={intl.formatMessage({ id: "country" })}
result={country}
onChange={(value) => setCountry(value)}

View file

@ -78,7 +78,7 @@ const PerformerStashBoxModal: React.FC<IProps> = ({
) : performers.length > 0 ? (
<ul className={CLASSNAME_LIST}>
{performers.map((p) => (
<li key={p.url}>
<li key={p.remote_site_id}>
<Button variant="link" onClick={() => onSelectPerformer(p)}>
{p.name}
</Button>

View file

@ -1,21 +1,28 @@
import React from "react";
import { getISOCountry } from "src/utils";
import { useIntl } from "react-intl";
import { getCountryByISO } from "src/utils";
interface ICountryFlag {
country?: string | null;
className?: string;
}
const CountryFlag: React.FC<ICountryFlag> = ({ className, country }) => {
const ISOCountry = getISOCountry(country);
if (!ISOCountry?.code) return <></>;
const CountryFlag: React.FC<ICountryFlag> = ({
className,
country: isoCountry,
}) => {
const { locale } = useIntl();
const country = getCountryByISO(isoCountry, locale);
if (!isoCountry || !country) return <></>;
return (
<span
className={`${
className ?? ""
} flag-icon flag-icon-${ISOCountry.code.toLowerCase()}`}
title={ISOCountry.name}
} flag-icon flag-icon-${isoCountry.toLowerCase()}`}
title={country}
/>
);
};

View file

@ -0,0 +1,24 @@
import React from "react";
import { useIntl } from "react-intl";
import { CountryFlag } from "src/components/Shared";
import { getCountryByISO } from "src/utils";
interface IProps {
country: string | undefined;
showFlag?: boolean;
}
const CountryLabel: React.FC<IProps> = ({ country, showFlag = true }) => {
const { locale } = useIntl();
const fromISO = getCountryByISO(country, locale);
return (
<div>
{showFlag && <CountryFlag className="mr-2" country={country} />}
<span>{fromISO ?? country}</span>
</div>
);
};
export default CountryLabel;

View file

@ -0,0 +1,51 @@
import React from "react";
import Creatable from "react-select/creatable";
import { useIntl } from "react-intl";
import { getCountries } from "src/utils";
import CountryLabel from "./CountryLabel";
interface IProps {
value?: string | undefined;
onChange?: (value: string) => void;
disabled?: boolean;
className?: string;
showFlag?: boolean;
isClearable?: boolean;
}
const CountrySelect: React.FC<IProps> = ({
value,
onChange,
disabled = false,
isClearable = true,
showFlag,
className,
}) => {
const { locale } = useIntl();
const options = getCountries(locale);
const selected = options.find((opt) => opt.value === value) ?? {
label: value,
value,
};
return (
<Creatable
classNamePrefix="react-select"
value={selected}
isClearable={isClearable}
formatOptionLabel={(option) => (
<CountryLabel country={option.value} showFlag={showFlag} />
)}
placeholder="Country"
options={options}
onChange={(selectedOption) => onChange?.(selectedOption?.value ?? "")}
isDisabled={disabled || !onChange}
components={{
IndicatorSeparator: null,
}}
className={`CountrySelect ${className}`}
/>
);
};
export default CountrySelect;

View file

@ -20,6 +20,8 @@ import {
faPlus,
faTimes,
} from "@fortawesome/free-solid-svg-icons";
import { getCountryByISO } from "src/utils";
import CountrySelect from "./CountrySelect";
export class ScrapeResult<T> {
public newValue?: T;
@ -392,3 +394,48 @@ export const ScrapeDialog: React.FC<IScrapeDialogProps> = (
</Modal>
);
};
interface IScrapedCountryRowProps {
title: string;
result: ScrapeResult<string>;
onChange: (value: ScrapeResult<string>) => void;
locked?: boolean;
locale?: string;
}
export const ScrapedCountryRow: React.FC<IScrapedCountryRowProps> = ({
title,
result,
onChange,
locked,
locale,
}) => (
<ScrapeDialogRow
title={title}
result={result}
renderOriginalField={() => (
<FormControl
value={
getCountryByISO(result.originalValue, locale) ?? result.originalValue
}
readOnly
className="bg-secondary text-white border-secondary"
/>
)}
renderNewField={() => (
<CountrySelect
value={result.newValue}
disabled={locked}
onChange={(value) => {
if (onChange) {
onChange(result.cloneWithValue(value));
}
}}
showFlag={false}
isClearable={false}
className="flex-grow-1"
/>
)}
onChange={onChange}
/>
);

View file

@ -21,4 +21,7 @@ export { default as DeleteEntityDialog } from "./DeleteEntityDialog";
export { IndeterminateCheckbox } from "./IndeterminateCheckbox";
export { OperationButton } from "./OperationButton";
export { URLField } from "./URLField";
export { default as CountrySelect } from "./CountrySelect";
export { default as CountryLabel } from "./CountryLabel";
export const TITLE_SUFFIX = " | Stash";

View file

@ -298,3 +298,11 @@ button.collapse-button.btn-primary:not(:disabled):not(.disabled):active {
visibility: hidden;
}
}
.CountrySelect {
/* stylelint-disable */
.react-select__control:hover {
border: none;
}
/* stylelint-enable */
}

View file

@ -132,7 +132,7 @@ const PerformerModal: React.FC<IPerformerModalProps> = ({
birthdate: performer.birthdate,
ethnicity: performer.ethnicity,
eye_color: performer.eye_color,
country: getCountryByISO(performer.country),
country: performer.country,
height: performer.height,
measurements: performer.measurements,
fake_tits: performer.fake_tits,

View file

@ -1,4 +1,5 @@
### ✨ New Features
* Added selector for Country field. ([#1922](https://github.com/stashapp/stash/pull/1922))
* Added tag description filter criterion. ([#3011](https://github.com/stashapp/stash/pull/3011))
### 🐛 Bug fixes

View file

@ -0,0 +1,255 @@
{
"locale": "tw",
"countries": {
"AD": "安道爾",
"AE": "阿聯酋",
"AF": "阿富汗",
"AG": "安地卡及巴布達",
"AI": "安圭拉",
"AL": "阿爾巴尼亞",
"AM": "亞美尼亞",
"AO": "安哥拉",
"AQ": "南極洲",
"AR": "阿根廷",
"AS": "美屬薩摩亞",
"AT": "奧地利",
"AU": "澳大利亞",
"AW": "阿魯巴",
"AX": "奧蘭",
"AZ": "阿塞拜疆",
"BA": "波斯尼亞和黑塞哥維那",
"BB": "巴巴多斯",
"BD": "孟加拉國",
"BE": "比利時",
"BF": "布吉納法索",
"BG": "保加利亞",
"BH": "巴林",
"BI": "布隆迪",
"BJ": "貝寧",
"BL": "聖巴泰勒米",
"BM": "百慕大",
"BN": "文萊",
"BO": "玻利維亞",
"BQ": "加勒比荷蘭",
"BR": "巴西",
"BS": "巴哈馬",
"BT": "不丹",
"BV": "布韋島",
"BW": "博茨瓦納",
"BY": "白俄羅斯",
"BZ": "伯利茲",
"CA": "加拿大",
"CC": "科科斯(基林)群島",
"CD": "剛果(金)",
"CF": "中非",
"CG": "剛果(布)",
"CH": "瑞士",
"CI": "科特迪瓦",
"CK": "庫克群島",
"CL": "智利",
"CM": "喀麥隆",
"CN": "中國",
"CO": "哥倫比亞",
"CR": "哥斯達黎加",
"CU": "古巴",
"CV": "佛得角",
"CW": "庫拉索",
"CX": "聖誕島",
"CY": "賽普勒斯",
"CZ": "捷克",
"DE": "德國",
"DJ": "吉布提",
"DK": "丹麥",
"DM": "多米尼克",
"DO": "多米尼加",
"DZ": "阿爾及利亞",
"EC": "厄瓜多爾",
"EE": "愛沙尼亞",
"EG": "埃及",
"EH": "阿拉伯撒哈拉民主共和國",
"ER": "厄立特里亞",
"ES": "西班牙",
"ET": "衣索比亞",
"FI": "芬蘭",
"FJ": "斐濟",
"FK": "福克蘭群島",
"FM": "密克羅尼西亞聯邦",
"FO": "法羅群島",
"FR": "法國",
"GA": "加彭",
"GB": "英國",
"GD": "格瑞那達",
"GE": "格魯吉亞",
"GF": "法屬圭亞那",
"GG": "根西",
"GH": "加納",
"GI": "直布羅陀",
"GL": "格陵蘭",
"GM": "岡比亞",
"GN": "幾內亞",
"GP": "瓜德羅普",
"GQ": "赤道幾內亞",
"GR": "希臘",
"GS": "南喬治亞和南桑威奇群島",
"GT": "危地馬拉",
"GU": "關島",
"GW": "幾內亞比紹",
"GY": "圭亞那",
"HK": "香港",
"HM": "赫德島和麥克唐納群島",
"HN": "宏都拉斯",
"HR": "克羅地亞",
"HT": "海地",
"HU": "匈牙利",
"ID": "印尼",
"IE": "愛爾蘭",
"IL": "以色列",
"IM": "馬恩島",
"IN": "印度",
"IO": "英屬印度洋領地",
"IQ": "伊拉克",
"IR": "伊朗",
"IS": "冰島",
"IT": "意大利",
"JE": "澤西",
"JM": "牙買加",
"JO": "約旦",
"JP": "日本",
"KE": "肯尼亞",
"KG": "吉爾吉斯斯坦",
"KH": "柬埔寨",
"KI": "基里巴斯",
"KM": "科摩羅",
"KN": "聖基茨和尼維斯",
"KP": "朝鮮",
"KR": "韓國",
"KW": "科威特",
"KY": "開曼群島",
"KZ": "哈薩克斯坦",
"LA": "老撾",
"LB": "黎巴嫩",
"LC": "聖盧西亞",
"LI": "列支敦斯登",
"LK": "斯里蘭卡",
"LR": "利比里亞",
"LS": "賴索托",
"LT": "立陶宛",
"LU": "盧森堡",
"LV": "拉脫維亞",
"LY": "利比亞",
"MA": "摩洛哥",
"MC": "摩納哥",
"MD": "摩爾多瓦",
"ME": "蒙特內哥羅",
"MF": "法屬聖馬丁",
"MG": "馬達加斯加",
"MH": "馬紹爾群島",
"MK": "馬其頓",
"ML": "馬里",
"MM": "緬甸",
"MN": "蒙古",
"MO": "澳門",
"MP": "北馬里亞納群島",
"MQ": "馬提尼克",
"MR": "毛里塔尼亞",
"MS": "蒙特塞拉特",
"MT": "馬爾他",
"MU": "模里西斯",
"MV": "馬爾地夫",
"MW": "馬拉維",
"MX": "墨西哥",
"MY": "馬來西亞",
"MZ": "莫桑比克",
"NA": "納米比亞",
"NC": "新喀裡多尼亞",
"NE": "尼日爾",
"NF": "諾福克島",
"NG": "奈及利亞",
"NI": "尼加拉瓜",
"NL": "荷蘭",
"NO": "挪威",
"NP": "尼泊爾",
"NR": "瑙魯",
"NU": "紐埃",
"NZ": "新西蘭",
"OM": "阿曼",
"PA": "巴拿馬",
"PE": "秘魯",
"PF": "法屬玻里尼西亞",
"PG": "巴布亞新幾內亞",
"PH": "菲律賓",
"PK": "巴基斯坦",
"PL": "波蘭",
"PM": "聖皮埃爾和密克隆",
"PN": "皮特凱恩群島",
"PR": "波多黎各",
"PS": "巴勒斯坦",
"PT": "葡萄牙",
"PW": "帛琉",
"PY": "巴拉圭",
"QA": "卡塔爾",
"RE": "留尼汪",
"RO": "羅馬尼亞",
"RS": "塞爾維亞",
"RU": "俄羅斯",
"RW": "盧旺達",
"SA": "沙烏地阿拉伯",
"SB": "所羅門群島",
"SC": "塞舌爾",
"SD": "蘇丹",
"SE": "瑞典",
"SG": "新加坡",
"SH": "聖赫勒拿",
"SI": "斯洛維尼亞",
"SJ": "斯瓦爾巴群島和揚馬延島",
"SK": "斯洛伐克",
"SL": "塞拉利昂",
"SM": "聖馬力諾",
"SN": "塞內加爾",
"SO": "索馬利亞",
"SR": "蘇里南",
"SS": "南蘇丹",
"ST": "聖多美和普林西比",
"SV": "薩爾瓦多",
"SX": "荷屬聖馬丁",
"SY": "敘利亞",
"SZ": "斯威士蘭",
"TC": "特克斯和凱科斯群島",
"TD": "乍得",
"TF": "法屬南部領地",
"TG": "多哥",
"TH": "泰國",
"TJ": "塔吉克斯坦",
"TK": "托克勞",
"TL": "東帝汶",
"TM": "土庫曼斯坦",
"TN": "突尼西亞",
"TO": "湯加",
"TR": "土耳其",
"TT": "千里達及托巴哥",
"TV": "圖瓦盧",
"TW": "臺灣",
"TZ": "坦桑尼亞",
"UA": "烏克蘭",
"UG": "烏干達",
"UM": "美國本土外小島嶼",
"US": "美國",
"UY": "烏拉圭",
"UZ": "烏茲別克斯坦",
"VA": "梵蒂岡",
"VC": "聖文森及格瑞那丁",
"VE": "委內瑞拉",
"VG": "英屬維爾京群島",
"VI": "美屬維爾京群島",
"VN": "越南",
"VU": "瓦努阿圖",
"WF": "瓦利斯和富圖納",
"WS": "薩摩亞",
"YE": "葉門",
"YT": "馬約特",
"ZA": "南非",
"ZM": "尚比亞",
"ZW": "辛巴威",
"XK": "科索沃"
}
}

View file

@ -1,3 +1,40 @@
import Countries from "i18n-iso-countries";
export const localeCountries = {
en: () => import("i18n-iso-countries/langs/en.json"),
da: () => import("i18n-iso-countries/langs/da.json"),
de: () => import("i18n-iso-countries/langs/de.json"),
es: () => import("i18n-iso-countries/langs/es.json"),
fi: () => import("i18n-iso-countries/langs/fi.json"),
fr: () => import("i18n-iso-countries/langs/fr.json"),
hr: () => import("i18n-iso-countries/langs/hr.json"),
it: () => import("i18n-iso-countries/langs/it.json"),
ja: () => import("i18n-iso-countries/langs/ja.json"),
ko: () => import("i18n-iso-countries/langs/ko.json"),
nl: () => import("i18n-iso-countries/langs/nl.json"),
pl: () => import("i18n-iso-countries/langs/pl.json"),
pt: () => import("i18n-iso-countries/langs/pt.json"),
ru: () => import("i18n-iso-countries/langs/ru.json"),
sv: () => import("i18n-iso-countries/langs/sv.json"),
tr: () => import("i18n-iso-countries/langs/tr.json"),
uk: () => import("i18n-iso-countries/langs/uk.json"),
zh: () => import("i18n-iso-countries/langs/zh.json"),
tw: () => import("src/locales/countryNames/zh-TW.json"),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as { [key: string]: any };
export const getLocaleCode = (code: string) => {
if (code === "zh-CN") return "zh";
if (code === "zh-TW") return "tw";
return code.slice(0, 2);
};
export async function registerCountry(locale: string) {
const localeCode = getLocaleCode(locale);
const countries = await localeCountries[localeCode]();
Countries.registerLocale(countries);
}
export const localeLoader = {
deDE: () => import("./de-DE.json"),
enGB: () => import("./en-GB.json"),

View file

@ -1,3 +1,6 @@
import { IntlShape } from "react-intl";
import { CriterionModifier } from "src/core/generated-graphql";
import { getCountryByISO } from "src/utils";
import { StringCriterion, StringCriterionOption } from "./criterion";
const countryCriterionOption = new StringCriterionOption(
@ -10,4 +13,15 @@ export class CountryCriterion extends StringCriterion {
constructor() {
super(countryCriterionOption);
}
public getLabelValue(intl: IntlShape) {
if (
this.modifier === CriterionModifier.Equals ||
this.modifier === CriterionModifier.NotEquals
) {
return getCountryByISO(this.value, intl.locale) ?? this.value;
}
return super.getLabelValue(intl);
}
}

View file

@ -1,4 +1,5 @@
/* eslint-disable consistent-return */
/* eslint @typescript-eslint/no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */
import { IntlShape } from "react-intl";
import {
@ -61,7 +62,7 @@ export abstract class Criterion<V extends CriterionValue> {
this._value = newValue;
}
public abstract getLabelValue(): string;
public abstract getLabelValue(intl: IntlShape): string;
constructor(type: CriterionOption, value: V) {
this.criterionOption = type;
@ -85,7 +86,7 @@ export abstract class Criterion<V extends CriterionValue> {
this.modifier !== CriterionModifier.IsNull &&
this.modifier !== CriterionModifier.NotNull
) {
valueString = this.getLabelValue();
valueString = this.getLabelValue(intl);
}
return intl.formatMessage(
@ -215,7 +216,7 @@ export class StringCriterion extends Criterion<string> {
super(type, "");
}
public getLabelValue() {
public getLabelValue(_intl: IntlShape) {
return this.value;
}
}
@ -345,7 +346,7 @@ export class NumberCriterion extends Criterion<INumberValue> {
};
}
public getLabelValue() {
public getLabelValue(_intl: IntlShape) {
const { value, value2 } = this.value;
if (
this.modifier === CriterionModifier.Between ||
@ -393,7 +394,7 @@ export class ILabeledIdCriterionOption extends CriterionOption {
}
export class ILabeledIdCriterion extends Criterion<ILabeledId[]> {
public getLabelValue(): string {
public getLabelValue(_intl: IntlShape): string {
return this.value.map((v) => v.label).join(", ");
}
@ -418,7 +419,7 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
};
}
public getLabelValue(): string {
public getLabelValue(_intl: IntlShape): string {
const labels = (this.value.items ?? []).map((v) => v.label).join(", ");
if (this.value.depth === 0) {
@ -478,7 +479,7 @@ export class DurationCriterion extends Criterion<INumberValue> {
};
}
public getLabelValue() {
public getLabelValue(_intl: IntlShape) {
return this.modifier === CriterionModifier.Between ||
this.modifier === CriterionModifier.NotBetween
? `${DurationUtils.secondsToString(

View file

@ -44,6 +44,7 @@ import { InteractiveCriterion } from "./interactive";
import { RatingCriterionOption } from "./rating";
import { DuplicatedCriterion, PhashCriterionOption } from "./phash";
import { CaptionCriterion } from "./captions";
import { CountryCriterion } from "./country";
export function makeCriteria(type: CriterionType = "none") {
switch (type) {
@ -144,8 +145,9 @@ export function makeCriteria(type: CriterionType = "none") {
return new StringCriterion(PhashCriterionOption);
case "duplicated":
return new DuplicatedCriterion();
case "ethnicity":
case "country":
return new CountryCriterion();
case "ethnicity":
case "hair_color":
case "eye_color":
case "height":

View file

@ -1,41 +1,32 @@
import Countries from "i18n-iso-countries";
import english from "i18n-iso-countries/langs/en.json";
import { getLocaleCode } from "src/locales";
Countries.registerLocale(english);
export const getCountryByISO = (
iso: string | null | undefined,
locale: string = "en"
): string | undefined => {
if (!iso) return;
const fuzzyDict: Record<string, string> = {
USA: "US",
"United States": "US",
America: "US",
American: "US",
Czechia: "CZ",
England: "GB",
"United Kingdom": "GB",
Russia: "RU",
"Slovak Republic": "SK",
Iran: "IR",
Moldova: "MD",
Laos: "LA",
const ret = Countries.getName(iso, getLocaleCode(locale));
if (ret) {
return ret;
}
// fallback to english if locale is not en
if (locale !== "en") {
return Countries.getName(iso, "en");
}
};
const getISOCountry = (country: string | null | undefined) => {
if (!country) return null;
export const getCountries = (locale: string = "en") => {
let countries = Countries.getNames(getLocaleCode(locale));
const code =
fuzzyDict[country] ?? Countries.getAlpha2Code(country, "en") ?? country;
// Check if code is valid alpha2 iso
if (!Countries.alpha2ToAlpha3(code)) return null;
if (!countries.length) {
countries = Countries.getNames("en");
}
return {
code,
name: Countries.getName(code, "en"),
return Object.entries(countries).map(([code, name]) => ({
label: name,
value: code,
}));
};
};
export const getCountryByISO = (iso: string | null | undefined) => {
if (!iso) return null;
return Countries.getName(iso, "en") ?? null;
};
export default getISOCountry;

View file

@ -8,9 +8,10 @@ export { default as FormUtils } from "./form";
export { default as DurationUtils } from "./duration";
export { default as SessionUtils } from "./session";
export { default as flattenMessages } from "./flattenMessages";
export { default as getISOCountry } from "./country";
export * from "./country";
export { default as useFocus } from "./focus";
export { default as downloadFile } from "./download";
export * from "./data";
export { getStashIDs } from "./stashIds";
export * from "./stashbox";
export * from "./gender";