diff --git a/resources/dictionaries/countries.json b/resources/dictionaries/countries.json new file mode 100644 index 0000000000..41592b0d81 --- /dev/null +++ b/resources/dictionaries/countries.json @@ -0,0 +1 @@ +[{"name":"Afghanistan","iso3":"AFG","iso2":"AF","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\udde6\ud83c\uddeb"},{"name":"Aland Islands","iso3":"ALA","iso2":"AX","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\udde6\ud83c\uddfd"},{"name":"Albania","iso3":"ALB","iso2":"AL","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\udde6\ud83c\uddf1"},{"name":"Algeria","iso3":"DZA","iso2":"DZ","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\udde9\ud83c\uddff"},{"name":"American Samoa","iso3":"ASM","iso2":"AS","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\udde6\ud83c\uddf8"},{"name":"Andorra","iso3":"AND","iso2":"AD","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\udde6\ud83c\udde9"},{"name":"Angola","iso3":"AGO","iso2":"AO","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\udde6\ud83c\uddf4"},{"name":"Anguilla","iso3":"AIA","iso2":"AI","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde6\ud83c\uddee"},{"name":"Antarctica","iso3":"ATA","iso2":"AQ","region":"Polar","subregion":"","emoji":"\ud83c\udde6\ud83c\uddf6"},{"name":"Antigua And Barbuda","iso3":"ATG","iso2":"AG","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde6\ud83c\uddec"},{"name":"Argentina","iso3":"ARG","iso2":"AR","region":"Americas","subregion":"South America","emoji":"\ud83c\udde6\ud83c\uddf7"},{"name":"Armenia","iso3":"ARM","iso2":"AM","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\udde6\ud83c\uddf2"},{"name":"Aruba","iso3":"ABW","iso2":"AW","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde6\ud83c\uddfc"},{"name":"Australia","iso3":"AUS","iso2":"AU","region":"Oceania","subregion":"Australia and New Zealand","emoji":"\ud83c\udde6\ud83c\uddfa"},{"name":"Austria","iso3":"AUT","iso2":"AT","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\udde6\ud83c\uddf9"},{"name":"Azerbaijan","iso3":"AZE","iso2":"AZ","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\udde6\ud83c\uddff"},{"name":"Bahamas The","iso3":"BHS","iso2":"BS","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde7\ud83c\uddf8"},{"name":"Bahrain","iso3":"BHR","iso2":"BH","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\udde7\ud83c\udded"},{"name":"Bangladesh","iso3":"BGD","iso2":"BD","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\udde7\ud83c\udde9"},{"name":"Barbados","iso3":"BRB","iso2":"BB","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde7\ud83c\udde7"},{"name":"Belarus","iso3":"BLR","iso2":"BY","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\udde7\ud83c\uddfe"},{"name":"Belgium","iso3":"BEL","iso2":"BE","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\udde7\ud83c\uddea"},{"name":"Belize","iso3":"BLZ","iso2":"BZ","region":"Americas","subregion":"Central America","emoji":"\ud83c\udde7\ud83c\uddff"},{"name":"Benin","iso3":"BEN","iso2":"BJ","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\udde7\ud83c\uddef"},{"name":"Bermuda","iso3":"BMU","iso2":"BM","region":"Americas","subregion":"Northern America","emoji":"\ud83c\udde7\ud83c\uddf2"},{"name":"Bhutan","iso3":"BTN","iso2":"BT","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\udde7\ud83c\uddf9"},{"name":"Bolivia","iso3":"BOL","iso2":"BO","region":"Americas","subregion":"South America","emoji":"\ud83c\udde7\ud83c\uddf4"},{"name":"Bonaire, Sint Eustatius and Saba","iso3":"BES","iso2":"BQ","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde7\ud83c\uddf6"},{"name":"Bosnia and Herzegovina","iso3":"BIH","iso2":"BA","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\udde7\ud83c\udde6"},{"name":"Botswana","iso3":"BWA","iso2":"BW","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\udde7\ud83c\uddfc"},{"name":"Bouvet Island","iso3":"BVT","iso2":"BV","region":"","subregion":"","emoji":"\ud83c\udde7\ud83c\uddfb"},{"name":"Brazil","iso3":"BRA","iso2":"BR","region":"Americas","subregion":"South America","emoji":"\ud83c\udde7\ud83c\uddf7"},{"name":"British Indian Ocean Territory","iso3":"IOT","iso2":"IO","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddee\ud83c\uddf4"},{"name":"Brunei","iso3":"BRN","iso2":"BN","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\udde7\ud83c\uddf3"},{"name":"Bulgaria","iso3":"BGR","iso2":"BG","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\udde7\ud83c\uddec"},{"name":"Burkina Faso","iso3":"BFA","iso2":"BF","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\udde7\ud83c\uddeb"},{"name":"Burundi","iso3":"BDI","iso2":"BI","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\udde7\ud83c\uddee"},{"name":"Cambodia","iso3":"KHM","iso2":"KH","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf0\ud83c\udded"},{"name":"Cameroon","iso3":"CMR","iso2":"CM","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\udde8\ud83c\uddf2"},{"name":"Canada","iso3":"CAN","iso2":"CA","region":"Americas","subregion":"Northern America","emoji":"\ud83c\udde8\ud83c\udde6"},{"name":"Cape Verde","iso3":"CPV","iso2":"CV","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\udde8\ud83c\uddfb"},{"name":"Cayman Islands","iso3":"CYM","iso2":"KY","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf0\ud83c\uddfe"},{"name":"Central African Republic","iso3":"CAF","iso2":"CF","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\udde8\ud83c\uddeb"},{"name":"Chad","iso3":"TCD","iso2":"TD","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\uddf9\ud83c\udde9"},{"name":"Chile","iso3":"CHL","iso2":"CL","region":"Americas","subregion":"South America","emoji":"\ud83c\udde8\ud83c\uddf1"},{"name":"China","iso3":"CHN","iso2":"CN","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\udde8\ud83c\uddf3"},{"name":"Christmas Island","iso3":"CXR","iso2":"CX","region":"Oceania","subregion":"Australia and New Zealand","emoji":"\ud83c\udde8\ud83c\uddfd"},{"name":"Cocos (Keeling) Islands","iso3":"CCK","iso2":"CC","region":"Oceania","subregion":"Australia and New Zealand","emoji":"\ud83c\udde8\ud83c\udde8"},{"name":"Colombia","iso3":"COL","iso2":"CO","region":"Americas","subregion":"South America","emoji":"\ud83c\udde8\ud83c\uddf4"},{"name":"Comoros","iso3":"COM","iso2":"KM","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf0\ud83c\uddf2"},{"name":"Congo","iso3":"COG","iso2":"CG","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\udde8\ud83c\uddec"},{"name":"Cook Islands","iso3":"COK","iso2":"CK","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\udde8\ud83c\uddf0"},{"name":"Costa Rica","iso3":"CRI","iso2":"CR","region":"Americas","subregion":"Central America","emoji":"\ud83c\udde8\ud83c\uddf7"},{"name":"Cote D'Ivoire (Ivory Coast)","iso3":"CIV","iso2":"CI","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\udde8\ud83c\uddee"},{"name":"Croatia","iso3":"HRV","iso2":"HR","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\udded\ud83c\uddf7"},{"name":"Cuba","iso3":"CUB","iso2":"CU","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde8\ud83c\uddfa"},{"name":"Cura\u00e7ao","iso3":"CUW","iso2":"CW","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde8\ud83c\uddfc"},{"name":"Cyprus","iso3":"CYP","iso2":"CY","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\udde8\ud83c\uddfe"},{"name":"Czech Republic","iso3":"CZE","iso2":"CZ","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\udde8\ud83c\uddff"},{"name":"Democratic Republic of the Congo","iso3":"COD","iso2":"CD","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\udde8\ud83c\udde9"},{"name":"Denmark","iso3":"DNK","iso2":"DK","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\udde9\ud83c\uddf0"},{"name":"Djibouti","iso3":"DJI","iso2":"DJ","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\udde9\ud83c\uddef"},{"name":"Dominica","iso3":"DMA","iso2":"DM","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde9\ud83c\uddf2"},{"name":"Dominican Republic","iso3":"DOM","iso2":"DO","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde9\ud83c\uddf4"},{"name":"East Timor","iso3":"TLS","iso2":"TL","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf9\ud83c\uddf1"},{"name":"Ecuador","iso3":"ECU","iso2":"EC","region":"Americas","subregion":"South America","emoji":"\ud83c\uddea\ud83c\udde8"},{"name":"Egypt","iso3":"EGY","iso2":"EG","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddea\ud83c\uddec"},{"name":"El Salvador","iso3":"SLV","iso2":"SV","region":"Americas","subregion":"Central America","emoji":"\ud83c\uddf8\ud83c\uddfb"},{"name":"Equatorial Guinea","iso3":"GNQ","iso2":"GQ","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\uddec\ud83c\uddf6"},{"name":"Eritrea","iso3":"ERI","iso2":"ER","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddea\ud83c\uddf7"},{"name":"Estonia","iso3":"EST","iso2":"EE","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddea\ud83c\uddea"},{"name":"Ethiopia","iso3":"ETH","iso2":"ET","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddea\ud83c\uddf9"},{"name":"Falkland Islands","iso3":"FLK","iso2":"FK","region":"Americas","subregion":"South America","emoji":"\ud83c\uddeb\ud83c\uddf0"},{"name":"Faroe Islands","iso3":"FRO","iso2":"FO","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddeb\ud83c\uddf4"},{"name":"Fiji Islands","iso3":"FJI","iso2":"FJ","region":"Oceania","subregion":"Melanesia","emoji":"\ud83c\uddeb\ud83c\uddef"},{"name":"Finland","iso3":"FIN","iso2":"FI","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddeb\ud83c\uddee"},{"name":"France","iso3":"FRA","iso2":"FR","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\uddeb\ud83c\uddf7"},{"name":"French Guiana","iso3":"GUF","iso2":"GF","region":"Americas","subregion":"South America","emoji":"\ud83c\uddec\ud83c\uddeb"},{"name":"French Polynesia","iso3":"PYF","iso2":"PF","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf5\ud83c\uddeb"},{"name":"French Southern Territories","iso3":"ATF","iso2":"TF","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\uddf9\ud83c\uddeb"},{"name":"Gabon","iso3":"GAB","iso2":"GA","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\uddec\ud83c\udde6"},{"name":"Gambia The","iso3":"GMB","iso2":"GM","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddec\ud83c\uddf2"},{"name":"Georgia","iso3":"GEO","iso2":"GE","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddec\ud83c\uddea"},{"name":"Germany","iso3":"DEU","iso2":"DE","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\udde9\ud83c\uddea"},{"name":"Ghana","iso3":"GHA","iso2":"GH","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddec\ud83c\udded"},{"name":"Gibraltar","iso3":"GIB","iso2":"GI","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddec\ud83c\uddee"},{"name":"Greece","iso3":"GRC","iso2":"GR","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddec\ud83c\uddf7"},{"name":"Greenland","iso3":"GRL","iso2":"GL","region":"Americas","subregion":"Northern America","emoji":"\ud83c\uddec\ud83c\uddf1"},{"name":"Grenada","iso3":"GRD","iso2":"GD","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddec\ud83c\udde9"},{"name":"Guadeloupe","iso3":"GLP","iso2":"GP","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddec\ud83c\uddf5"},{"name":"Guam","iso3":"GUM","iso2":"GU","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddec\ud83c\uddfa"},{"name":"Guatemala","iso3":"GTM","iso2":"GT","region":"Americas","subregion":"Central America","emoji":"\ud83c\uddec\ud83c\uddf9"},{"name":"Guernsey and Alderney","iso3":"GGY","iso2":"GG","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddec\ud83c\uddec"},{"name":"Guinea","iso3":"GIN","iso2":"GN","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddec\ud83c\uddf3"},{"name":"Guinea-Bissau","iso3":"GNB","iso2":"GW","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddec\ud83c\uddfc"},{"name":"Guyana","iso3":"GUY","iso2":"GY","region":"Americas","subregion":"South America","emoji":"\ud83c\uddec\ud83c\uddfe"},{"name":"Haiti","iso3":"HTI","iso2":"HT","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udded\ud83c\uddf9"},{"name":"Heard Island and McDonald Islands","iso3":"HMD","iso2":"HM","region":"","subregion":"","emoji":"\ud83c\udded\ud83c\uddf2"},{"name":"Honduras","iso3":"HND","iso2":"HN","region":"Americas","subregion":"Central America","emoji":"\ud83c\udded\ud83c\uddf3"},{"name":"Hong Kong S.A.R.","iso3":"HKG","iso2":"HK","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\udded\ud83c\uddf0"},{"name":"Hungary","iso3":"HUN","iso2":"HU","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\udded\ud83c\uddfa"},{"name":"Iceland","iso3":"ISL","iso2":"IS","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddee\ud83c\uddf8"},{"name":"India","iso3":"IND","iso2":"IN","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddee\ud83c\uddf3"},{"name":"Indonesia","iso3":"IDN","iso2":"ID","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddee\ud83c\udde9"},{"name":"Iran","iso3":"IRN","iso2":"IR","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddee\ud83c\uddf7"},{"name":"Iraq","iso3":"IRQ","iso2":"IQ","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddee\ud83c\uddf6"},{"name":"Ireland","iso3":"IRL","iso2":"IE","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddee\ud83c\uddea"},{"name":"Israel","iso3":"ISR","iso2":"IL","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddee\ud83c\uddf1"},{"name":"Italy","iso3":"ITA","iso2":"IT","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddee\ud83c\uddf9"},{"name":"Jamaica","iso3":"JAM","iso2":"JM","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddef\ud83c\uddf2"},{"name":"Japan","iso3":"JPN","iso2":"JP","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddef\ud83c\uddf5"},{"name":"Jersey","iso3":"JEY","iso2":"JE","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddef\ud83c\uddea"},{"name":"Jordan","iso3":"JOR","iso2":"JO","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddef\ud83c\uddf4"},{"name":"Kazakhstan","iso3":"KAZ","iso2":"KZ","region":"Asia","subregion":"Central Asia","emoji":"\ud83c\uddf0\ud83c\uddff"},{"name":"Kenya","iso3":"KEN","iso2":"KE","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf0\ud83c\uddea"},{"name":"Kiribati","iso3":"KIR","iso2":"KI","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddf0\ud83c\uddee"},{"name":"Kosovo","iso3":"XKX","iso2":"XK","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddfd\ud83c\uddf0"},{"name":"Kuwait","iso3":"KWT","iso2":"KW","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf0\ud83c\uddfc"},{"name":"Kyrgyzstan","iso3":"KGZ","iso2":"KG","region":"Asia","subregion":"Central Asia","emoji":"\ud83c\uddf0\ud83c\uddec"},{"name":"Laos","iso3":"LAO","iso2":"LA","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf1\ud83c\udde6"},{"name":"Latvia","iso3":"LVA","iso2":"LV","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddf1\ud83c\uddfb"},{"name":"Lebanon","iso3":"LBN","iso2":"LB","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf1\ud83c\udde7"},{"name":"Lesotho","iso3":"LSO","iso2":"LS","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\uddf1\ud83c\uddf8"},{"name":"Liberia","iso3":"LBR","iso2":"LR","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf1\ud83c\uddf7"},{"name":"Libya","iso3":"LBY","iso2":"LY","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddf1\ud83c\uddfe"},{"name":"Liechtenstein","iso3":"LIE","iso2":"LI","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\uddf1\ud83c\uddee"},{"name":"Lithuania","iso3":"LTU","iso2":"LT","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddf1\ud83c\uddf9"},{"name":"Luxembourg","iso3":"LUX","iso2":"LU","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\uddf1\ud83c\uddfa"},{"name":"Macau S.A.R.","iso3":"MAC","iso2":"MO","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddf2\ud83c\uddf4"},{"name":"Macedonia","iso3":"MKD","iso2":"MK","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf2\ud83c\uddf0"},{"name":"Madagascar","iso3":"MDG","iso2":"MG","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf2\ud83c\uddec"},{"name":"Malawi","iso3":"MWI","iso2":"MW","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf2\ud83c\uddfc"},{"name":"Malaysia","iso3":"MYS","iso2":"MY","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf2\ud83c\uddfe"},{"name":"Maldives","iso3":"MDV","iso2":"MV","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddf2\ud83c\uddfb"},{"name":"Mali","iso3":"MLI","iso2":"ML","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf2\ud83c\uddf1"},{"name":"Malta","iso3":"MLT","iso2":"MT","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf2\ud83c\uddf9"},{"name":"Man (Isle of)","iso3":"IMN","iso2":"IM","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddee\ud83c\uddf2"},{"name":"Marshall Islands","iso3":"MHL","iso2":"MH","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddf2\ud83c\udded"},{"name":"Martinique","iso3":"MTQ","iso2":"MQ","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf2\ud83c\uddf6"},{"name":"Mauritania","iso3":"MRT","iso2":"MR","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf2\ud83c\uddf7"},{"name":"Mauritius","iso3":"MUS","iso2":"MU","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf2\ud83c\uddfa"},{"name":"Mayotte","iso3":"MYT","iso2":"YT","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddfe\ud83c\uddf9"},{"name":"Mexico","iso3":"MEX","iso2":"MX","region":"Americas","subregion":"Central America","emoji":"\ud83c\uddf2\ud83c\uddfd"},{"name":"Micronesia","iso3":"FSM","iso2":"FM","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddeb\ud83c\uddf2"},{"name":"Moldova","iso3":"MDA","iso2":"MD","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddf2\ud83c\udde9"},{"name":"Monaco","iso3":"MCO","iso2":"MC","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\uddf2\ud83c\udde8"},{"name":"Mongolia","iso3":"MNG","iso2":"MN","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddf2\ud83c\uddf3"},{"name":"Montenegro","iso3":"MNE","iso2":"ME","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf2\ud83c\uddea"},{"name":"Montserrat","iso3":"MSR","iso2":"MS","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf2\ud83c\uddf8"},{"name":"Morocco","iso3":"MAR","iso2":"MA","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddf2\ud83c\udde6"},{"name":"Mozambique","iso3":"MOZ","iso2":"MZ","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf2\ud83c\uddff"},{"name":"Myanmar","iso3":"MMR","iso2":"MM","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf2\ud83c\uddf2"},{"name":"Namibia","iso3":"NAM","iso2":"NA","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\uddf3\ud83c\udde6"},{"name":"Nauru","iso3":"NRU","iso2":"NR","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddf3\ud83c\uddf7"},{"name":"Nepal","iso3":"NPL","iso2":"NP","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddf3\ud83c\uddf5"},{"name":"Netherlands","iso3":"NLD","iso2":"NL","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\uddf3\ud83c\uddf1"},{"name":"New Caledonia","iso3":"NCL","iso2":"NC","region":"Oceania","subregion":"Melanesia","emoji":"\ud83c\uddf3\ud83c\udde8"},{"name":"New Zealand","iso3":"NZL","iso2":"NZ","region":"Oceania","subregion":"Australia and New Zealand","emoji":"\ud83c\uddf3\ud83c\uddff"},{"name":"Nicaragua","iso3":"NIC","iso2":"NI","region":"Americas","subregion":"Central America","emoji":"\ud83c\uddf3\ud83c\uddee"},{"name":"Niger","iso3":"NER","iso2":"NE","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf3\ud83c\uddea"},{"name":"Nigeria","iso3":"NGA","iso2":"NG","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf3\ud83c\uddec"},{"name":"Niue","iso3":"NIU","iso2":"NU","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf3\ud83c\uddfa"},{"name":"Norfolk Island","iso3":"NFK","iso2":"NF","region":"Oceania","subregion":"Australia and New Zealand","emoji":"\ud83c\uddf3\ud83c\uddeb"},{"name":"North Korea","iso3":"PRK","iso2":"KP","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddf0\ud83c\uddf5"},{"name":"Northern Mariana Islands","iso3":"MNP","iso2":"MP","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddf2\ud83c\uddf5"},{"name":"Norway","iso3":"NOR","iso2":"NO","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddf3\ud83c\uddf4"},{"name":"Oman","iso3":"OMN","iso2":"OM","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf4\ud83c\uddf2"},{"name":"Pakistan","iso3":"PAK","iso2":"PK","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddf5\ud83c\uddf0"},{"name":"Palau","iso3":"PLW","iso2":"PW","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddf5\ud83c\uddfc"},{"name":"Palestinian Territory Occupied","iso3":"PSE","iso2":"PS","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf5\ud83c\uddf8"},{"name":"Panama","iso3":"PAN","iso2":"PA","region":"Americas","subregion":"Central America","emoji":"\ud83c\uddf5\ud83c\udde6"},{"name":"Papua new Guinea","iso3":"PNG","iso2":"PG","region":"Oceania","subregion":"Melanesia","emoji":"\ud83c\uddf5\ud83c\uddec"},{"name":"Paraguay","iso3":"PRY","iso2":"PY","region":"Americas","subregion":"South America","emoji":"\ud83c\uddf5\ud83c\uddfe"},{"name":"Peru","iso3":"PER","iso2":"PE","region":"Americas","subregion":"South America","emoji":"\ud83c\uddf5\ud83c\uddea"},{"name":"Philippines","iso3":"PHL","iso2":"PH","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf5\ud83c\udded"},{"name":"Pitcairn Island","iso3":"PCN","iso2":"PN","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf5\ud83c\uddf3"},{"name":"Poland","iso3":"POL","iso2":"PL","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddf5\ud83c\uddf1"},{"name":"Portugal","iso3":"PRT","iso2":"PT","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf5\ud83c\uddf9"},{"name":"Puerto Rico","iso3":"PRI","iso2":"PR","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf5\ud83c\uddf7"},{"name":"Qatar","iso3":"QAT","iso2":"QA","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf6\ud83c\udde6"},{"name":"Reunion","iso3":"REU","iso2":"RE","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf7\ud83c\uddea"},{"name":"Romania","iso3":"ROU","iso2":"RO","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddf7\ud83c\uddf4"},{"name":"Russia","iso3":"RUS","iso2":"RU","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddf7\ud83c\uddfa"},{"name":"Rwanda","iso3":"RWA","iso2":"RW","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf7\ud83c\uddfc"},{"name":"Saint Helena","iso3":"SHN","iso2":"SH","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf8\ud83c\udded"},{"name":"Saint Kitts And Nevis","iso3":"KNA","iso2":"KN","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf0\ud83c\uddf3"},{"name":"Saint Lucia","iso3":"LCA","iso2":"LC","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf1\ud83c\udde8"},{"name":"Saint Pierre and Miquelon","iso3":"SPM","iso2":"PM","region":"Americas","subregion":"Northern America","emoji":"\ud83c\uddf5\ud83c\uddf2"},{"name":"Saint Vincent And The Grenadines","iso3":"VCT","iso2":"VC","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddfb\ud83c\udde8"},{"name":"Saint-Barthelemy","iso3":"BLM","iso2":"BL","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde7\ud83c\uddf1"},{"name":"Saint-Martin (French part)","iso3":"MAF","iso2":"MF","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf2\ud83c\uddeb"},{"name":"Samoa","iso3":"WSM","iso2":"WS","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddfc\ud83c\uddf8"},{"name":"San Marino","iso3":"SMR","iso2":"SM","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf8\ud83c\uddf2"},{"name":"Sao Tome and Principe","iso3":"STP","iso2":"ST","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\uddf8\ud83c\uddf9"},{"name":"Saudi Arabia","iso3":"SAU","iso2":"SA","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf8\ud83c\udde6"},{"name":"Senegal","iso3":"SEN","iso2":"SN","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf8\ud83c\uddf3"},{"name":"Serbia","iso3":"SRB","iso2":"RS","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf7\ud83c\uddf8"},{"name":"Seychelles","iso3":"SYC","iso2":"SC","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf8\ud83c\udde8"},{"name":"Sierra Leone","iso3":"SLE","iso2":"SL","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf8\ud83c\uddf1"},{"name":"Singapore","iso3":"SGP","iso2":"SG","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf8\ud83c\uddec"},{"name":"Sint Maarten (Dutch part)","iso3":"SXM","iso2":"SX","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf8\ud83c\uddfd"},{"name":"Slovakia","iso3":"SVK","iso2":"SK","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddf8\ud83c\uddf0"},{"name":"Slovenia","iso3":"SVN","iso2":"SI","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf8\ud83c\uddee"},{"name":"Solomon Islands","iso3":"SLB","iso2":"SB","region":"Oceania","subregion":"Melanesia","emoji":"\ud83c\uddf8\ud83c\udde7"},{"name":"Somalia","iso3":"SOM","iso2":"SO","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf8\ud83c\uddf4"},{"name":"South Africa","iso3":"ZAF","iso2":"ZA","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\uddff\ud83c\udde6"},{"name":"South Georgia","iso3":"SGS","iso2":"GS","region":"Americas","subregion":"South America","emoji":"\ud83c\uddec\ud83c\uddf8"},{"name":"South Korea","iso3":"KOR","iso2":"KR","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddf0\ud83c\uddf7"},{"name":"South Sudan","iso3":"SSD","iso2":"SS","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\uddf8\ud83c\uddf8"},{"name":"Spain","iso3":"ESP","iso2":"ES","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddea\ud83c\uddf8"},{"name":"Sri Lanka","iso3":"LKA","iso2":"LK","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddf1\ud83c\uddf0"},{"name":"Sudan","iso3":"SDN","iso2":"SD","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddf8\ud83c\udde9"},{"name":"Suriname","iso3":"SUR","iso2":"SR","region":"Americas","subregion":"South America","emoji":"\ud83c\uddf8\ud83c\uddf7"},{"name":"Svalbard And Jan Mayen Islands","iso3":"SJM","iso2":"SJ","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddf8\ud83c\uddef"},{"name":"Swaziland","iso3":"SWZ","iso2":"SZ","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\uddf8\ud83c\uddff"},{"name":"Sweden","iso3":"SWE","iso2":"SE","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddf8\ud83c\uddea"},{"name":"Switzerland","iso3":"CHE","iso2":"CH","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\udde8\ud83c\udded"},{"name":"Syria","iso3":"SYR","iso2":"SY","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf8\ud83c\uddfe"},{"name":"Taiwan","iso3":"TWN","iso2":"TW","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddf9\ud83c\uddfc"},{"name":"Tajikistan","iso3":"TJK","iso2":"TJ","region":"Asia","subregion":"Central Asia","emoji":"\ud83c\uddf9\ud83c\uddef"},{"name":"Tanzania","iso3":"TZA","iso2":"TZ","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf9\ud83c\uddff"},{"name":"Thailand","iso3":"THA","iso2":"TH","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf9\ud83c\udded"},{"name":"Togo","iso3":"TGO","iso2":"TG","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf9\ud83c\uddec"},{"name":"Tokelau","iso3":"TKL","iso2":"TK","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf9\ud83c\uddf0"},{"name":"Tonga","iso3":"TON","iso2":"TO","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf9\ud83c\uddf4"},{"name":"Trinidad And Tobago","iso3":"TTO","iso2":"TT","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf9\ud83c\uddf9"},{"name":"Tunisia","iso3":"TUN","iso2":"TN","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddf9\ud83c\uddf3"},{"name":"Turkey","iso3":"TUR","iso2":"TR","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf9\ud83c\uddf7"},{"name":"Turkmenistan","iso3":"TKM","iso2":"TM","region":"Asia","subregion":"Central Asia","emoji":"\ud83c\uddf9\ud83c\uddf2"},{"name":"Turks And Caicos Islands","iso3":"TCA","iso2":"TC","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf9\ud83c\udde8"},{"name":"Tuvalu","iso3":"TUV","iso2":"TV","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf9\ud83c\uddfb"},{"name":"Uganda","iso3":"UGA","iso2":"UG","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddfa\ud83c\uddec"},{"name":"Ukraine","iso3":"UKR","iso2":"UA","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddfa\ud83c\udde6"},{"name":"United Arab Emirates","iso3":"ARE","iso2":"AE","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\udde6\ud83c\uddea"},{"name":"United Kingdom","iso3":"GBR","iso2":"GB","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddec\ud83c\udde7"},{"name":"United States","iso3":"USA","iso2":"US","region":"Americas","subregion":"Northern America","emoji":"\ud83c\uddfa\ud83c\uddf8"},{"name":"United States Minor Outlying Islands","iso3":"UMI","iso2":"UM","region":"Americas","subregion":"Northern America","emoji":"\ud83c\uddfa\ud83c\uddf2"},{"name":"Uruguay","iso3":"URY","iso2":"UY","region":"Americas","subregion":"South America","emoji":"\ud83c\uddfa\ud83c\uddfe"},{"name":"Uzbekistan","iso3":"UZB","iso2":"UZ","region":"Asia","subregion":"Central Asia","emoji":"\ud83c\uddfa\ud83c\uddff"},{"name":"Vanuatu","iso3":"VUT","iso2":"VU","region":"Oceania","subregion":"Melanesia","emoji":"\ud83c\uddfb\ud83c\uddfa"},{"name":"Vatican City State (Holy See)","iso3":"VAT","iso2":"VA","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddfb\ud83c\udde6"},{"name":"Venezuela","iso3":"VEN","iso2":"VE","region":"Americas","subregion":"South America","emoji":"\ud83c\uddfb\ud83c\uddea"},{"name":"Vietnam","iso3":"VNM","iso2":"VN","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddfb\ud83c\uddf3"},{"name":"Virgin Islands (British)","iso3":"VGB","iso2":"VG","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddfb\ud83c\uddec"},{"name":"Virgin Islands (US)","iso3":"VIR","iso2":"VI","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddfb\ud83c\uddee"},{"name":"Wallis And Futuna Islands","iso3":"WLF","iso2":"WF","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddfc\ud83c\uddeb"},{"name":"Western Sahara","iso3":"ESH","iso2":"EH","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddea\ud83c\udded"},{"name":"Yemen","iso3":"YEM","iso2":"YE","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddfe\ud83c\uddea"},{"name":"Zambia","iso3":"ZMB","iso2":"ZM","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddff\ud83c\uddf2"},{"name":"Zimbabwe","iso3":"ZWE","iso2":"ZW","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddff\ud83c\uddfc"}] \ No newline at end of file diff --git a/resources/dictionaries/currencies.json b/resources/dictionaries/currencies.json new file mode 100644 index 0000000000..317457f03e --- /dev/null +++ b/resources/dictionaries/currencies.json @@ -0,0 +1 @@ +[{"code":"AED","name":"United Arab Emirates Dirham","symbol":"\u062f.\u0625.\u200f","decimal_digits":2},{"code":"AFN","name":"Afghan Afghani","symbol":"\u060b","decimal_digits":0},{"code":"ALL","name":"Albanian Lek","symbol":"Lek","decimal_digits":0},{"code":"AMD","name":"Armenian Dram","symbol":"\u0564\u0580.","decimal_digits":0},{"code":"ARS","name":"Argentine Peso","symbol":"$","decimal_digits":2},{"code":"AUD","name":"Australian Dollar","symbol":"$","decimal_digits":2},{"code":"AZN","name":"Azerbaijani Manat","symbol":"\u043c\u0430\u043d.","decimal_digits":2},{"code":"BAM","name":"Bosnia-Herzegovina Convertible Mark","symbol":"KM","decimal_digits":2},{"code":"BDT","name":"Bangladeshi Taka","symbol":"\u09f3","decimal_digits":2},{"code":"BGN","name":"Bulgarian Lev","symbol":"\u043b\u0432.","decimal_digits":2},{"code":"BHD","name":"Bahraini Dinar","symbol":"\u062f.\u0628.\u200f","decimal_digits":3},{"code":"BIF","name":"Burundian Franc","symbol":"FBu","decimal_digits":0},{"code":"BND","name":"Brunei Dollar","symbol":"$","decimal_digits":2},{"code":"BOB","name":"Bolivian Boliviano","symbol":"Bs","decimal_digits":2},{"code":"BRL","name":"Brazilian Real","symbol":"R$","decimal_digits":2},{"code":"BWP","name":"Botswanan Pula","symbol":"P","decimal_digits":2},{"code":"BYN","name":"Belarusian Ruble","symbol":"\u0440\u0443\u0431.","decimal_digits":2},{"code":"BZD","name":"Belize Dollar","symbol":"$","decimal_digits":2},{"code":"CAD","name":"Canadian Dollar","symbol":"$","decimal_digits":2},{"code":"CDF","name":"Congolese Franc","symbol":"FrCD","decimal_digits":2},{"code":"CHF","name":"Swiss Franc","symbol":"CHF","decimal_digits":2},{"code":"CLP","name":"Chilean Peso","symbol":"$","decimal_digits":0},{"code":"CNY","name":"Chinese Yuan","symbol":"CN\u00a5","decimal_digits":2},{"code":"COP","name":"Colombian Peso","symbol":"$","decimal_digits":0},{"code":"CRC","name":"Costa Rican Col\u00f3n","symbol":"\u20a1","decimal_digits":0},{"code":"CVE","name":"Cape Verdean Escudo","symbol":"CV$","decimal_digits":2},{"code":"CZK","name":"Czech Republic Koruna","symbol":"K\u010d","decimal_digits":2},{"code":"DJF","name":"Djiboutian Franc","symbol":"Fdj","decimal_digits":0},{"code":"DKK","name":"Danish Krone","symbol":"kr","decimal_digits":2},{"code":"DOP","name":"Dominican Peso","symbol":"RD$","decimal_digits":2},{"code":"DZD","name":"Algerian Dinar","symbol":"\u062f.\u062c.\u200f","decimal_digits":2},{"code":"EEK","name":"Estonian Kroon","symbol":"kr","decimal_digits":2},{"code":"EGP","name":"Egyptian Pound","symbol":"\u062c.\u0645.\u200f","decimal_digits":2},{"code":"ERN","name":"Eritrean Nakfa","symbol":"Nfk","decimal_digits":2},{"code":"ETB","name":"Ethiopian Birr","symbol":"Br","decimal_digits":2},{"code":"EUR","name":"Euro","symbol":"\u20ac","decimal_digits":2},{"code":"GBP","name":"British Pound Sterling","symbol":"\u00a3","decimal_digits":2},{"code":"GEL","name":"Georgian Lari","symbol":"GEL","decimal_digits":2},{"code":"GHS","name":"Ghanaian Cedi","symbol":"GH\u20b5","decimal_digits":2},{"code":"GNF","name":"Guinean Franc","symbol":"FG","decimal_digits":0},{"code":"GTQ","name":"Guatemalan Quetzal","symbol":"Q","decimal_digits":2},{"code":"HKD","name":"Hong Kong Dollar","symbol":"$","decimal_digits":2},{"code":"HNL","name":"Honduran Lempira","symbol":"L","decimal_digits":2},{"code":"HRK","name":"Croatian Kuna","symbol":"kn","decimal_digits":2},{"code":"HUF","name":"Hungarian Forint","symbol":"Ft","decimal_digits":0},{"code":"IDR","name":"Indonesian Rupiah","symbol":"Rp","decimal_digits":0},{"code":"ILS","name":"Israeli New Sheqel","symbol":"\u20aa","decimal_digits":2},{"code":"INR","name":"Indian Rupee","symbol":"\u099f\u0995\u09be","decimal_digits":2},{"code":"IQD","name":"Iraqi Dinar","symbol":"\u062f.\u0639.\u200f","decimal_digits":0},{"code":"IRR","name":"Iranian Rial","symbol":"\ufdfc","decimal_digits":0},{"code":"ISK","name":"Icelandic Kr\u00f3na","symbol":"kr","decimal_digits":0},{"code":"JMD","name":"Jamaican Dollar","symbol":"$","decimal_digits":2},{"code":"JOD","name":"Jordanian Dinar","symbol":"\u062f.\u0623.\u200f","decimal_digits":3},{"code":"JPY","name":"Japanese Yen","symbol":"\uffe5","decimal_digits":0},{"code":"KES","name":"Kenyan Shilling","symbol":"Ksh","decimal_digits":2},{"code":"KHR","name":"Cambodian Riel","symbol":"\u17db","decimal_digits":2},{"code":"KMF","name":"Comorian Franc","symbol":"FC","decimal_digits":0},{"code":"KRW","name":"South Korean Won","symbol":"\u20a9","decimal_digits":0},{"code":"KWD","name":"Kuwaiti Dinar","symbol":"\u062f.\u0643.\u200f","decimal_digits":3},{"code":"KZT","name":"Kazakhstani Tenge","symbol":"\u0442\u04a3\u0433.","decimal_digits":2},{"code":"LBP","name":"Lebanese Pound","symbol":"\u0644.\u0644.\u200f","decimal_digits":0},{"code":"LKR","name":"Sri Lankan Rupee","symbol":"SL Re","decimal_digits":2},{"code":"LTL","name":"Lithuanian Litas","symbol":"Lt","decimal_digits":2},{"code":"LVL","name":"Latvian Lats","symbol":"Ls","decimal_digits":2},{"code":"LYD","name":"Libyan Dinar","symbol":"\u062f.\u0644.\u200f","decimal_digits":3},{"code":"MAD","name":"Moroccan Dirham","symbol":"\u062f.\u0645.\u200f","decimal_digits":2},{"code":"MDL","name":"Moldovan Leu","symbol":"MDL","decimal_digits":2},{"code":"MGA","name":"Malagasy Ariary","symbol":"MGA","decimal_digits":0},{"code":"MKD","name":"Macedonian Denar","symbol":"MKD","decimal_digits":2},{"code":"MMK","name":"Myanma Kyat","symbol":"K","decimal_digits":0},{"code":"MOP","name":"Macanese Pataca","symbol":"MOP$","decimal_digits":2},{"code":"MUR","name":"Mauritian Rupee","symbol":"MURs","decimal_digits":0},{"code":"MXN","name":"Mexican Peso","symbol":"$","decimal_digits":2},{"code":"MYR","name":"Malaysian Ringgit","symbol":"RM","decimal_digits":2},{"code":"MZN","name":"Mozambican Metical","symbol":"MTn","decimal_digits":2},{"code":"NAD","name":"Namibian Dollar","symbol":"N$","decimal_digits":2},{"code":"NGN","name":"Nigerian Naira","symbol":"\u20a6","decimal_digits":2},{"code":"NIO","name":"Nicaraguan C\u00f3rdoba","symbol":"C$","decimal_digits":2},{"code":"NOK","name":"Norwegian Krone","symbol":"kr","decimal_digits":2},{"code":"NPR","name":"Nepalese Rupee","symbol":"\u0928\u0947\u0930\u0942","decimal_digits":2},{"code":"NZD","name":"New Zealand Dollar","symbol":"$","decimal_digits":2},{"code":"OMR","name":"Omani Rial","symbol":"\u0631.\u0639.\u200f","decimal_digits":3},{"code":"PAB","name":"Panamanian Balboa","symbol":"B\/.","decimal_digits":2},{"code":"PEN","name":"Peruvian Nuevo Sol","symbol":"S\/.","decimal_digits":2},{"code":"PHP","name":"Philippine Peso","symbol":"\u20b1","decimal_digits":2},{"code":"PKR","name":"Pakistani Rupee","symbol":"\u20a8","decimal_digits":0},{"code":"PLN","name":"Polish Zloty","symbol":"z\u0142","decimal_digits":2},{"code":"PYG","name":"Paraguayan Guarani","symbol":"\u20b2","decimal_digits":0},{"code":"QAR","name":"Qatari Rial","symbol":"\u0631.\u0642.\u200f","decimal_digits":2},{"code":"RON","name":"Romanian Leu","symbol":"RON","decimal_digits":2},{"code":"RSD","name":"Serbian Dinar","symbol":"\u0434\u0438\u043d.","decimal_digits":0},{"code":"RUB","name":"Russian Ruble","symbol":"\u20bd.","decimal_digits":2},{"code":"RWF","name":"Rwandan Franc","symbol":"FR","decimal_digits":0},{"code":"SAR","name":"Saudi Riyal","symbol":"\u0631.\u0633.\u200f","decimal_digits":2},{"code":"SDG","name":"Sudanese Pound","symbol":"SDG","decimal_digits":2},{"code":"SEK","name":"Swedish Krona","symbol":"kr","decimal_digits":2},{"code":"SGD","name":"Singapore Dollar","symbol":"$","decimal_digits":2},{"code":"SOS","name":"Somali Shilling","symbol":"Ssh","decimal_digits":0},{"code":"SYP","name":"Syrian Pound","symbol":"\u0644.\u0633.\u200f","decimal_digits":0},{"code":"THB","name":"Thai Baht","symbol":"\u0e3f","decimal_digits":2},{"code":"TND","name":"Tunisian Dinar","symbol":"\u062f.\u062a.\u200f","decimal_digits":3},{"code":"TOP","name":"Tongan Pa\u02bbanga","symbol":"T$","decimal_digits":2},{"code":"TRY","name":"Turkish Lira","symbol":"TL","decimal_digits":2},{"code":"TTD","name":"Trinidad and Tobago Dollar","symbol":"$","decimal_digits":2},{"code":"TWD","name":"New Taiwan Dollar","symbol":"NT$","decimal_digits":2},{"code":"TZS","name":"Tanzanian Shilling","symbol":"TSh","decimal_digits":0},{"code":"UAH","name":"Ukrainian Hryvnia","symbol":"\u20b4","decimal_digits":2},{"code":"UGX","name":"Ugandan Shilling","symbol":"USh","decimal_digits":0},{"code":"USD","name":"US Dollar","symbol":"$","decimal_digits":2},{"code":"UYU","name":"Uruguayan Peso","symbol":"$","decimal_digits":2},{"code":"UZS","name":"Uzbekistan Som","symbol":"UZS","decimal_digits":0},{"code":"VEF","name":"Venezuelan Bol\u00edvar","symbol":"Bs.F.","decimal_digits":2},{"code":"VND","name":"Vietnamese Dong","symbol":"\u20ab","decimal_digits":0},{"code":"XAF","name":"CFA Franc BEAC","symbol":"FCFA","decimal_digits":0},{"code":"XOF","name":"CFA Franc BCEAO","symbol":"CFA","decimal_digits":0},{"code":"YER","name":"Yemeni Rial","symbol":"\u0631.\u064a.\u200f","decimal_digits":0},{"code":"ZAR","name":"South African Rand","symbol":"R","decimal_digits":2},{"code":"ZMK","name":"Zambian Kwacha","symbol":"ZK","decimal_digits":0},{"code":"ZWL","name":"Zimbabwean Dollar","symbol":"ZWL$","decimal_digits":0}] \ No newline at end of file diff --git a/resources/js/bootstrap/fieldtypes.js b/resources/js/bootstrap/fieldtypes.js index 2608b7a861..8a4e8f773e 100644 --- a/resources/js/bootstrap/fieldtypes.js +++ b/resources/js/bootstrap/fieldtypes.js @@ -24,6 +24,8 @@ import Routes from '../components/collections/Routes.vue'; import TitleFormats from '../components/collections/TitleFormats.vue'; import ColorFieldtype from '../components/fieldtypes/ColorFieldtype.vue'; import DateFieldtype from '../components/fieldtypes/DateFieldtype.vue'; +import DictionaryFieldtype from "../components/fieldtypes/DictionaryFieldtype.vue"; +import DictionaryFields from "../components/fieldtypes/DictionaryFields.vue"; import FieldDisplayFieldtype from '../components/fieldtypes/FieldDisplayFieldtype.vue'; import FieldsFieldtype from '../components/fieldtypes/grid/FieldsFieldtype.vue'; import FilesFieldtype from '../components/fieldtypes/FilesFieldtype.vue'; @@ -85,6 +87,8 @@ Vue.component('collection_routes-fieldtype', Routes); Vue.component('collection_title_formats-fieldtype', TitleFormats); Vue.component('color-fieldtype', ColorFieldtype); Vue.component('date-fieldtype', DateFieldtype); +Vue.component('dictionary-fieldtype', DictionaryFieldtype); +Vue.component('dictionary_fields-fieldtype', DictionaryFields); Vue.component('field_display-fieldtype', FieldDisplayFieldtype); Vue.component('fields-fieldtype', FieldsFieldtype); Vue.component('files-fieldtype', FilesFieldtype); diff --git a/resources/js/components/fieldtypes/DictionaryFields.vue b/resources/js/components/fieldtypes/DictionaryFields.vue new file mode 100644 index 0000000000..bca2ad5dd7 --- /dev/null +++ b/resources/js/components/fieldtypes/DictionaryFields.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/resources/js/components/fieldtypes/DictionaryFieldtype.vue b/resources/js/components/fieldtypes/DictionaryFieldtype.vue new file mode 100644 index 0000000000..e991b0fe34 --- /dev/null +++ b/resources/js/components/fieldtypes/DictionaryFieldtype.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/resources/lang/en/fieldtypes.php b/resources/lang/en/fieldtypes.php index a2776c2c38..6d526d6b4b 100644 --- a/resources/lang/en/fieldtypes.php +++ b/resources/lang/en/fieldtypes.php @@ -72,6 +72,7 @@ 'date.config.time_enabled' => 'Enable the timepicker.', 'date.config.time_seconds_enabled' => 'Show seconds in the timepicker.', 'date.title' => 'Date', + 'dictionary.config.dictionary' => 'Please select the dictionary you wish to pull options from.', 'entries.config.create' => 'Allow creation of new entries.', 'entries.config.collections' => 'Choose which collections the user can select from.', 'entries.config.query_scopes' => 'Choose which query scopes should be applied when retrieving selectable entries.', diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php index b4c985c413..36114bdaff 100644 --- a/resources/lang/en/messages.php +++ b/resources/lang/en/messages.php @@ -69,6 +69,7 @@ 'collections_sort_direction_instructions' => 'The default sort direction.', 'collections_preview_target_refresh_instructions' => 'Automatically refresh the preview while editing. Disabling this will use postMessage.', 'collections_taxonomies_instructions' => 'Connect entries in this collection to taxonomies. Fields will be automatically added to publish forms.', + 'dictionaries_countries_region_instructions' => 'Select a region to filter the list of countries.', 'duplicate_action_warning_localization' => 'This entry is a localization. The origin entry will be duplicated.', 'duplicate_action_warning_localizations' => 'One or more selected entries are localizations. In those cases, the origin entry will be duplicated instead.', 'duplicate_action_localizations_confirmation' => 'Are you sure you want to run this action? Localizations will also be duplicated.', diff --git a/resources/svg/icons/light/dictionary.svg b/resources/svg/icons/light/dictionary.svg new file mode 100644 index 0000000000..4147382376 --- /dev/null +++ b/resources/svg/icons/light/dictionary.svg @@ -0,0 +1 @@ + diff --git a/resources/views/extend/forms/fields/dictionary.antlers.html b/resources/views/extend/forms/fields/dictionary.antlers.html new file mode 100644 index 0000000000..b093f6a026 --- /dev/null +++ b/resources/views/extend/forms/fields/dictionary.antlers.html @@ -0,0 +1,21 @@ + diff --git a/resources/views/forms/automagic-email.antlers.html b/resources/views/forms/automagic-email.antlers.html index f8fd90528b..cab5234e64 100644 --- a/resources/views/forms/automagic-email.antlers.html +++ b/resources/views/forms/automagic-email.antlers.html @@ -12,7 +12,7 @@ {{ elseif fieldtype == "radio" }} {{ value:label ?? value }} - {{ elseif fieldtype == "select" || fieldtype == "checkboxes" }} + {{ elseif fieldtype == "select" || fieldtype == "checkboxes" || fieldtype == "dictionary" }} {{ value }}{{ label ?? value }}{{ if !last }}, {{ /if }}{{ /value }} {{ elseif value|is_iterable }} diff --git a/routes/cp.php b/routes/cp.php index 959e0f63c1..75af4e9206 100644 --- a/routes/cp.php +++ b/routes/cp.php @@ -45,6 +45,7 @@ use Statamic\Http\Controllers\CP\Fields\FieldsetController; use Statamic\Http\Controllers\CP\Fields\FieldtypesController; use Statamic\Http\Controllers\CP\Fields\MetaController; +use Statamic\Http\Controllers\CP\Fieldtypes\DictionaryFieldtypeController; use Statamic\Http\Controllers\CP\Fieldtypes\FilesFieldtypeController; use Statamic\Http\Controllers\CP\Fieldtypes\MarkdownFieldtypeController; use Statamic\Http\Controllers\CP\Fieldtypes\RelationshipFieldtypeController; @@ -309,6 +310,7 @@ Route::get('relationship/filters', [RelationshipFieldtypeController::class, 'filters'])->name('relationship.filters'); Route::post('markdown', [MarkdownFieldtypeController::class, 'preview'])->name('markdown.preview'); Route::post('files/upload', [FilesFieldtypeController::class, 'upload'])->name('files.upload'); + Route::get('dictionaries/{dictionary}', DictionaryFieldtypeController::class)->name('dictionary-fieldtype'); }); Route::group(['prefix' => 'api', 'as' => 'api.'], function () { diff --git a/src/Console/Commands/MakeDictionary.php b/src/Console/Commands/MakeDictionary.php new file mode 100644 index 0000000000..f1a8618496 --- /dev/null +++ b/src/Console/Commands/MakeDictionary.php @@ -0,0 +1,74 @@ +argument('addon')) { + $this->updateServiceProvider(); + } + } + + /** + * Update the Service Provider to register dictionary components. + */ + protected function updateServiceProvider() + { + $factory = new BuilderFactory(); + + $dictionaryClassValue = $factory->classConstFetch('Dictionaries\\'.$this->getNameInput(), 'class'); + + try { + PHPFile::load("addons/{$this->package}/src/ServiceProvider.php") + ->add()->protected()->property('dictionaries', $dictionaryClassValue) + ->save(); + } catch (\Exception $e) { + $this->comment("Don't forget to register the Dictionary class in your addon's service provider."); + } + } +} diff --git a/src/Console/Commands/stubs/dictionary.php.stub b/src/Console/Commands/stubs/dictionary.php.stub new file mode 100644 index 0000000000..1857928fac --- /dev/null +++ b/src/Console/Commands/stubs/dictionary.php.stub @@ -0,0 +1,46 @@ +getItems() + ->when($search ?? false, function ($collection) use ($search) { + return $collection->filter(fn ($item) => str_contains($item['name'], $search)); + }) + ->mapWithKeys(fn ($item) => [$item['slug'] => $item['name']]) + ->all(); + } + + /** + * Returns a single option. + * + * @param string $key + * @return string|array + */ + public function get(string $key): string|array + { + return $this->getItems()->firstWhere('slug', $key); + } + + private function getItems(): Collection + { + return collect([ + ['name' => 'January', 'slug' => 'january'], + ['name' => 'February', 'slug' => 'february'], + ['name' => 'March', 'slug' => 'march'], + // ... + ]); + } +} diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php new file mode 100644 index 0000000000..5a20c1bf9d --- /dev/null +++ b/src/Dictionaries/Countries.php @@ -0,0 +1,48 @@ +getCountries() + ->when($this->context['region'] ?? false, function ($collection) { + return $collection->where('region', $this->context['region']); + }) + ->when($search ?? false, function ($collection) use ($search) { + return $collection->filter(function (array $country) use ($search) { + return str_contains(strtolower($country['name']), strtolower($search)); + }); + }) + ->mapWithKeys(function (array $country) { + return [$country['iso3'] => "{$country['emoji']} {$country['name']}"]; + }) + ->all(); + } + + public function get(string $key): string|array + { + return $this->getCountries()->firstWhere('iso3', $key); + } + + protected function fieldItems() + { + return [ + 'region' => [ + 'display' => __('Region'), + 'instructions' => __('statamic::messages.dictionaries_countries_region_instructions'), + 'type' => 'select', + 'options' => $this->getCountries()->unique('region')->pluck('region', 'region')->filter()->all(), + ], + ]; + } + + private function getCountries(): Collection + { + return collect(json_decode(File::get(__DIR__.'/../../resources/dictionaries/countries.json'), true)); + } +} diff --git a/src/Dictionaries/Currencies.php b/src/Dictionaries/Currencies.php new file mode 100644 index 0000000000..c31ff4557b --- /dev/null +++ b/src/Dictionaries/Currencies.php @@ -0,0 +1,34 @@ +getCurrencies() + ->when($search ?? false, function ($collection) use ($search) { + return $collection->filter(function (array $currency) use ($search) { + return str_contains(strtolower($currency['name']), strtolower($search)) + || str_contains(strtolower($currency['code']), strtolower($search)); + }); + }) + ->mapWithKeys(function (array $currency) { + return [$currency['code'] => "{$currency['name']} ({$currency['code']})"]; + }) + ->all(); + } + + public function get(string $key): string|array + { + return $this->getCurrencies()->firstWhere('code', $key); + } + + private function getCurrencies(): Collection + { + return collect(json_decode(File::get(__DIR__.'/../../resources/dictionaries/currencies.json'), true)); + } +} diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php new file mode 100644 index 0000000000..f4688d7c23 --- /dev/null +++ b/src/Dictionaries/Dictionary.php @@ -0,0 +1,40 @@ +context = $context; + + return $this; + } + + protected function fieldItems() + { + return $this->fields; + } +} diff --git a/src/Dictionaries/DictionaryRepository.php b/src/Dictionaries/DictionaryRepository.php new file mode 100644 index 0000000000..c57a774033 --- /dev/null +++ b/src/Dictionaries/DictionaryRepository.php @@ -0,0 +1,35 @@ +map(fn ($class) => app($class)) + ->filter() + ->values(); + } + + public function find(string $handle, array $context = []): ?Dictionary + { + if (! $dictionary = app('statamic.dictionaries')->get($handle)) { + return null; + } + + $dictionary = app($dictionary); + + if (! $dictionary) { + return null; + } + + if (! method_exists($dictionary, 'context')) { + return $dictionary; + } + + return $dictionary->context($context); + } +} diff --git a/src/Dictionaries/Timezones.php b/src/Dictionaries/Timezones.php new file mode 100644 index 0000000000..e99c67450f --- /dev/null +++ b/src/Dictionaries/Timezones.php @@ -0,0 +1,19 @@ +filter(fn ($timezone) => $search ? str_contains(strtolower($timezone), strtolower($search)) : true) + ->mapWithKeys(fn ($timezone) => [$timezone => $timezone]) + ->all(); + } + + public function get(string $key): string|array + { + return $key; + } +} diff --git a/src/Exceptions/DictionaryNotFoundException.php b/src/Exceptions/DictionaryNotFoundException.php new file mode 100644 index 0000000000..dd672f1985 --- /dev/null +++ b/src/Exceptions/DictionaryNotFoundException.php @@ -0,0 +1,11 @@ +dictionaryHandle}] not found"); + } +} diff --git a/src/Exceptions/UndefinedDictionaryException.php b/src/Exceptions/UndefinedDictionaryException.php new file mode 100644 index 0000000000..a9c1f0f56d --- /dev/null +++ b/src/Exceptions/UndefinedDictionaryException.php @@ -0,0 +1,13 @@ + __('Options'), + 'fields' => [ + 'dictionary' => [ + 'type' => 'dictionary_fields', + 'hide_display' => true, + 'full_width_setting' => true, + ], + ], + ], + [ + 'display' => __('Selection'), + 'fields' => [ + 'placeholder' => [ + 'display' => __('Placeholder'), + 'instructions' => __('statamic::fieldtypes.select.config.placeholder'), + 'type' => 'text', + 'default' => '', + ], + 'multiple' => [ + 'display' => __('Multiple'), + 'instructions' => __('statamic::fieldtypes.select.config.multiple'), + 'type' => 'toggle', + 'default' => false, + ], + 'max_items' => [ + 'display' => __('Max Items'), + 'instructions' => __('statamic::messages.max_items_instructions'), + 'min' => 1, + 'type' => 'integer', + ], + 'clearable' => [ + 'display' => __('Clearable'), + 'instructions' => __('statamic::fieldtypes.select.config.clearable'), + 'type' => 'toggle', + 'default' => false, + ], + 'searchable' => [ + 'display' => __('Searchable'), + 'instructions' => __('statamic::fieldtypes.select.config.searchable'), + 'type' => 'toggle', + 'default' => true, + ], + ], + ], + [ + 'display' => __('Data'), + 'fields' => [ + 'default' => [ + 'display' => __('Default Value'), + 'instructions' => __('statamic::messages.fields_default_instructions'), + 'type' => 'text', + ], + ], + ], + ]; + } + + public function preload(): array + { + return [ + 'url' => cp_route('dictionary-fieldtype', $this->dictionary()->handle()), + 'selectedOptions' => collect($this->dictionary()->options()) + ->only($this->field->value()) + ->map(fn ($label, $value) => ['value' => $value, 'label' => $label]) + ->values() + ->all(), + ]; + } + + public function augment($value) + { + if (is_null($value)) { + return []; + } + + $dictionary = $this->dictionary(); + + if ($this->multiple()) { + return collect($value)->map(function ($value) use ($dictionary) { + return $dictionary->get($value); + })->all(); + } + + return $dictionary->get($value); + } + + public function extraRenderableFieldData(): array + { + return [ + 'options' => $this->dictionary()->options(), + ]; + } + + protected function multiple(): bool + { + return $this->config('multiple'); + } + + public function dictionary(): \Statamic\Dictionaries\Dictionary + { + if (! $this->config('dictionary')) { + throw new UndefinedDictionaryException(); + } + + $config = $this->config('dictionary'); + + $handle = is_array($config) ? Arr::get($config, 'type') : $config; + $context = is_array($config) ? Arr::except($config, 'type') : []; + + $dictionary = \Statamic\Facades\Dictionary::find($handle, $context); + + if (! $dictionary) { + throw new DictionaryNotFoundException($handle); + } + + return $dictionary; + } +} diff --git a/src/Fieldtypes/DictionaryFields.php b/src/Fieldtypes/DictionaryFields.php new file mode 100644 index 0000000000..35cac097bc --- /dev/null +++ b/src/Fieldtypes/DictionaryFields.php @@ -0,0 +1,100 @@ + 'type', + 'field' => [ + 'display' => __('Dictionary'), + 'instructions' => __('statamic::fieldtypes.dictionary.config.dictionary'), + 'type' => 'select', + 'options' => Dictionary::all() + ->mapWithKeys(fn ($dictionary) => [$dictionary->handle() => $dictionary->title()]) + ->all(), + 'max_items' => 1, + 'validate' => 'required', + ], + ]]); + + return [ + 'type' => [ + 'fields' => $typeField->toPublishArray(), + 'meta' => $typeField->meta(), + ], + 'dictionaries' => Dictionary::all()->mapWithKeys(function (\Statamic\Dictionaries\Dictionary $dictionary) { + return [$dictionary->handle() => [ + 'fields' => $dictionary->fields()->toPublishArray(), + 'meta' => $dictionary->fields()->meta(), + 'defaults' => $dictionary->fields()->all()->map(function ($field) { + return $field->fieldtype()->preProcess($field->defaultValue()); + }), + ]]; + }), + ]; + } + + public function preProcess($data): array + { + if (is_null($data)) { + return ['type' => null]; + } + + if (is_string($data)) { + return ['type' => $data]; + } + + $dictionary = Dictionary::find($data['type']); + + return array_merge( + ['type' => $data['type']], + $dictionary->fields()->addValues($data)->preProcess()->values()->all() + ); + } + + public function process($data): string|array + { + $dictionary = Dictionary::find($data['type']); + $values = $dictionary->fields()->addValues($data)->process()->values(); + + if ($values->filter()->isEmpty()) { + return $dictionary->handle(); + } + + return array_merge(['type' => $dictionary->handle()], $values->all()); + } + + public function extraRules(): array + { + if (! $dictionary = Arr::get($this->field->value(), 'type')) { + return [ + $this->field->handle().'.type' => ['required'], + ]; + } + + $dictionary = Dictionary::find($dictionary); + + $rules = $dictionary + ->fields() + ->addValues((array) $this->field->value()) + ->validator() + ->withContext([ + 'prefix' => $this->field->handle().'.', + ]) + ->rules(); + + return collect($rules)->mapWithKeys(function ($rules, $handle) { + return [$this->field->handle().'.'.$handle => $rules]; + })->all(); + } +} diff --git a/src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php b/src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php new file mode 100644 index 0000000000..5d442c916f --- /dev/null +++ b/src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php @@ -0,0 +1,46 @@ +fieldtype($request); + + return [ + 'data' => $fieldtype->dictionary()->options($request->search), + ]; + } + + protected function fieldtype($request) + { + $config = $this->getConfig($request); + + return Fieldtype::find($config['type'])->setField( + new Field('relationship', $config) + ); + } + + private function getConfig($request) + { + // The fieldtype base64-encodes the config. + $json = base64_decode($request->config); + + // The json may include unicode characters, so we'll try to convert it to UTF-8. + // See https://github.com/statamic/cms/issues/566 + $utf8 = mb_convert_encoding($json, 'UTF-8', mb_list_encodings()); + + // In PHP 8.1 there's a bug where encoding will return null. It's fixed in 8.1.2. + // In this case, we'll fall back to the original JSON, but without the encoding. + // Issue #566 may still occur, but it's better than failing completely. + $json = empty($utf8) ? $json : $utf8; + + return json_decode($json, true); + } +} diff --git a/src/Providers/AddonServiceProvider.php b/src/Providers/AddonServiceProvider.php index 307bd9e76c..44d463ed76 100644 --- a/src/Providers/AddonServiceProvider.php +++ b/src/Providers/AddonServiceProvider.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; use Statamic\Actions\Action; +use Statamic\Dictionaries\Dictionary; use Statamic\Exceptions\NotBootedException; use Statamic\Extend\Manifest; use Statamic\Facades\Addon; @@ -55,6 +56,11 @@ abstract class AddonServiceProvider extends ServiceProvider */ protected $actions = []; + /** + * @var list> + */ + protected $dictionaries = []; + /** * @var list> */ @@ -187,6 +193,7 @@ public function boot() ->bootTags() ->bootScopes() ->bootActions() + ->bootDictionaries() ->bootFieldtypes() ->bootModifiers() ->bootWidgets() @@ -258,6 +265,15 @@ protected function bootActions() return $this; } + protected function bootDictionaries() + { + foreach ($this->dictionaries as $class) { + $class::register(); + } + + return $this; + } + protected function bootFieldtypes() { foreach ($this->fieldtypes as $class) { diff --git a/src/Providers/ConsoleServiceProvider.php b/src/Providers/ConsoleServiceProvider.php index 79f5bb4517..618a82358b 100644 --- a/src/Providers/ConsoleServiceProvider.php +++ b/src/Providers/ConsoleServiceProvider.php @@ -22,6 +22,7 @@ class ConsoleServiceProvider extends ServiceProvider Commands\LicenseSet::class, Commands\MakeAction::class, Commands\MakeAddon::class, + Commands\MakeDictionary::class, Commands\MakeFieldtype::class, Commands\MakeModifier::class, Commands\MakeScope::class, diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php index 10a0da94a3..30de3f4d76 100644 --- a/src/Providers/ExtensionServiceProvider.php +++ b/src/Providers/ExtensionServiceProvider.php @@ -6,6 +6,8 @@ use Illuminate\Support\ServiceProvider; use Statamic\Actions; use Statamic\Actions\Action; +use Statamic\Dictionaries; +use Statamic\Dictionaries\Dictionary; use Statamic\Extend\Manifest; use Statamic\Fields\Fieldtype; use Statamic\Fieldtypes; @@ -48,6 +50,12 @@ class ExtensionServiceProvider extends ServiceProvider Actions\Impersonate::class, ]; + protected $dictionaries = [ + Dictionaries\Countries::class, + Dictionaries\Currencies::class, + Dictionaries\Timezones::class, + ]; + protected $fieldtypes = [ Fieldtypes\Arr::class, Fieldtypes\AssetContainer::class, @@ -63,6 +71,8 @@ class ExtensionServiceProvider extends ServiceProvider Fieldtypes\Collections::class, Fieldtypes\Color::class, Fieldtypes\Date::class, + Fieldtypes\Dictionary::class, + Fieldtypes\DictionaryFields::class, Fieldtypes\Entries::class, Fieldtypes\FieldDisplay::class, Fieldtypes\Files::class, @@ -263,6 +273,11 @@ protected function registerExtensions() 'directory' => 'Actions', 'extensions' => $this->actions, ], + 'dictionaries' => [ + 'class' => Dictionary::class, + 'directory' => 'Dictionaries', + 'extensions' => $this->dictionaries, + ], 'fieldtypes' => [ 'class' => Fieldtype::class, 'directory' => 'Fieldtypes', diff --git a/tests/Console/Commands/Concerns/CleansUpGeneratedPaths.php b/tests/Console/Commands/Concerns/CleansUpGeneratedPaths.php index c72fb1dcc3..45d2a63ed4 100644 --- a/tests/Console/Commands/Concerns/CleansUpGeneratedPaths.php +++ b/tests/Console/Commands/Concerns/CleansUpGeneratedPaths.php @@ -11,6 +11,7 @@ protected function cleanupPaths() $dirs = [ base_path('addons'), base_path('app/Actions'), + base_path('app/Dictionaries'), base_path('app/Fieldtypes'), base_path('app/Modifiers'), base_path('app/Scopes'), diff --git a/tests/Console/Commands/MakeDictionaryTest.php b/tests/Console/Commands/MakeDictionaryTest.php new file mode 100644 index 0000000000..48c9a61832 --- /dev/null +++ b/tests/Console/Commands/MakeDictionaryTest.php @@ -0,0 +1,92 @@ +files = app(Filesystem::class); + $this->fakeSuccessfulComposerRequire(); + } + + public function tearDown(): void + { + $this->cleanupPaths(); + + parent::tearDown(); + } + + #[Test] + public function it_can_make_a_dictionary() + { + $path = base_path('app/Dictionaries/Provinces.php'); + + $this->assertFileDoesNotExist($path); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces']); + + $this->assertFileExists($path); + $this->assertStringContainsString('namespace App\Dictionaries;', $this->files->get($path)); + } + + #[Test] + public function it_will_not_overwrite_an_existing_dictionary() + { + $path = base_path('app/Dictionaries/Provinces.php'); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces']); + $this->files->put($path, 'overwritten action'); + + $this->assertStringContainsString('overwritten action', $this->files->get($path)); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces']); + + $this->assertStringContainsString('overwritten action', $this->files->get($path)); + } + + #[Test] + public function using_force_option_will_overwrite_original_dictionary() + { + $path = base_path('app/Dictionaries/Provinces.php'); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces']); + $this->files->put($path, 'overwritten action'); + + $this->assertStringContainsString('overwritten action', $this->files->get($path)); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces', '--force' => true]); + + $this->assertStringNotContainsString('overwritten action', $this->files->get($path)); + } + + #[Test] + public function it_can_make_a_dictionary_into_an_addon() + { + $path = base_path('addons/yoda/bag-odah'); + + $this->artisan('statamic:make:addon', ['addon' => 'yoda/bag-odah']); + + Composer::shouldReceive('installedPath')->andReturn($path); + + $this->assertFileDoesNotExist($action = "$path/src/Dictionaries/Provinces.php"); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces', 'addon' => 'yoda/bag-odah']); + + $this->assertFileExists($action); + $this->assertStringContainsString('namespace Yoda\BagOdah\Dictionaries;', $this->files->get($action)); + } +} diff --git a/tests/Dictionaries/DictionariesTest.php b/tests/Dictionaries/DictionariesTest.php new file mode 100644 index 0000000000..1f5f4add92 --- /dev/null +++ b/tests/Dictionaries/DictionariesTest.php @@ -0,0 +1,129 @@ +assertCount(4, $all); // The built-in dictionaries + our fake one + $this->assertEveryItem($all, fn ($item) => $item instanceof Dictionary); + } + + #[Test] + public function can_get_a_dictionary() + { + $find = DictionaryFacade::find('fake_dictionary'); + + $this->assertInstanceOf(Dictionary::class, $find); + $this->assertSame('fake_dictionary', $find->handle()); + } + + #[Test] + public function can_get_options() + { + $dictionary = DictionaryFacade::find('fake_dictionary'); + + $this->assertEquals([ + 'foo' => 'Foo', + 'bar' => 'Bar', + 'baz' => 'Baz', + 'qux' => 'Qux', + ], $dictionary->options()); + } + + #[Test] + public function can_get_options_with_search_query() + { + $dictionary = DictionaryFacade::find('fake_dictionary'); + + $this->assertEquals([ + 'bar' => 'Bar', + 'baz' => 'Baz', + ], $dictionary->options('ba')); + } + + #[Test] + public function can_get_option() + { + $dictionary = DictionaryFacade::find('fake_dictionary'); + + $this->assertEquals([ + 'name' => 'Foo', + 'id' => 'foo', + ], $dictionary->get('foo')); + } + + #[Test] + public function ensure_context_is_passed_to_dictionary() + { + $dictionary = DictionaryFacade::find('fake_dictionary', [ + 'sort_in_alphabetical_order' => true, + ]); + + // When the sort_in_alphabetical_order context is passed, + // the options should be returned in alphabetical order. + $this->assertEquals([ + 'bar' => 'Bar', + 'baz' => 'Baz', + 'foo' => 'Foo', + 'qux' => 'Qux', + ], $dictionary->options()); + } +} + +class FakeDictionary extends Dictionary +{ + public function options(?string $search = null): array + { + return $this->data() + ->when($search ?? false, function ($collection) use ($search) { + return $collection->filter(fn ($item) => str_contains($item['id'], $search)); + }) + ->mapWithKeys(fn ($item) => [$item['id'] => $item['name']]) + ->when($this->context['sort_in_alphabetical_order'] ?? false, function ($collection) { + return $collection->sortBy('id'); + }) + ->all(); + } + + public function get(string $key): string|array + { + return $this->data()->firstWhere('id', $key); + } + + protected function data() + { + return collect([ + ['name' => 'Foo', 'id' => 'foo'], + ['name' => 'Bar', 'id' => 'bar'], + ['name' => 'Baz', 'id' => 'baz'], + ['name' => 'Qux', 'id' => 'qux'], + ]); + } + + protected function fieldItems() + { + return [ + 'sort_in_alphabetical_order' => [ + 'display' => 'Sort in alphabetical order?', + 'type' => 'toggle', + ], + ]; + } +} diff --git a/tests/Fieldtypes/DictionariesTest.php b/tests/Fieldtypes/DictionariesTest.php new file mode 100644 index 0000000000..d818ebdbbe --- /dev/null +++ b/tests/Fieldtypes/DictionariesTest.php @@ -0,0 +1,106 @@ + 'dictionary', 'dictionary' => 'countries'])); + $field->setValue(['USA', 'AUS', 'CAN', 'DEU', 'GBR']); + + $fieldtype = FieldtypeRepository::find('dictionary'); + $fieldtype->setField($field); + + $preload = $fieldtype->preload(); + + $this->assertArraySubset([ + 'url' => 'http://localhost/cp/fieldtypes/dictionaries/countries', + 'selectedOptions' => [ + ['value' => 'AUS', 'label' => 'πŸ‡¦πŸ‡Ί Australia'], + ['value' => 'CAN', 'label' => 'πŸ‡¨πŸ‡¦ Canada'], + ['value' => 'DEU', 'label' => 'πŸ‡©πŸ‡ͺ Germany'], + ['value' => 'GBR', 'label' => 'πŸ‡¬πŸ‡§ United Kingdom'], + ['value' => 'USA', 'label' => 'πŸ‡ΊπŸ‡Έ United States'], + ], + ], $preload); + } + + #[Test] + public function it_augments_a_single_option() + { + $field = (new Field('test', ['type' => 'dictionary', 'dictionary' => 'countries'])); + + $fieldtype = FieldtypeRepository::find('dictionary'); + $fieldtype->setField($field); + + $augment = $fieldtype->augment('USA'); + + $this->assertEquals([ + 'name' => 'United States', + 'iso3' => 'USA', + 'iso2' => 'US', + 'region' => 'Americas', + 'subregion' => 'Northern America', + 'emoji' => 'πŸ‡ΊπŸ‡Έ', + ], $augment); + } + + #[Test] + public function it_augments_multiple_options() + { + $field = (new Field('test', ['type' => 'dictionary', 'dictionary' => 'countries', 'multiple' => true])); + + $fieldtype = FieldtypeRepository::find('dictionary'); + $fieldtype->setField($field); + + $augment = $fieldtype->augment(['USA', 'GBR']); + + $this->assertEquals([ + [ + 'name' => 'United States', + 'iso3' => 'USA', + 'iso2' => 'US', + 'region' => 'Americas', + 'subregion' => 'Northern America', + 'emoji' => 'πŸ‡ΊπŸ‡Έ', + ], + [ + 'name' => 'United Kingdom', + 'iso3' => 'GBR', + 'iso2' => 'GB', + 'region' => 'Europe', + 'subregion' => 'Northern Europe', + 'emoji' => 'πŸ‡¬πŸ‡§', + ], + ], $augment); + } + + #[Test] + public function it_returns_extra_renderable_field_data() + { + $field = (new Field('test', ['type' => 'dictionary', 'dictionary' => 'countries'])); + $field->setValue(['USA', 'AUS', 'CAN', 'DEU', 'GBR']); + + $fieldtype = FieldtypeRepository::find('dictionary'); + $fieldtype->setField($field); + + $extraRenderableFieldData = $fieldtype->extraRenderableFieldData(); + + $this->assertArraySubset([ + 'options' => [ + 'AUS' => 'πŸ‡¦πŸ‡Ί Australia', + 'CAN' => 'πŸ‡¨πŸ‡¦ Canada', + 'DEU' => 'πŸ‡©πŸ‡ͺ Germany', + 'GBR' => 'πŸ‡¬πŸ‡§ United Kingdom', + 'USA' => 'πŸ‡ΊπŸ‡Έ United States', + ], + ], $extraRenderableFieldData); + } +} diff --git a/tests/Fieldtypes/DictionaryFieldsTest.php b/tests/Fieldtypes/DictionaryFieldsTest.php new file mode 100644 index 0000000000..862c3cbf4a --- /dev/null +++ b/tests/Fieldtypes/DictionaryFieldsTest.php @@ -0,0 +1,155 @@ +preload(); + + $this->assertArraySubset([ + 'type' => [ + 'fields' => [ + ['handle' => 'type', 'type' => 'select'], + ], + 'meta' => collect(['type' => null]), + ], + ], $preload); + + $this->assertArraySubset([ + 'fake_dictionary' => [ + 'fields' => [ + ['handle' => 'category', 'type' => 'select'], + ], + 'meta' => collect(['category' => null]), + 'defaults' => collect(['category' => null]), + ], + ], $preload['dictionaries']->all()); + } + + #[Test] + public function it_pre_processes_dictionary_fields() + { + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + + $preProcess = $fieldtype->preProcess([ + 'type' => 'fake_dictionary', + 'category' => 'foo', + ]); + + $this->assertEquals([ + 'type' => 'fake_dictionary', + 'category' => 'foo', + ], $preProcess); + } + + #[Test] + public function it_pre_processes_dictionary_fields_saved_as_a_string() + { + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + + $preProcess = $fieldtype->preProcess('fake_dictionary'); + + $this->assertEquals([ + 'type' => 'fake_dictionary', + ], $preProcess); + } + + #[Test] + public function it_processes_dictionary_fields() + { + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + + $process = $fieldtype->process([ + 'type' => 'fake_dictionary', + 'category' => 'foo', + ]); + + $this->assertEquals([ + 'type' => 'fake_dictionary', + 'category' => 'foo', + ], $process); + } + + #[Test] + public function it_processes_dictionary_fields_into_a_string_when_dictionary_has_no_config_values() + { + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + + $process = $fieldtype->process([ + 'type' => 'fake_dictionary', + ]); + + $this->assertEquals('fake_dictionary', $process); + } + + #[Test] + public function it_returns_validation_rules() + { + $field = (new Field('test', ['type' => 'dictionary_fields']))->setValue(['type' => 'fake_dictionary']); + + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + $fieldtype->setField($field); + + $extraRules = $fieldtype->extraRules(); + + $this->assertEquals([ + 'test.category' => ['required'], + ], $extraRules); + } + + #[Test] + public function it_returns_validation_rules_when_no_dictionary_is_selected() + { + $field = (new Field('test', ['type' => 'dictionary_fields'])); + + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + $fieldtype->setField($field); + + $extraRules = $fieldtype->extraRules(); + + $this->assertEquals([ + 'test.type' => ['required'], + ], $extraRules); + } +} + +class FakeDictionary extends Dictionary +{ + public function options(?string $search = null): array + { + return []; + } + + public function get(string $key): string|array + { + return []; + } + + protected function fieldItems() + { + return [ + 'category' => [ + 'type' => 'select', + 'validate' => 'required', + ], + ]; + } +}