worldwide 1.25.6 → 1.26.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9995bb8a7911cba1d7e0602921122a2101d9fb4c1420259b8466f9f175f4a72e
4
- data.tar.gz: 8c0702f3d76fd8eafd7683b31de542e655a33157b9c9c8ff1881cf816c4d75bf
3
+ metadata.gz: 60cb3f293340533a39f8316ec7dbc1fe4dfb35483f2603cf348240db5c34ae8c
4
+ data.tar.gz: 74fc4ebce8fc936e6f0c80c5fab79a6c7d8534c5a4aae289a0784b2a9f9ebee1
5
5
  SHA512:
6
- metadata.gz: 1f07d3fd68d9438584c69c46330df60ae256a4ccd86b1fc8120c43165fa3a1ee4c69a9fed8a292791eeefe18ac9a68a0245e526bb58d99e6f2e6d129b3e2ac15
7
- data.tar.gz: 77f7959d3aa5d385f6f6012cc9acdfd4e0d7502bb6f2b47bb6a37411a009f394389af071e9948f86d83d7250ab88cc0eadcb35cd99fe56be1fa946f18bbdde3a
6
+ metadata.gz: 3e347d426de7e52e23504e3632a8fb4ad3e3473994138f2506859696e78b04e6ceaf2bbbab9074d34c2cbcc0828427227d2ff9eea630b97aafa2ddd5379a66be
7
+ data.tar.gz: 7107f99efa3ca3a9532585f69cfee927b619988bedb31f470bf46fc6b05b6ec28ba7a9f1ac2aa8fc1246922b29c83d7ba9a3ca8168c396fbd371d9cd954a814b
@@ -15,7 +15,7 @@ jobs:
15
15
  working-directory: lang/typescript
16
16
  steps:
17
17
  - name: Check out code
18
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
18
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
19
  - name: Setup Node
20
20
  uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
21
21
  with:
@@ -20,9 +20,9 @@ jobs:
20
20
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
21
21
  steps:
22
22
  - name: Check out code
23
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
24
  - name: Set up Ruby ${{ matrix.ruby }}
25
- uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0
25
+ uses: ruby/setup-ruby@a30dfa457ad68707b8b910ac3a244714b61c0626 # v1.320.0
26
26
  with:
27
27
  ruby-version: ${{ matrix.ruby }}
28
28
  bundler-cache: true
@@ -20,7 +20,7 @@ jobs:
20
20
  working-directory: lang/typescript
21
21
  steps:
22
22
  - name: Checkout Repo
23
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
24
  with:
25
25
  token: ${{ secrets.SHOPIFY_GH_ACCESS_TOKEN }}
26
26
  - name: Setup Node
data/.rubocop.yml CHANGED
@@ -2,8 +2,10 @@
2
2
  inherit_gem:
3
3
  rubocop-shopify: rubocop.yml
4
4
 
5
- require:
5
+ plugins:
6
6
  - rubocop-minitest
7
+
8
+ require:
7
9
  - rubocop-performance
8
10
  - rubocop-rake
9
11
 
data/CHANGELOG.md CHANGED
@@ -29,6 +29,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
29
29
 
30
30
  ---
31
31
 
