doubledrones-i18n_routing 0.3.6.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,55 @@
1
+ 0.3.6 (June 13, 2010)
2
+
3
+ * Works even if translations are the same as the translated name
4
+
5
+ 0.3.5 (June 9, 2010)
6
+
7
+ * Clean up some .to_sym in order to avoid unnecessary symbols during runtime
8
+ * Works with both I18n.locale as string or as symbol
9
+ * Try to fix a Pathname instead of string issue
10
+
11
+ 0.3.4 (June 3, 2010)
12
+
13
+ * Fix Rails3 issue with nested single resource again and pluralize controller name as it should be
14
+
15
+ 0.3.3 (June 2, 2010)
16
+
17
+ * Fix Rails3 issue with nested single resource
18
+
19
+ 0.3.2 (June 2, 2010)
20
+
21
+ * Add automatically config/locales/*.yml in the i18n load path in order to fix Rails3 issue
22
+
23
+ 0.3.1 (May 27, 2010)
24
+
25
+ * Add a small Rails3 verification
26
+
27
+ 0.3.0 (May 27, 2010)
28
+
29
+ * Add support for translating path_names, member and collection
30
+ * Fix Rails3 nested resources
31
+ * All specs are now passing under both Rails 2.3.5 and Rails 3
32
+
33
+ 0.2.4 (Apr 21, 2010)
34
+
35
+ * Fix latest Rails3 edge compatibility (previous rails3 beta version not supported any more)
36
+
37
+ 0.2.3 (Apr 21, 2010)
38
+
39
+ * Works on ruby 1.9.2-head
40
+
41
+ 0.2.2 (Apr 21, 2010)
42
+
43
+ * Add I18n gem dependency
44
+
45
+ 0.2.1 (Apr 09, 2010)
46
+
47
+ * Minor Rails3 modification, added gemspec and pushed gem on gemcutter
48
+
49
+ 0.2.0 (Apr 07, 2010)
50
+
51
+ * All specs pass for Rails2, including nested deep resources
52
+
53
+ 0.1.0 (Mar 22, 2010)
54
+
55
+ * Initial release
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Guillaume Luccisano - g-mai|: guillaume.luccisano
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,28 @@
1
+ = I18nRouting
2
+
3
+ I18n_routing is a plugin for Ruby on Rails that lets you easily translate your routes trough the I18n api included in Rails since version 2.2
4
+
5
+ All necessary informations are available on the wiki: http://wiki.github.com/kwi/i18n_routing
6
+
7
+ For any question, use the i18_routing google group: http://groups.google.com/group/i18n-routing
8
+
9
+ Works with Rails 2.3 and Rails3 beta4 !
10
+
11
+ == TODO for next releases (written the 9th of June)
12
+
13
+ * Handle multiple translations for same resources name (Example: nested and not nested resources)
14
+ * Handle namespace translation
15
+
16
+ == I18n gem version warning
17
+
18
+ Be careful when running Rails 2.3 with the shipped i18n gem: this is an old i18n version and i18_routing will install the latest i18n version as a gem.
19
+ This latest version may be incompatible with some old usages.
20
+ Furthermore, if the i18n gem is present on your system, Rails will load it and skip the shipped version; Keep that in mind.
21
+
22
+ == Contributors
23
+
24
+ * kwi (Guillaume Luccisano)
25
+ * bigtiger (Jim Remsik)
26
+ * mixr (Reiner Dieterich)
27
+
28
+ Copyright (c) 2010 Guillaume Luccisano - g-mai|: guillaume.luccisano, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'spec/rake/spectask'
4
+
5
+ spec_files = Rake::FileList["spec/**/*_spec.rb"]
6
+
7
+
8
+ desc "Run specs for current Rails version"
9
+ Spec::Rake::SpecTask.new do |t|
10
+ t.spec_files = spec_files
11
+ t.spec_opts = lambda {
12
+ @rails_spec_version ? ["-c --format specdoc -- rails_spec_version=#{@rails_spec_version}"] : ["-c --format specdoc"]
13
+ }
14
+ end
15
+
16
+ task :default => :spec
17
+
18
+ desc "Run Rails 2.x specs"
19
+ task :rails2_spec do
20
+ @rails_spec_version = 2
21
+ Rake::Task['spec'].invoke
22
+ end
23
+
24
+ desc "Run Rails 3.x specs"
25
+ task :rails3_spec do
26
+ @rails_spec_version = 3
27
+ Rake::Task['spec'].invoke
28
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # encoding: utf-8
2
+ require 'i18n_routing'
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+ require 'i18n_routing_common'
3
+
4
+ if Rails.version < '3'
5
+ require 'i18n_routing_rails2'
6
+ else
7
+ require 'i18n_routing_rails3'
8
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ # I18nRouting module for common usage methods
4
+ module I18nRouting
5
+ # Return the correct translation for given values
6
+ def self.translation_for(name, type = :resources, option = nil)
7
+
8
+ # First, if an option is given, try to get the translation in the routes scope
9
+ if option
10
+ default = "{option}Noi18nRoutingTranslation"
11
+ t = I18n.t(option, :scope => "routes.#{name}.#{type}", :default => default)
12
+ return (t == default ? nil : t)
13
+ else
14
+ default = "{name}Noi18nRoutingTranslation"
15
+
16
+ # Try to get the translation in routes namescope first
17
+ t = I18n.t(:as, :scope => "routes.#{name}", :default => default)
18
+
19
+ return t if t and t != default
20
+
21
+ t = I18n.t(name.to_s, :scope => type, :default => default)
22
+ return (t == default ? nil : t)
23
+ end
24
+ end
25
+
26
+ DefaultPathNames = [:new, :edit]
27
+ PathNamesKeys = [:path_names, :member, :collection]
28
+
29
+ # Return path names hash for given resource
30
+ def self.path_names(name, options)
31
+ h = (options[:path_names] || {}).dup
32
+
33
+ path_names = DefaultPathNames
34
+ PathNamesKeys.each do |v|
35
+ path_names += options[v].keys if options[v] and Hash === options[v]
36
+ end
37
+
38
+ path_names.each do |pn|
39
+ n = translation_for(name, :path_names, pn)
40
+ n = nil if n == pn.to_s
41
+ # Get default path_names in path_names scope if no path_names found
42
+ n ||= I18n.t(pn, :scope => :path_names, :default => name.to_s)
43
+
44
+ h[pn] = n if n and n != name.to_s
45
+ end
46
+
47
+ return h
48
+ end
49
+
50
+ end
@@ -0,0 +1,239 @@
1
+ # encoding: utf-8
2
+
3
+ #
4
+ # WARNING : Old and dirty Rails 2.x code
5
+ # Need clean up and intensive refactoring
6
+ #
7
+
8
+ module ActionController
9
+ module Routing
10
+ class Route #:nodoc:
11
+
12
+ alias_method :mkd_initialize, :initialize
13
+ def initialize(segments = [], requirements = {}, conditions = {})
14
+ @glang = requirements.delete(:glang)
15
+ mkd_initialize(segments, requirements, conditions)
16
+ end
17
+
18
+ private
19
+ alias_method :mkd_generation_requirements, :generation_requirements
20
+ # Add locale requirements to ensure good route is used when using url_for
21
+ def generation_requirements
22
+ r = mkd_generation_requirements
23
+ if @glang and !r.blank?
24
+ r << " and I18n.locale.to_sym == :#{@glang}"
25
+ end
26
+
27
+ return r
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ module ActionController
34
+ module Routing
35
+ class RouteSet #:nodoc:
36
+
37
+ attr :locales, true
38
+ attr :i18n_verbose, true
39
+
40
+ class Mapper
41
+ def localized(locales = I18n.available_locales, opts = {})
42
+ old_value = @set.locales
43
+ @set.locales = locales
44
+ @set.i18n_verbose ||= opts.delete(:verbose)
45
+ yield
46
+ ensure
47
+ @set.locales = old_value
48
+ end
49
+
50
+ end
51
+
52
+ class NamedRouteCollection #:nodoc:
53
+
54
+ alias_method :mkd_define_url_helper, :define_url_helper
55
+ def define_url_helper(route, name, kind, options)
56
+ gl = Thread.current[:globalized]
57
+
58
+ mkd_define_url_helper(route, name, kind, options)
59
+
60
+ if gl
61
+ selector = url_helper_name(name, kind)
62
+
63
+ rlang = if i = name.to_s.rindex("_#{gl}")
64
+ "#{selector.to_s[0, i]}_glang_#{gl}#{selector.to_s[i + "_#{gl}".size, selector.to_s.size]}"
65
+ elsif (gls = Thread.current[:globalized_s]) and i = name.to_s.rindex("_#{gls}")
66
+ "#{selector.to_s[0, i]}_glang_#{gls}#{selector.to_s[i + "_#{gls}".size, selector.to_s.size]}"
67
+ else
68
+ "glang_#{selector}"
69
+ end
70
+
71
+ # surcharge de l'helper
72
+ @module.module_eval <<-end_eval # We use module_eval to avoid leaks
73
+ alias_method :gl#{selector}, :#{selector}
74
+
75
+ def #{selector}(*args)
76
+ selector_g = '#{rlang}'.gsub('glang', I18n.locale.to_s).to_sym
77
+
78
+ #logger.debug "Call routes : #{selector} => \#{selector_g} (#{rlang}) "
79
+ #puts "Call routes : #{selector} => \#{selector_g} (#{rlang}) Found:\#{respond_to? selector_g and selector_g != :#{selector}}"
80
+ if respond_to? selector_g and selector_g != :#{selector}
81
+ send(selector_g, *args)
82
+ else
83
+ gl#{selector}(*args)
84
+ end
85
+ end
86
+ protected :gl#{selector}
87
+ end_eval
88
+
89
+ end
90
+ end
91
+ end
92
+
93
+ alias_method :gl_add_named_route, :add_named_route
94
+ def add_named_route(name, path, options = {}) #:nodoc:
95
+ if @locales and !path.blank? and !Thread.current[:i18n_no_named_localization]
96
+ # Here, try to translate standard named routes
97
+ name = name.to_s
98
+
99
+ @locales.each do |l|
100
+ I18n.locale = l
101
+ nt = "#{l}_#{name}"
102
+ if nt != name and (t = I18nRouting.translation_for(path, :named_routes_path))
103
+ gl_add_named_route(nt, t, options.merge(:glang => l))
104
+ puts("[I18n] > localize %-10s: %40s (%s) => %s" % ['route', name, l, t]) if @i18n_verbose
105
+ end
106
+ end
107
+
108
+ old_v = Thread.current[:globalized]
109
+ Thread.current[:globalized] = true
110
+ gl_add_named_route(name, path, options)
111
+ Thread.current[:globalized] = old_v
112
+ return
113
+ end
114
+
115
+ gl_add_named_route(name, path, options)
116
+ end
117
+
118
+ end
119
+ end
120
+ end
121
+
122
+ module ActionController
123
+ module Resources
124
+ class Resource
125
+ alias_method :mkd_initialize, :initialize
126
+ def initialize(entities, options)
127
+ @real_path = options.delete(:real_path)
128
+ @real_path = @real_path.to_s.singularize if @real_path
129
+
130
+ mkd_initialize(entities, options)
131
+ end
132
+
133
+ def nesting_name_prefix
134
+ @real_path ? "#{shallow_name_prefix}#{@real_path}_" : "#{shallow_name_prefix}#{singular}_"
135
+ end
136
+
137
+ def nesting_path_prefix
138
+ @nesting_path_prefix ||= (@real_path ? "#{shallow_path_prefix}/#{path_segment}/:#{@real_path}_id" : "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id")
139
+ end
140
+ end
141
+
142
+ def switch_globalized_state(state)
143
+ old_g = Thread.current[:globalized]
144
+ Thread.current[:globalized] = state
145
+ yield
146
+ Thread.current[:globalized] = old_g
147
+ end
148
+
149
+ def switch_no_named_localization(state)
150
+ old_g = Thread.current[:i18n_no_named_localization]
151
+ Thread.current[:i18n_no_named_localization] = state
152
+ yield
153
+ Thread.current[:i18n_no_named_localization] = old_g
154
+ end
155
+
156
+ def create_globalized_resources(type, namespace, *entities, &block)
157
+
158
+ Thread.current[:i18n_nested_deep] ||= 0
159
+ Thread.current[:i18n_nested_deep] += 1
160
+
161
+ if @set.locales
162
+ name = entities.dup.shift.to_s
163
+
164
+ options = entities.extract_options!
165
+ opts = options.dup
166
+
167
+ locales = @set.locales
168
+ localized(nil) do
169
+ locales.each do |l|
170
+ I18n.locale = l
171
+ nt = "#{l}_#{name}"
172
+ if nt != name and (t = I18nRouting.translation_for(name, namespace))
173
+ nt = "#{l}_#{name}"
174
+ opts[:as] = t
175
+ opts[:glang] = l
176
+ opts[:controller] ||= name.to_s.pluralize
177
+ opts[:real_path] = opts[:singular] || name
178
+ opts[:path_names] = I18nRouting.path_names(name, options)
179
+ path_prefix_t = I18n.t(:path_prefix, :scope => :"routes.#{name}", :default => "NoPathPrefixTranslation")
180
+ opts[:path_prefix] = path_prefix_t unless path_prefix_t == "NoPathPrefixTranslation"
181
+
182
+ localized([l]) do
183
+ switch_no_named_localization(true) do
184
+ send(type, nt.to_sym, opts, &block)
185
+ end
186
+ end
187
+ puts("[I18n] > localize %-10s: %40s (%s) => %s" % [namespace, nt, l, t]) if @set.i18n_verbose
188
+ end
189
+ end
190
+
191
+ if Thread.current[:i18n_nested_deep] < 2
192
+ switch_no_named_localization(nil) do
193
+ switch_globalized_state(true) do
194
+ send(type, *(entities << options), &block)
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ else
201
+ send(type, *entities, &block)
202
+ end
203
+
204
+ Thread.current[:i18n_nested_deep] -= 1
205
+ end
206
+
207
+ alias_method :gl_resources, :resources
208
+ def resources(*entities, &block)
209
+ create_globalized_resources(:gl_resources, :resources, *entities, &block)
210
+ end
211
+
212
+ alias_method :gl_resource, :resource
213
+ def resource(*entities, &block)
214
+ create_globalized_resources(:gl_resource, :resource, *entities, &block)
215
+ end
216
+
217
+ private
218
+ alias_method :gl_action_options_for, :action_options_for
219
+ def action_options_for(action, resource, method = nil, resource_options = {})
220
+ opts = gl_action_options_for(action, resource, method, resource_options)
221
+
222
+ if Thread.current[:globalized]
223
+ Thread.current[:globalized] = resource.plural
224
+ if resource.uncountable?
225
+ Thread.current[:globalized] = resource.plural.to_s + '_index'
226
+ end
227
+ Thread.current[:globalized_s] = resource.singular
228
+ else
229
+ Thread.current[:globalized] = nil
230
+ Thread.current[:globalized_s] = nil
231
+ end
232
+ if resource.options[:glang]
233
+ opts[:glang] = resource.options[:glang]
234
+ end
235
+
236
+ opts
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,376 @@
1
+ # encoding: utf-8
2
+ require 'rack/mount'
3
+ require 'action_dispatch'
4
+ require 'active_support/core_ext/module'
5
+
6
+ module I18nRouting
7
+ module Mapper
8
+
9
+ private
10
+
11
+ # Just create a Mapper:Resource with given parameters
12
+ def resource_from_params(type, *resources)
13
+ res = resources.clone
14
+
15
+ options = res.extract_options!
16
+ r = res.first
17
+
18
+ type == :resource ? ActionDispatch::Routing::Mapper::SingletonResource.new(r, options.dup) : ActionDispatch::Routing::Mapper::Resource.new(r, options.dup)
19
+ end
20
+
21
+ # Localize a resources or a resource
22
+ def localized_resources(type = :resources, *resources, &block)
23
+ localizable_route = nil
24
+
25
+ if @locales
26
+ res = resources.clone
27
+
28
+ options = res.extract_options!
29
+ r = res.first
30
+
31
+ resource = type == :resource ? ActionDispatch::Routing::Mapper::SingletonResource.new(r, options.dup) : ActionDispatch::Routing::Mapper::Resource.new(r, options.dup)
32
+
33
+ # Check for translated resource
34
+ @locales.each do |locale|
35
+ I18n.locale = locale
36
+ localized_path = I18nRouting.translation_for(resource.name, type)
37
+
38
+ # A translated route exists :
39
+ if localized_path and String === localized_path
40
+ puts("[I18n] > localize %-10s: %40s (%s) => /%s" % [type, resource.name, locale, localized_path]) if @i18n_verbose
41
+ opts = options.dup
42
+ opts[:path] = localized_path
43
+ opts[:controller] ||= r.to_s.pluralize
44
+
45
+ res = ["#{locale}_#{r}".to_sym, opts]
46
+
47
+ constraints = opts[:constraints] ? opts[:constraints].dup : {}
48
+ constraints[:i18n_locale] = locale.to_s
49
+
50
+ scope(:constraints => constraints, :path_names => I18nRouting.path_names(resource.name, @scope)) do
51
+ localized_branch(locale) do
52
+ send(type, *res) do
53
+ # In the resource(s) block, we need to keep and restore some context :
54
+
55
+ if block
56
+ old_name = @scope[:i18n_real_resource_name]
57
+ old = @scope[:scope_level_resource]
58
+
59
+ @scope[:i18n_real_resource_name] = resource.name
60
+ @scope[:i18n_scope_level_resource] = old
61
+ @scope[:scope_level_resource] = resource
62
+
63
+ if type == :resource and @scope[:name_prefix]
64
+ # Need to fake name_prefix for singleton resource
65
+ @scope[:name_prefix] = @scope[:name_prefix].gsub(Regexp.new("#{old.name}$"), resource.name)
66
+ end
67
+
68
+ block.call if block
69
+
70
+ @scope[:scope_level_resource] = old
71
+ @scope[:i18n_real_resource_name] = old_name
72
+ end
73
+
74
+ @scope[:i18n_scope_level_resource] = nil
75
+
76
+ end
77
+ end
78
+ end
79
+
80
+ localizable_route = resource
81
+ end
82
+ end
83
+ end
84
+ return localizable_route
85
+ end
86
+
87
+ # Set if the next route created will be a localized route or not
88
+ # If yes, localizable is a name, or a Mapper::Resource
89
+ # Can take a block, if so, save the current context, set the new
90
+ # Call the block, then restore the old context and return the block return
91
+ def set_localizable_route(localizable)
92
+ if block_given?
93
+ old = @set.named_routes.localizable
94
+ @set.named_routes.set_localizable_route(localizable)
95
+ r = yield
96
+ @set.named_routes.set_localizable_route(old)
97
+ return r
98
+ else
99
+ @set.named_routes.set_localizable_route(localizable)
100
+ end
101
+ end
102
+
103
+ def localizable_route
104
+ @set.named_routes.localizable
105
+ end
106
+
107
+ # Return the aproximate deep in scope level
108
+ def nested_deep
109
+ (@scope and Array === @scope[:blocks] and @scope[:scope_level]) ? @scope[:blocks].size : 0
110
+ end
111
+
112
+ public
113
+
114
+ # On Routing::Mapper initialization (when doing Application.routes.draw do ...)
115
+ # prepare routing system to be i18n ready
116
+ def initialize(*args)
117
+ super
118
+
119
+ # Add i18n as valid conditions for Rack::Mount
120
+ @valid_conditions = @set.instance_eval { @set }.instance_eval { @valid_conditions }
121
+ @valid_conditions << :i18n_locale if !@valid_conditions.include?(:i18n_locale)
122
+
123
+ # Extends the current RouteSet in order to define localized helper for named routes
124
+ # When calling define_url_helper, it calls define_localized_url_helper too.
125
+ if !@set.named_routes.respond_to?(:define_localized_url_helper)
126
+ @set.named_routes.class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
127
+ alias_method :localized_define_url_helper, :define_url_helper
128
+ def define_url_helper(route, name, kind, options)
129
+ localized_define_url_helper(route, name, kind, options)
130
+ define_localized_url_helper(route, name, kind, options)
131
+ end
132
+ END_EVAL
133
+
134
+ @set.named_routes.extend I18nRouting::NamedRouteCollection
135
+ end
136
+ end
137
+
138
+ # Rails 3 routing system
139
+ # Create a block for localized routes, in your routes.rb :
140
+ #
141
+ # localized do
142
+ # resources :users
143
+ # match 'about' => 'contents#about', :as => :about
144
+ # end
145
+ #
146
+ def localized(locales = I18n.available_locales, opts = {})
147
+ # Add if not added Rails.root/config/locales/*.yml in the I18n.load_path
148
+ if !@i18n_routing_path_set and defined?(Rails) and Rails.respond_to?(:root) and Rails.root
149
+ I18n.load_path = (I18n.load_path << Dir[Rails.root.join('config', 'locales', '*.yml').to_s]).uniq
150
+ @i18n_routing_path_set = true
151
+ end
152
+
153
+
154
+ old_value = @locales
155
+ @locales = locales
156
+ @i18n_verbose ||= opts.delete(:verbose)
157
+ yield
158
+ ensure
159
+ @locales = old_value
160
+ end
161
+
162
+ # Create a branch for create routes in the specified locale
163
+ def localized_branch(locale)
164
+ set_localizable_route(nil) do
165
+ old = @localized_branch
166
+ @localized_branch = locale
167
+ localized([locale]) do
168
+ yield
169
+ end
170
+ @localized_branch = old
171
+ end
172
+ end
173
+
174
+ # Set we do not want to localize next resource
175
+ def skip_localization
176
+ old = @skip_localization
177
+ @skip_localization = @localized_branch ? nil : true
178
+ yield
179
+ @skip_localization = old
180
+ end
181
+
182
+ def match(*args)
183
+ # Localize simple match only if there is no resource scope.
184
+ if args.size == 1 and @locales and !parent_resource and args.first[:as]
185
+ @locales.each do |locale|
186
+ mapping = LocalizedMapping.new(locale, @set, @scope, Marshal.load(Marshal.dump(args))) # Dump is dirty but how to make deep cloning easily ? :/
187
+ if mapping.localizable?
188
+ puts("[I18n] > localize %-10s: %40s (%s) => %s" % ['route', args.first[:as], locale, mapping.path]) if @i18n_verbose
189
+ @set.add_route(*mapping.to_route)
190
+ end
191
+ end
192
+
193
+ # Now, create the real match :
194
+ return set_localizable_route(args.first[:as]) do
195
+ super
196
+ end
197
+ end
198
+
199
+ super
200
+ end
201
+
202
+ def create_globalized_resources(type, *resources, &block)
203
+
204
+ #puts "#{' ' * nested_deep}Call #{type} : #{resources.inspect} (#{@locales.inspect}) (#{@localized_branch}) (#{@skip_localization})"
205
+
206
+ cur_scope = nil
207
+ if @locales
208
+ localized = localized_resources(type, *resources, &block) if !@skip_localization
209
+
210
+ ## We do not translate if we are in a translations branch :
211
+ return if localized and nested_deep > 0
212
+
213
+ # Set the current standard resource in order to customize url helper :
214
+ if !@localized_branch
215
+ r = resource_from_params(type, *resources)
216
+ cur_scope = (parent_resource and parent_resource.name == r.name) ? parent_resource : r
217
+ end
218
+ end
219
+
220
+ set_localizable_route(cur_scope) do
221
+ skip_localization do
222
+ #puts "#{' ' * nested_deep} \\- Call original #{type} : for #{resources.inspect}}"
223
+ send("#{type}_without_i18n_routing".to_sym, *resources, &block)
224
+ end
225
+ end
226
+
227
+ end
228
+
229
+ # Alias methods in order to handle i18n routes
230
+ def self.included(mod)
231
+ mod.send :alias_method_chain, :resource, :i18n_routing
232
+ mod.send :alias_method_chain, :resources, :i18n_routing
233
+
234
+ # Here we redefine some methods, in order to handle
235
+ # correct path_names translation on the fly
236
+ [:map_method, :member, :collection].each do |m|
237
+ rfname = "#{m}_without_i18n_routing".to_sym
238
+ mod.send :define_method, "#{m}_with_i18n_routing".to_sym do |*args, &block|
239
+
240
+ if @localized_branch and @scope[:i18n_scope_level_resource] and @scope[:i18n_real_resource_name]
241
+ o = @scope[:scope_level_resource]
242
+ @scope[:scope_level_resource] = @scope[:i18n_scope_level_resource]
243
+
244
+ pname = @scope[:path_names] || {}
245
+ pname[args[1]] = args[1]
246
+ scope(:path_names => I18nRouting.path_names(@scope[:i18n_real_resource_name], {:path_names => pname})) do
247
+ send(rfname, *args, &block)
248
+ end
249
+ @scope[:scope_level_resource] = o
250
+ return
251
+ end
252
+
253
+ send(rfname, *args, &block)
254
+
255
+ end
256
+
257
+ mod.send :alias_method_chain, m, :i18n_routing
258
+ end
259
+ end
260
+
261
+ def resource_with_i18n_routing(*resources, &block)
262
+ create_globalized_resources(:resource, *resources, &block)
263
+ end
264
+
265
+ def resources_with_i18n_routing(*resources, &block)
266
+ create_globalized_resources(:resources, *resources, &block)
267
+ end
268
+
269
+ end
270
+
271
+ # Used for localize simple named routes
272
+ class LocalizedMapping < ActionDispatch::Routing::Mapper::Mapping
273
+
274
+ attr_reader :path
275
+
276
+ def initialize(locale, set, scope, args)
277
+ super(set, scope, args.clone)
278
+
279
+ # try to get translated path :
280
+ I18n.locale = locale
281
+ ts = @path.gsub(/^\//, '')
282
+ @localized_path = '/' + (I18nRouting.translation_for(ts, :named_routes_path) || ts)
283
+
284
+ # If a translated path exists, set localized infos
285
+ if @localized_path and @localized_path != @path
286
+ #@options[:controller] ||= @options[:as]
287
+ @options[:as] = "#{locale}_#{@options[:as]}"
288
+ @path = @localized_path
289
+ @options[:constraints] = @options[:constraints] ? @options[:constraints].dup : {}
290
+ @options[:constraints][:i18n_locale] = locale.to_s
291
+ @options[:anchor] = true
292
+ else
293
+ @localized_path = nil
294
+ end
295
+
296
+ end
297
+
298
+ # Return true if this route is localizable
299
+ def localizable?
300
+ @localized_path != nil
301
+ end
302
+
303
+ end
304
+
305
+ module NamedRouteCollection
306
+
307
+ attr_reader :localizable
308
+
309
+ def set_localizable_route(localizable)
310
+ @localizable = localizable
311
+ end
312
+
313
+ # Alias named route helper in order to check if a localized helper exists
314
+ # If not use the standard one.
315
+ def define_localized_url_helper(route, name, kind, options)
316
+ if n = localizable
317
+ selector = url_helper_name(name, kind)
318
+
319
+ rlang = if n.kind_of?(ActionDispatch::Routing::Mapper::Resources::Resource) and i = name.to_s.rindex("_#{n.plural}")
320
+ "#{selector.to_s[0, i]}_glang_#{n.plural}#{selector.to_s[i + "_#{n.plural}".size, selector.to_s.size]}"
321
+ elsif n.kind_of?(ActionDispatch::Routing::Mapper::Resources::Resource) and i = name.to_s.rindex("_#{n.singular}")
322
+ "#{selector.to_s[0, i]}_glang_#{n.singular}#{selector.to_s[i + "_#{n.singular}".size, selector.to_s.size]}"
323
+ else
324
+ "glang_#{selector}"
325
+ end
326
+
327
+ @module.module_eval <<-end_eval # We use module_eval to avoid leaks
328
+ alias_method :localized_#{selector}, :#{selector}
329
+
330
+ def #{selector}(*args)
331
+ selector_g = '#{rlang}'.gsub('glang', I18n.locale.to_s).to_sym
332
+
333
+ #puts "Call routes : #{selector} => \#{selector_g} (\#{I18n.locale}) "
334
+ if respond_to? selector_g and selector_g != :#{selector}
335
+ send(selector_g, *args)
336
+ else
337
+ localized_#{selector}(*args)
338
+ end
339
+ end
340
+
341
+ end_eval
342
+
343
+ end
344
+ end
345
+ end
346
+
347
+ # Rack::Mount::Route module
348
+ # Exists in order to use apropriate localized route when using url_for
349
+ module RackMountRoute
350
+
351
+ # Alias methods in order to handle i18n routes
352
+ def self.included(mod)
353
+ mod.send :alias_method_chain, :generate, :i18n_routing
354
+ mod.send :alias_method_chain, :initialize, :i18n_routing
355
+ end
356
+
357
+ # During route initialization, if a condition i18n_locale is present
358
+ # Delete it, and store it in @locale
359
+ def initialize_with_i18n_routing(app, conditions, defaults, name)
360
+ @locale = conditions[:i18n_locale] ? conditions.delete(:i18n_locale).source.to_sym : nil
361
+ initialize_without_i18n_routing(app, conditions, defaults, name)
362
+ end
363
+
364
+ # Called for dynamic route generation
365
+ # If a @locale is present and if this locale is not the current one
366
+ # => return nil and refuse to generate the route
367
+ def generate_with_i18n_routing(method, params = {}, recall = {}, options = {})
368
+ return nil if @locale and @locale != I18n.locale.to_sym
369
+ generate_without_i18n_routing(method, params, recall, options)
370
+ end
371
+
372
+ end
373
+ end
374
+
375
+ ActionDispatch::Routing::Mapper.send :include, I18nRouting::Mapper
376
+ Rack::Mount::Route.send :include, I18nRouting::RackMountRoute
@@ -0,0 +1,309 @@
1
+ require 'spec_helper'
2
+
3
+ describe :localized_routes do
4
+
5
+ $r = nil # Global routes in order to speed up testing
6
+
7
+ before(:all) do
8
+
9
+ if !$r
10
+ if !rails3?
11
+ ActionController::Routing::Routes.clear!
12
+ ActionController::Routing::Routes.draw do |map|
13
+ map.not_about 'not_about', :controller => 'not_about'
14
+ map.resources :not_users
15
+ map.resource :not_contact
16
+
17
+ map.localized(I18n.available_locales, :verbose => false) do
18
+ map.about 'about', :controller => 'about', :action => :show
19
+
20
+ map.resources :users, :member => {:level => :get}, :collection => {:groups => :get}
21
+ map.resource :contact
22
+
23
+ map.resources :authors do |m|
24
+ m.resources :books
25
+ end
26
+
27
+ map.resource :foo do |m|
28
+ m.resources :bars
29
+ m.resource :foofoo do |mm|
30
+ mm.resources :bars
31
+ end
32
+ end
33
+
34
+ map.resources :universes do |m|
35
+ m.resources :galaxies do |mm|
36
+ mm.resources :planets do |mmm|
37
+ mmm.resources :countries
38
+ end
39
+ end
40
+ end
41
+
42
+ map.resources :drones, :path_prefix => 'doubledrones/unimatrix/zero'
43
+ end
44
+ end
45
+
46
+ $r = ActionController::Routing::Routes
47
+
48
+ class UrlTester
49
+ include ActionController::UrlWriter
50
+ end
51
+
52
+ else
53
+
54
+ $r = ActionDispatch::Routing::RouteSet.new
55
+ $r.draw do
56
+ match 'not_about' => "not_about#show", :as => :not_about
57
+ resources :not_users
58
+ resource :not_contact
59
+
60
+ localized(I18n.available_locales, :verbose => false) do
61
+ match 'about' => "about#show", :as => :about
62
+
63
+ resources :users do
64
+ member do
65
+ get :level
66
+ end
67
+ get :groups, :on => :collection
68
+ end
69
+ resource :contact
70
+
71
+ resources :authors do
72
+ resources :books
73
+ end
74
+
75
+ resource :foo do
76
+ resources :bars
77
+ resource :foofoo do
78
+ resources :bars
79
+ end
80
+ end
81
+
82
+ resources :universes do
83
+ resources :galaxies do
84
+ resources :planets do
85
+ scope do
86
+ resources :countries
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+
95
+ class UrlTester; end
96
+ UrlTester.send :include, $r.url_helpers
97
+
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ let(:nested_routes) { $r.named_routes.instance_eval { routes } }
104
+ let(:routes) { UrlTester.new }
105
+
106
+ def url_for(opts)
107
+ $r.generate_extras(opts).first
108
+ end
109
+
110
+ context "do not break existing behavior" do
111
+
112
+ it "of named_routes" do
113
+ routes.send(:not_about_path).should == "/not_about"
114
+ end
115
+
116
+ it "of a singular resource" do
117
+ routes.send(:not_contact_path).should == "/not_contact"
118
+ end
119
+
120
+ it "of resources" do
121
+ routes.send(:not_users_path).should == "/not_users"
122
+ end
123
+
124
+ end
125
+
126
+ context "for default routes" do
127
+
128
+ before do
129
+ I18n.locale = :de # Not localized
130
+ end
131
+
132
+ it "named_route uses default values" do
133
+ routes.send(:about_path).should == "/about"
134
+ end
135
+
136
+ it "resource generates routes using default values" do
137
+ routes.send(:contact_path).should == "/contact"
138
+ end
139
+
140
+ it "resources generates routes using default values" do
141
+ routes.send(:users_path).should == "/users"
142
+ end
143
+
144
+ it "url_for generates route using default values" do
145
+ url_for(:controller => :users).should == "/users"
146
+ end
147
+
148
+ it "nested resources generate routes using default values" do
149
+ routes.send(:author_books_path, 1).should == "/authors/1/books"
150
+ end
151
+
152
+ it "deep nested resources generate routes using default values" do
153
+ routes.send(:universes_path).should == "/universes"
154
+ routes.send(:universe_galaxies_path, 1).should == "/universes/1/galaxies"
155
+ routes.send(:universe_galaxy_planets_path, 1, 1).should == "/universes/1/galaxies/1/planets"
156
+ end
157
+
158
+ it "single resource should have by default the pluralized controller" do
159
+ nested_routes[:foo].defaults[:controller].should == 'foos'
160
+ end
161
+
162
+ end
163
+
164
+ context "" do
165
+
166
+ before do
167
+ I18n.locale = :fr
168
+ end
169
+
170
+ it "named_route generates route using localized values" do
171
+ routes.send(:about_path).should == "/#{I18n.t :about, :scope => :named_routes_path}"
172
+ end
173
+
174
+ it "named_route generates route using localized values and I18n.locale as a string" do
175
+ o = I18n.locale
176
+ I18n.locale = "fr"
177
+ routes.send(:about_path).should == "/#{I18n.t :about, :scope => :named_routes_path}"
178
+ I18n.locale = o
179
+ end
180
+
181
+ it "resource generates routes using localized values" do
182
+ routes.send(:contact_path).should == "/#{I18n.t :contact, :scope => :resource}"
183
+ end
184
+
185
+ it "resources generates routes using localized values" do
186
+ routes.send(:users_path).should == "/#{I18n.t :as, :scope => :'routes.users'}"
187
+ end
188
+
189
+ it "url_for generates routes using localized values" do
190
+ url_for(:controller => :users).should == "/#{I18n.t :as, :scope => :'routes.users'}"
191
+ url_for(:controller => :about, :action => :show).should == "/#{I18n.t :about, :scope => :named_routes_path}"
192
+ end
193
+
194
+ it "url_for generates routes for drones with path prefix" do
195
+ url_for(:controller => :drones).should == "#{I18n.t :path_prefix, :scope => :'routes.drones'}/#{I18n.t :as, :scope => :'routes.drones'}"
196
+ end
197
+
198
+ it "nested resources generate routes using localized values" do
199
+ routes.send(:author_books_path, 1).should == "/#{I18n.t :authors, :scope => :resources}/1/#{I18n.t :books, :scope => :resources}"
200
+ end
201
+
202
+ it "deep nested resources generate routes using localized values and translate routes even translated name is the same" do
203
+ routes.send(:universes_path).should == "/#{I18n.t :universes, :scope => :resources}"
204
+ routes.send(:universe_galaxies_path, 1).should == "/#{I18n.t :universes, :scope => :resources}/1/#{I18n.t :galaxies, :scope => :resources}"
205
+ routes.send(:universe_galaxy_planets_path, 1, 1).should == "/#{I18n.t :universes, :scope => :resources}/1/#{I18n.t :galaxies, :scope => :resources}/1/#{I18n.t :planets, :scope => :resources}"
206
+ routes.send(:universe_galaxy_planet_countries_path, 1, 1, 42).should == "/#{I18n.t :universes, :scope => :resources}/1/#{I18n.t :galaxies, :scope => :resources}/1/#{I18n.t :planets, :scope => :resources}/42/#{I18n.t :countries, :scope => :resources}"
207
+ end
208
+
209
+ context "with path_names" do
210
+
211
+ it "default translated path names" do
212
+ routes.send(:new_universe_path).should == "/#{I18n.t :universes, :scope => :resources}/#{I18n.t :new, :scope => :path_names}"
213
+ routes.send(:edit_universe_path, 42).should == "/#{I18n.t :universes, :scope => :resources}/42/#{I18n.t :edit, :scope => :path_names}"
214
+ end
215
+
216
+ it "custom translated path names" do
217
+ routes.send(:new_user_path).should == "/#{I18n.t :users, :scope => :resources}/#{I18n.t :new, :scope => :'routes.users.path_names'}"
218
+ routes.send(:edit_user_path, 42).should == "/#{I18n.t :users, :scope => :resources}/42/#{I18n.t :edit, :scope => :'routes.users.path_names'}"
219
+ end
220
+
221
+ end
222
+
223
+ context "with member and collection" do
224
+
225
+ it "custom member" do
226
+ I18n.locale = :en
227
+ routes.send(:level_user_path, 42).should == "/#{I18n.t :users, :scope => :resources}/42/level"
228
+ I18n.locale = :fr
229
+ routes.send(:level_user_path, 42).should == "/#{I18n.t :users, :scope => :resources}/42/#{I18n.t :level, :scope => :'routes.users.path_names'}"
230
+ end
231
+
232
+ it "custom collection" do
233
+ I18n.locale = :en
234
+ routes.send(:groups_users_path).should == "/#{I18n.t :users, :scope => :resources}/groups"
235
+ I18n.locale = :fr
236
+ routes.send(:groups_users_path).should == "/#{I18n.t :users, :scope => :resources}/#{I18n.t :groups, :scope => :'routes.users.path_names'}"
237
+ end
238
+
239
+ end
240
+
241
+ context "when nested" do
242
+
243
+ it "named routes should not be nil" do
244
+ nested_routes[:author_fr_books].should_not be_nil
245
+ end
246
+
247
+ context "in Rails #{Rails.version}" do
248
+ it "include the correct significant keys" do
249
+ v = !rails3? ? :significant_keys : :segment_keys
250
+ nested_routes[:author_books].send(v).should include(:author_id)
251
+ nested_routes[:author_fr_books].send(v).should include(:author_id)
252
+ end
253
+ end
254
+
255
+ end
256
+
257
+ context "when nested inside a singleton resource" do
258
+
259
+ it "named routes should have locale placed at correct position" do
260
+ nested_routes[:fr_foo_fr_bars].should be_nil
261
+ nested_routes[:foo_fr_bars].should_not be_nil
262
+ end
263
+
264
+ it "routes for the singleton resource alone should be translated correctly" do
265
+ routes.send(:foo_path).should == "/#{I18n.t :foo, :scope => :resource}"
266
+ end
267
+
268
+ it "routes should be translated correctly" do
269
+ routes.send(:foo_bars_path).should == "/#{I18n.t :foo, :scope => :resource}/#{I18n.t :bars, :scope => :resources}"
270
+ end
271
+
272
+ it "routes should be translated correctly also with deep nested singleton resource" do
273
+ routes.send(:foo_foofoo_bars_path).should == "/#{I18n.t :foo, :scope => :resource}/#{I18n.t :foofoo, :scope => :resource}/#{I18n.t :bars, :scope => :resources}"
274
+ end
275
+
276
+ end
277
+
278
+ context "when deeply nested" do
279
+
280
+ it "named routes should not be nil" do
281
+ nested_routes[:universe_galaxy_fr_planet].should_not be_nil
282
+ end
283
+
284
+ context "in Rails #{Rails.version}" do
285
+ it "include the correct significant keys" do
286
+ v = !rails3? ? :significant_keys : :segment_keys
287
+ nested_routes[:universe_galaxy_planet].send(v).should include(:universe_id)
288
+ nested_routes[:universe_galaxy_fr_planet].send(v).should include(:universe_id)
289
+ nested_routes[:universe_galaxy_planet].send(v).should include(:galaxy_id)
290
+ nested_routes[:universe_galaxy_fr_planet].send(v).should include(:galaxy_id)
291
+ end
292
+ end
293
+ end
294
+
295
+ it "nested resources do not deep translate with multi helpers" do
296
+ nested_routes.keys.should_not include(:fr_author_books) # Do not want fr_author_books
297
+ end
298
+
299
+ end
300
+
301
+ # context "just output" do
302
+ # it "output all routes properly" do
303
+ # nested_routes.keys.collect(&:to_s).sort.each do |k|
304
+ # puts("%50s: %.80s" % [k, (nested_routes[k.to_sym].path rescue nested_routes[k.to_sym].to_s)])
305
+ # end
306
+ # end
307
+ # end
308
+
309
+ end
@@ -0,0 +1,13 @@
1
+ en:
2
+ resources:
3
+ users: members
4
+ authors: 'authors-en'
5
+ galaxies: 'galaxies-en'
6
+ planets: 'planets-en'
7
+ universes: 'universes-en'
8
+ bars: bars_en
9
+ resource:
10
+ foo: foo_en
11
+ contact: 'contact-us'
12
+ named_routes_path:
13
+ about: 'about-our-company'
@@ -0,0 +1,30 @@
1
+ fr:
2
+ routes:
3
+ users:
4
+ as: utilisateurs
5
+ path_names:
6
+ level: 'niveau'
7
+ groups: 'groupe'
8
+ new: 'nouvel_utilisateur'
9
+ edit: 'edition_utilisateur'
10
+ drones:
11
+ as: senord
12
+ path_prefix: '/senordelbuod/xirtaminu/orez'
13
+ resources:
14
+ users: utilisateurs
15
+ authors: 'auteurs'
16
+ books: 'livres'
17
+ galaxies: 'galaxies'
18
+ planets: 'planetes'
19
+ universes: 'univers'
20
+ countries: 'pays'
21
+ bars: bars_fr
22
+ resource:
23
+ foo: foo_fr
24
+ foofoo: froufrou
25
+ contact: 'contactez-nous'
26
+ named_routes_path:
27
+ about: 'a-propos'
28
+ path_names:
29
+ new: 'nouveau'
30
+ edit: 'edition'
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ $rails_version = ARGV.find { |e| e =~ /rails_spec_version=.*/ }.split('=').last.to_i rescue nil
5
+
6
+ if !$rails_version
7
+ begin
8
+ require 'rails'
9
+ rescue Exception
10
+ $rails_version = 2
11
+ end
12
+ end
13
+
14
+ if !Module.constants.include?('Rails') and $rails_version
15
+ module Rails
16
+ def self.version
17
+ $rails_version.to_s
18
+ end
19
+ end
20
+ end
21
+
22
+ if Rails.version < '3'
23
+ gem 'actionpack', '< 2.9.9'
24
+ require 'action_controller'
25
+ require 'action_controller/routing'
26
+ else
27
+ gem 'actionpack', '> 2.9'
28
+ require 'action_controller'
29
+ require 'action_dispatch'
30
+ require 'rack/mount'
31
+ end
32
+
33
+ def rails3?
34
+ !(Rails.version < '3')
35
+ end
36
+
37
+ puts "Launching spec for Rails #{Rails.version}"
38
+
39
+ # Add I18n load_path
40
+ I18n.load_path = (I18n.load_path << Dir[File.join(File.dirname(__FILE__), 'locales', '*.yml')]).uniq
41
+
42
+ require File.dirname(__FILE__) + '/../init.rb'
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: doubledrones-i18n_routing
3
+ version: !ruby/object:Gem::Version
4
+ hash: 77
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 6
10
+ - 1
11
+ version: 0.3.6.1
12
+ platform: ruby
13
+ authors:
14
+ - Guillaume Luccisano
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-08-11 00:00:00 +02:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: i18n
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">"
29
+ - !ruby/object:Gem::Version
30
+ hash: 25
31
+ segments:
32
+ - 0
33
+ - 3
34
+ - 5
35
+ version: 0.3.5
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ description: I18n_routing is a plugin for Ruby on Rails that lets you easily translate your routes trough the I18n api included in Rails since version 2.2
39
+ email: guillaume.luccisano@gmail.com
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ files:
47
+ - lib/i18n_routing.rb
48
+ - lib/i18n_routing_common.rb
49
+ - lib/i18n_routing_rails2.rb
50
+ - lib/i18n_routing_rails3.rb
51
+ - spec/i18n_routing/i18n_spec.rb
52
+ - spec/locales/en.yml
53
+ - spec/locales/fr.yml
54
+ - spec/spec_helper.rb
55
+ - CHANGELOG.rdoc
56
+ - MIT-LICENSE
57
+ - Rakefile
58
+ - README.rdoc
59
+ - init.rb
60
+ has_rdoc: true
61
+ homepage: http://github.com/kwi/i18n_routing
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 19
84
+ segments:
85
+ - 1
86
+ - 3
87
+ - 4
88
+ version: 1.3.4
89
+ requirements: []
90
+
91
+ rubyforge_project: doubledrones-i18n_routing
92
+ rubygems_version: 1.3.7
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: I18n routing module for Rails 2.3.x and Rails 3. Translate your routes with ease !
96
+ test_files: []
97
+