delocalize 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 00c6fb17341720d91984d417a166c15085fd39fa
4
+ data.tar.gz: b9c1d5688872a8692ab3fc2c82b8283ea434d4d7
5
+ SHA512:
6
+ metadata.gz: 71bdd8bf81b15f4cb496dc7d266ea237a51ab5e18e657f0869bbdfed27363c69f13ddf96b2d88041a18ae0c4f095d6f6c9fbd1a3341115115eca4ee5eef4c962
7
+ data.tar.gz: e9032a044a45f7456a19d6f80a262d1448e058265c45cca990bf051eaa639a7ded115fe42f89c11f8b4f35c4f170158edef024f624595af3c39a47d80ecc01bf
@@ -4,17 +4,24 @@
4
4
 
5
5
  delocalize provides localized date/time and number parsing functionality for Rails.
6
6
 
7
- ## 1.0 Beta
7
+ ## Demo Application
8
8
 
9
- Check out the [1-0-beta branch](https://github.com/clemens/delocalize/tree/1-0-beta) for an unfinished/unreleased improved version of delocalize. The goal is to get rid of all the nasty hacks and have a library that doesn't break with every new release of Rails.
9
+ Find a demo application [here](https://github.com/clemens/delocalize_demo).
10
10
 
11
- Also check out the [ongoing discussion the wiki](https://github.com/clemens/delocalize/wiki/delocalize-1.0) and feel free to put in your opinion there.
11
+ ## Compability
12
+
13
+ This gem requires the following versions:
14
+
15
+ * Ruby >= 1.9.2
16
+ * Rails >= 3.0 (Rails 2 and probably even Rails 1 *should* work but aren't officially supported)
17
+
18
+ Check [the Travis configuration](https://github.com/clemens/delocalize/blob/1-0-beta/.travis.yml) in order to see which configurations we are testing.
12
19
 
13
20
  ## Installation
14
21
 
15
- You can use delocalize as a gem (preferred). Using delocalize as a Rails plugin has been discontinued and is no supported. If you want/need to use delocalize as a gem (I really don't see a reason why you'd want to), consider using the `0-2-stable` branch.
22
+ You can use delocalize as a gem. Using delocalize as a Rails plugin has been discontinued and is no supported. If you want/need to use delocalize as a plugin (I really don't see a reason why you'd want to), consider using the `0-2-stable` branch.
16
23
 
17
- ### Rails 3 and Rails 4
24
+ ### Rails 3
18
25
 
19
26
  To use delocalize, put the following gem requirement in your `Gemfile`:
20
27
 
@@ -24,7 +31,7 @@ gem "delocalize"
24
31
 
25
32
  ### Rails 2
26
33
 
27
- Note: Support for Rails 2 has been discontinued. This version is only considered stable for Rails >= 3. If you need Rails 2 support, please use the `0.2.x` versions or the `0-2-stable` branch respectively.
34
+ Note: Official support for Rails 2 has been discontinued. However, due to the way this gem has been rewritten for its 1.0.0 release, it *should* work with Rails 2 just fine. If you run into any problems, consider filing an issue.
28
35
 
29
36
  To use delocalize, put the following gem requirement in your `environment.rb`:
30
37
 
@@ -46,7 +53,72 @@ def price=(price)
46
53
  end
47
54
  ```
48
55
 
49
- delocalize does this under the covers -- all you need is your regular translation data (as YAML or Ruby file) where you need Rails' standard translations:
56
+ You also had to take care of proper formatting in forms on the frontend so people would see localized values in their forms.
57
+
58
+ Delocalize does most of this under the covers. All you need is a simple setup in your controllers and your regular translation data (as YAML or Ruby file) where you need Rails' standard translations.
59
+
60
+ ### Controller setup
61
+
62
+ The approach used in delocalize is based on Rails' own `strong_parameters`. In fact, if you are on Rails 3 with the `strong_parameters` gem installed or Rails 4 (which includes it by default), delocalize is mixed straight into the provided `ActionController::Parameters` class. Otherwise it uses its own similar class (`Delocalize::Parameters`).
63
+
64
+ You can then use delocalize as you would use strong_parameters:
65
+
66
+ ``` ruby
67
+ class ProductsController < ApplicationController
68
+ def create
69
+ Product.create(product_params)
70
+ end
71
+
72
+ private
73
+
74
+ def product_params
75
+ delocalize_config = { :released_on => :date, :available_until => :time, :price => :number }
76
+ # with strong_parameters
77
+ params.require(:product).permit(*delocalize_config.keys).delocalize(delocalize_config)
78
+ # without strong_parameters
79
+ params.delocalize(:product => delocalize_config)[:product]
80
+ # or
81
+ params[:product].delocalize(delocalize_config)
82
+ end
83
+
84
+ end
85
+ ```
86
+
87
+ If you want to delocalize only certain parameters, configure those parameters and leave the others out – they will be kept as they are.
88
+
89
+ ### Views
90
+
91
+ Delocalize doesn't automatically localize your data again (yet). There are various reasons for that but the main reasons are:
92
+
93
+ - It's hard to do this properly with some amount of flexibility for you as the user of the gem without crazy hacks of Rails internals (a problem that delocalize previously suffered from).
94
+ - Personally I feel that presentation logic (including forms) should be split out into separate objects (presenters, decorators, form objects and the like).
95
+
96
+ I might change my mind but as it stands but for the time being the gist is: Wherever you want to see localized values, you have to localize them yourself.
97
+
98
+ Examples:
99
+
100
+ ``` ruby
101
+ text_field :product, :released_on, :value => product.released_on ? l(product.released_on) : nil
102
+ text_field_tag 'product[price]', number_with_precision(product.price, :precision => 2)
103
+ ```
104
+
105
+ You can of course use something like the [Draper gem](https://github.com/drapergem/draper) or the great [Reform gem](https://github.com/apotonick/reform) to wrap your actual object and override the relevant accessors.
106
+
107
+ Check out how this can be done in the [demo app](https://github.com/clemens/delocalize_demo).
108
+
109
+ There's also a wiki page on [how to write a custom input for SimpleForm](https://github.com/clemens/delocalize/wiki/Using-with-simple-form).
110
+
111
+ ### Locale setup
112
+
113
+ In addition to your controller setup, you also need to configure your locale file(s). If you intend to use delocalize, you probably have a working locale file anyways. In this case, you only need to add two extra keys: `date.input.formats` and `time.input.formats`.
114
+
115
+ Assuming you want to use all of delocalize's parsers (date, time, number), the required keys are:
116
+ * number.format.delimiter
117
+ * number.format.separator
118
+ * date.input.formats
119
+ * time.input.formats
120
+ * date.formats.SOME_FORMAT for all formats specified in date.input.formats
121
+ * time.formats.SOME_FORMAT for all formats specified in time.input.formats
50
122
 
51
123
  ```yml
52
124
  de:
@@ -85,164 +157,12 @@ de:
85
157
 
86
158
  For dates and times, you have to define input formats which are taken from the actual formats. The important thing here is to define input formats sorted by descending complexity; in other words: the format which contains the most (preferably non-numeric) information should be first in the list because it can produce the most reliable match. Exception: If you think there most complex format is not the one that most users will input, you can put the most-used in front so you save unnecessary iterations.
87
159
 
88
- Careful with formats containing only numbers: It's very hard to produce reliable matches if you provide multiple strictly numeric formats!
89
-
90
- delocalize then overrides `to_input_field_tag` in ActionView's `InstanceTag` so you can use localized text fields:
91
-
92
- ```erb
93
- <% form_for @product do |f| %>
94
- <%= f.text_field :name %>
95
- <%= f.text_field :released_on %>
96
- <%= f.text_field :price %>
97
- <% end %>
98
- ```
99
-
100
- In this example, a user can enter the release date and the price just like he's used to in his language, for example:
101
-
102
- > Name: "Couch"
103
- > Released on: "12. Oktober 2009"
104
- > Price: "2.999,90"
105
-
106
- When saved, ActiveRecord automatically converts these to a regular Ruby date and number.
107
-
108
- Edit forms then also show localized dates/numbers. By default, dates and times are localized using the format named :default in your locale file. So with the above locale file, dates would use `%d.%m.%Y` and times would use `%A, %e. %B %Y, %H:%M Uhr`. Numbers are also formatted using your locale's thousands delimiter and decimal separator.
109
-
110
- You can also customize the output using some options:
111
-
112
- The price should always show two decimal digits and we don't need the delimiter:
113
-
114
- ```erb
115
- <%= f.text_field :price, :precision => 2, :delimiter => '' %>
116
- ```
117
-
118
- The `released_on` date should be shown in the `:full` format:
119
-
120
- ```erb
121
- <%= f.text_field :released_on, :format => :full %>
122
- ```
123
-
124
- Since `I18n.localize` supports localizing `strftime` strings, we can also do this:
125
-
126
- ```erb
127
- <%= f.text_field :released_on, :format => "%B %Y" %>
128
- ```
129
-
130
- ## Ruby 1.9 + Psych YAML Parser
131
-
132
- You will need to adjust the localization formatting when using the new YAML parser Psych. Below is an example error message you may receive in your logs as well as an example of acceptable formatting and helpful links for reference:
133
-
134
- __Error message from logs:__
135
-
136
- Psych::SyntaxError (couldn't parse YAML at line x column y):
137
-
138
- __References:__
139
-
140
- The solution can be found here: http://stackoverflow.com/questions/4980877/rails-error-couldnt-parse-yaml#answer-5323060
141
-
142
- http://pivotallabs.com/users/mkocher/blog/articles/1692-yaml-psych-and-ruby-1-9-2-p180-here-there-be-dragons
143
-
144
- __Psych Preferred Formatting:__
145
-
146
- ```yml
147
- en:
148
- number:
149
- format:
150
- separator: '.'
151
- delimiter: ','
152
- precision: 2
153
- date:
154
- input:
155
- formats:
156
- - :default
157
- - :long
158
- - :short
159
- formats:
160
- default: "%m/%d/%Y"
161
- short: "%b %e"
162
- long: "%B %e, %Y"
163
- only_day: "%e"
164
- day_names:
165
- - Sunday
166
- - Monday
167
- - Tuesday
168
- - Wednesday
169
- - Thursday
170
- - Friday
171
- - Saturday
172
- abbr_day_names:
173
- - Sun
174
- - Mon
175
- - Tue
176
- - Wed
177
- - Thur
178
- - Fri
179
- - Sat
180
- month_names:
181
- - ~
182
- - January
183
- - February
184
- - March
185
- - April
186
- - May
187
- - June
188
- - July
189
- - August
190
- - September
191
- - October
192
- - November
193
- - December
194
- abbr_month_names:
195
- - ~
196
- - Jan
197
- - Feb
198
- - Mar
199
- - Apr
200
- - May
201
- - Jun
202
- - Jul
203
- - Aug
204
- - Sep
205
- - Oct
206
- - Nov
207
- - Dec
208
- order:
209
- - :month
210
- - :day
211
- - :year
212
- time:
213
- input:
214
- formats:
215
- - :default
216
- - :long
217
- - :short
218
- - :time
219
- formats:
220
- default: "%m/%d/%Y %I:%M%p"
221
- short: "%B %e %I:%M %p"
222
- long: "%A, %B %e, %Y %I:%M%p"
223
- time: "%l:%M%p"
224
- am: "am"
225
- pm: "pm"
226
- ```
227
-
228
- ## Compatibility
229
-
230
- * Tested with Rails 2.3.5 in Ruby 1.8.7, Ruby 1.9.1 and Ruby 1.9.2 (head)
231
- * Tested with Rails 3 in Ruby 1.9.3, Ruby 2.0 and Ruby 2.1 (head)
232
- * Tested with Rails 4 in Ruby 1.9.3, Ruby 2.0 and Ruby 2.1 (head)
233
-
234
- ## Contributors
235
-
236
- Thanks to [all the people who contributed](https://github.com/clemens/delocalize/graphs/contributors) and submitted issues.
160
+ **Be careful with formats containing only numbers: It's very hard to produce reliable matches if you provide multiple strictly numeric formats!**
237
161
 
238
- ## TODO
162
+ ### Contributors and Copyright
239
163
 
240
- * Improve test coverage
241
- * Separate Ruby/Rails stuff to make it usable outside Rails
242
- * Decide on other ActionView hacks (e.g. `text_field_tag`)
243
- * Implement AM/PM support
244
- * Cleanup, cleanup, cleanup ...
164
+ [Here](https://github.com/clemens/delocalize/graphs/contributors) is a list of all people who ever contributed to delocalize.
245
165
 
246
- Copyright (c) 2009-2014 Clemens Kofler <clemens@railway.at>
166
+ Copyright (c) 2009-2015 Clemens Kofler <clemens@railway.at>
247
167
  <http://www.railway.at/>
248
168
  Released under the MIT license
@@ -1,9 +1,17 @@
1
- require 'delocalize/ruby_ext'
2
- require 'delocalize/i18n_ext'
3
- require 'delocalize/localized_date_time_parser'
4
-
5
1
  if defined?(Rails::Railtie)
6
2
  require 'delocalize/railtie'
7
3
  elsif defined?(Rails::Initializer)
8
4
  raise "This version of delocalize is only compatible with Rails 3.0 or newer"
9
5
  end
6
+
7
+ module Delocalize
8
+ class ParserNotFound < ArgumentError; end
9
+
10
+ autoload :Parsers, 'delocalize/parsers'
11
+
12
+ autoload :Parameters, 'delocalize/parameters'
13
+ autoload :ParameterDelocalizing, 'delocalize/parameter_delocalizing'
14
+
15
+ autoload :NumberParser, 'delocalize/number_parser'
16
+ autoload :DateTimeParser, 'delocalize/date_time_parser'
17
+ end
@@ -0,0 +1,15 @@
1
+ # blatantly copied from strong_parameters's ActionController::Parameters :)
2
+
3
+ module Delocalize
4
+ module DelocalizableParameters
5
+ extend ActiveSupport::Concern
6
+
7
+ def params
8
+ @_params ||= Parameters.new(request.parameters)
9
+ end
10
+
11
+ def params=(val)
12
+ @_params = val.is_a?(Hash) ? Parameters.new(val) : val
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,70 @@
1
+ module Delocalize
2
+ module ParameterDelocalizing
3
+ def delocalize(options)
4
+ delocalize_hash(self, options)
5
+ end
6
+
7
+ private
8
+
9
+ def delocalize_hash(hash, options, base_key_stack = [])
10
+ hash.each do |key, value|
11
+ key_stack = [*base_key_stack, key] # don't modify original key stack!
12
+
13
+ hash[key] = case value
14
+ when Hash
15
+ delocalize_hash(value, options, key_stack)
16
+ when Array
17
+ key_stack += [:[]] # pseudo-key to denote arrays
18
+ value.map { |item| delocalize_parse(options, key_stack, item) }
19
+ else
20
+ delocalize_parse(options, key_stack, value)
21
+ end
22
+ end
23
+ end
24
+
25
+ def delocalize_parse(options, key_stack, value)
26
+ parser = delocalize_parser_for(options, key_stack)
27
+ parser ? parser.parse(value) : value
28
+ end
29
+
30
+ def delocalize_parser_for(options, key_stack)
31
+ parser_type = key_stack.reduce(options) do |h, key|
32
+ case h
33
+ when Hash
34
+ h = h.stringify_keys
35
+ key = key.to_s
36
+ if key =~ /\A-?\d+\z/ && !h.key?(key)
37
+ h
38
+ else
39
+ h[key]
40
+ end
41
+ when Array
42
+ break unless key == :[]
43
+ h.first
44
+ else
45
+ break
46
+ end
47
+ end
48
+
49
+ return unless parser_type
50
+
51
+ parser_name = "delocalize_#{parser_type}_parser"
52
+ respond_to?(parser_name, true) ?
53
+ send(parser_name) :
54
+ raise(Delocalize::ParserNotFound.new("Unknown parser: #{parser_type}"))
55
+ end
56
+
57
+ def delocalize_number_parser
58
+ @delocalize_number_parser ||= Parsers::Number.new
59
+ end
60
+
61
+ def delocalize_time_parser
62
+ @delocalize_time_parser ||= Parsers::DateTime.new(Time)
63
+ end
64
+
65
+ def delocalize_date_parser
66
+ @delocalize_date_parser ||= Parsers::DateTime.new(Date)
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,25 @@
1
+ # blatantly copied from strong_parameters's ActionController::Parameters :)
2
+
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+
5
+ module Delocalize
6
+ class Parameters < ActiveSupport::HashWithIndifferentAccess
7
+ include ParameterDelocalizing
8
+
9
+ def [](key)
10
+ convert_hashes_to_parameters(key, super)
11
+ end
12
+
13
+ private
14
+
15
+ def convert_hashes_to_parameters(key, value)
16
+ if value.is_a?(self.class) || !value.is_a?(Hash)
17
+ value
18
+ else
19
+ # convert on first access
20
+ self[key] = self.class.new(value)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ module Delocalize
2
+ module Parsers
3
+ autoload :Number, 'delocalize/parsers/number'
4
+ autoload :DateTime, 'delocalize/parsers/date_time'
5
+ end
6
+ end
@@ -1,26 +1,34 @@
1
+ require 'date'
2
+
1
3
  # TODO:
2
4
  # * AM/PM calculation
3
5
  # * proper documentation (comments)
4
6
  module Delocalize
5
- class LocalizedDateTimeParser
6
- # extend/change this according to your needs by merging your custom regexps
7
- REGEXPS = {
8
- '%B' => "(#{Date::MONTHNAMES.compact.join('|')})", # long month name
9
- '%b' => "(#{Date::ABBR_MONTHNAMES.compact.join('|')})", # short month name
10
- '%m' => "(\\d{2})", # numeric month
11
- '%A' => "(#{Date::DAYNAMES.join('|')})", # full day name
12
- '%a' => "(#{Date::ABBR_DAYNAMES.join('|')})", # short day name
13
- '%Y' => "(\\d{4})", # long year
14
- '%y' => "(\\d{2})", # short year
15
- '%e' => "(\\s\\d|\\d{2})", # short day
16
- '%d' => "(\\d{2})", # full day
17
- '%H' => "(\\d{2})", # hour (24)
18
- '%M' => "(\\d{2})", # minute
19
- '%S' => "(\\d{2})" # second
20
- }
7
+ module Parsers
8
+ class DateTime
9
+ # extend/change this according to your needs by merging your custom regexps
10
+ REGEXPS = {
11
+ '%B' => "(#{Date::MONTHNAMES.compact.join('|')})", # long month name
12
+ '%b' => "(#{Date::ABBR_MONTHNAMES.compact.join('|')})", # short month name
13
+ '%m' => "(\\d{2})", # numeric month
14
+ '%A' => "(#{Date::DAYNAMES.join('|')})", # full day name
15
+ '%a' => "(#{Date::ABBR_DAYNAMES.join('|')})", # short day name
16
+ '%Y' => "(\\d{4})", # long year
17
+ '%y' => "(\\d{2})", # short year
18
+ '%e' => "(\\s\\d|\\d{2})", # short day
19
+ '%d' => "(\\d{2})", # full day
20
+ '%H' => "(\\d{2})", # hour (24)
21
+ '%M' => "(\\d{2})", # minute
22
+ '%S' => "(\\d{2})" # second
23
+ }
24
+
25
+ attr_reader :type
21
26
 
22
- class << self
23
- def parse(datetime, type)
27
+ def initialize(type)
28
+ @type = type
29
+ end
30
+
31
+ def parse(datetime)
24
32
  return unless datetime
25
33
  return datetime if datetime.respond_to?(:strftime) # already a Date/Time object -> no need to parse it
26
34
 
@@ -28,7 +36,7 @@ module Delocalize
28
36
  input_formats(type).each do |original_format|
29
37
  next unless datetime =~ /^#{apply_regex(original_format)}$/
30
38
 
31
- datetime = DateTime.strptime(datetime, original_format)
39
+ datetime = ::DateTime.strptime(datetime, original_format)
32
40
  return Date == type ?
33
41
  datetime.to_date :
34
42
  Time.zone.local(datetime.year, datetime.mon, datetime.mday, datetime.hour, datetime.min, datetime.sec)
@@ -36,20 +44,20 @@ module Delocalize
36
44
  default_parse(datetime, type)
37
45
  end
38
46
 
39
- private
47
+ private
48
+
40
49
  def default_parse(datetime, type)
41
50
  return if datetime.blank?
42
- begin
43
- today = Date.current
44
- parsed = Date._parse(datetime)
45
- return if parsed.empty? # the datetime value is invalid
46
- # set default year, month and day if not found
47
- parsed.reverse_merge!(:year => today.year, :mon => today.mon, :mday => today.mday)
48
- datetime = Time.zone.local(*parsed.values_at(:year, :mon, :mday, :hour, :min, :sec))
49
- Date == type ? datetime.to_date : datetime
50
- rescue
51
- datetime
52
- end
51
+
52
+ today = Date.current
53
+ parsed = Date._parse(datetime)
54
+ return if parsed.empty? # the datetime value is invalid
55
+
56
+ # set default year, month and day if not found
57
+ parsed.reverse_merge!(:year => today.year, :mon => today.mon, :mday => today.mday)
58
+ datetime = Time.zone.local(*parsed.values_at(:year, :mon, :mday, :hour, :min, :sec))
59
+
60
+ Date == type ? datetime.to_date : datetime
53
61
  end
54
62
 
55
63
  def translate_month_and_day_names(datetime)