google_i18n 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +57 -0
- data/README.md +89 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/google_i18n.gemspec +45 -0
- data/lib/google_i18n.rb +12 -0
- data/lib/google_i18n/address.rb +495 -0
- data/lib/google_i18n/data/ac.json +1 -0
- data/lib/google_i18n/data/ad.json +1 -0
- data/lib/google_i18n/data/ae.json +1 -0
- data/lib/google_i18n/data/af.json +1 -0
- data/lib/google_i18n/data/ag.json +1 -0
- data/lib/google_i18n/data/ai.json +1 -0
- data/lib/google_i18n/data/al.json +1 -0
- data/lib/google_i18n/data/all.json +1 -0
- data/lib/google_i18n/data/am.json +1 -0
- data/lib/google_i18n/data/ao.json +1 -0
- data/lib/google_i18n/data/aq.json +1 -0
- data/lib/google_i18n/data/ar.json +1 -0
- data/lib/google_i18n/data/as.json +1 -0
- data/lib/google_i18n/data/at.json +1 -0
- data/lib/google_i18n/data/au.json +1 -0
- data/lib/google_i18n/data/aw.json +1 -0
- data/lib/google_i18n/data/ax.json +1 -0
- data/lib/google_i18n/data/az.json +1 -0
- data/lib/google_i18n/data/ba.json +1 -0
- data/lib/google_i18n/data/bb.json +1 -0
- data/lib/google_i18n/data/bd.json +1 -0
- data/lib/google_i18n/data/be.json +1 -0
- data/lib/google_i18n/data/bf.json +1 -0
- data/lib/google_i18n/data/bg.json +1 -0
- data/lib/google_i18n/data/bh.json +1 -0
- data/lib/google_i18n/data/bi.json +1 -0
- data/lib/google_i18n/data/bj.json +1 -0
- data/lib/google_i18n/data/bl.json +1 -0
- data/lib/google_i18n/data/bm.json +1 -0
- data/lib/google_i18n/data/bn.json +1 -0
- data/lib/google_i18n/data/bo.json +1 -0
- data/lib/google_i18n/data/bq.json +1 -0
- data/lib/google_i18n/data/br.json +1 -0
- data/lib/google_i18n/data/bs.json +1 -0
- data/lib/google_i18n/data/bt.json +1 -0
- data/lib/google_i18n/data/bv.json +1 -0
- data/lib/google_i18n/data/bw.json +1 -0
- data/lib/google_i18n/data/by.json +1 -0
- data/lib/google_i18n/data/bz.json +1 -0
- data/lib/google_i18n/data/ca.json +1 -0
- data/lib/google_i18n/data/cc.json +1 -0
- data/lib/google_i18n/data/cd.json +1 -0
- data/lib/google_i18n/data/cf.json +1 -0
- data/lib/google_i18n/data/cg.json +1 -0
- data/lib/google_i18n/data/ch.json +1 -0
- data/lib/google_i18n/data/ci.json +1 -0
- data/lib/google_i18n/data/ck.json +1 -0
- data/lib/google_i18n/data/cl.json +1 -0
- data/lib/google_i18n/data/cm.json +1 -0
- data/lib/google_i18n/data/cn.json +1 -0
- data/lib/google_i18n/data/co.json +1 -0
- data/lib/google_i18n/data/cr.json +1 -0
- data/lib/google_i18n/data/cu.json +1 -0
- data/lib/google_i18n/data/cv.json +1 -0
- data/lib/google_i18n/data/cw.json +1 -0
- data/lib/google_i18n/data/cx.json +1 -0
- data/lib/google_i18n/data/cy.json +1 -0
- data/lib/google_i18n/data/cz.json +1 -0
- data/lib/google_i18n/data/de.json +1 -0
- data/lib/google_i18n/data/dj.json +1 -0
- data/lib/google_i18n/data/dk.json +1 -0
- data/lib/google_i18n/data/dm.json +1 -0
- data/lib/google_i18n/data/do.json +1 -0
- data/lib/google_i18n/data/dz.json +1 -0
- data/lib/google_i18n/data/ec.json +1 -0
- data/lib/google_i18n/data/ee.json +1 -0
- data/lib/google_i18n/data/eg.json +1 -0
- data/lib/google_i18n/data/eh.json +1 -0
- data/lib/google_i18n/data/er.json +1 -0
- data/lib/google_i18n/data/es.json +1 -0
- data/lib/google_i18n/data/et.json +1 -0
- data/lib/google_i18n/data/fi.json +1 -0
- data/lib/google_i18n/data/fj.json +1 -0
- data/lib/google_i18n/data/fk.json +1 -0
- data/lib/google_i18n/data/fm.json +1 -0
- data/lib/google_i18n/data/fo.json +1 -0
- data/lib/google_i18n/data/fr.json +1 -0
- data/lib/google_i18n/data/ga.json +1 -0
- data/lib/google_i18n/data/gb.json +1 -0
- data/lib/google_i18n/data/gd.json +1 -0
- data/lib/google_i18n/data/ge.json +1 -0
- data/lib/google_i18n/data/gf.json +1 -0
- data/lib/google_i18n/data/gg.json +1 -0
- data/lib/google_i18n/data/gh.json +1 -0
- data/lib/google_i18n/data/gi.json +1 -0
- data/lib/google_i18n/data/gl.json +1 -0
- data/lib/google_i18n/data/gm.json +1 -0
- data/lib/google_i18n/data/gn.json +1 -0
- data/lib/google_i18n/data/gp.json +1 -0
- data/lib/google_i18n/data/gq.json +1 -0
- data/lib/google_i18n/data/gr.json +1 -0
- data/lib/google_i18n/data/gs.json +1 -0
- data/lib/google_i18n/data/gt.json +1 -0
- data/lib/google_i18n/data/gu.json +1 -0
- data/lib/google_i18n/data/gw.json +1 -0
- data/lib/google_i18n/data/gy.json +1 -0
- data/lib/google_i18n/data/hk.json +1 -0
- data/lib/google_i18n/data/hm.json +1 -0
- data/lib/google_i18n/data/hn.json +1 -0
- data/lib/google_i18n/data/hr.json +1 -0
- data/lib/google_i18n/data/ht.json +1 -0
- data/lib/google_i18n/data/hu.json +1 -0
- data/lib/google_i18n/data/id.json +1 -0
- data/lib/google_i18n/data/ie.json +1 -0
- data/lib/google_i18n/data/il.json +1 -0
- data/lib/google_i18n/data/im.json +1 -0
- data/lib/google_i18n/data/in.json +1 -0
- data/lib/google_i18n/data/io.json +1 -0
- data/lib/google_i18n/data/iq.json +1 -0
- data/lib/google_i18n/data/ir.json +1 -0
- data/lib/google_i18n/data/is.json +1 -0
- data/lib/google_i18n/data/it.json +1 -0
- data/lib/google_i18n/data/je.json +1 -0
- data/lib/google_i18n/data/jm.json +1 -0
- data/lib/google_i18n/data/jo.json +1 -0
- data/lib/google_i18n/data/jp.json +1 -0
- data/lib/google_i18n/data/ke.json +1 -0
- data/lib/google_i18n/data/kg.json +1 -0
- data/lib/google_i18n/data/kh.json +1 -0
- data/lib/google_i18n/data/ki.json +1 -0
- data/lib/google_i18n/data/km.json +1 -0
- data/lib/google_i18n/data/kn.json +1 -0
- data/lib/google_i18n/data/kp.json +1 -0
- data/lib/google_i18n/data/kr.json +1 -0
- data/lib/google_i18n/data/kw.json +1 -0
- data/lib/google_i18n/data/ky.json +1 -0
- data/lib/google_i18n/data/kz.json +1 -0
- data/lib/google_i18n/data/la.json +1 -0
- data/lib/google_i18n/data/lb.json +1 -0
- data/lib/google_i18n/data/lc.json +1 -0
- data/lib/google_i18n/data/li.json +1 -0
- data/lib/google_i18n/data/lk.json +1 -0
- data/lib/google_i18n/data/lr.json +1 -0
- data/lib/google_i18n/data/ls.json +1 -0
- data/lib/google_i18n/data/lt.json +1 -0
- data/lib/google_i18n/data/lu.json +1 -0
- data/lib/google_i18n/data/lv.json +1 -0
- data/lib/google_i18n/data/ly.json +1 -0
- data/lib/google_i18n/data/ma.json +1 -0
- data/lib/google_i18n/data/mc.json +1 -0
- data/lib/google_i18n/data/md.json +1 -0
- data/lib/google_i18n/data/me.json +1 -0
- data/lib/google_i18n/data/mf.json +1 -0
- data/lib/google_i18n/data/mg.json +1 -0
- data/lib/google_i18n/data/mh.json +1 -0
- data/lib/google_i18n/data/mk.json +1 -0
- data/lib/google_i18n/data/ml.json +1 -0
- data/lib/google_i18n/data/mm.json +1 -0
- data/lib/google_i18n/data/mn.json +1 -0
- data/lib/google_i18n/data/mo.json +1 -0
- data/lib/google_i18n/data/mp.json +1 -0
- data/lib/google_i18n/data/mq.json +1 -0
- data/lib/google_i18n/data/mr.json +1 -0
- data/lib/google_i18n/data/ms.json +1 -0
- data/lib/google_i18n/data/mt.json +1 -0
- data/lib/google_i18n/data/mu.json +1 -0
- data/lib/google_i18n/data/mv.json +1 -0
- data/lib/google_i18n/data/mw.json +1 -0
- data/lib/google_i18n/data/mx.json +1 -0
- data/lib/google_i18n/data/my.json +1 -0
- data/lib/google_i18n/data/mz.json +1 -0
- data/lib/google_i18n/data/na.json +1 -0
- data/lib/google_i18n/data/nc.json +1 -0
- data/lib/google_i18n/data/ne.json +1 -0
- data/lib/google_i18n/data/nf.json +1 -0
- data/lib/google_i18n/data/ng.json +1 -0
- data/lib/google_i18n/data/ni.json +1 -0
- data/lib/google_i18n/data/nl.json +1 -0
- data/lib/google_i18n/data/no.json +1 -0
- data/lib/google_i18n/data/np.json +1 -0
- data/lib/google_i18n/data/nr.json +1 -0
- data/lib/google_i18n/data/nu.json +1 -0
- data/lib/google_i18n/data/nz.json +1 -0
- data/lib/google_i18n/data/om.json +1 -0
- data/lib/google_i18n/data/pa.json +1 -0
- data/lib/google_i18n/data/pe.json +1 -0
- data/lib/google_i18n/data/pf.json +1 -0
- data/lib/google_i18n/data/pg.json +1 -0
- data/lib/google_i18n/data/ph.json +1 -0
- data/lib/google_i18n/data/pk.json +1 -0
- data/lib/google_i18n/data/pl.json +1 -0
- data/lib/google_i18n/data/pm.json +1 -0
- data/lib/google_i18n/data/pn.json +1 -0
- data/lib/google_i18n/data/pr.json +1 -0
- data/lib/google_i18n/data/ps.json +1 -0
- data/lib/google_i18n/data/pt.json +1 -0
- data/lib/google_i18n/data/pw.json +1 -0
- data/lib/google_i18n/data/py.json +1 -0
- data/lib/google_i18n/data/qa.json +1 -0
- data/lib/google_i18n/data/re.json +1 -0
- data/lib/google_i18n/data/ro.json +1 -0
- data/lib/google_i18n/data/rs.json +1 -0
- data/lib/google_i18n/data/ru.json +1 -0
- data/lib/google_i18n/data/rw.json +1 -0
- data/lib/google_i18n/data/sa.json +1 -0
- data/lib/google_i18n/data/sb.json +1 -0
- data/lib/google_i18n/data/sc.json +1 -0
- data/lib/google_i18n/data/sd.json +1 -0
- data/lib/google_i18n/data/se.json +1 -0
- data/lib/google_i18n/data/sg.json +1 -0
- data/lib/google_i18n/data/sh.json +1 -0
- data/lib/google_i18n/data/si.json +1 -0
- data/lib/google_i18n/data/sj.json +1 -0
- data/lib/google_i18n/data/sk.json +1 -0
- data/lib/google_i18n/data/sl.json +1 -0
- data/lib/google_i18n/data/sm.json +1 -0
- data/lib/google_i18n/data/sn.json +1 -0
- data/lib/google_i18n/data/so.json +1 -0
- data/lib/google_i18n/data/sr.json +1 -0
- data/lib/google_i18n/data/ss.json +1 -0
- data/lib/google_i18n/data/st.json +1 -0
- data/lib/google_i18n/data/sv.json +1 -0
- data/lib/google_i18n/data/sx.json +1 -0
- data/lib/google_i18n/data/sy.json +1 -0
- data/lib/google_i18n/data/sz.json +1 -0
- data/lib/google_i18n/data/ta.json +1 -0
- data/lib/google_i18n/data/tc.json +1 -0
- data/lib/google_i18n/data/td.json +1 -0
- data/lib/google_i18n/data/tf.json +1 -0
- data/lib/google_i18n/data/tg.json +1 -0
- data/lib/google_i18n/data/th.json +1 -0
- data/lib/google_i18n/data/tj.json +1 -0
- data/lib/google_i18n/data/tk.json +1 -0
- data/lib/google_i18n/data/tl.json +1 -0
- data/lib/google_i18n/data/tm.json +1 -0
- data/lib/google_i18n/data/tn.json +1 -0
- data/lib/google_i18n/data/to.json +1 -0
- data/lib/google_i18n/data/tr.json +1 -0
- data/lib/google_i18n/data/tt.json +1 -0
- data/lib/google_i18n/data/tv.json +1 -0
- data/lib/google_i18n/data/tw.json +1 -0
- data/lib/google_i18n/data/tz.json +1 -0
- data/lib/google_i18n/data/ua.json +1 -0
- data/lib/google_i18n/data/ug.json +1 -0
- data/lib/google_i18n/data/um.json +1 -0
- data/lib/google_i18n/data/us.json +1 -0
- data/lib/google_i18n/data/uy.json +1 -0
- data/lib/google_i18n/data/uz.json +1 -0
- data/lib/google_i18n/data/va.json +1 -0
- data/lib/google_i18n/data/vc.json +1 -0
- data/lib/google_i18n/data/ve.json +1 -0
- data/lib/google_i18n/data/vg.json +1 -0
- data/lib/google_i18n/data/vi.json +1 -0
- data/lib/google_i18n/data/vn.json +1 -0
- data/lib/google_i18n/data/vu.json +1 -0
- data/lib/google_i18n/data/wf.json +1 -0
- data/lib/google_i18n/data/ws.json +1 -0
- data/lib/google_i18n/data/xk.json +1 -0
- data/lib/google_i18n/data/ye.json +1 -0
- data/lib/google_i18n/data/yt.json +1 -0
- data/lib/google_i18n/data/za.json +1 -0
- data/lib/google_i18n/data/zm.json +1 -0
- data/lib/google_i18n/data/zw.json +1 -0
- data/lib/google_i18n/data/zz.json +1 -0
- data/lib/google_i18n/validation_rules.rb +31 -0
- data/lib/google_i18n/version.rb +3 -0
- metadata +397 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2ec23d03d53d26a896f98d7fabc66aca0c4746f2ed186726f3f539f7a0596861
|
|
4
|
+
data.tar.gz: 9140529284abd1d9baaf8a892f819be6488eed5b9b90dfd1194e18590a604fdf
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b4f5fd00aa37ff3af5c0ff170a89d393a16314818627f4327419890c4591504e897642f844bd787f19cf9b1c8a4d65202c0019d6248aee6ceb3951aeac95c94f
|
|
7
|
+
data.tar.gz: 8c17dc7c93918202286e304be5f971c4003ad996a3473562bc230516298f30a9dffbf9fa52483d9f0840e39c9defb7e6ccd16d0e579c0c2878bf42ffc800c6d2
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
google_i18n (0.1.3)
|
|
5
|
+
activesupport
|
|
6
|
+
json
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
activesupport (6.1.3.1)
|
|
12
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
13
|
+
i18n (>= 1.6, < 2)
|
|
14
|
+
minitest (>= 5.1)
|
|
15
|
+
tzinfo (~> 2.0)
|
|
16
|
+
zeitwerk (~> 2.3)
|
|
17
|
+
coderay (1.1.3)
|
|
18
|
+
concurrent-ruby (1.1.8)
|
|
19
|
+
diff-lcs (1.4.4)
|
|
20
|
+
i18n (1.8.10)
|
|
21
|
+
concurrent-ruby (~> 1.0)
|
|
22
|
+
json (2.5.1)
|
|
23
|
+
method_source (1.0.0)
|
|
24
|
+
minitest (5.14.4)
|
|
25
|
+
pry (0.13.1)
|
|
26
|
+
coderay (~> 1.1)
|
|
27
|
+
method_source (~> 1.0)
|
|
28
|
+
rake (10.5.0)
|
|
29
|
+
rspec (3.10.0)
|
|
30
|
+
rspec-core (~> 3.10.0)
|
|
31
|
+
rspec-expectations (~> 3.10.0)
|
|
32
|
+
rspec-mocks (~> 3.10.0)
|
|
33
|
+
rspec-core (3.10.1)
|
|
34
|
+
rspec-support (~> 3.10.0)
|
|
35
|
+
rspec-expectations (3.10.1)
|
|
36
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
37
|
+
rspec-support (~> 3.10.0)
|
|
38
|
+
rspec-mocks (3.10.2)
|
|
39
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
40
|
+
rspec-support (~> 3.10.0)
|
|
41
|
+
rspec-support (3.10.2)
|
|
42
|
+
tzinfo (2.0.4)
|
|
43
|
+
concurrent-ruby (~> 1.0)
|
|
44
|
+
zeitwerk (2.4.2)
|
|
45
|
+
|
|
46
|
+
PLATFORMS
|
|
47
|
+
ruby
|
|
48
|
+
|
|
49
|
+
DEPENDENCIES
|
|
50
|
+
bundler (~> 1.17)
|
|
51
|
+
google_i18n!
|
|
52
|
+
pry (~> 0.13.1)
|
|
53
|
+
rake (~> 10.0)
|
|
54
|
+
rspec (~> 3.0)
|
|
55
|
+
|
|
56
|
+
BUNDLED WITH
|
|
57
|
+
1.17.3
|
data/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Google I18n Address
|
|
2
|
+
|
|
3
|
+
This gem provides I18n address normalization and validation based on
|
|
4
|
+
[Google's i18n address repository](https://chromium-i18n.appspot.com/ssl-address).
|
|
5
|
+
|
|
6
|
+
Code was ported to Ruby from this
|
|
7
|
+
[Python package](https://github.com/mirumee/google-i18n-address).
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Add this line to your application's Gemfile:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem 'google_i18n'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
And then execute:
|
|
19
|
+
|
|
20
|
+
$ bundle
|
|
21
|
+
|
|
22
|
+
Or install it yourself as:
|
|
23
|
+
|
|
24
|
+
$ gem install google_i18n
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
Initialize with some combination of the following:
|
|
29
|
+
- `country_code` The two-letter ISO 3166-1 country code
|
|
30
|
+
- `street_address` The street address
|
|
31
|
+
- `city` The city or town
|
|
32
|
+
- `city_area` The sublocality or district
|
|
33
|
+
- `name` A person's name
|
|
34
|
+
- `company_name` The name of a company or organization
|
|
35
|
+
- `country_area` The region, province or state
|
|
36
|
+
- `sorting_code` The sorting code
|
|
37
|
+
- `postal_code` The postal code or zip code
|
|
38
|
+
|
|
39
|
+
### Normalization
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
address = {country_code: 'US', country_area: 'California', city: 'Mountain View', postal_code: '94043', street_address: '1600 Amphitheatre Pkwy'}
|
|
43
|
+
a = GoogleI18n::Address.new(address)
|
|
44
|
+
a.normalize
|
|
45
|
+
=> #<GoogleI18n::Address @country_code="US", @country_area="CA", @city="MOUNTAIN VIEW", @city_area="", @postal_code="94043", @street_address="1600 Amphitheatre Pkwy", @sorting_code="">
|
|
46
|
+
a.to_s
|
|
47
|
+
=> "1600 Amphitheatre Pkwy\nMOUNTAIN VIEW , CA 94043\nUNITED STATES"
|
|
48
|
+
a.field_order
|
|
49
|
+
=> [["name"], ["company_name"], ["street_address"], ["city", "country_area", "postal_code"], ["country_name"]]
|
|
50
|
+
|
|
51
|
+
address = {country_code: 'CN', 'country_area': 'Beijing', 'city': 'Chaoyang', 'postal_code': '100600', street_address: '55 Anjialou Rd'}
|
|
52
|
+
GoogleI18n::Address.new(address).normalize
|
|
53
|
+
=> #<GoogleI18n::Address @country_code="CN", @street_address="55 Anjialou Rd", @city="朝阳区", @country_area="北京市", @postal_code="100600", @city_area="", @sorting_code="">
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Latinization
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
address = {'country_code': 'CN', 'country_area': '北京市', 'city': '朝阳区', 'city_area': '三元桥', 'street_address': '安家楼路55号', 'postal_code': '100600'}
|
|
60
|
+
GoogleI18n::Address.new(address).latinize
|
|
61
|
+
=> #<GoogleI18n::Address @country_code="CN", @country_area="Beijing Shi", @city="Chaoyang Qu", @city_area="三元桥", @postal_code="100600", @street_address="安家楼路55号", @sorting_code="">
|
|
62
|
+
GoogleI18n::Address.new(address).latinize.normalize
|
|
63
|
+
=> #<GoogleI18n::Address @country_code="CN", @street_address="55 Anjialou Rd", @city="朝阳区", @country_area="北京市", @postal_code="100600", @city_area="", @sorting_code="">
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Validation
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
GoogleI18n::Address.new({country_code: 'US'}).normalize
|
|
70
|
+
=> GoogleI18n::InvalidAddress (Invalid address {"country_area"=>"required", "city"=>"required", "postal_code"=>"required", "street_address"=>"required"})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Country Areas
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
GoogleI18n::Address.new({country_code: 'US'}).country_area_choices
|
|
77
|
+
=> [['AL', 'Alabama'], ..., ['WY', 'Wyoming']]
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Development
|
|
82
|
+
|
|
83
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
84
|
+
|
|
85
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
86
|
+
|
|
87
|
+
## Contributing
|
|
88
|
+
|
|
89
|
+
Bug reports and pull requests are welcome on GitLab at https://gitlab.com/dimitridee/google_i18n
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "google_i18n"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/google_i18n.gemspec
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require "google_i18n/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "google_i18n"
|
|
8
|
+
spec.version = GoogleI18n::VERSION
|
|
9
|
+
spec.authors = ["Dimitri D"]
|
|
10
|
+
spec.email = ["demonitri@gmail.com"]
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{i18n address standardization and validation using Google's i18n address repo}
|
|
13
|
+
# spec.description = %q{TODO: Write a longer description or delete this line.}
|
|
14
|
+
spec.homepage = "https://gitlab.com/dimitri_dee/google_i18n"
|
|
15
|
+
|
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
|
18
|
+
if spec.respond_to?(:metadata)
|
|
19
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
|
20
|
+
|
|
21
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
22
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
23
|
+
spec.metadata["changelog_uri"] = spec.homepage
|
|
24
|
+
else
|
|
25
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
|
26
|
+
"public gem pushes."
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Specify which files should be added to the gem when it is released.
|
|
30
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
31
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
32
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
33
|
+
end
|
|
34
|
+
spec.bindir = "exe"
|
|
35
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
36
|
+
spec.require_paths = ["lib"]
|
|
37
|
+
|
|
38
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
|
39
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
40
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
41
|
+
spec.add_development_dependency "pry", "~> 0.13.1"
|
|
42
|
+
|
|
43
|
+
spec.add_dependency 'json'
|
|
44
|
+
spec.add_dependency "activesupport"
|
|
45
|
+
end
|
data/lib/google_i18n.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "active_support/core_ext/object/blank"
|
|
3
|
+
require "active_support/ordered_hash"
|
|
4
|
+
|
|
5
|
+
module GoogleI18n
|
|
6
|
+
class InvalidAddress < StandardError; end
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
require "google_i18n/version"
|
|
10
|
+
require "google_i18n/validation_rules"
|
|
11
|
+
require "google_i18n/address"
|
|
12
|
+
end
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
module GoogleI18n
|
|
2
|
+
class Address
|
|
3
|
+
VALID_COUNTRY_CODE = %r(^\w{2,3}$)
|
|
4
|
+
VALIDATION_DATA_DIR = File.join(File.dirname(__FILE__), "data")
|
|
5
|
+
VALIDATION_DATA_PATH = File.join(VALIDATION_DATA_DIR, "%s.json")
|
|
6
|
+
|
|
7
|
+
FIELD_MAPPING = {
|
|
8
|
+
"A" => "street_address",
|
|
9
|
+
"C" => "city",
|
|
10
|
+
"D" => "city_area",
|
|
11
|
+
"N" => "name",
|
|
12
|
+
"O" => "company_name",
|
|
13
|
+
"S" => "country_area",
|
|
14
|
+
"X" => "sorting_code",
|
|
15
|
+
"Z" => "postal_code",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
KNOWN_FIELDS = FIELD_MAPPING.values.push("country_code")
|
|
19
|
+
|
|
20
|
+
attr_accessor :country_code, :street_address, :city, :city_area, :name, :company_name,
|
|
21
|
+
:country_area, :sorting_code, :postal_code
|
|
22
|
+
|
|
23
|
+
# Creates a new Address object from component parts
|
|
24
|
+
#
|
|
25
|
+
# @option options [String] :country_code The two-letter ISO 3166-1 country code
|
|
26
|
+
# @option options [String] :street_address The street address
|
|
27
|
+
# @option options [String] :city The city or town
|
|
28
|
+
# @option options [String] :city_area The sublocality or district
|
|
29
|
+
# @option options [String] :name A person's name
|
|
30
|
+
# @option options [String] :company_name The name of a company or organization
|
|
31
|
+
# @option options [String] :country_area The region, province or state
|
|
32
|
+
# @option options [String] :sorting_code The sorting code
|
|
33
|
+
# @option options [String] :postal_code The postal code or zip code
|
|
34
|
+
#
|
|
35
|
+
# @return [GoogleI18n::Address] The constructed Address
|
|
36
|
+
def initialize(options={})
|
|
37
|
+
@country_code = options[:country_code] if options[:country_code]
|
|
38
|
+
@street_address = options[:street_address] if options[:street_address]
|
|
39
|
+
@city = options[:city] if options[:city]
|
|
40
|
+
@city_area = options[:city_area] if options[:city_area]
|
|
41
|
+
@name = options[:name] if options[:name]
|
|
42
|
+
@company_name = options[:company_name] if options[:company_name]
|
|
43
|
+
@country_area = options[:country_area] if options[:country_area]
|
|
44
|
+
@sorting_code = options[:sorting_code] if options[:sorting_code]
|
|
45
|
+
@postal_code = options[:postal_code] if options[:postal_code]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def dup
|
|
49
|
+
self.class.new(
|
|
50
|
+
country_code: self.country_code ? self.country_code.dup : nil,
|
|
51
|
+
street_address: self.street_address ? self.street_address.dup : nil,
|
|
52
|
+
city: self.city ? self.city.dup : nil,
|
|
53
|
+
city_area: self.city_area ? self.city_area.dup : nil,
|
|
54
|
+
name: self.name ? self.name.dup : nil,
|
|
55
|
+
company_name: self.company_name ? self.company_name.dup : nil,
|
|
56
|
+
country_area: self.country_area ? self.country_area.dup : nil,
|
|
57
|
+
sorting_code: self.sorting_code ? self.sorting_code.dup : nil,
|
|
58
|
+
postal_code: self.postal_code ? self.postal_code.dup : nil,
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_h
|
|
63
|
+
{
|
|
64
|
+
country_code: self.country_code,
|
|
65
|
+
street_address: self.street_address,
|
|
66
|
+
city: self.city,
|
|
67
|
+
city_area: self.city_area,
|
|
68
|
+
name: self.name,
|
|
69
|
+
company_name: self.company_name,
|
|
70
|
+
country_area: self.country_area,
|
|
71
|
+
sorting_code: self.sorting_code,
|
|
72
|
+
postal_code: self.postal_code,
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Displays the address in a newline delimited format suitable for printing
|
|
77
|
+
#
|
|
78
|
+
# @param normalize [Boolean] If true, address will be normalized first
|
|
79
|
+
# @param latin [Boolean] Show the address fields in Latinized order
|
|
80
|
+
#
|
|
81
|
+
# @return [String]
|
|
82
|
+
def to_s(normalize: true, latin: false)
|
|
83
|
+
cleaned_data = address.dup
|
|
84
|
+
cleaned_data = cleaned_data.normalize if normalize
|
|
85
|
+
|
|
86
|
+
rules = get_validation_rules
|
|
87
|
+
|
|
88
|
+
address_format = if latin
|
|
89
|
+
rules.address_latin_format
|
|
90
|
+
else
|
|
91
|
+
rules.address_format
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
address_line_formats = address_format.split("%n")
|
|
95
|
+
|
|
96
|
+
address_lines = address_line_formats.inject([]) do |m, line_format|
|
|
97
|
+
m << format_address_line(line_format, cleaned_data, rules)
|
|
98
|
+
m
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
address_lines << rules.country_name
|
|
102
|
+
address_lines.keep_if(&:present?)
|
|
103
|
+
address_lines.join("\n")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns an array where each element is an array representing the fields to include on each address line, e.g:
|
|
107
|
+
# [["name"], ["company_name"], ["street_address"], ["city", "country_area", "postal_code"], ["country_name"]]
|
|
108
|
+
#
|
|
109
|
+
# @param latin [Boolean] Show the address fields in Latinized order
|
|
110
|
+
#
|
|
111
|
+
# @return [Array<Array<String>>]
|
|
112
|
+
def field_order(latin: false)
|
|
113
|
+
rules = get_validation_rules
|
|
114
|
+
|
|
115
|
+
address_format = if latin
|
|
116
|
+
rules.address_latin_format
|
|
117
|
+
else
|
|
118
|
+
rules.address_format
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
address_lines = address_format.split("%n")
|
|
122
|
+
|
|
123
|
+
replacements = FIELD_MAPPING.inject({}) do |m, (code, field_name)|
|
|
124
|
+
m["%#{code}"] = field_name
|
|
125
|
+
m
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
all_lines = []
|
|
129
|
+
address_lines.each do |line|
|
|
130
|
+
fields = line.split(%r{(%.)})
|
|
131
|
+
single_line = fields.map {|field| replacements[field]}.compact
|
|
132
|
+
all_lines << single_line
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
all_lines << Array('country_name')
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Returns an array where each element is an array representing the key and value for each country area. e.g:
|
|
139
|
+
# [['AL', 'Alabama'], ..., ['WY', 'Wyoming']]
|
|
140
|
+
def country_area_choices
|
|
141
|
+
get_validation_rules.country_area_choices
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Format the address in a way suitable for use in its part of the world
|
|
145
|
+
#
|
|
146
|
+
# @return [GoogleI18n::Address]
|
|
147
|
+
def normalize
|
|
148
|
+
errors = {}
|
|
149
|
+
# begin
|
|
150
|
+
rules = get_validation_rules
|
|
151
|
+
# rescue StandardError
|
|
152
|
+
# errors["country_code"] = "invalid"
|
|
153
|
+
# end
|
|
154
|
+
|
|
155
|
+
if errors.blank?
|
|
156
|
+
cleaned_data = address.dup
|
|
157
|
+
country_code = cleaned_data.country_code
|
|
158
|
+
if country_code.blank?
|
|
159
|
+
errors["country_code"] = "required"
|
|
160
|
+
else
|
|
161
|
+
cleaned_data.country_code = country_code.upcase
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
cleaned_data.country_area, errors = normalize_field("country_area", rules, cleaned_data, rules.country_area_choices, errors)
|
|
165
|
+
cleaned_data.city, errors = normalize_field("city", rules, cleaned_data, rules.city_choices, errors)
|
|
166
|
+
cleaned_data.city_area, errors = normalize_field("city_area", rules, cleaned_data, rules.city_area_choices, errors)
|
|
167
|
+
cleaned_data.postal_code, errors = normalize_field("postal_code", rules, cleaned_data, [], errors)
|
|
168
|
+
|
|
169
|
+
postal_code = cleaned_data.postal_code
|
|
170
|
+
if rules.postal_code_matchers.present? && postal_code.present?
|
|
171
|
+
rules.postal_code_matchers.each do |matcher|
|
|
172
|
+
if !matcher.match(postal_code)
|
|
173
|
+
errors["postal_code"] = "invalid"
|
|
174
|
+
break
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
cleaned_data.street_address, errors = normalize_field("street_address", rules, cleaned_data, [], errors)
|
|
180
|
+
cleaned_data.sorting_code, errors = normalize_field("sorting_code", rules, cleaned_data, [], errors)
|
|
181
|
+
cleaned_data
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
if errors.present?
|
|
185
|
+
raise InvalidAddress, "Invalid address #{errors}"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
cleaned_data
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Format the address in a way suitable for use in Latin alphabets.
|
|
192
|
+
# If a latinized version is not available, the local version will be used instead.
|
|
193
|
+
#
|
|
194
|
+
# @param normalize [Boolean] If true, address will be normalized first
|
|
195
|
+
#
|
|
196
|
+
# @return [GoogleI18n::Address]
|
|
197
|
+
def latinize(normalize: true)
|
|
198
|
+
cleaned_data = address.dup
|
|
199
|
+
cleaned_data = cleaned_data.normalize if normalize
|
|
200
|
+
|
|
201
|
+
country_code = cleaned_data.country_code&.upcase
|
|
202
|
+
dummy_country_data, database = load_country_data(country_code)
|
|
203
|
+
|
|
204
|
+
if country_code.present?
|
|
205
|
+
country_area = cleaned_data.country_area
|
|
206
|
+
if country_area.present?
|
|
207
|
+
key = "%s/%s" % [country_code, country_area]
|
|
208
|
+
country_area_data = database[key]
|
|
209
|
+
if country_area_data.present?
|
|
210
|
+
cleaned_data.country_area = country_area_data.fetch("lname", country_area_data.fetch("name", country_area))
|
|
211
|
+
city = cleaned_data.city
|
|
212
|
+
key = "%s/%s/%s" % [country_code, country_area, city]
|
|
213
|
+
city_data = database[key]
|
|
214
|
+
if city_data.present?
|
|
215
|
+
cleaned_data.city = city_data.fetch("lname", city_data.fetch("name", city))
|
|
216
|
+
city_area = cleaned_data.city_area
|
|
217
|
+
key = "%s/%s/%s/%s" % [country_code, country_area, city, city_area]
|
|
218
|
+
city_area_data = database[key]
|
|
219
|
+
if city_area_data.present?
|
|
220
|
+
cleaned_data.city_area = city_area_data.fetch("lname", city_area_data.fetch("name", city_area))
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
cleaned_data
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
private
|
|
231
|
+
|
|
232
|
+
def address
|
|
233
|
+
self
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def format_address_line(line_format, address, rules)
|
|
237
|
+
replacements = FIELD_MAPPING.inject({}) do |m, (code, field_name)|
|
|
238
|
+
value = address.send(field_name)
|
|
239
|
+
value = value.upcase if rules.upper_fields.include?(field_name)
|
|
240
|
+
m["%#{code}"] = value
|
|
241
|
+
m
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
fields = line_format.split(%r{(%.)})
|
|
245
|
+
fields = fields.map {|field| replacements.fetch(field, field)&.strip}
|
|
246
|
+
fields.keep_if(&:present?)
|
|
247
|
+
fields.join(' ')
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def normalize_field(name, rules, data, choices, errors)
|
|
251
|
+
value = data.send(name)
|
|
252
|
+
|
|
253
|
+
if rules.upper_fields.include?(name) && value.present?
|
|
254
|
+
data.send("#{name}=", value.upcase)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
if !rules.allowed_fields.include?(name)
|
|
258
|
+
data.send("#{name}=", "")
|
|
259
|
+
elsif value.blank?
|
|
260
|
+
if rules.required_fields.include?(name)
|
|
261
|
+
errors[name] = "required"
|
|
262
|
+
else
|
|
263
|
+
data.send("#{name}=", "")
|
|
264
|
+
end
|
|
265
|
+
elsif choices.present?
|
|
266
|
+
if value.present? || rules.required_fields.include?(name)
|
|
267
|
+
value = match_choices(value, choices)
|
|
268
|
+
if value.present?
|
|
269
|
+
data.send("#{name}=", value)
|
|
270
|
+
else
|
|
271
|
+
errors[name] = "invalid"
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
[data.send(name), errors]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def make_choices(rules, translated = false)
|
|
280
|
+
sub_keys = rules["sub_keys"]
|
|
281
|
+
return if sub_keys.blank?
|
|
282
|
+
|
|
283
|
+
choices = []
|
|
284
|
+
sub_keys = sub_keys.split("~")
|
|
285
|
+
|
|
286
|
+
if sub_names = rules["sub_names"]
|
|
287
|
+
choices += sub_keys.zip(sub_names.split("~"))
|
|
288
|
+
elsif !translated
|
|
289
|
+
choices += sub_keys.zip(sub_keys)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
if !translated
|
|
293
|
+
if sub_lnames = rules["sub_lnames"]
|
|
294
|
+
choices += sub_keys.zip(sub_lnames.split("~"))
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
if sub_lfnames = rules["sub_lfnames"]
|
|
298
|
+
choices += sub_keys.zip(sub_lfnames.split("~"))
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
choices
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# This returns the regional form of `value`.
|
|
306
|
+
# `choices` looks something like this: [['安徽省', 'Anhui Sheng'], ['安徽省', '安徽省']]
|
|
307
|
+
# the idea is that whether `value` is 'Anhui Sheng' or '安徽省', it will return '安徽省'
|
|
308
|
+
def match_choices(value, choices)
|
|
309
|
+
return if value.blank?
|
|
310
|
+
value = value.strip.downcase
|
|
311
|
+
# using `match` here rather than `==` because we want them to be able to use "New York" and "New York City"
|
|
312
|
+
matched = choices.select { |name, label| name&.downcase.to_s.match(value) || label&.downcase.to_s.match(value) }
|
|
313
|
+
matched.flatten.first
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# This returns something like:
|
|
317
|
+
# [['AL', 'Alabama'], ['AK', 'Alaska'], ['AS', 'American Samoa'], ['AZ', 'Arizona'], ...]
|
|
318
|
+
# or for non-latin alphabets:
|
|
319
|
+
# [['安徽省', 'Anhui Sheng'], ['安徽省', '安徽省'], ['澳门', 'Macau'], ['澳门', '澳门'], ['北京市', 'Beijing Shi'], ['北京市', '北京市'], ...]
|
|
320
|
+
def compact_choices(choices)
|
|
321
|
+
value_map = ActiveSupport::OrderedHash.new {|hsh, key| hsh[key] = [] }
|
|
322
|
+
choices.each do |key, value|
|
|
323
|
+
value_map[key] << value.to_s
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
ret = []
|
|
327
|
+
value_map.each do |k,vv|
|
|
328
|
+
vv.sort.each do |v|
|
|
329
|
+
ret << [k,v]
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
ret
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def get_validation_rules
|
|
337
|
+
country_code = address.country_code&.upcase
|
|
338
|
+
country_data, database = load_country_data(country_code)
|
|
339
|
+
country_name = country_data.fetch("name", "")
|
|
340
|
+
address_format = country_data["fmt"]
|
|
341
|
+
address_latin_format = country_data.fetch("lfmt", address_format)
|
|
342
|
+
|
|
343
|
+
allowed_fields = address_format.scan(%r{%([#{FIELD_MAPPING.keys.join}])}).flatten
|
|
344
|
+
allowed_fields = FIELD_MAPPING.fetch_values(*allowed_fields)
|
|
345
|
+
required_fields = FIELD_MAPPING.fetch_values(*country_data["require"].split(//))
|
|
346
|
+
upper_fields = FIELD_MAPPING.fetch_values(*country_data["upper"].split(//))
|
|
347
|
+
|
|
348
|
+
languages = country_data["languages"]&.split("~") || []
|
|
349
|
+
|
|
350
|
+
postal_code_matchers = []
|
|
351
|
+
if allowed_fields.include?("postal_code") && country_data.has_key?("zip")
|
|
352
|
+
postal_code_matchers << %r(^#{country_data["zip"]}$)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
postal_code_examples = country_data["zipex"]&.split(",") || []
|
|
356
|
+
|
|
357
|
+
country_area_choices = []
|
|
358
|
+
city_choices = []
|
|
359
|
+
city_area_choices = []
|
|
360
|
+
country_area_type = country_data["state_name_type"]
|
|
361
|
+
city_type = country_data["locality_name_type"]
|
|
362
|
+
city_area_type = country_data["sublocality_name_type"]
|
|
363
|
+
postal_code_type = country_data["zip_name_type"]
|
|
364
|
+
postal_code_prefix = country_data.fetch("postprefix", "")
|
|
365
|
+
|
|
366
|
+
# second level of data is for administrative areas
|
|
367
|
+
country_area = nil
|
|
368
|
+
city = nil
|
|
369
|
+
city_area = nil
|
|
370
|
+
if database.has_key?(country_code)
|
|
371
|
+
if country_data.has_key?("sub_keys")
|
|
372
|
+
languages.each do |language|
|
|
373
|
+
is_default_language = (language.blank? || language == country_data["lang"])
|
|
374
|
+
matched_country_area = nil
|
|
375
|
+
matched_city = nil
|
|
376
|
+
if is_default_language
|
|
377
|
+
localized_country_data = database[country_code]
|
|
378
|
+
else
|
|
379
|
+
localized_country_data = database["#{country_code}--#{language}"]
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
localized_country_area_choices = make_choices(localized_country_data)
|
|
383
|
+
country_area_choices += localized_country_area_choices
|
|
384
|
+
existing_choice = country_area.present?
|
|
385
|
+
matched_country_area = country_area = match_choices(address.country_area, localized_country_area_choices)
|
|
386
|
+
|
|
387
|
+
# third level of data is for cities
|
|
388
|
+
if matched_country_area.present?
|
|
389
|
+
if is_default_language
|
|
390
|
+
country_area_data = database["#{country_code}/#{country_area}"]
|
|
391
|
+
else
|
|
392
|
+
country_area_data = database["#{country_code}/#{country_area}--#{language}"]
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
if existing_choice.blank?
|
|
396
|
+
if country_area_data.include?("zip")
|
|
397
|
+
postal_code_matchers << %r(^#{country_area_data["zip"]})
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
if country_area_data.include?("zipex")
|
|
401
|
+
postal_code_examples = country_area_data["zipex"].split(",")
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
if country_area_data.include?("sub_keys")
|
|
406
|
+
localized_city_choices = make_choices(country_area_data)
|
|
407
|
+
city_choices += localized_city_choices
|
|
408
|
+
existing_choice = city.present?
|
|
409
|
+
matched_city = city = match_choices(address.city, localized_city_choices)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# fourth level of data is for dependent sublocalities
|
|
413
|
+
if matched_city.present?
|
|
414
|
+
if is_default_language
|
|
415
|
+
city_data = database["#{country_code}/#{country_area}/#{city}"]
|
|
416
|
+
else
|
|
417
|
+
city_data = database["#{country_code}/#{country_area}/#{city}--#{language}"]
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
if existing_choice.blank?
|
|
421
|
+
if city_data.include?("zip")
|
|
422
|
+
postal_code_matchers << %r(^#{city_data["zip"]})
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
if city_data.include?("zipex")
|
|
426
|
+
postal_code_examples = city_data["zipex"].split(",")
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
if city_data.include?("sub_keys")
|
|
431
|
+
localized_city_area_choices = make_choices(city_data)
|
|
432
|
+
city_area_choices += localized_city_area_choices
|
|
433
|
+
existing_choice = city_area.present?
|
|
434
|
+
matched_city_area = city_area = match_choices(address.city_area, localized_city_area_choices)
|
|
435
|
+
if matched_city_area.present?
|
|
436
|
+
if is_default_language
|
|
437
|
+
city_area_data = database["#{country_code}/#{country_area}/#{city}/#{city_area}"]
|
|
438
|
+
else
|
|
439
|
+
city_area_data = database["#{country_code}/#{country_area}/#{city}/#{city_area}--#{language}"]
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
if existing_choice.blank?
|
|
443
|
+
if city_area_data.include?("zip")
|
|
444
|
+
postal_code_matchers << %r(^#{city_area_data["zip"]})
|
|
445
|
+
end
|
|
446
|
+
if city_area_data.include?("zipex")
|
|
447
|
+
postal_code_examples = city_area_data["zipex"].split(",")
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
country_area_choices = compact_choices(country_area_choices)
|
|
456
|
+
city_choices = compact_choices(city_choices)
|
|
457
|
+
city_area_choices = compact_choices(city_area_choices)
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
ValidationRules.new(country_code, country_name, address_format, address_latin_format,
|
|
462
|
+
allowed_fields, required_fields, upper_fields, country_area_type,
|
|
463
|
+
country_area_choices, city_type, city_choices, city_area_type, city_area_choices,
|
|
464
|
+
postal_code_type, postal_code_matchers, postal_code_examples, postal_code_prefix)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def load_validation_data(country_code="all")
|
|
468
|
+
if !VALID_COUNTRY_CODE.match(country_code)
|
|
469
|
+
raise "#{country_code} is not a valid country code"
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
path = VALIDATION_DATA_PATH % country_code.downcase
|
|
473
|
+
if !File.exist?(path)
|
|
474
|
+
raise "No metadata file exists at path: #{path}"
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
JSON.parse(File.read(path))
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def load_country_data(country_code)
|
|
481
|
+
# This ZZ stuff is just defaults to init with
|
|
482
|
+
database = load_validation_data("zz")
|
|
483
|
+
country_data = database["ZZ"]
|
|
484
|
+
if country_code
|
|
485
|
+
country_code = country_code.upcase
|
|
486
|
+
if country_code.downcase == "zz"
|
|
487
|
+
raise "'#{country_code}' is not a valid country code"
|
|
488
|
+
end
|
|
489
|
+
database = load_validation_data(country_code.downcase)
|
|
490
|
+
country_data.merge!(database[country_code.upcase])
|
|
491
|
+
end
|
|
492
|
+
[country_data, database]
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
end
|