32
+ ## [1.26.0] - 2026-07-22
33
+ - Add `Worldwide::BusinessNames.abbreviated` for script-aware abbreviation of a single business name string. [#538](https://github.com/Shopify/worldwide/pull/538) [#553](https://github.com/Shopify/worldwide/pull/553)
34
+ - Add `Worldwide::Names.abbreviated` to abbreviate personal names by script. [#539](https://github.com/Shopify/worldwide/pull/539)
35
+ - Fix trailing whitespace in formatted Japanese addresses when the postal code is blank or excluded. [#548](https://github.com/Shopify/worldwide/pull/548)
36
+ - Fix `Address#format` dropping backslash sequences (e.g. `\2`) in field values. [#533](https://github.com/Shopify/worldwide/pull/533)
37
+
32
38
  ## [1.25.6] - 2026-07-21
33
39
  - Bump i18n from 1.14.1 to 1.15.2 (now required as `>= 1.15`) and update `Cldr.with_cldr` to mirror i18n's fiber-aware config and fallbacks storage so the CLDR config still applies on Ruby 3.2+. [#540](https://github.com/Shopify/worldwide/pull/540)
34
40
 
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ gem "ruby-cldr", git: "https://github.com/ruby-i18n/ruby-cldr.git", ref: "86a5d7
9
9
  # Specify your gem's dependencies in worldwide.gemspec
10
10
  gemspec
11
11
 
12
- gem "rake", "~> 13.0"
12
+ gem "rake", "~> 13.4"
13
13
 
14
14
  group :development do
15
15
  gem "minitest-focus", require: false
@@ -24,7 +24,7 @@ group :development do
24
24
  end
25
25
 
26
26
  group :test do
27
- gem "minitest", "~> 5.0"
27
+ gem "minitest", "~> 5.27"
28
28
  gem "minitest-reporters"
29
29
  gem "mocha"
30
30
  end
data/Gemfile.lock CHANGED
@@ -13,7 +13,7 @@ GIT
13
13
  PATH
14
14
  remote: .
15
15
  specs:
16
- worldwide (1.25.6)
16
+ worldwide (1.26.0)
17
17
  activesupport (>= 7.0)
18
18
  i18n (>= 1.15)
19
19
  phonelib (~> 0.8)
@@ -21,94 +21,100 @@ PATH
21
21
  GEM
22
22
  remote: https://rubygems.org/
23
23
  specs:
24
- activesupport (7.1.1)
24
+ activesupport (7.2.3.1)
25
25
  base64
26
+ benchmark (>= 0.3)
26
27
  bigdecimal
27
- concurrent-ruby (~> 1.0, >= 1.0.2)
28
+ concurrent-ruby (~> 1.0, >= 1.3.1)
28
29
  connection_pool (>= 2.2.5)
29
30
  drb
30
31
  i18n (>= 1.6, < 2)
31
- minitest (>= 5.1)
32
- mutex_m
33
- tzinfo (~> 2.0)
34
- ansi (1.5.0)
35
- ast (2.4.2)
36
- base64 (0.1.1)
37
- bigdecimal (3.1.4)
38
- builder (3.2.4)
39
- byebug (11.1.3)
32
+ logger (>= 1.4.2)
33
+ minitest (>= 5.1, < 6)
34
+ securerandom (>= 0.3)
35
+ tzinfo (~> 2.0, >= 2.0.5)
36
+ ansi (1.6.0)
37
+ ast (2.4.3)
38
+ base64 (0.3.0)
39
+ benchmark (0.5.0)
40
+ bigdecimal (4.1.2)
41
+ builder (3.3.0)
42
+ byebug (12.0.0)
40
43
  coderay (1.1.3)
41
- concurrent-ruby (1.3.7)
42
- connection_pool (2.4.1)
43
- drb (2.1.1)
44
- ruby2_keywords
44
+ concurrent-ruby (1.3.8)
45
+ connection_pool (2.5.5)
46
+ drb (2.2.3)
45
47
  i18n (1.15.2)
46
48
  concurrent-ruby (~> 1.0)
47
- json (2.7.1)
49
+ json (2.21.1)
48
50
  language_server-protocol (3.17.0.6)
51
+ lint_roller (1.1.0)
49
52
  logger (1.7.0)
50
53
  method_source (1.1.0)
51
- minitest (5.17.0)
52
- minitest-focus (1.3.1)
53
- minitest (>= 4, < 6)
54
- minitest-reporters (1.5.0)
54
+ minitest (5.27.0)
55
+ minitest-focus (1.4.1)
56
+ minitest (> 5.0)
57
+ minitest-reporters (1.8.0)
55
58
  ansi
56
59
  builder
57
- minitest (>= 5.0)
60
+ minitest (>= 5.0, < 7)
58
61
  ruby-progressbar
59
62
  mocha (3.1.0)
60
63
  ruby2_keywords (>= 0.0.5)
61
- mutex_m (0.1.2)
62
64
  nokogiri (1.19.3-arm64-darwin)
63
65
  racc (~> 1.4)
64
66
  nokogiri (1.19.3-x86_64-linux-gnu)
65
67
  racc (~> 1.4)
66
- parallel (1.24.0)
67
- parser (3.3.0.5)
68
+ parallel (1.28.0)
69
+ parser (3.3.12.0)
68
70
  ast (~> 2.4.1)
69
71
  racc
70
72
  phonelib (0.10.22)
71
73
  prism (1.9.0)
72
- pry (0.14.2)
74
+ pry (0.15.2)
73
75
  coderay (~> 1.1)
74
76
  method_source (~> 1.0)
75
- pry-byebug (3.10.1)
76
- byebug (~> 11.0)
77
- pry (>= 0.13, < 0.15)
77
+ pry-byebug (3.11.0)
78
+ byebug (~> 12.0)
79
+ pry (>= 0.13, < 0.16)
78
80
  psych (5.1.0)
79
81
  stringio
80
82
  racc (1.8.1)
81
83
  rainbow (3.1.1)
82
- rake (13.0.6)
84
+ rake (13.4.2)
83
85
  rake-compiler (1.3.1)
84
86
  rake
85
87
  rbs (3.10.4)
86
88
  logger
87
89
  tsort
88
- regexp_parser (2.9.0)
89
- rexml (3.4.2)
90
- rubocop (1.62.1)
90
+ regexp_parser (2.12.0)
91
+ rubocop (1.88.2)
91
92
  json (~> 2.3)
92
- language_server-protocol (>= 3.17.0)
93
- parallel (~> 1.10)
93
+ language_server-protocol (~> 3.17.0.2)
94
+ lint_roller (~> 1.1.0)
95
+ parallel (>= 1.10)
94
96
  parser (>= 3.3.0.2)
95
97
  rainbow (>= 2.2.2, < 4.0)
96
- regexp_parser (>= 1.8, < 3.0)
97
- rexml (>= 3.2.5, < 4.0)
98
- rubocop-ast (>= 1.31.1, < 2.0)
98
+ regexp_parser (>= 2.9.3, < 3.0)
99
+ rubocop-ast (>= 1.49.0, < 2.0)
99
100
  ruby-progressbar (~> 1.7)
100
- unicode-display_width (>= 2.4.0, < 3.0)
101
- rubocop-ast (1.31.2)
102
- parser (>= 3.3.0.4)
103
- rubocop-minitest (0.27.0)
104
- rubocop (>= 0.90, < 2.0)
105
- rubocop-performance (1.16.0)
106
- rubocop (>= 1.7.0, < 2.0)
107
- rubocop-ast (>= 0.4.0)
108
- rubocop-rake (0.6.0)
109
- rubocop (~> 1.0)
110
- rubocop-shopify (2.12.0)
111
- rubocop (~> 1.44)
101
+ unicode-display_width (>= 2.4.0, < 4.0)
102
+ rubocop-ast (1.50.0)
103
+ parser (>= 3.3.7.2)
104
+ prism (~> 1.7)
105
+ rubocop-minitest (0.40.0)
106
+ lint_roller (~> 1.1)
107
+ rubocop (>= 1.75.0, < 2.0)
108
+ rubocop-ast (>= 1.38.0, < 2.0)
109
+ rubocop-performance (1.26.1)
110
+ lint_roller (~> 1.1)
111
+ rubocop (>= 1.75.0, < 2.0)
112
+ rubocop-ast (>= 1.47.1, < 2.0)
113
+ rubocop-rake (0.7.1)
114
+ lint_roller (~> 1.1)
115
+ rubocop (>= 1.72.1)
116
+ rubocop-shopify (2.18.0)
117
+ rubocop (~> 1.62)
112
118
  ruby-lsp (0.26.10)
113
119
  language_server-protocol (~> 3.17.0)
114
120
  prism (>= 1.2, < 2.0)
@@ -116,12 +122,15 @@ GEM
116
122
  ruby-progressbar (1.13.0)
117
123
  ruby2_keywords (0.0.5)
118
124
  rubyzip (2.3.2)
125
+ securerandom (0.4.1)
119
126
  stringio (3.1.0)
120
127
  thor (1.2.1)
121
128
  tsort (0.2.0)
122
129
  tzinfo (2.0.6)
123
130
  concurrent-ruby (~> 1.0)
124
- unicode-display_width (2.5.0)
131
+ unicode-display_width (3.2.0)
132
+ unicode-emoji (~> 4.1)
133
+ unicode-emoji (4.2.0)
125
134
 
126
135
  PLATFORMS
127
136
  arm64-darwin-21
@@ -131,12 +140,12 @@ PLATFORMS
131
140
  x86_64-linux
132
141
 
133
142
  DEPENDENCIES
134
- minitest (~> 5.0)
143
+ minitest (~> 5.27)
135
144
  minitest-focus
136
145
  minitest-reporters
137
146
  mocha
138
147
  pry-byebug
139
- rake (~> 13.0)
148
+ rake (~> 13.4)
140
149
  rake-compiler
141
150
  rubocop
142
151
  rubocop-minitest
data/README.md CHANGED
@@ -91,6 +91,7 @@ Here the list of the features we currently support:
91
91
  - [🕰 Localized Timezone](#--localized-timezone)
92
92
  - [➡🕰 Map Deprecated Timezone Name to Modern Name](#--map-deprecated-timezone-name-to-modern-name)
93
93
  - [👥 Names](#--names)
94
+ - [🏢 Business names](#-business-names)
94
95
  - [👥 Lists](#--lists)
95
96
  - [❣️ Punctuation](#️---punctuation)
96
97
  - [🤑 Currency support](#--currency-support)
@@ -625,6 +626,8 @@ Say, for example, your customer has the given name "Ken" and surname "Tanaka".
625
626
 
626
627
  `Names.surname_first?` will let you know if the current locale places the surname (family name, last name) before the given name ("first name", forename).
627
628
 
629
+ `Names.abbreviated` returns a short form of a personal name based on its writing script. Latin names use the first letters of the given name and surname. Han, Hiragana, and Katakana names use the surname. Hangul names use the given name when it is within `ideal_max_length`, or its first grapheme cluster when it is longer. Thai names use the first grapheme cluster of the given name. The Hangul and Thai rules fall back to the surname when the given name is blank. The method returns `nil` when it cannot abbreviate the name, allowing the caller to fall back to `Names.greeting`.
630
+
628
631
  ```ruby
629
632
  I18n.with_locale(:en) { Worldwide.names.full(given: "John", surname: "Smith") }
630
633
  => "John Smith"
@@ -636,12 +639,37 @@ I18n.with_locale(:en) { Worldwide.names.greeting(given: "John", surname: "Smith"
636
639
  => "John"
637
640
  I18n.with_locale(:ja) { Worldwide.names.greeting(given: "John", surname: "Smith") }
638
641
  => "Smith様"
642
+ Worldwide.names.abbreviated(given: "John", surname: "Smith")
643
+ => "JS"
644
+ Worldwide.names.abbreviated(given: "이슬", surname: "재현", ideal_max_length: 1)
645
+ => "이"
639
646
  Worldwide.names.surname_first?("en")
640
647
  => false
641
648
  Worldwide.names.surname_first?("ja")
642
649
  => true
643
650
  ```
644
651
 
652
+ ### 🏢 Business names
653
+
654
+ `Worldwide::BusinessNames` abbreviates a single business name according to its detected script. The default `ideal_max_length` is 3. The method returns `nil` when the input is blank, uses multiple or unsupported scripts, or cannot be abbreviated by the script's rules. It is also available through `Worldwide.business_names`.
655
+
656
+ ```ruby
657
+ Worldwide.business_names.abbreviated(name: "Shopify")
658
+ => "Sho"
659
+ Worldwide::BusinessNames.abbreviated(name: "A Better Shop")
660
+ => "ABS"
661
+ Worldwide::BusinessNames.abbreviated(name: "A Better Looking Shop")
662
+ => "AS"
663
+ Worldwide::BusinessNames.abbreviated(name: "삼성 한국어", ideal_max_length: 2)
664
+ => "삼성"
665
+ Worldwide::BusinessNames.abbreviated(name: "任天堂")
666
+ => "任天堂"
667
+ Worldwide::BusinessNames.abbreviated(name: "อภัADSยวงศ์")
668
+ => nil
669
+ ```
670
+
671
+ Latin names use the first grapheme clusters of one word, the initials of up to `ideal_max_length` words, or the initials of the first and last words when there are more words. Hangul names use the first `ideal_max_length` grapheme clusters of the first word. Thai names without spaces use the first grapheme cluster. Han, Katakana, and Hiragana names without spaces are returned in full. Thai, Han, Katakana, and Hiragana names with spaces return `nil`.
672
+
645
673
  ### 👥 Lists
646
674
 
647
675
  This module will offer you a way to display a list while respecting locale preferences.
@@ -296,7 +296,7 @@ module Worldwide
296
296
  result << line unless blank?(line.join)
297
297
  end
298
298
 
299
- result + additional_lines.map { |x| [x] }
299
+ result + additional_lines.zip
300
300
  end
301
301
 
302
302
  # Returns a hash containing the values for each address field
@@ -343,7 +343,7 @@ module Worldwide
343
343
 
344
344
  replacement ||= ""
345
345
 
346
- mapped = mapped.gsub("{#{snake_to_camel_case(key)}}", replacement)
346
+ mapped = mapped.gsub("{#{snake_to_camel_case(key)}}") { replacement }
347
347
  end
348
348
  mapped
349
349
  end
@@ -354,7 +354,7 @@ module Worldwide
354
354
  def japan_with_non_japanese_script?
355
355
  text = build_formatted_address_data.values.join
356
356
 
357
- country_code == "JP" && Worldwide::Scripts.identify(text: text).intersection(JAPANESE_SCRIPTS).empty?
357
+ country_code == "JP" && !Worldwide::Scripts.identify(text: text).intersect?(JAPANESE_SCRIPTS)
358
358
  end
359
359
 
360
360
  def normalize_city
@@ -446,13 +446,11 @@ module Worldwide
446
446
  def strip_extra_chars(lines:, excluded_fields:)
447
447
  result = []
448
448
  lines.each do |components|
449
- line = components.filter_map do |component|
450
- component unless component.empty?
451
- end
452
-
453
- line = strip_extra_japanese_chars(line: line, excluded_fields: excluded_fields)
449
+ line = strip_extra_japanese_chars(line: components, excluded_fields: excluded_fields)
450
+ line.map!(&:strip)
451
+ line.reject!(&:empty?)
454
452
 
455
- result << line.map(&:strip) unless blank?(line.join(""))
453
+ result << line unless blank?(line.join(""))
456
454
  end
457
455
  result
458
456
  end
@@ -463,7 +461,7 @@ module Worldwide
463
461
  # - `〒` ("yuubin" mark) is a prefix that's prepended to the postal code (zip)
464
462
  # If the associated field is excluded/empty, we need to suppress the associated special character.
465
463
  def strip_extra_japanese_chars(line:, excluded_fields:)
466
- return nil if line.nil?
464
+ return if line.nil?
467
465
 
468
466
  line.map do |field|
469
467
  stripped = field
@@ -9,6 +9,7 @@ module Worldwide
9
9
 
10
10
  class << self
11
11
  extend Forwardable
12
+
12
13
  def_delegators :instance, :errors, :valid?
13
14
  end
14
15
 
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Worldwide
4
+ class BusinessNames
5
+ class << self
6
+ # Returns a script aware abbreviation, or nil when the name cannot be abbreviated.
7
+ def abbreviated(name:, ideal_max_length: 3)
8
+ name_stripped = name&.strip
9
+
10
+ return if Util.blank?(name_stripped)
11
+ return unless ideal_max_length.is_a?(Integer) && ideal_max_length.positive?
12
+
13
+ scripts = Scripts.identify(text: name_stripped)
14
+ return unless scripts.one?
15
+
16
+ case scripts.first
17
+ when :Latin
18
+ latin_abbreviation(name_stripped, ideal_max_length)
19
+ when :Hangul
20
+ name_stripped.split(" ", 2).first.grapheme_clusters[0, ideal_max_length].join
21
+ when :Thai
22
+ # Thai does not use spaces between words.
23
+ return if name_stripped.include?(" ")
24
+
25
+ name_stripped.grapheme_clusters.first
26
+ when :Han, :Katakana, :Hiragana
27
+ return if name_stripped.include?(" ")
28
+
29
+ name_stripped
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def latin_abbreviation(name, ideal_max_length)
36
+ words = name.split(" ")
37
+
38
+ if words.length == 1
39
+ words.first.grapheme_clusters[0, ideal_max_length].join
40
+ elsif words.length <= ideal_max_length
41
+ words.map { |word| word.grapheme_clusters.first }.join
42
+ else
43
+ words.first.grapheme_clusters.first + words.last.grapheme_clusters.first
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -42,36 +42,36 @@ module Worldwide
42
42
  tokens
43
43
  end
44
44
 
45
- private
46
-
47
45
  PATTERN_FIELD = /([a-zA-Z])\1*/
48
46
  SINGLE_QUOTE_LITERAL = /''/
49
47
  QUOTED_LITERAL = /'(([^']|#{SINGLE_QUOTE_LITERAL})*[^'])'/
50
48
  UNQUOTED_LITERAL = /[^a-zA-Z']+/
51
49
 
50
+ private
51
+
52
52
  def parse_field(format)
53
- return nil unless format.start_with?(PATTERN_FIELD)
53
+ return unless format.start_with?(PATTERN_FIELD)
54
54
 
55
55
  match = PATTERN_FIELD.match(format)
56
56
  [Field.from(match[0]), match.end(0)]
57
57
  end
58
58
 
59
59
  def parse_single_quote(format)
60
- return nil unless format.start_with?(SINGLE_QUOTE_LITERAL)
60
+ return unless format.start_with?(SINGLE_QUOTE_LITERAL)
61
61
 
62
62
  match = SINGLE_QUOTE_LITERAL.match(format)
63
63
  [Literal.new("'"), match.end(0)]
64
64
  end
65
65
 
66
66
  def parse_quoted_literal(format)
67
- return nil unless format.start_with?(QUOTED_LITERAL)
67
+ return unless format.start_with?(QUOTED_LITERAL)
68
68
 
69
69
  match = QUOTED_LITERAL.match(format)
70
70
  [Literal.new(match[1].gsub(SINGLE_QUOTE_LITERAL, "'")), match.end(0)]
71
71
  end
72
72
 
73
73
  def parse_unquoted_literal(format)
74
- return nil unless format.start_with?(UNQUOTED_LITERAL)
74
+ return unless format.start_with?(UNQUOTED_LITERAL)
75
75
 
76
76
  match = UNQUOTED_LITERAL.match(format)
77
77
  [Literal.new(match[0]), match.end(0)]
@@ -38,7 +38,7 @@ module Worldwide
38
38
  loop do
39
39
  if cldr_defined_parents[ancestors.last]
40
40
  ancestors << cldr_defined_parents[ancestors.last]
41
- elsif I18n::Locale::Tag.tag(ancestors.last).parents.count > 0
41
+ elsif I18n::Locale::Tag.tag(ancestors.last).parents.any?
42
42
  ancestors << I18n::Locale::Tag.tag(ancestors.last).parents.first.to_sym
43
43
  else
44
44
  break
@@ -70,7 +70,7 @@ module Worldwide
70
70
  # we don't want to trigger the backend resolution until after we've determined whether they have set it explicitly.
71
71
  class_variable = "@@#{key}".to_sym
72
72
  instance_variable = "@#{key}".to_sym
73
- i18n_config.class.class_variables.include?(class_variable) || i18n_config.instance_variables.include?(instance_variable)
73
+ i18n_config.class.class_variable_defined?(class_variable) || i18n_config.instance_variables.include?(instance_variable)
74
74
  else
75
75
  i18n_config.key?(key)
76
76
  end
@@ -117,7 +117,7 @@ module Worldwide
117
117
  raw_symbol = Worldwide::Cldr.t("currencies.#{@currency_code}.narrow_symbol", default: nil, locale: locale) ||
118
118
  Worldwide::Cldr.t("currencies.#{@currency_code}.symbol", default: nil, locale: locale)
119
119
 
120
- return nil if raw_symbol.nil?
120
+ return if raw_symbol.nil?
121
121
 
122
122
  # For some locales (e.g., HK), in-market folks have requested that we leave the CLDR behaviour untouched
123
123
  exceptional_symbol = EXCEPTIONS.fetch(@currency_code.to_sym, nil)
@@ -154,7 +154,7 @@ module Worldwide
154
154
  private
155
155
 
156
156
  def combine(amount:, decimal_places:, humanize:, locale:, symbol:)
157
- space = if has_space(locale)
157
+ space = if has_space?(locale)
158
158
  # This is U+00A0, the Unicode non-breaking space character
159
159
  [160].pack("U*")
160
160
  else
@@ -237,7 +237,7 @@ module Worldwide
237
237
  end
238
238
 
239
239
  # Returns true if there should be a space between the amount and the currency symbol
240
- def has_space(locale)
240
+ def has_space?(locale)
241
241
  pattern = Worldwide::Cldr.t("numbers.latn.formats.currency.patterns.default.standard", locale: locale)
242
242
 
243
243
  # Note that CLDR uses these characters in its currency formats:
@@ -250,7 +250,7 @@ module Worldwide
250
250
  def minor_symbol
251
251
  key = @currency_code
252
252
 
253
- return nil unless Currency.minor_symbols.key?(key)
253
+ return unless Currency.minor_symbols.key?(key)
254
254
 
255
255
  Currency.minor_symbols[key]["symbol"]
256
256
  end
@@ -10,6 +10,7 @@ module Worldwide
10
10
 
11
11
  class << self
12
12
  extend Forwardable
13
+
13
14
  def_delegators :instance, :for_country
14
15
  end
15
16
 
@@ -12,6 +12,7 @@ module Worldwide
12
12
 
13
13
  class << self
14
14
  extend Forwardable
15
+
15
16
  def_delegators :instance, :field
16
17
  end
17
18
 
@@ -3,6 +3,7 @@
3
3
  module Worldwide
4
4
  module Lists
5
5
  extend self
6
+
6
7
  # We assume that the data is expected to returned in the same
7
8
  # order as it is received. We are aware of one counter example,
8
9
  # Urdu, that we do not support.
@@ -71,7 +71,7 @@ module Worldwide
71
71
  if @name_cache[context].is_a?(I18n::MissingTranslation)
72
72
  raise @name_cache[context] if throw
73
73
 
74
- return nil
74
+ return
75
75
  end
76
76
 
77
77
  @name_cache[context]
@@ -93,7 +93,7 @@ module Worldwide
93
93
  if @endonym.is_a?(I18n::MissingTranslation)
94
94
  raise @endonym if throw
95
95
 
96
- return nil
96
+ return
97
97
  end
98
98
 
99
99
  @endonym
@@ -113,7 +113,7 @@ module Worldwide
113
113
  result = lookup(language, locale: target_locale)
114
114
  if result
115
115
  transformed = Worldwide::Cldr::ContextTransforms.transform(result, :languages, context, locale: target_locale)
116
- break "#{transformed}#{i > 0 ? territories_suffix(code.to_s) : ""}"
116
+ break "#{transformed}#{territories_suffix(code.to_s) if i > 0}"
117
117
  end
118
118
  end
119
119
  end
@@ -30,6 +30,44 @@ module Worldwide
30
30
  names.map { |name| name[0] }
31
31
  end
32
32
 
33
+ def abbreviated(given:, surname:, ideal_max_length: 3)
34
+ given_stripped = given&.strip
35
+ surname_stripped = surname&.strip
36
+
37
+ combined_name = given_stripped.to_s + surname_stripped.to_s
38
+ return if combined_name.blank? || combined_name.match?(/[\p{Punctuation}\s]/)
39
+ return unless ideal_max_length.is_a?(Integer) && ideal_max_length.positive?
40
+
41
+ scripts = Scripts.identify(text: combined_name)
42
+ return unless scripts.length == 1
43
+
44
+ given_clusters = given_stripped&.grapheme_clusters
45
+ surname_clusters = surname_stripped&.grapheme_clusters
46
+
47
+ # Scripts that Scripts.identify recognizes but that have no abbreviation rule
48
+ # here (e.g. Arabic) intentionally return nil so callers fall back to a greeting.
49
+ case scripts.first
50
+ when :Latin
51
+ [given_stripped, surname_stripped].reject(&:blank?).map { |name| name[0] }.join
52
+ when :Han, :Katakana, :Hiragana
53
+ surname_stripped.presence
54
+ when :Hangul
55
+ if given_stripped.blank?
56
+ surname_stripped
57
+ elsif given_clusters.length > ideal_max_length
58
+ given_clusters[0]
59
+ else
60
+ given_stripped
61
+ end
62
+ when :Thai
63
+ if given_stripped.present?
64
+ given_clusters[0]
65
+ elsif surname_stripped.present?
66
+ surname_clusters[0]
67
+ end
68
+ end
69
+ end
70
+
33
71
  private
34
72
 
35
73
  def format_name(format, given, surname)
@@ -15,7 +15,6 @@ module Worldwide
15
15
 
16
16
  def initialize(number:, country_code: nil)
17
17
  @number = number
18
- @country_code = country_code&.to_s&.downcase&.to_sym
19
18
  @parsed_number, @extension, @country_code = parse_number(number: @number, country_code: country_code)
20
19
  end
21
20
 
@@ -375,7 +375,7 @@ module Worldwide
375
375
  # An Worldwide::Field that can be used to ask about the field, including
376
376
  # labels, error messages, and an autofill value if there is one.
377
377
  def field(key:)
378
- return nil unless country?
378
+ return unless country?
379
379
 
380
380
  Worldwide::Fields.field(country_code: iso_code, field_key: key)
381
381
  end
@@ -479,7 +479,7 @@ module Worldwide
479
479
  #
480
480
  # @return [String, nil] One of "ALPHANUMERIC", "NUMERIC", "NUMERIC_AND_PUNCTUATION", or nil if no zip_example
481
481
  def zip_type
482
- return nil if zip_example.nil?
482
+ return if zip_example.nil?
483
483
 
484
484
  if zip_example.match?(/[A-Za-z]/)
485
485
  FORMAT_TYPES[:ALPHANUMERIC]
@@ -554,7 +554,7 @@ module Worldwide
554
554
  field ? !!field["required"] : false
555
555
  end
556
556
 
557
- def answers_to_cldr_code(search_code)
557
+ def answers_to_cldr_code?(search_code)
558
558
  return false if Util.blank?(search_code) || Util.blank?(cldr_code)
559
559
  return true if search_code.casecmp(cldr_code).zero?
560
560
 
@@ -562,7 +562,7 @@ module Worldwide
562
562
  "#{pc&.cldr_code&.downcase}#{cldr_code.downcase}" == search_code.downcase
563
563
  end
564
564
 
565
- def answers_to_iso_code(search_code)
565
+ def answers_to_iso_code?(search_code)
566
566
  return true if search_code == iso_code
567
567
 
568
568
  pc = parent_country
@@ -616,9 +616,9 @@ module Worldwide
616
616
  #
617
617
  # Returns the Region in which the zip belongs based on the prefix, or `nil` if no match is found.
618
618
  def search_prefixes_by_normalized_zip(prefixes:, zip:, allow_partial_zip: false)
619
- return nil if Worldwide::Util.blank?(prefixes)
620
- return nil if Worldwide::Util.blank?(zip)
621
- return nil unless allow_partial_zip || passes_country_zip_regexp?(value: zip)
619
+ return if Worldwide::Util.blank?(prefixes)
620
+ return if Worldwide::Util.blank?(zip)
621
+ return unless allow_partial_zip || passes_country_zip_regexp?(value: zip)
622
622
 
623
623
  stripped = Zip.strip_optional_country_prefix(country_code: iso_code, zip: zip)
624
624
 
@@ -12,6 +12,7 @@ module Worldwide
12
12
 
13
13
  class << self
14
14
  extend Forwardable
15
+
15
16
  def_delegators :instance, :all, :region_by_cldr_code, :region
16
17
  end
17
18
 
@@ -225,8 +225,6 @@ module Worldwide
225
225
  spec = YAML.safe_load_file(filename)
226
226
  code = spec["code"]
227
227
 
228
- loaded_regions = []
229
-
230
228
  region = Region.new(
231
229
  alpha_three: country_codes.dig(code, "alpha3"),
232
230
  continent: false,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Worldwide
4
- VERSION = "1.25.6"
4
+ VERSION = "1.26.0"
5
5
  end
data/lib/worldwide/zip.rb CHANGED
@@ -50,7 +50,7 @@ module Worldwide
50
50
  # @param min_confidence [Integer] The minimum confidence level (between 0-100) that is accepted from a suggestion (optional)
51
51
  # @return [Region] which is a "country" if we have a suggestion, or `nil` if we do not.
52
52
  def find_country(country_code: nil, zip:, min_confidence: 0)
53
- return nil unless Util.present?(zip)
53
+ return unless Util.present?(zip)
54
54
 
55
55
  country = Worldwide.region(code: country_code) unless country_code.nil?
56
56
  return country if country&.valid_zip?(zip)
@@ -62,7 +62,7 @@ module Worldwide
62
62
  return suggestion unless suggestion.nil? || confidence.nil? || confidence < min_confidence
63
63
 
64
64
  # If our postal code is wholly numeric, we can't make an intelligent suggestion without an alleged country.
65
- return nil unless adjusted_zip.match?(/[A-Z]/)
65
+ return unless adjusted_zip.match?(/[A-Z]/)
66
66
 
67
67
  # Try a broader-ranging match without considering the alleged country
68
68
  # We'll see if we have only a single suggestion and, if so, return it.
@@ -95,7 +95,7 @@ module Worldwide
95
95
  return autofill if Util.present?(autofill)
96
96
  end
97
97
 
98
- return nil if zip.nil?
98
+ return if zip.nil?
99
99
 
100
100
  # Convert to uppercase
101
101
  # Convert numeric and romaji full-width to half-width
@@ -261,8 +261,6 @@ module Worldwide
261
261
  end
262
262
  end
263
263
 
264
- private
265
-
266
264
  # Countries that use GB-style postal code spacing, and should normalize the same way as GB
267
265
  GB_STYLE_ZIP_COUNTRIES = ["GB", "GG", "GI", "IM", "JE"]
268
266
  private_constant :GB_STYLE_ZIP_COUNTRIES
@@ -355,6 +353,8 @@ module Worldwide
355
353
  }
356
354
  private_constant :OPTIONAL_PREFIX_COUNTRIES
357
355
 
356
+ private
357
+
358
358
  REQUIRED_PREFIX_COUNTRIES = {
359
359
  VG: "VG",
360
360
  }
data/lib/worldwide.rb CHANGED
@@ -14,6 +14,7 @@ require "worldwide/extant_outcodes"
14
14
 
15
15
  require "worldwide/address"
16
16
  require "worldwide/address_validator"
17
+ require "worldwide/business_names"
17
18
  require "worldwide/calendar"
18
19
  require "worldwide/cldr"
19
20
  require "worldwide/cldr/context_transforms"
@@ -54,6 +55,10 @@ module Worldwide
54
55
  Address.new(**kwargs)
55
56
  end
56
57
 
58
+ def business_names
59
+ BusinessNames
60
+ end
61
+
57
62
  def currency(code:)
58
63
  currency_code = code.to_s.upcase.rjust(3, "0")
59
64
  @currencies_cache[currency_code] ||= Currency.new(code: currency_code)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: worldwide
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.25.6
4
+ version: 1.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -8677,6 +8677,7 @@ files:
8677
8677
  - lib/worldwide.rb
8678
8678
  - lib/worldwide/address.rb
8679
8679
  - lib/worldwide/address_validator.rb
8680
+ - lib/worldwide/business_names.rb
8680
8681
  - lib/worldwide/calendar.rb
8681
8682
  - lib/worldwide/calendar/gregorian.rb
8682
8683
  - lib/worldwide/cldr.rb
@@ -8735,7 +8736,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
8735
8736
  - !ruby/object:Gem::Version
8736
8737
  version: '0'
8737
8738
  requirements: []
8738
- rubygems_version: 4.0.14
8739
+ rubygems_version: 4.0.16
8739
8740
  specification_version: 4
8740
8741
  summary: Internationalization and localization APIs
8741
8742
  test_files: []