Skip to main content

seedfaker_core/locale/
mod.rs

1#[path = "_shared.rs"]
2pub mod shared;
3
4pub mod ar;
5pub mod ar_ae;
6pub mod ar_sa;
7pub mod bd;
8pub mod be;
9pub mod bg;
10pub mod cl;
11pub mod co;
12pub mod cs;
13pub mod cy;
14pub mod da;
15pub mod de;
16pub mod de_at;
17pub mod ec;
18pub mod eg;
19pub mod el;
20pub mod en;
21pub mod en_au;
22pub mod en_ca;
23pub mod en_gb;
24pub mod en_ng;
25pub mod en_nz;
26pub mod en_sg;
27pub mod en_za;
28pub mod es;
29pub mod et;
30pub mod fi;
31pub mod fr;
32pub mod fr_be;
33pub mod fr_ca;
34pub mod ga;
35pub mod he;
36pub mod hi;
37pub mod hr;
38pub mod hu;
39pub mod id;
40pub mod it;
41pub mod ja;
42pub mod ko;
43pub mod lb;
44pub mod lt;
45pub mod lv;
46pub mod ms;
47pub mod mt;
48pub mod mx;
49pub mod nb;
50pub mod nl;
51pub mod nl_be;
52pub mod pe;
53pub mod pk;
54pub mod pl;
55pub mod pt;
56pub mod pt_br;
57pub mod ro;
58pub mod ru;
59pub mod sk;
60pub mod sl;
61pub mod sr;
62pub mod sv;
63pub mod th;
64pub mod tl;
65pub mod tr;
66pub mod tw;
67pub mod uk;
68pub mod uy;
69pub mod ve;
70pub mod vi;
71pub mod zh;
72
73#[derive(Clone, Copy)]
74pub struct City {
75    pub name: &'static str,
76    pub region: &'static str,
77    pub postal: &'static str,
78    pub lat: f64,
79    pub lon: f64,
80    pub tz: &'static str,
81}
82
83#[derive(Clone, Copy, PartialEq, Eq)]
84pub enum NameOrder {
85    /// First Last (default for most locales)
86    Western,
87    /// Last First (hu, ja, ko, zh, vi)
88    Eastern,
89    /// First Last1 Last2
90    DoubleSurname,
91    /// First bin Father Last (Gulf Arabic)
92    Patronymic { particle: &'static str },
93    /// First Father Last (Egyptian style)
94    PatronymicMiddle,
95}
96
97#[derive(Clone, Copy)]
98pub struct Locale {
99    pub code: &'static str,
100    pub name_order: NameOrder,
101    pub first_names: &'static [&'static str],
102    /// Items before this index are "common" — selected with ~70% probability.
103    /// 0 = uniform selection.
104    pub first_names_common: usize,
105    pub last_names: &'static [&'static str],
106    pub last_names_common: usize,
107    pub domains: &'static [&'static str],
108    /// First N domains are personal email providers (gmail-like), rest are corporate.
109    pub domains_common: usize,
110    pub companies: &'static [&'static str],
111    pub cities: &'static [City],
112    pub streets: &'static [&'static str],
113    pub native_first_names: Option<&'static [&'static str]>,
114    pub native_last_names: Option<&'static [&'static str]>,
115    pub native_cities: Option<&'static [City]>,
116    pub native_streets: Option<&'static [&'static str]>,
117}
118
119// Single source of truth: (user-facing code, module::LOCALE).
120// get() and ALL_CODES both derive from this array.
121const LOCALE_TABLE: &[(&str, &Locale)] = &[
122    ("en", &en::LOCALE),
123    ("en-gb", &en_gb::LOCALE),
124    ("en-ca", &en_ca::LOCALE),
125    ("en-au", &en_au::LOCALE),
126    ("en-nz", &en_nz::LOCALE),
127    ("en-sg", &en_sg::LOCALE),
128    ("en-za", &en_za::LOCALE),
129    ("en-ng", &en_ng::LOCALE),
130    ("de", &de::LOCALE),
131    ("fr", &fr::LOCALE),
132    ("ja", &ja::LOCALE),
133    ("es", &es::LOCALE),
134    ("pt-br", &pt_br::LOCALE),
135    ("it", &it::LOCALE),
136    ("nl", &nl::LOCALE),
137    ("pl", &pl::LOCALE),
138    ("se", &sv::LOCALE),
139    ("tr", &tr::LOCALE),
140    ("ru", &ru::LOCALE),
141    ("uk", &uk::LOCALE),
142    ("be", &be::LOCALE),
143    ("sr", &sr::LOCALE),
144    ("ar", &ar::LOCALE),
145    ("ro", &ro::LOCALE),
146    ("hr", &hr::LOCALE),
147    ("bg", &bg::LOCALE),
148    ("cs", &cs::LOCALE),
149    ("sk", &sk::LOCALE),
150    ("hu", &hu::LOCALE),
151    ("fi", &fi::LOCALE),
152    ("da", &da::LOCALE),
153    ("no", &nb::LOCALE),
154    ("el", &el::LOCALE),
155    ("pt", &pt::LOCALE),
156    ("mx", &mx::LOCALE),
157    ("cl", &cl::LOCALE),
158    ("co", &co::LOCALE),
159    ("sl", &sl::LOCALE),
160    ("et", &et::LOCALE),
161    ("lt", &lt::LOCALE),
162    ("lv", &lv::LOCALE),
163    ("ie", &ga::LOCALE),
164    ("pe", &pe::LOCALE),
165    ("uy", &uy::LOCALE),
166    ("hi", &hi::LOCALE),
167    ("vi", &vi::LOCALE),
168    ("zh", &zh::LOCALE),
169    ("ko", &ko::LOCALE),
170    ("id", &id::LOCALE),
171    ("th", &th::LOCALE),
172    ("ms", &ms::LOCALE),
173    ("tl", &tl::LOCALE),
174    ("tw", &tw::LOCALE),
175    ("ve", &ve::LOCALE),
176    ("ec", &ec::LOCALE),
177    ("pk", &pk::LOCALE),
178    ("bd", &bd::LOCALE),
179    ("eg", &eg::LOCALE),
180    ("de-at", &de_at::LOCALE),
181    ("nl-be", &nl_be::LOCALE),
182    ("fr-be", &fr_be::LOCALE),
183    ("fr-ca", &fr_ca::LOCALE),
184    ("cy", &cy::LOCALE),
185    ("mt", &mt::LOCALE),
186    ("lb", &lb::LOCALE),
187    ("he", &he::LOCALE),
188    ("ar-sa", &ar_sa::LOCALE),
189    ("ar-ae", &ar_ae::LOCALE),
190];
191
192pub fn get(code: &str) -> Option<&'static Locale> {
193    LOCALE_TABLE.iter().find(|(c, _)| *c == code).map(|(_, l)| *l)
194}
195
196pub const ALL_CODES: &[&str] = &{
197    const N: usize = LOCALE_TABLE.len();
198    let mut out = [""; N];
199    let mut i = 0;
200    while i < N {
201        out[i] = LOCALE_TABLE[i].0;
202        i += 1;
203    }
204    out
205};
206
207/// Parse locale string(s) into resolved locale references.
208///
209/// Accepts codes with optional weights: `["en", "de"]` or `["en=7", "es=2", "de=1"]`.
210/// Weight = repeat count in the pool (higher weight = more likely selection).
211/// Empty input returns all locales. `["all"]` also returns all locales.
212/// Unknown locale codes produce an error.
213pub fn resolve(codes: &[String]) -> Result<Vec<&'static Locale>, String> {
214    if codes.is_empty() || (codes.len() == 1 && codes[0] == "all") {
215        return Ok(ALL_CODES.iter().filter_map(|c| get(c)).collect());
216    }
217    let has_weights = codes.iter().any(|c| c.contains('='));
218    let mut result = Vec::new();
219    for code in codes {
220        let (name, weight) = if has_weights {
221            if let Some((n, w)) = code.split_once('=') {
222                let w: usize = w.parse().map_err(|_| format!("invalid locale weight: '{code}'"))?;
223                if w == 0 {
224                    return Err(format!("locale weight must be > 0: '{code}'"));
225                }
226                (n, w)
227            } else {
228                (code.as_str(), 1)
229            }
230        } else {
231            (code.as_str(), 1)
232        };
233        match get(name) {
234            Some(loc) => {
235                for _ in 0..weight {
236                    result.push(loc);
237                }
238            }
239            None => {
240                return Err(format!("unknown locale '{name}'; valid: {}", ALL_CODES.join(", ")))
241            }
242        }
243    }
244    Ok(result)
245}
246
247/// Parse a comma-separated locale string into resolved locale references.
248/// Convenience wrapper for APIs that accept a single string (Python, Node, MCP).
249pub fn resolve_str(s: &str) -> Result<Vec<&'static Locale>, String> {
250    let codes: Vec<String> = s.split(',').map(|c| c.trim().to_string()).collect();
251    resolve(&codes)
252}