1use rand::seq::IndexedRandom;
7use rand::Rng;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
13#[serde(rename_all = "snake_case")]
14pub enum NameCulture {
15 #[default]
17 WesternUs,
18 German,
20 French,
22 Chinese,
24 Japanese,
26 Indian,
28 Hispanic,
30}
31
32impl NameCulture {
33 pub fn all() -> &'static [NameCulture] {
35 &[
36 NameCulture::WesternUs,
37 NameCulture::German,
38 NameCulture::French,
39 NameCulture::Chinese,
40 NameCulture::Japanese,
41 NameCulture::Indian,
42 NameCulture::Hispanic,
43 ]
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PersonName {
50 pub first_name: String,
52 pub last_name: String,
54 pub display_name: String,
56 pub culture: NameCulture,
58 pub is_male: bool,
60}
61
62impl PersonName {
63 pub fn to_user_id(&self, index: usize) -> String {
65 let first_initial = self.first_name.chars().next().unwrap_or('X');
66 let last_part: String = self
67 .last_name
68 .chars()
69 .filter(|c| c.is_ascii_alphabetic())
70 .take(5)
71 .collect();
72 format!("{}{}{:03}", first_initial, last_part.to_uppercase(), index)
73 }
74
75 pub fn to_email(&self, domain: &str) -> String {
77 let first = self
78 .first_name
79 .chars()
80 .filter(|c| c.is_ascii_alphabetic())
81 .collect::<String>()
82 .to_lowercase();
83 let last = self
84 .last_name
85 .chars()
86 .filter(|c| c.is_ascii_alphabetic())
87 .collect::<String>()
88 .to_lowercase();
89 format!("{}.{}@{}", first, last, domain)
90 }
91}
92
93#[derive(Debug, Clone)]
95pub struct NamePool {
96 pub first_names_male: Vec<String>,
98 pub first_names_female: Vec<String>,
100 pub last_names: Vec<String>,
102 pub culture: NameCulture,
104}
105
106fn sv(items: &[&str]) -> Vec<String> {
108 items.iter().map(|s| (*s).to_string()).collect()
109}
110
111impl NamePool {
112 pub fn western_us() -> Self {
114 Self {
115 culture: NameCulture::WesternUs,
116 first_names_male: sv(&[
117 "James",
118 "John",
119 "Robert",
120 "Michael",
121 "William",
122 "David",
123 "Richard",
124 "Joseph",
125 "Thomas",
126 "Christopher",
127 "Charles",
128 "Daniel",
129 "Matthew",
130 "Anthony",
131 "Mark",
132 "Donald",
133 "Steven",
134 "Paul",
135 "Andrew",
136 "Joshua",
137 "Kenneth",
138 "Kevin",
139 "Brian",
140 "George",
141 "Timothy",
142 "Ronald",
143 "Edward",
144 "Jason",
145 "Jeffrey",
146 "Ryan",
147 "Jacob",
148 "Gary",
149 "Nicholas",
150 "Eric",
151 "Jonathan",
152 "Stephen",
153 "Larry",
154 "Justin",
155 "Scott",
156 "Brandon",
157 "Benjamin",
158 "Samuel",
159 "Raymond",
160 "Gregory",
161 "Frank",
162 "Alexander",
163 "Patrick",
164 "Jack",
165 "Dennis",
166 "Jerry",
167 "Tyler",
168 "Aaron",
169 "Jose",
170 "Adam",
171 "Nathan",
172 ]),
173 first_names_female: sv(&[
174 "Mary",
175 "Patricia",
176 "Jennifer",
177 "Linda",
178 "Barbara",
179 "Elizabeth",
180 "Susan",
181 "Jessica",
182 "Sarah",
183 "Karen",
184 "Lisa",
185 "Nancy",
186 "Betty",
187 "Margaret",
188 "Sandra",
189 "Ashley",
190 "Kimberly",
191 "Emily",
192 "Donna",
193 "Michelle",
194 "Dorothy",
195 "Carol",
196 "Amanda",
197 "Melissa",
198 "Deborah",
199 "Stephanie",
200 "Rebecca",
201 "Sharon",
202 "Laura",
203 "Cynthia",
204 "Kathleen",
205 "Amy",
206 "Angela",
207 "Shirley",
208 "Anna",
209 "Brenda",
210 "Pamela",
211 "Emma",
212 "Nicole",
213 "Helen",
214 "Samantha",
215 "Katherine",
216 "Christine",
217 "Debra",
218 "Rachel",
219 "Carolyn",
220 "Janet",
221 "Catherine",
222 "Maria",
223 "Heather",
224 "Diane",
225 "Ruth",
226 "Julie",
227 ]),
228 last_names: sv(&[
229 "Smith",
230 "Johnson",
231 "Williams",
232 "Brown",
233 "Jones",
234 "Garcia",
235 "Miller",
236 "Davis",
237 "Rodriguez",
238 "Martinez",
239 "Hernandez",
240 "Lopez",
241 "Gonzalez",
242 "Wilson",
243 "Anderson",
244 "Thomas",
245 "Taylor",
246 "Moore",
247 "Jackson",
248 "Martin",
249 "Lee",
250 "Perez",
251 "Thompson",
252 "White",
253 "Harris",
254 "Sanchez",
255 "Clark",
256 "Ramirez",
257 "Lewis",
258 "Robinson",
259 "Walker",
260 "Young",
261 "Allen",
262 "King",
263 "Wright",
264 "Scott",
265 "Torres",
266 "Nguyen",
267 "Hill",
268 "Flores",
269 "Green",
270 "Adams",
271 "Nelson",
272 "Baker",
273 "Hall",
274 "Rivera",
275 "Campbell",
276 "Mitchell",
277 "Carter",
278 "Roberts",
279 "Turner",
280 "Phillips",
281 "Evans",
282 "Parker",
283 "Edwards",
284 "Collins",
285 ]),
286 }
287 }
288
289 pub fn german() -> Self {
291 Self {
292 culture: NameCulture::German,
293 first_names_male: sv(&[
294 "Hans",
295 "Klaus",
296 "Wolfgang",
297 "Dieter",
298 "Jürgen",
299 "Peter",
300 "Michael",
301 "Thomas",
302 "Andreas",
303 "Stefan",
304 "Markus",
305 "Christian",
306 "Martin",
307 "Frank",
308 "Bernd",
309 "Uwe",
310 "Ralf",
311 "Werner",
312 "Heinz",
313 "Helmut",
314 "Gerhard",
315 "Manfred",
316 "Horst",
317 "Karl",
318 "Heinrich",
319 "Friedrich",
320 "Wilhelm",
321 "Otto",
322 "Matthias",
323 "Tobias",
324 "Sebastian",
325 "Florian",
326 "Alexander",
327 "Maximilian",
328 "Felix",
329 "Lukas",
330 "Jonas",
331 "Leon",
332 "Paul",
333 "Philipp",
334 "Tim",
335 "Jan",
336 "Nico",
337 "Erik",
338 "Lars",
339 "Sven",
340 "Kai",
341 "Olaf",
342 "Rainer",
343 ]),
344 first_names_female: sv(&[
345 "Anna",
346 "Maria",
347 "Elisabeth",
348 "Ursula",
349 "Helga",
350 "Monika",
351 "Petra",
352 "Sabine",
353 "Karin",
354 "Renate",
355 "Ingrid",
356 "Brigitte",
357 "Gisela",
358 "Erika",
359 "Christa",
360 "Andrea",
361 "Claudia",
362 "Susanne",
363 "Birgit",
364 "Heike",
365 "Martina",
366 "Nicole",
367 "Stefanie",
368 "Julia",
369 "Katharina",
370 "Christina",
371 "Sandra",
372 "Melanie",
373 "Daniela",
374 "Anja",
375 "Tanja",
376 "Simone",
377 "Silke",
378 "Nadine",
379 "Yvonne",
380 "Manuela",
381 "Sonja",
382 "Michaela",
383 "Angelika",
384 "Barbara",
385 "Gabriele",
386 "Beate",
387 "Doris",
388 "Eva",
389 "Franziska",
390 "Lena",
391 "Hannah",
392 "Sophie",
393 "Leonie",
394 "Laura",
395 ]),
396 last_names: sv(&[
397 "Müller",
398 "Schmidt",
399 "Schneider",
400 "Fischer",
401 "Weber",
402 "Meyer",
403 "Wagner",
404 "Becker",
405 "Schulz",
406 "Hoffmann",
407 "Schäfer",
408 "Koch",
409 "Bauer",
410 "Richter",
411 "Klein",
412 "Wolf",
413 "Schröder",
414 "Neumann",
415 "Schwarz",
416 "Zimmermann",
417 "Braun",
418 "Krüger",
419 "Hofmann",
420 "Hartmann",
421 "Lange",
422 "Schmitt",
423 "Werner",
424 "Schmitz",
425 "Krause",
426 "Meier",
427 "Lehmann",
428 "Schmid",
429 "Schulze",
430 "Maier",
431 "Köhler",
432 "Herrmann",
433 "König",
434 "Walter",
435 "Mayer",
436 "Huber",
437 "Kaiser",
438 "Fuchs",
439 "Peters",
440 "Lang",
441 "Scholz",
442 "Möller",
443 "Weiß",
444 "Jung",
445 "Hahn",
446 "Schubert",
447 ]),
448 }
449 }
450
451 pub fn french() -> Self {
453 Self {
454 culture: NameCulture::French,
455 first_names_male: sv(&[
456 "Jean",
457 "Pierre",
458 "Michel",
459 "André",
460 "Philippe",
461 "Jacques",
462 "Bernard",
463 "Alain",
464 "François",
465 "Robert",
466 "Marcel",
467 "René",
468 "Louis",
469 "Claude",
470 "Daniel",
471 "Yves",
472 "Christian",
473 "Patrick",
474 "Nicolas",
475 "Julien",
476 "Thomas",
477 "Antoine",
478 "Alexandre",
479 "Maxime",
480 "Lucas",
481 "Hugo",
482 "Théo",
483 "Mathieu",
484 "Guillaume",
485 "Laurent",
486 "Olivier",
487 "Christophe",
488 "Sébastien",
489 "Frédéric",
490 "Vincent",
491 "David",
492 "Eric",
493 "Pascal",
494 "Gilles",
495 "Thierry",
496 "Stéphane",
497 "Bruno",
498 "Dominique",
499 "Serge",
500 "Maurice",
501 "Henri",
502 "Paul",
503 "Charles",
504 "Emmanuel",
505 "Raphaël",
506 ]),
507 first_names_female: sv(&[
508 "Marie",
509 "Jeanne",
510 "Françoise",
511 "Monique",
512 "Catherine",
513 "Nathalie",
514 "Isabelle",
515 "Sylvie",
516 "Anne",
517 "Martine",
518 "Nicole",
519 "Christine",
520 "Sophie",
521 "Valérie",
522 "Julie",
523 "Camille",
524 "Léa",
525 "Manon",
526 "Emma",
527 "Chloé",
528 "Inès",
529 "Sarah",
530 "Laura",
531 "Louise",
532 "Jade",
533 "Alice",
534 "Lola",
535 "Margot",
536 "Charlotte",
537 "Clara",
538 "Pauline",
539 "Marine",
540 "Aurélie",
541 "Céline",
542 "Sandrine",
543 "Virginie",
544 "Stéphanie",
545 "Élodie",
546 "Delphine",
547 "Laurence",
548 "Brigitte",
549 "Jacqueline",
550 "Simone",
551 "Denise",
552 "Madeleine",
553 "Thérèse",
554 "Hélène",
555 "Élise",
556 "Juliette",
557 "Marguerite",
558 ]),
559 last_names: sv(&[
560 "Martin",
561 "Bernard",
562 "Dubois",
563 "Thomas",
564 "Robert",
565 "Richard",
566 "Petit",
567 "Durand",
568 "Leroy",
569 "Moreau",
570 "Simon",
571 "Laurent",
572 "Lefebvre",
573 "Michel",
574 "Garcia",
575 "David",
576 "Bertrand",
577 "Roux",
578 "Vincent",
579 "Fournier",
580 "Morel",
581 "Girard",
582 "André",
583 "Lefèvre",
584 "Mercier",
585 "Dupont",
586 "Lambert",
587 "Bonnet",
588 "François",
589 "Martinez",
590 "Legrand",
591 "Garnier",
592 "Faure",
593 "Rousseau",
594 "Blanc",
595 "Guérin",
596 "Muller",
597 "Henry",
598 "Roussel",
599 "Nicolas",
600 "Perrin",
601 "Morin",
602 "Mathieu",
603 "Clément",
604 "Gauthier",
605 "Dumont",
606 "Lopez",
607 "Fontaine",
608 "Chevalier",
609 "Robin",
610 ]),
611 }
612 }
613
614 pub fn chinese() -> Self {
616 Self {
617 culture: NameCulture::Chinese,
618 first_names_male: sv(&[
619 "Wei", "Fang", "Lei", "Jun", "Jian", "Hao", "Chen", "Yang", "Ming", "Tao", "Long",
620 "Feng", "Bin", "Qiang", "Gang", "Hui", "Peng", "Xiang", "Bo", "Chao", "Dong",
621 "Liang", "Ning", "Kai", "Jie", "Yong", "Hai", "Lin", "Wen", "Zheng", "Hong", "Xin",
622 "Da", "Zhi", "Guang", "Cheng", "Yi", "Sheng", "Biao", "Ping", "Yun", "Song",
623 "Chang", "Kang", "Rui", "Nan", "Jia", "Xiao", "Yu", "Hua",
624 ]),
625 first_names_female: sv(&[
626 "Fang", "Min", "Jing", "Li", "Yan", "Hong", "Juan", "Mei", "Ying", "Xia", "Hui",
627 "Lin", "Ling", "Ping", "Dan", "Yun", "Na", "Qian", "Xin", "Ya", "Wei", "Wen",
628 "Jie", "Qing", "Yu", "Hua", "Yue", "Xue", "Lan", "Zhen", "Rong", "Shu", "Fei",
629 "Lei", "Shan", "Ting", "Ni", "Ying", "Chen", "Huan", "Lu", "Ai", "Xiao", "Xiang",
630 "Yao", "Meng", "Qi", "Jun", "Bei", "Zhi",
631 ]),
632 last_names: sv(&[
633 "Wang", "Li", "Zhang", "Liu", "Chen", "Yang", "Huang", "Zhao", "Wu", "Zhou", "Xu",
634 "Sun", "Ma", "Zhu", "Hu", "Guo", "He", "Lin", "Gao", "Luo", "Zheng", "Liang",
635 "Xie", "Tang", "Song", "Xu", "Han", "Deng", "Feng", "Cao", "Peng", "Xiao", "Cheng",
636 "Yuan", "Tian", "Dong", "Pan", "Cai", "Jiang", "Wei", "Yu", "Du", "Ye", "Shi",
637 "Lu", "Shen", "Su", "Jia", "Fan", "Jin",
638 ]),
639 }
640 }
641
642 pub fn japanese() -> Self {
644 Self {
645 culture: NameCulture::Japanese,
646 first_names_male: sv(&[
647 "Hiroshi", "Takeshi", "Kenji", "Yuki", "Kazuki", "Ryota", "Daiki", "Shota", "Yuto",
648 "Kenta", "Haruto", "Sota", "Riku", "Yuma", "Kaito", "Ren", "Hayato", "Takumi",
649 "Kouki", "Ryuu", "Naoki", "Tsubasa", "Yuuki", "Akira", "Satoshi", "Makoto",
650 "Tetsuya", "Masaki", "Shin", "Kei", "Daisuke", "Shunsuke", "Tomoya", "Yusuke",
651 "Tatsuya", "Katsuki", "Shun", "Yamato", "Koji", "Hideo", "Takahiro", "Noboru",
652 "Shinji", "Osamu", "Minoru", "Hideki", "Jun", "Masaru", "Ken", "Ryo",
653 ]),
654 first_names_female: sv(&[
655 "Yuki", "Sakura", "Hana", "Yui", "Mio", "Rin", "Aoi", "Mei", "Saki", "Miku",
656 "Nanami", "Ayaka", "Misaki", "Haruka", "Momoka", "Rina", "Yuna", "Hinata",
657 "Koharu", "Miyu", "Akari", "Hikari", "Kaede", "Natsuki", "Mai", "Ami", "Aya",
658 "Emi", "Kana", "Megumi", "Tomoko", "Yoko", "Keiko", "Naomi", "Mayumi", "Chika",
659 "Nana", "Risa", "Asuka", "Fumiko", "Kyoko", "Reiko", "Noriko", "Sachiko", "Mariko",
660 "Shiori", "Midori", "Kanako", "Minami", "Eriko",
661 ]),
662 last_names: sv(&[
663 "Sato",
664 "Suzuki",
665 "Takahashi",
666 "Tanaka",
667 "Watanabe",
668 "Ito",
669 "Yamamoto",
670 "Nakamura",
671 "Kobayashi",
672 "Kato",
673 "Yoshida",
674 "Yamada",
675 "Sasaki",
676 "Yamaguchi",
677 "Matsumoto",
678 "Inoue",
679 "Kimura",
680 "Hayashi",
681 "Shimizu",
682 "Yamazaki",
683 "Mori",
684 "Abe",
685 "Ikeda",
686 "Hashimoto",
687 "Yamashita",
688 "Ishikawa",
689 "Nakajima",
690 "Maeda",
691 "Fujita",
692 "Ogawa",
693 "Goto",
694 "Okada",
695 "Hasegawa",
696 "Murakami",
697 "Kondo",
698 "Ishii",
699 "Saito",
700 "Sakamoto",
701 "Endo",
702 "Aoki",
703 "Fujii",
704 "Nishimura",
705 "Fukuda",
706 "Ota",
707 "Miura",
708 "Okamoto",
709 "Matsuda",
710 "Nakagawa",
711 "Fujiwara",
712 "Kawamura",
713 ]),
714 }
715 }
716
717 pub fn indian() -> Self {
719 Self {
720 culture: NameCulture::Indian,
721 first_names_male: sv(&[
722 "Raj", "Amit", "Vikram", "Rahul", "Sanjay", "Arun", "Suresh", "Rajesh", "Deepak",
723 "Vijay", "Prakash", "Manoj", "Sunil", "Anil", "Ravi", "Ashok", "Ramesh", "Mukesh",
724 "Sandeep", "Ajay", "Naveen", "Pradeep", "Sachin", "Nitin", "Vinod", "Rakesh",
725 "Srinivas", "Ganesh", "Krishna", "Mohan", "Kiran", "Venkat", "Hari", "Shankar",
726 "Dinesh", "Mahesh", "Satish", "Girish", "Naresh", "Harish", "Pavan", "Arjun",
727 "Anand", "Vivek", "Rohit", "Gaurav", "Kunal", "Vishal", "Akhil", "Dev",
728 ]),
729 first_names_female: sv(&[
730 "Priya",
731 "Anita",
732 "Sunita",
733 "Kavita",
734 "Neha",
735 "Pooja",
736 "Anjali",
737 "Deepa",
738 "Meena",
739 "Rekha",
740 "Lakshmi",
741 "Padma",
742 "Gita",
743 "Sita",
744 "Rani",
745 "Devi",
746 "Shalini",
747 "Nisha",
748 "Swati",
749 "Preeti",
750 "Divya",
751 "Shreya",
752 "Aishwarya",
753 "Ritu",
754 "Seema",
755 "Jyoti",
756 "Shweta",
757 "Pallavi",
758 "Rashmi",
759 "Smita",
760 "Varsha",
761 "Archana",
762 "Asha",
763 "Manisha",
764 "Usha",
765 "Vandana",
766 "Geeta",
767 "Rita",
768 "Maya",
769 "Radha",
770 "Sapna",
771 "Megha",
772 "Nikita",
773 "Tanvi",
774 "Aditi",
775 "Bhavna",
776 "Chitra",
777 "Komal",
778 "Madhuri",
779 "Parul",
780 ]),
781 last_names: sv(&[
782 "Sharma",
783 "Patel",
784 "Singh",
785 "Kumar",
786 "Gupta",
787 "Joshi",
788 "Verma",
789 "Shah",
790 "Mehta",
791 "Reddy",
792 "Rao",
793 "Iyer",
794 "Nair",
795 "Menon",
796 "Pillai",
797 "Das",
798 "Bose",
799 "Sen",
800 "Chatterjee",
801 "Banerjee",
802 "Mukherjee",
803 "Ghosh",
804 "Roy",
805 "Dutta",
806 "Kapoor",
807 "Malhotra",
808 "Agarwal",
809 "Sinha",
810 "Thakur",
811 "Saxena",
812 "Mishra",
813 "Pandey",
814 "Trivedi",
815 "Desai",
816 "Modi",
817 "Kulkarni",
818 "Patil",
819 "Kaur",
820 "Chopra",
821 "Khanna",
822 "Bhatia",
823 "Choudhury",
824 "Rajan",
825 "Subramaniam",
826 "Venkatesh",
827 "Naidu",
828 "Hegde",
829 "Shukla",
830 "Prasad",
831 "Murthy",
832 ]),
833 }
834 }
835
836 pub fn hispanic() -> Self {
838 Self {
839 culture: NameCulture::Hispanic,
840 first_names_male: sv(&[
841 "José",
842 "Juan",
843 "Carlos",
844 "Luis",
845 "Miguel",
846 "Francisco",
847 "Antonio",
848 "Manuel",
849 "Pedro",
850 "Javier",
851 "Fernando",
852 "Rafael",
853 "Alejandro",
854 "Ricardo",
855 "Eduardo",
856 "Roberto",
857 "Daniel",
858 "Pablo",
859 "Sergio",
860 "Jorge",
861 "Andrés",
862 "Raúl",
863 "Diego",
864 "Enrique",
865 "Óscar",
866 "Adrián",
867 "Víctor",
868 "Martín",
869 "Gabriel",
870 "Álvaro",
871 "Iván",
872 "Mario",
873 "César",
874 "Héctor",
875 "Alberto",
876 "Gustavo",
877 "Arturo",
878 "Ramón",
879 "Hugo",
880 "Salvador",
881 "Ernesto",
882 "Guillermo",
883 "Ignacio",
884 "Jaime",
885 "Felipe",
886 "Tomás",
887 "Santiago",
888 "Mateo",
889 "Sebastián",
890 "Nicolás",
891 ]),
892 first_names_female: sv(&[
893 "María",
894 "Carmen",
895 "Ana",
896 "Laura",
897 "Rosa",
898 "Isabel",
899 "Elena",
900 "Patricia",
901 "Teresa",
902 "Lucia",
903 "Pilar",
904 "Dolores",
905 "Paula",
906 "Sara",
907 "Marta",
908 "Julia",
909 "Cristina",
910 "Claudia",
911 "Andrea",
912 "Mónica",
913 "Gloria",
914 "Beatriz",
915 "Alicia",
916 "Rocío",
917 "Victoria",
918 "Silvia",
919 "Eva",
920 "Raquel",
921 "Adriana",
922 "Lorena",
923 "Gabriela",
924 "Marina",
925 "Sandra",
926 "Verónica",
927 "Natalia",
928 "Carolina",
929 "Diana",
930 "Alejandra",
931 "Cecilia",
932 "Daniela",
933 "Sofía",
934 "Valentina",
935 "Camila",
936 "Isabella",
937 "Mariana",
938 "Fernanda",
939 "Paola",
940 "Liliana",
941 "Angela",
942 "Inés",
943 ]),
944 last_names: sv(&[
945 "García",
946 "Rodríguez",
947 "Martínez",
948 "López",
949 "González",
950 "Hernández",
951 "Pérez",
952 "Sánchez",
953 "Ramírez",
954 "Torres",
955 "Flores",
956 "Rivera",
957 "Gómez",
958 "Díaz",
959 "Reyes",
960 "Cruz",
961 "Morales",
962 "Ortiz",
963 "Gutiérrez",
964 "Chávez",
965 "Ramos",
966 "Vargas",
967 "Castillo",
968 "Jiménez",
969 "Moreno",
970 "Romero",
971 "Herrera",
972 "Medina",
973 "Aguilar",
974 "Vega",
975 "Castro",
976 "Mendoza",
977 "Ruiz",
978 "Fernández",
979 "Álvarez",
980 "Muñoz",
981 "Rojas",
982 "Silva",
983 "Suárez",
984 "Delgado",
985 "Navarro",
986 "Santos",
987 "Molina",
988 "Espinoza",
989 "Guerrero",
990 "Cabrera",
991 "Campos",
992 "Cortés",
993 "Salazar",
994 "Luna",
995 ]),
996 }
997 }
998
999 pub fn for_culture(culture: NameCulture) -> Self {
1001 match culture {
1002 NameCulture::WesternUs => Self::western_us(),
1003 NameCulture::German => Self::german(),
1004 NameCulture::French => Self::french(),
1005 NameCulture::Chinese => Self::chinese(),
1006 NameCulture::Japanese => Self::japanese(),
1007 NameCulture::Indian => Self::indian(),
1008 NameCulture::Hispanic => Self::hispanic(),
1009 }
1010 }
1011
1012 pub fn generate_name(&self, rng: &mut impl Rng) -> PersonName {
1014 let is_male = rng.random_bool(0.5);
1015
1016 let first_name = if is_male {
1017 self.first_names_male
1018 .choose(rng)
1019 .expect("non-empty name list")
1020 } else {
1021 self.first_names_female
1022 .choose(rng)
1023 .expect("non-empty name list")
1024 };
1025
1026 let last_name = self.last_names.choose(rng).expect("non-empty name list");
1027
1028 PersonName {
1029 first_name: first_name.clone(),
1030 last_name: last_name.clone(),
1031 display_name: format!("{} {}", first_name, last_name),
1032 culture: self.culture,
1033 is_male,
1034 }
1035 }
1036
1037 pub fn from_culture_config(
1039 config: &crate::country::schema::CultureConfig,
1040 culture: NameCulture,
1041 ) -> Self {
1042 Self {
1043 culture,
1044 first_names_male: config.male_first_names.clone(),
1045 first_names_female: config.female_first_names.clone(),
1046 last_names: config.last_names.clone(),
1047 }
1048 }
1049}
1050
1051#[derive(Debug, Clone)]
1053pub struct MultiCultureNameGenerator {
1054 pools: HashMap<NameCulture, NamePool>,
1055 distribution: Vec<(NameCulture, f64)>,
1056 email_domain: String,
1057}
1058
1059impl Default for MultiCultureNameGenerator {
1060 fn default() -> Self {
1061 Self::new()
1062 }
1063}
1064
1065impl MultiCultureNameGenerator {
1066 pub fn new() -> Self {
1068 let mut pools = HashMap::new();
1069 for culture in NameCulture::all() {
1070 pools.insert(*culture, NamePool::for_culture(*culture));
1071 }
1072
1073 Self {
1074 pools,
1075 distribution: vec![
1076 (NameCulture::WesternUs, 0.40),
1077 (NameCulture::Hispanic, 0.20),
1078 (NameCulture::German, 0.10),
1079 (NameCulture::French, 0.05),
1080 (NameCulture::Chinese, 0.10),
1081 (NameCulture::Japanese, 0.05),
1082 (NameCulture::Indian, 0.10),
1083 ],
1084 email_domain: "company.com".to_string(),
1085 }
1086 }
1087
1088 pub fn with_distribution(distribution: Vec<(NameCulture, f64)>) -> Self {
1090 let mut gen = Self::new();
1091 gen.distribution = distribution;
1092 gen
1093 }
1094
1095 pub fn with_email_domain(mut self, domain: &str) -> Self {
1097 self.email_domain = domain.to_string();
1098 self
1099 }
1100
1101 pub fn set_email_domain(&mut self, domain: &str) {
1103 self.email_domain = domain.to_string();
1104 }
1105
1106 pub fn email_domain(&self) -> &str {
1108 &self.email_domain
1109 }
1110
1111 fn select_culture(&self, rng: &mut impl Rng) -> NameCulture {
1113 let roll: f64 = rng.random();
1114 let mut cumulative = 0.0;
1115
1116 for (culture, weight) in &self.distribution {
1117 cumulative += weight;
1118 if roll < cumulative {
1119 return *culture;
1120 }
1121 }
1122
1123 NameCulture::WesternUs
1125 }
1126
1127 pub fn generate_name(&self, rng: &mut impl Rng) -> PersonName {
1129 let culture = self.select_culture(rng);
1130 self.pools
1131 .get(&culture)
1132 .map(|pool| pool.generate_name(rng))
1133 .unwrap_or_else(|| NamePool::western_us().generate_name(rng))
1134 }
1135
1136 pub fn generate_name_for_culture(
1138 &self,
1139 culture: NameCulture,
1140 rng: &mut impl Rng,
1141 ) -> PersonName {
1142 self.pools
1143 .get(&culture)
1144 .map(|pool| pool.generate_name(rng))
1145 .unwrap_or_else(|| NamePool::western_us().generate_name(rng))
1146 }
1147
1148 pub fn generate_user_id(&self, name: &PersonName, index: usize) -> String {
1150 name.to_user_id(index)
1151 }
1152
1153 pub fn generate_email(&self, name: &PersonName) -> String {
1155 name.to_email(&self.email_domain)
1156 }
1157
1158 pub fn from_country_pack(pack: &crate::country::schema::CountryPack) -> Self {
1160 let mut pools = HashMap::new();
1161 let mut distribution = Vec::new();
1162
1163 for culture_config in &pack.names.cultures {
1164 let culture = match culture_config.culture_id.as_str() {
1166 "western_us" | "western" => NameCulture::WesternUs,
1167 "german" | "deutsch" => NameCulture::German,
1168 "french" | "français" => NameCulture::French,
1169 "chinese" | "中文" => NameCulture::Chinese,
1170 "japanese" | "日本語" => NameCulture::Japanese,
1171 "indian" | "hindi" => NameCulture::Indian,
1172 "hispanic" | "latino" | "español" => NameCulture::Hispanic,
1173 _ => NameCulture::WesternUs,
1174 };
1175
1176 let pool = NamePool::from_culture_config(culture_config, culture);
1177 pools.insert(culture, pool);
1178 distribution.push((culture, culture_config.weight));
1179 }
1180
1181 if pools.is_empty() {
1183 return Self::new();
1184 }
1185
1186 let email_domain = pack
1187 .names
1188 .email_domains
1189 .first()
1190 .cloned()
1191 .unwrap_or_else(|| "company.com".to_string());
1192
1193 Self {
1194 pools,
1195 distribution,
1196 email_domain,
1197 }
1198 }
1199}
1200
1201#[cfg(test)]
1202#[allow(clippy::unwrap_used)]
1203mod tests {
1204 use super::*;
1205 use rand::SeedableRng;
1206 use rand_chacha::ChaCha8Rng;
1207
1208 #[test]
1209 fn test_name_pool_generation() {
1210 let mut rng = ChaCha8Rng::seed_from_u64(42);
1211 let pool = NamePool::western_us();
1212 let name = pool.generate_name(&mut rng);
1213
1214 assert!(!name.first_name.is_empty());
1215 assert!(!name.last_name.is_empty());
1216 assert_eq!(name.culture, NameCulture::WesternUs);
1217 }
1218
1219 #[test]
1220 fn test_multi_culture_generator() {
1221 let mut rng = ChaCha8Rng::seed_from_u64(42);
1222 let generator = MultiCultureNameGenerator::new();
1223
1224 let mut culture_counts: HashMap<NameCulture, usize> = HashMap::new();
1226 for _ in 0..100 {
1227 let name = generator.generate_name(&mut rng);
1228 *culture_counts.entry(name.culture).or_insert(0) += 1;
1229 }
1230
1231 assert!(culture_counts.len() > 1);
1233 }
1234
1235 #[test]
1236 fn test_user_id_generation() {
1237 let name = PersonName {
1238 first_name: "John".to_string(),
1239 last_name: "Smith".to_string(),
1240 display_name: "John Smith".to_string(),
1241 culture: NameCulture::WesternUs,
1242 is_male: true,
1243 };
1244
1245 let user_id = name.to_user_id(42);
1246 assert_eq!(user_id, "JSMITH042");
1247 }
1248
1249 #[test]
1250 fn test_email_generation() {
1251 let name = PersonName {
1252 first_name: "María".to_string(),
1253 last_name: "García".to_string(),
1254 display_name: "María García".to_string(),
1255 culture: NameCulture::Hispanic,
1256 is_male: false,
1257 };
1258
1259 let email = name.to_email("acme.com");
1260 assert_eq!(email, "mara.garca@acme.com"); }
1262
1263 #[test]
1264 fn test_all_culture_pools() {
1265 let mut rng = ChaCha8Rng::seed_from_u64(42);
1266
1267 for culture in NameCulture::all() {
1268 let pool = NamePool::for_culture(*culture);
1269 let name = pool.generate_name(&mut rng);
1270 assert!(!name.first_name.is_empty());
1271 assert!(!name.last_name.is_empty());
1272 assert_eq!(name.culture, *culture);
1273 }
1274 }
1275}