From 69e2ca791430c47b888ac0ed2e6970bff95962f8 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Tue, 21 Apr 2026 10:15:58 -0700 Subject: [PATCH] feat(browser): expand cuisine taxonomy to 13 categories + 105 subcategories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 5 new top-level categories: BBQ & Smoke, Central American, African, Pacific & Oceania, Central Asian & Caucasus - British/Irish split into British + Irish + Scottish with regional keywords - Scandinavian: dish-level keyword expansion to fix zero-count gap - Mediterranean: Israeli → Jewish (Ashkenazi/Sephardic/NY deli/z'houg/hawaiij); Palestinian, Yemeni, Egyptian, Syrian added; Moroccan moved to African - Mexican: +Baja/Cal-Mex, +Mexico City - Asian: +Hong Kong, +Cambodian, +Laotian, +Mongolian (16 subcategories) - Indian: +Bangladeshi, +Pakistani, +Sri Lankan, +Nepali (8 subcategories) - Latin American: full Caribbean depth (Jamaican, Puerto Rican, Dominican, Haitian, Trinidad); +Argentinian, +Venezuelan, +Chilean - American: +Pacific Northwest, +Hawaiian; BBQ promoted to own category - BBQ & Smoke: 8 regional styles (Texas, Carolina, KC, Memphis, Alabama, Kentucky, St. Louis, Backyard) - feat(shopping): locale_config.py — Amazon/Instacart/Walmart locale routing for multi-currency affiliate link support (#114) - chore: gitleaks allowlist for Amazon grocery dept IDs in locale_config.py --- .gitleaks.toml | 10 + app/services/recipe/browser_domains.py | 386 ++++++++++++++++++++----- app/services/recipe/locale_config.py | 160 ++++++++++ 3 files changed, 490 insertions(+), 66 deletions(-) create mode 100644 app/services/recipe/locale_config.py diff --git a/.gitleaks.toml b/.gitleaks.toml index c2af7f9..9539230 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -3,6 +3,16 @@ [extend] path = "/Library/Development/CircuitForge/circuitforge-hooks/gitleaks.toml" +# ── Global allowlist ────────────────────────────────────────────────────────── +# Amazon grocery department IDs (rh=n:<10-digit>) false-positive as phone +# numbers. locale_config.py is a static lookup table with no secrets. + +[allowlist] +# Amazon grocery dept IDs (rh=n:) false-positive as phone numbers. +regexes = [ + '''rh=n:\d{8,12}''', +] + # ── Test fixture allowlists ─────────────────────────────────────────────────── [[rules]] diff --git a/app/services/recipe/browser_domains.py b/app/services/recipe/browser_domains.py index 4d576b6..2f394f7 100644 --- a/app/services/recipe/browser_domains.py +++ b/app/services/recipe/browser_domains.py @@ -43,88 +43,204 @@ DOMAINS: dict[str, dict] = { }, }, "Mexican": { - "keywords": ["mexican", "tex-mex", "taco", "enchilada", "burrito", - "salsa", "guacamole"], + "keywords": ["mexican", "taco", "enchilada", "burrito", "salsa", + "guacamole", "mole", "tamale"], "subcategories": { - "Oaxacan": ["oaxacan", "oaxaca", "mole negro", "tlayuda", - "chapulines", "mezcal"], - "Yucatecan": ["yucatecan", "yucatan", "cochinita pibil", "poc chuc", - "sopa de lima", "panuchos"], - "Veracruz": ["veracruz", "huachinango", "picadas", "enfrijoladas"], - "Street Food": ["taco", "elote", "tlacoyos", "torta", - "tamale", "quesadilla"], - "Mole": ["mole", "mole negro", "mole rojo", "mole verde", - "mole poblano"], + "Oaxacan": ["oaxacan", "oaxaca", "mole negro", "tlayuda", + "chapulines", "mezcal", "tasajo", "memelas"], + "Yucatecan": ["yucatecan", "yucatan", "cochinita pibil", "poc chuc", + "sopa de lima", "panuchos", "papadzules"], + "Veracruz": ["veracruz", "veracruzana", "huachinango", + "picadas", "enfrijoladas", "caldo de mariscos"], + "Street Food": ["taco", "elote", "tlacoyos", "torta", "tamale", + "quesadilla", "tostada", "sope", "gordita"], + "Mole": ["mole", "mole negro", "mole rojo", "mole verde", + "mole poblano", "mole amarillo", "pipián"], + "Baja / Cal-Mex": ["baja", "baja california", "cal-mex", "baja fish taco", + "fish taco", "carne asada fries", "california burrito", + "birria", "birria tacos", "quesabirria", + "lobster puerto nuevo", "tijuana", "ensenada", + "agua fresca", "caesar salad tijuana"], + "Mexico City": ["mexico city", "chilaquiles", "tlayuda cdmx", + "tacos de canasta", "torta ahogada", "pozole", + "chiles en nogada"], }, }, "Asian": { "keywords": ["asian", "chinese", "japanese", "thai", "korean", "vietnamese", - "stir fry", "stir-fry", "ramen", "sushi"], + "stir fry", "stir-fry", "ramen", "sushi", "malaysian", + "taiwanese", "singaporean", "burmese", "cambodian", + "laotian", "mongolian", "hong kong"], "subcategories": { - "Korean": ["korean", "kimchi", "bibimbap", "bulgogi", "japchae", - "doenjang", "gochujang"], - "Japanese": ["japanese", "sushi", "ramen", "tempura", "miso", - "teriyaki", "udon", "soba", "bento", "yakitori"], - "Chinese": ["chinese", "dim sum", "fried rice", "dumplings", "wonton", - "spring roll", "szechuan", "sichuan", "cantonese", - "chow mein", "mapo", "lo mein"], - "Thai": ["thai", "pad thai", "green curry", "red curry", - "coconut milk", "lemongrass", "satay", "tom yum"], - "Vietnamese": ["vietnamese", "pho", "banh mi", "spring rolls", - "vermicelli", "nuoc cham", "bun bo"], - "Filipino": ["filipino", "adobo", "sinigang", "pancit", "lumpia", - "kare-kare", "lechon"], - "Indonesian": ["indonesian", "rendang", "nasi goreng", "gado-gado", - "tempeh", "sambal"], + "Korean": ["korean", "kimchi", "bibimbap", "bulgogi", "japchae", + "doenjang", "gochujang", "tteokbokki", "sundubu", + "galbi", "jjigae", "kbbq", "korean fried chicken"], + "Japanese": ["japanese", "sushi", "ramen", "tempura", "miso", + "teriyaki", "udon", "soba", "bento", "yakitori", + "tonkatsu", "onigiri", "okonomiyaki", "takoyaki", + "kaiseki", "izakaya"], + "Chinese": ["chinese", "dim sum", "fried rice", "dumplings", "wonton", + "spring roll", "szechuan", "sichuan", "cantonese", + "chow mein", "mapo tofu", "lo mein", "hot pot", + "peking duck", "char siu", "congee"], + "Thai": ["thai", "pad thai", "green curry", "red curry", + "coconut milk", "lemongrass", "satay", "tom yum", + "larb", "khao man gai", "massaman", "pad see ew"], + "Vietnamese": ["vietnamese", "pho", "banh mi", "spring rolls", + "vermicelli", "nuoc cham", "bun bo hue", + "banh xeo", "com tam", "bun cha"], + "Filipino": ["filipino", "adobo", "sinigang", "pancit", "lumpia", + "kare-kare", "lechon", "sisig", "halo-halo", + "dinuguan", "tinola", "bistek"], + "Indonesian": ["indonesian", "rendang", "nasi goreng", "gado-gado", + "tempeh", "sambal", "soto", "opor ayam", + "bakso", "mie goreng", "nasi uduk"], + "Malaysian": ["malaysian", "laksa", "nasi lemak", "char kway teow", + "satay malaysia", "roti canai", "bak kut teh", + "cendol", "mee goreng mamak", "curry laksa"], + "Taiwanese": ["taiwanese", "beef noodle soup", "lu rou fan", + "oyster vermicelli", "scallion pancake taiwan", + "pork chop rice", "three cup chicken", + "bubble tea", "stinky tofu", "ba wan"], + "Singaporean": ["singaporean", "chicken rice", "chili crab", + "singaporean laksa", "bak chor mee", "rojak", + "kaya toast", "nasi padang", "satay singapore"], + "Burmese": ["burmese", "myanmar", "mohinga", "laphet thoke", + "tea leaf salad", "ohn no khao swe", + "mont di", "nangyi thoke"], + "Hong Kong": ["hong kong", "hk style", "pineapple bun", + "wonton noodle soup", "hk milk tea", "egg tart", + "typhoon shelter crab", "char siu bao", "jook", + "congee hk", "silk stocking tea", "dan tat", + "siu mai hk", "cheung fun"], + "Cambodian": ["cambodian", "khmer", "amok", "lok lak", + "kuy teav", "bai sach chrouk", "nom banh chok", + "samlor korko", "beef loc lac"], + "Laotian": ["laotian", "lao", "larb", "tam mak hoong", + "or lam", "khao niaw", "ping kai", + "naem khao", "khao piak sen", "mok pa"], + "Mongolian": ["mongolian", "buuz", "khuushuur", "tsuivan", + "boodog", "airag", "khorkhog", "bansh", + "guriltai shol", "suutei tsai"], + "South Asian Fusion": ["south asian fusion", "indo-chinese", + "hakka chinese", "chilli chicken", + "manchurian", "schezwan"], }, }, "Indian": { "keywords": ["indian", "curry", "lentil", "dal", "tikka", "masala", - "biryani", "naan", "chutney"], + "biryani", "naan", "chutney", "pakistani", "sri lankan", + "bangladeshi", "nepali"], "subcategories": { - "North Indian": ["north indian", "punjabi", "mughal", "tikka masala", - "naan", "tandoori", "butter chicken", "palak"], - "South Indian": ["south indian", "tamil", "kerala", "dosa", "idli", - "sambar", "rasam", "coconut chutney"], - "Bengali": ["bengali", "mustard fish", "hilsa", "shorshe"], - "Gujarati": ["gujarati", "dhokla", "thepla", "undhiyu"], + "North Indian": ["north indian", "punjabi", "mughal", "tikka masala", + "naan", "tandoori", "butter chicken", "palak paneer", + "chole", "rajma", "aloo gobi"], + "South Indian": ["south indian", "tamil", "kerala", "dosa", "idli", + "sambar", "rasam", "coconut chutney", "appam", + "fish curry kerala", "puttu", "payasam"], + "Bengali": ["bengali", "mustard fish", "hilsa", "shorshe ilish", + "mishti doi", "rasgulla", "kosha mangsho"], + "Gujarati": ["gujarati", "dhokla", "thepla", "undhiyu", + "khandvi", "fafda", "gujarati dal"], + "Pakistani": ["pakistani", "nihari", "haleem", "seekh kebab", + "karahi", "biryani karachi", "chapli kebab", + "halwa puri", "paya"], + "Sri Lankan": ["sri lankan", "kottu roti", "hoppers", "pol sambol", + "sri lankan curry", "lamprais", "string hoppers", + "wambatu moju"], + "Bangladeshi": ["bangladeshi", "bangladesh", "dhaka biryani", + "shutki", "pitha", "hilsa curry", "kacchi biryani", + "bhuna khichuri", "doi maach", "rezala"], + "Nepali": ["nepali", "dal bhat", "momos", "sekuwa", + "sel roti", "gundruk", "thukpa"], }, }, "Mediterranean": { "keywords": ["mediterranean", "greek", "middle eastern", "turkish", - "moroccan", "lebanese"], + "lebanese", "jewish", "palestinian", "yemeni", "egyptian", + "syrian", "iraqi", "jordanian"], "subcategories": { "Greek": ["greek", "feta", "tzatziki", "moussaka", "spanakopita", - "souvlaki", "dolmades"], + "souvlaki", "dolmades", "spanakopita", "tiropita", + "galaktoboureko"], "Turkish": ["turkish", "kebab", "borek", "meze", "baklava", - "lahmacun"], - "Moroccan": ["moroccan", "tagine", "couscous", "harissa", - "chermoula", "preserved lemon"], + "lahmacun", "menemen", "pide", "iskender", + "kisir", "simit"], + "Syrian": ["syrian", "fattet hummus", "kibbeh syria", + "muhammara", "maklouba syria", "sfeeha", + "halawet el jibn"], "Lebanese": ["lebanese", "middle eastern", "hummus", "falafel", - "tabbouleh", "kibbeh", "fattoush"], - "Israeli": ["israeli", "shakshuka", "sabich", "za'atar", - "tahini"], + "tabbouleh", "kibbeh", "fattoush", "manakish", + "kafta", "sfiha"], + "Jewish": ["jewish", "israeli", "ashkenazi", "sephardic", + "shakshuka", "sabich", "za'atar", "tahini", + "zhug", "zhoug", "s'khug", "z'houg", + "hawaiij", "hawaij", "hawayej", + "matzo", "latke", "rugelach", "babka", "challah", + "cholent", "gefilte fish", "brisket", "kugel", + "new york jewish", "new york deli", "pastrami", + "knish", "lox", "bagel and lox", "jewish deli"], + "Palestinian": ["palestinian", "musakhan", "maqluba", "knafeh", + "maftoul", "freekeh", "sumac chicken"], + "Yemeni": ["yemeni", "saltah", "lahoh", "bint al-sahn", + "zhug", "zhoug", "hulba", "fahsa", + "hawaiij", "hawaij", "hawayej"], + "Egyptian": ["egyptian", "koshari", "molokhia", "mahshi", + "ful medames", "ta'ameya", "feteer meshaltet"], }, }, "American": { - "keywords": ["american", "southern", "bbq", "barbecue", "comfort food", - "cajun", "creole"], + "keywords": ["american", "southern", "comfort food", "cajun", "creole", + "hawaiian", "tex-mex", "soul food"], "subcategories": { "Southern": ["southern", "soul food", "fried chicken", - "collard greens", "cornbread", "biscuits and gravy"], + "collard greens", "cornbread", "biscuits and gravy", + "mac and cheese", "sweet potato pie", "okra"], "Cajun/Creole": ["cajun", "creole", "new orleans", "gumbo", - "jambalaya", "etouffee", "dirty rice"], - "BBQ": ["bbq", "barbecue", "smoked", "brisket", "pulled pork", - "ribs", "pit"], + "jambalaya", "etouffee", "dirty rice", "po'boy", + "muffuletta", "red beans and rice"], "Tex-Mex": ["tex-mex", "southwestern", "chili", "fajita", - "queso"], + "queso", "breakfast taco", "chile con carne"], "New England": ["new england", "chowder", "lobster", "clam", - "maple", "yankee"], + "maple", "yankee", "boston baked beans", + "johnnycake", "fish and chips"], + "Pacific Northwest": ["pacific northwest", "pnw", "dungeness crab", + "salmon", "cedar plank", "razor clam", + "geoduck", "chanterelle", "marionberry"], + "Hawaiian": ["hawaiian", "hawaii", "plate lunch", "loco moco", + "poke", "spam musubi", "kalua pig", "lau lau", + "haupia", "poi", "manapua", "garlic shrimp", + "saimin", "huli huli", "malasada"], + }, + }, + "BBQ & Smoke": { + "keywords": ["bbq", "barbecue", "smoked", "pit", "smoke ring", + "low and slow", "brisket", "pulled pork", "ribs"], + "subcategories": { + "Texas BBQ": ["texas bbq", "central texas bbq", "brisket", + "beef ribs", "post oak", "salt and pepper rub", + "east texas bbq", "lockhart", "franklin style"], + "Carolina BBQ": ["carolina bbq", "north carolina bbq", "whole hog", + "vinegar sauce", "lexington style", "eastern nc", + "south carolina bbq", "mustard sauce"], + "Kansas City BBQ": ["kansas city bbq", "kc bbq", "burnt ends", + "sweet bbq sauce", "tomato molasses sauce", + "baby back ribs kc"], + "Memphis BBQ": ["memphis bbq", "dry rub ribs", "wet ribs", + "memphis style", "dry rub pork"], + "Alabama BBQ": ["alabama bbq", "white sauce", "alabama white sauce", + "smoked chicken alabama"], + "Kentucky BBQ": ["kentucky bbq", "mutton bbq", "owensboro bbq", + "black dip", "western kentucky barbecue"], + "St. Louis BBQ": ["st louis bbq", "st. louis ribs", "st louis cut ribs", + "st louis style spare ribs"], + "Backyard Grill": ["backyard bbq", "cookout", "grilled burgers", + "charcoal grill", "kettle grill", "tailgate"], }, }, "European": { - "keywords": ["french", "german", "spanish", "british", "irish", - "scandinavian"], + "keywords": ["french", "german", "spanish", "british", "irish", "scottish", + "welsh", "scandinavian", "nordic", "eastern european"], "subcategories": { "French": ["french", "provencal", "beurre", "crepe", "ratatouille", "cassoulet", "bouillabaisse"], @@ -132,26 +248,164 @@ DOMAINS: dict[str, dict] = { "tortilla espanola", "chorizo"], "German": ["german", "bratwurst", "sauerkraut", "schnitzel", "pretzel", "strudel"], - "British/Irish": ["british", "irish", "english", "pub food", - "shepherd's pie", "bangers", "scones"], + "British": ["british", "english", "pub food", "cornish", + "shepherd's pie", "bangers", "toad in the hole", + "coronation chicken", "london", "londoner", + "cornish pasty", "ploughman's"], + "Irish": ["irish", "ireland", "colcannon", "coddle", + "irish stew", "soda bread", "boxty", "champ"], + "Scottish": ["scottish", "scotland", "haggis", "cullen skink", + "cranachan", "scotch broth", "glaswegian", + "neeps and tatties", "tablet"], "Scandinavian": ["scandinavian", "nordic", "swedish", "norwegian", - "danish", "gravlax", "meatballs"], + "danish", "finnish", "gravlax", "swedish meatballs", + "lefse", "smörgåsbord", "fika", "crispbread", + "cardamom bun", "herring", "æbleskiver", + "lingonberry", "lutefisk", "janssons frestelse", + "knäckebröd", "kladdkaka"], + "Eastern European": ["eastern european", "polish", "russian", "ukrainian", + "czech", "hungarian", "pierogi", "borscht", + "goulash", "kielbasa", "varenyky", "pelmeni"], }, }, "Latin American": { "keywords": ["latin american", "peruvian", "argentinian", "colombian", - "cuban", "caribbean", "brazilian"], + "cuban", "caribbean", "brazilian", "venezuelan", "chilean"], "subcategories": { - "Peruvian": ["peruvian", "ceviche", "lomo saltado", "anticucho", - "aji amarillo"], - "Brazilian": ["brazilian", "churrasco", "feijoada", "pao de queijo", - "brigadeiro"], - "Colombian": ["colombian", "bandeja paisa", "arepas", "empanadas", - "sancocho"], - "Cuban": ["cuban", "ropa vieja", "moros y cristianos", - "picadillo", "mojito"], - "Caribbean": ["caribbean", "jamaican", "jerk", "trinidadian", - "plantain", "roti"], + "Peruvian": ["peruvian", "ceviche", "lomo saltado", "anticucho", + "aji amarillo", "causa", "leche de tigre", + "arroz con leche peru", "pollo a la brasa"], + "Brazilian": ["brazilian", "churrasco", "feijoada", "pao de queijo", + "brigadeiro", "coxinha", "moqueca", "vatapa", + "caipirinha", "acai bowl"], + "Colombian": ["colombian", "bandeja paisa", "arepas", "empanadas", + "sancocho", "ajiaco", "buñuelos", "changua"], + "Argentinian": ["argentinian", "asado", "chimichurri", "empanadas argentina", + "milanesa", "locro", "dulce de leche", "medialunas"], + "Venezuelan": ["venezuelan", "pabellón criollo", "arepas venezuela", + "hallacas", "cachapas", "tequeños", "caraotas"], + "Chilean": ["chilean", "cazuela", "pastel de choclo", "curanto", + "sopaipillas", "charquicán", "completo"], + "Cuban": ["cuban", "ropa vieja", "moros y cristianos", + "picadillo", "lechon cubano", "vaca frita", + "tostones", "platanos maduros"], + "Jamaican": ["jamaican", "jerk chicken", "jerk pork", "ackee saltfish", + "curry goat", "rice and peas", "escovitch", + "jamaican patty", "callaloo jamaica", "festival"], + "Puerto Rican": ["puerto rican", "mofongo", "pernil", "arroz con gandules", + "sofrito", "pasteles", "tostones pr", "tembleque", + "coquito", "asopao"], + "Dominican": ["dominican", "mangu", "sancocho dominicano", + "pollo guisado", "habichuelas guisadas", + "tostones dominicanos", "morir soñando"], + "Haitian": ["haitian", "griot", "pikliz", "riz et pois", + "joumou", "akra", "pain patate", "labouyi"], + "Trinidad": ["trinidadian", "doubles", "roti trinidad", "pelau", + "callaloo trinidad", "bake and shark", + "curry duck", "oil down"], + }, + }, + "Central American": { + "keywords": ["central american", "salvadoran", "guatemalan", + "honduran", "nicaraguan", "costa rican", "panamanian"], + "subcategories": { + "Salvadoran": ["salvadoran", "el salvador", "pupusas", "curtido", + "sopa de pata", "nuégados", "atol shuco"], + "Guatemalan": ["guatemalan", "pepián", "jocon", "kak'ik", + "hilachas", "rellenitos", "fiambre"], + "Costa Rican": ["costa rican", "gallo pinto", "casado", + "olla de carne", "arroz con leche cr", + "tres leches cr"], + "Honduran": ["honduran", "baleadas", "sopa de caracol", + "tapado", "machuca", "catrachitas"], + "Nicaraguan": ["nicaraguan", "nacatamal", "vigorón", "indio viejo", + "gallo pinto nicaragua", "güirilas"], + }, + }, + "African": { + "keywords": ["african", "west african", "east african", "ethiopian", + "nigerian", "ghanaian", "kenyan", "south african", + "senegalese", "tunisian"], + "subcategories": { + "West African": ["west african", "nigerian", "ghanaian", + "jollof rice", "egusi soup", "fufu", "suya", + "groundnut stew", "kelewele", "kontomire", + "waakye", "ofam", "bitterleaf soup"], + "Senegalese": ["senegalese", "senegal", "thieboudienne", + "yassa", "mafe", "thiou", "ceebu jen", + "domoda"], + "Ethiopian & Eritrean": ["ethiopian", "eritrean", "injera", "doro wat", + "kitfo", "tibs", "shiro", "misir wat", + "gomen", "ful ethiopian", "tegamino"], + "East African": ["east african", "kenyan", "tanzanian", "ugandan", + "nyama choma", "ugali", "sukuma wiki", + "pilau kenya", "mandazi", "matoke", + "githeri", "irio"], + "North African": ["north african", "tunisian", "algerian", "libyan", + "brik", "lablabi", "merguez", "shakshuka tunisian", + "harissa tunisian", "couscous algerian"], + "South African": ["south african", "braai", "bobotie", "boerewors", + "bunny chow", "pap", "chakalaka", "biltong", + "malva pudding", "koeksister", "potjiekos"], + "Moroccan": ["moroccan", "tagine", "couscous morocco", + "harissa", "chermoula", "preserved lemon", + "pastilla", "mechoui", "bastilla"], + }, + }, + "Pacific & Oceania": { + "keywords": ["pacific", "oceania", "polynesian", "melanesian", + "micronesian", "maori", "fijian", "samoan", "tongan", + "hawaiian", "australian", "new zealand"], + "subcategories": { + "Māori / New Zealand": ["maori", "new zealand", "hangi", "rewena bread", + "boil-up", "paua", "kumara", "pavlova nz", + "whitebait fritter", "kina", "hokey pokey"], + "Australian": ["australian", "meat pie", "lamington", + "anzac biscuits", "damper", "barramundi", + "vegemite", "pavlova australia", "tim tam", + "sausage sizzle", "chiko roll", "fairy bread"], + "Fijian": ["fijian", "fiji", "kokoda", "lovo", + "rourou", "palusami fiji", "duruka", + "vakalolo"], + "Samoan": ["samoan", "samoa", "palusami", "oka", + "fa'ausi", "chop suey samoa", "sapasui", + "koko alaisa", "supo esi"], + "Tongan": ["tongan", "tonga", "lu pulu", "'ota 'ika", + "fekkai", "faikakai topai", "kapisi pulu"], + "Papua New Guinean": ["papua new guinea", "png", "mumu", + "sago", "aibika", "kaukau", + "taro png", "coconut crab"], + "Hawaiian": ["hawaiian", "hawaii", "poke", "loco moco", + "plate lunch", "kalua pig", "haupia", + "spam musubi", "poi", "malasada"], + }, + }, + "Central Asian & Caucasus": { + "keywords": ["central asian", "caucasus", "georgian", "armenian", "uzbek", + "afghan", "persian", "iranian", "azerbaijani", "kazakh"], + "subcategories": { + "Persian / Iranian": ["persian", "iranian", "ghormeh sabzi", "fesenjan", + "tahdig", "joojeh kabab", "ash reshteh", + "zereshk polo", "khoresh", "mast o khiar", + "kashk-e-bademjan", "mirza ghasemi", + "baghali polo"], + "Georgian": ["georgian", "georgia", "khachapuri", "khinkali", + "churchkhela", "ajapsandali", "satsivi", + "pkhali", "lobiani", "badrijani nigvzit"], + "Armenian": ["armenian", "dolma armenia", "lahmajoun", + "manti armenia", "ghapama", "basturma", + "harissa armenia", "nazook", "tolma"], + "Azerbaijani": ["azerbaijani", "azerbaijan", "plov azerbaijan", + "dolma azeri", "dushbara", "levengi", + "shah plov", "gutab"], + "Uzbek": ["uzbek", "uzbekistan", "plov", "samsa", + "lagman", "shashlik", "manti uzbek", + "non bread", "dimlama", "sumalak"], + "Afghan": ["afghan", "afghanistan", "kabuli pulao", "mantu", + "bolani", "qorma", "ashak", "shorwa", + "aushak", "borani banjan"], + "Kazakh": ["kazakh", "beshbarmak", "kuyrdak", "baursak", + "kurt", "shubat", "kazy"], }, }, }, diff --git a/app/services/recipe/locale_config.py b/app/services/recipe/locale_config.py new file mode 100644 index 0000000..e7e798e --- /dev/null +++ b/app/services/recipe/locale_config.py @@ -0,0 +1,160 @@ +""" +Shopping locale configuration. + +Maps a locale key to Amazon domain, currency metadata, and retailer availability. +Instacart and Walmart are US/CA-only; all other locales get Amazon only. +Amazon Fresh (&i=amazonfresh) is US-only — international domains use the general +grocery department (&rh=n:16310101) where available, plain search elsewhere. +""" +from __future__ import annotations + +from typing import TypedDict + + +class LocaleConfig(TypedDict): + amazon_domain: str + amazon_grocery_dept: str # URL fragment for grocery department on this locale's site + currency_code: str + currency_symbol: str + instacart: bool + walmart: bool + + +LOCALES: dict[str, LocaleConfig] = { + "us": { + "amazon_domain": "amazon.com", + "amazon_grocery_dept": "i=amazonfresh", + "currency_code": "USD", + "currency_symbol": "$", + "instacart": True, + "walmart": True, + }, + "ca": { + "amazon_domain": "amazon.ca", + "amazon_grocery_dept": "rh=n:6967215011", # Grocery dept on .ca # gitleaks:allow + "currency_code": "CAD", + "currency_symbol": "CA$", + "instacart": True, + "walmart": False, + }, + "gb": { + "amazon_domain": "amazon.co.uk", + "amazon_grocery_dept": "rh=n:340831031", # Grocery dept on .co.uk + "currency_code": "GBP", + "currency_symbol": "£", + "instacart": False, + "walmart": False, + }, + "au": { + "amazon_domain": "amazon.com.au", + "amazon_grocery_dept": "rh=n:5765081051", # Pantry/grocery on .com.au # gitleaks:allow + "currency_code": "AUD", + "currency_symbol": "A$", + "instacart": False, + "walmart": False, + }, + "nz": { + # NZ has no Amazon storefront — route to .com.au as nearest option + "amazon_domain": "amazon.com.au", + "amazon_grocery_dept": "rh=n:5765081051", # gitleaks:allow + "currency_code": "NZD", + "currency_symbol": "NZ$", + "instacart": False, + "walmart": False, + }, + "de": { + "amazon_domain": "amazon.de", + "amazon_grocery_dept": "rh=n:340843031", # Lebensmittel & Getränke + "currency_code": "EUR", + "currency_symbol": "€", + "instacart": False, + "walmart": False, + }, + "fr": { + "amazon_domain": "amazon.fr", + "amazon_grocery_dept": "rh=n:197858031", + "currency_code": "EUR", + "currency_symbol": "€", + "instacart": False, + "walmart": False, + }, + "it": { + "amazon_domain": "amazon.it", + "amazon_grocery_dept": "rh=n:525616031", + "currency_code": "EUR", + "currency_symbol": "€", + "instacart": False, + "walmart": False, + }, + "es": { + "amazon_domain": "amazon.es", + "amazon_grocery_dept": "rh=n:599364031", + "currency_code": "EUR", + "currency_symbol": "€", + "instacart": False, + "walmart": False, + }, + "nl": { + "amazon_domain": "amazon.nl", + "amazon_grocery_dept": "rh=n:16584827031", + "currency_code": "EUR", + "currency_symbol": "€", + "instacart": False, + "walmart": False, + }, + "se": { + "amazon_domain": "amazon.se", + "amazon_grocery_dept": "rh=n:20741393031", + "currency_code": "SEK", + "currency_symbol": "kr", + "instacart": False, + "walmart": False, + }, + "jp": { + "amazon_domain": "amazon.co.jp", + "amazon_grocery_dept": "rh=n:2246283051", # gitleaks:allow + "currency_code": "JPY", + "currency_symbol": "¥", + "instacart": False, + "walmart": False, + }, + "in": { + "amazon_domain": "amazon.in", + "amazon_grocery_dept": "rh=n:2454178031", # gitleaks:allow + "currency_code": "INR", + "currency_symbol": "₹", + "instacart": False, + "walmart": False, + }, + "mx": { + "amazon_domain": "amazon.com.mx", + "amazon_grocery_dept": "rh=n:10737659011", + "currency_code": "MXN", + "currency_symbol": "MX$", + "instacart": False, + "walmart": False, + }, + "br": { + "amazon_domain": "amazon.com.br", + "amazon_grocery_dept": "rh=n:17878420011", + "currency_code": "BRL", + "currency_symbol": "R$", + "instacart": False, + "walmart": False, + }, + "sg": { + "amazon_domain": "amazon.sg", + "amazon_grocery_dept": "rh=n:6981647051", # gitleaks:allow + "currency_code": "SGD", + "currency_symbol": "S$", + "instacart": False, + "walmart": False, + }, +} + +DEFAULT_LOCALE = "us" + + +def get_locale(key: str) -> LocaleConfig: + """Return locale config for *key*, falling back to US if unknown.""" + return LOCALES.get(key, LOCALES[DEFAULT_LOCALE])