fishman-i18n_routing 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,392 @@
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 = resource_from_params(type, r, options.dup)
32
+
33
+ # Check for translated resource
34
+ stored_locale = I18n.locale
35
+ @locales.each do |locale|
36
+ I18n.locale = locale
37
+ localized_path = I18nRouting.translation_for(resource.name, type)
38
+
39
+ # A translated route exists :
40
+ if !localized_path.blank? and String === localized_path
41
+ puts("[I18n] > localize %-10s: %40s (%s) => /%s" % [type, resource.name, locale, localized_path]) if @i18n_verbose
42
+ opts = options.dup
43
+ opts[:path] = localized_path
44
+ opts[:controller] ||= r.to_s.pluralize
45
+
46
+ resource = resource_from_params(type, r, opts.dup)
47
+
48
+ res = ["#{I18nRouting.locale_escaped(locale)}_#{r}".to_sym, opts]
49
+
50
+ constraints = opts[:constraints] ? opts[:constraints].dup : {}
51
+ constraints[:i18n_locale] = locale.to_s
52
+
53
+ scope(:constraints => constraints, :path_names => I18nRouting.path_names(resource.name, @scope)) do
54
+ localized_branch(locale) do
55
+ send(type, *res) do
56
+
57
+ # In the resource(s) block, we need to keep and restore some context :
58
+ if block
59
+ old_name = @scope[:i18n_real_resource_name]
60
+ old = @scope[:scope_level_resource]
61
+
62
+ @scope[:i18n_real_resource_name] = resource.name
63
+ @scope[:i18n_scope_level_resource] = old
64
+ @scope[:scope_level_resource] = resource
65
+
66
+ if type == :resource and @scope[:name_prefix]
67
+ # Need to fake name_prefix for singleton resource
68
+ @scope[:name_prefix] = @scope[:name_prefix].gsub(Regexp.new("#{old.name}$"), resource.name)
69
+ end
70
+
71
+ block.call if block
72
+
73
+ @scope[:scope_level_resource] = old
74
+ @scope[:i18n_real_resource_name] = old_name
75
+ end
76
+
77
+ @scope[:i18n_scope_level_resource] = nil
78
+
79
+ end
80
+ end
81
+ end
82
+
83
+ localizable_route = resource
84
+ end
85
+ end
86
+ I18n.locale = stored_locale
87
+ end
88
+ return localizable_route
89
+ end
90
+
91
+ # Set if the next route created will be a localized route or not
92
+ # If yes, localizable is a name, or a Mapper::Resource
93
+ # Can take a block, if so, save the current context, set the new
94
+ # Call the block, then restore the old context and return the block return
95
+ def set_localizable_route(localizable)
96
+ if block_given?
97
+ old = @set.named_routes.localizable
98
+ @set.named_routes.set_localizable_route(localizable)
99
+ r = yield
100
+ @set.named_routes.set_localizable_route(old)
101
+ return r
102
+ else
103
+ @set.named_routes.set_localizable_route(localizable)
104
+ end
105
+ end
106
+
107
+ def localizable_route
108
+ @set.named_routes.localizable
109
+ end
110
+
111
+ # Return the aproximate deep in scope level
112
+ def nested_deep
113
+ (@scope and Array === @scope[:nested_deep] and @scope[:scope_level]) ? @scope[:nested_deep].size : 0
114
+ end
115
+
116
+ public
117
+
118
+ # On Routing::Mapper initialization (when doing Application.routes.draw do ...)
119
+ # prepare routing system to be i18n ready
120
+ def initialize(*args)
121
+ super
122
+
123
+ # Add i18n_locale as valid conditions for Rack::Mount / And add also :locale, as Rails 3.0.4 removed it ...
124
+ @valid_conditions = @set.instance_eval { @set }.instance_eval { @valid_conditions }
125
+ [:i18n_locale, :locale].each do |k|
126
+ @valid_conditions << k if !@valid_conditions.include?(k)
127
+ @set.valid_conditions << k if !@set.valid_conditions.include?(k)
128
+ end
129
+
130
+ # Extends the current RouteSet in order to define localized helper for named routes
131
+ # When calling define_url_helper, it calls define_localized_url_helper too.
132
+ if !@set.named_routes.respond_to?(:define_localized_url_helper)
133
+ @set.named_routes.class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
134
+ alias_method :localized_define_url_helper, :define_url_helper
135
+ def define_url_helper(route, name, kind, options)
136
+ localized_define_url_helper(route, name, kind, options)
137
+ define_localized_url_helper(route, name, kind, options)
138
+ end
139
+ END_EVAL
140
+
141
+ @set.named_routes.extend I18nRouting::NamedRouteCollection
142
+ end
143
+ end
144
+
145
+ # Rails 3 routing system
146
+ # Create a block for localized routes, in your routes.rb :
147
+ #
148
+ # localized do
149
+ # resources :users
150
+ # match 'about' => 'contents#about', :as => :about
151
+ # end
152
+ #
153
+ def localized(locales = I18n.available_locales, opts = {})
154
+ # Add if not added Rails.root/config/locales/*.yml in the I18n.load_path
155
+ if !@i18n_routing_path_set and defined?(Rails) and Rails.respond_to?(:root) and Rails.root
156
+ I18n.load_path = (I18n.load_path << Dir[Rails.root.join('config', 'locales', '*.yml')]).flatten.uniq
157
+ @i18n_routing_path_set = true
158
+ end
159
+
160
+ old_value = @locales
161
+ @locales = locales
162
+ @i18n_verbose ||= opts.delete(:verbose)
163
+ yield
164
+ ensure
165
+ @locales = old_value
166
+ end
167
+
168
+ # Create a branch for create routes in the specified locale
169
+ def localized_branch(locale)
170
+ set_localizable_route(nil) do
171
+ old = @localized_branch
172
+ @localized_branch = locale
173
+ localized([locale]) do
174
+ yield
175
+ end
176
+ @localized_branch = old
177
+ end
178
+ end
179
+
180
+ # Set we do not want to localize next resource
181
+ def skip_localization
182
+ old = @skip_localization
183
+ @skip_localization = @localized_branch ? nil : true
184
+ yield
185
+ @skip_localization = old
186
+ end
187
+
188
+ def match(*args)
189
+ # Localize simple match only if there is no resource scope.
190
+ if args.size == 1 and @locales and !parent_resource and args.last.is_a?(Hash) and args.first[:as]
191
+ options = Marshal.load(Marshal.dump(args.first)) # Dump is dirty but how to make deep cloning easily ? :/
192
+ path, to = options.find { |name, value| name.is_a?(String) }
193
+ options.merge!(:to => to).delete(path)
194
+ @locales.each do |locale|
195
+ mapping = LocalizedMapping.new(locale, @set, @scope, path, options)
196
+ if mapping.localizable?
197
+ puts("[I18n] > localize %-10s: %40s (%s) => %s" % ['route', args.first[:as], locale, mapping.path]) if @i18n_verbose
198
+ @set.add_route(*mapping.to_route)
199
+ end
200
+ end
201
+
202
+ # Now, create the real match :
203
+ return set_localizable_route(args.first[:as]) do
204
+ super
205
+ end
206
+ end
207
+
208
+ super
209
+ end
210
+
211
+ def create_globalized_resources(type, *resources, &block)
212
+ #puts "#{' ' * nested_deep}Call #{type} : #{resources.inspect} (#{@locales.inspect}) (#{@localized_branch}) (#{@skip_localization})"
213
+
214
+ @scope[:nested_deep] ||= []
215
+ @scope[:nested_deep] << 1
216
+
217
+ cur_scope = nil
218
+ if @locales
219
+ localized = localized_resources(type, *resources, &block) if !@skip_localization
220
+
221
+ ## We do not translate if we are in a translation branch :
222
+ return if localized and nested_deep > 0
223
+
224
+ # Set the current standard resource in order to customize url helper :
225
+ if !@localized_branch
226
+ r = resource_from_params(type, *resources)
227
+ cur_scope = (parent_resource and parent_resource.name == r.name) ? parent_resource : r
228
+ end
229
+ end
230
+
231
+ set_localizable_route(cur_scope) do
232
+ skip_localization do
233
+ #puts "#{' ' * nested_deep} \\- Call original #{type} : for #{resources.inspect}}"
234
+ send("#{type}_without_i18n_routing".to_sym, *resources, &block)
235
+ end
236
+ end
237
+
238
+ @scope[:nested_deep].pop
239
+ end
240
+
241
+ # Alias methods in order to handle i18n routes
242
+ def self.included(mod)
243
+ mod.send :alias_method_chain, :resource, :i18n_routing
244
+ mod.send :alias_method_chain, :resources, :i18n_routing
245
+
246
+ # Here we redefine some methods, in order to handle
247
+ # correct path_names translation on the fly
248
+ [:map_method, :member, :collection].each do |m|
249
+ rfname = "#{m}_without_i18n_routing".to_sym
250
+ mod.send :define_method, "#{m}_with_i18n_routing".to_sym do |*args, &block|
251
+ if @localized_branch and @scope[:i18n_scope_level_resource] and @scope[:i18n_real_resource_name]
252
+ o = @scope[:scope_level_resource]
253
+ @scope[:scope_level_resource] = @scope[:i18n_scope_level_resource]
254
+
255
+ pname = @scope[:path_names] || {}
256
+ i = 1
257
+ while i < args.size and (String === args[i] or Symbol === args[i])
258
+ pname[args[i]] = args[i]
259
+ i += 1
260
+ end
261
+ scope(:path_names => I18nRouting.path_names(@scope[:i18n_real_resource_name], {:path_names => pname})) do
262
+ send(rfname, *args, &block)
263
+ end
264
+ @scope[:scope_level_resource] = o
265
+ return
266
+ end
267
+
268
+ send(rfname, *args, &block)
269
+ end
270
+
271
+ mod.send :alias_method_chain, m, :i18n_routing
272
+ end
273
+ end
274
+
275
+ def resource_with_i18n_routing(*resources, &block)
276
+ create_globalized_resources(:resource, *resources, &block)
277
+ end
278
+
279
+ def resources_with_i18n_routing(*resources, &block)
280
+ create_globalized_resources(:resources, *resources, &block)
281
+ end
282
+ end
283
+
284
+ # Used for localize simple named routes
285
+ class LocalizedMapping < ActionDispatch::Routing::Mapper::Mapping
286
+ attr_reader :path
287
+
288
+ def initialize(locale, set, scope, path, options)
289
+ super(set, scope, path.clone, options ? options.clone : nil)
290
+ stored_locale = I18n.locale
291
+
292
+ # try to get translated path :
293
+ I18n.locale = locale
294
+ ts = @path.gsub(/^\//, '')
295
+ ts.gsub!('(.:format)', '')
296
+
297
+ tp = @options[:as] && I18nRouting.translation_for(@options[:as], :named_routes) ||
298
+ !ts.blank? && I18nRouting.translation_for(ts, :named_routes_path) || ts
299
+
300
+ @localized_path = File.join((@scope[:path] || ''), tp).gsub(/\/$/, '')
301
+
302
+ # If a translated path exists, set localized infos
303
+ if !@localized_path.blank? and @localized_path != @path
304
+ #@options[:controller] ||= @options[:as]
305
+ @options[:as] = "#{I18nRouting.locale_escaped(locale)}_#{@options[:as]}"
306
+ @path = @localized_path
307
+ @options[:constraints] = @options[:constraints] ? @options[:constraints].dup : {}
308
+ @options[:constraints][:i18n_locale] = locale.to_s
309
+ @options[:anchor] = true
310
+ # Force the recomputation of the requirements with the new values
311
+ @requirements = nil
312
+ else
313
+ @localized_path = nil
314
+ end
315
+ I18n.locale = stored_locale
316
+ end
317
+
318
+ # Return true if this route is localizable
319
+ def localizable?
320
+ @localized_path != nil
321
+ end
322
+ end
323
+
324
+ module NamedRouteCollection
325
+ attr_reader :localizable
326
+
327
+ def set_localizable_route(localizable)
328
+ @localizable = localizable
329
+ end
330
+
331
+ # Alias named route helper in order to check if a localized helper exists
332
+ # If not use the standard one.
333
+ def define_localized_url_helper(route, name, kind, options)
334
+ if n = localizable
335
+ selector = url_helper_name(name, kind)
336
+
337
+ rlang = if n.kind_of?(ActionDispatch::Routing::Mapper::Resources::Resource) and i = name.to_s.rindex("_#{n.plural}")
338
+ "#{selector.to_s[0, i]}_glang_#{n.plural}#{selector.to_s[i + "_#{n.plural}".size, selector.to_s.size]}"
339
+ elsif n.kind_of?(ActionDispatch::Routing::Mapper::Resources::Resource) and i = name.to_s.rindex("_#{n.singular}")
340
+ "#{selector.to_s[0, i]}_glang_#{n.singular}#{selector.to_s[i + "_#{n.singular}".size, selector.to_s.size]}"
341
+ else
342
+ "glang_#{selector}"
343
+ end
344
+
345
+ @module.module_eval <<-end_eval # We use module_eval to avoid leaks
346
+ alias_method :localized_#{selector}, :#{selector}
347
+
348
+ def #{selector}(*args)
349
+ selector_g = '#{rlang}'.gsub('glang', I18nRouting.locale_escaped(I18n.locale.to_s)).to_sym
350
+
351
+ #puts "Call routes : #{selector} => \#{selector_g} (\#{I18n.locale}) "
352
+ if respond_to? selector_g and selector_g != :#{selector}
353
+ send(selector_g, *args)
354
+ else
355
+ localized_#{selector}(*args)
356
+ end
357
+ end
358
+
359
+ end_eval
360
+
361
+ end
362
+ end
363
+ end
364
+
365
+ # Rack::Mount::Route module
366
+ # Exists in order to use apropriate localized route when using url_for
367
+ module RackMountRoute
368
+ # Alias methods in order to handle i18n routes
369
+ def self.included(mod)
370
+ mod.send :alias_method_chain, :generate, :i18n_routing
371
+ mod.send :alias_method_chain, :initialize, :i18n_routing
372
+ end
373
+
374
+ # During route initialization, if a condition i18n_locale is present
375
+ # Delete it, and store it in @locale
376
+ def initialize_with_i18n_routing(app, conditions, defaults, name)
377
+ @locale = conditions[:i18n_locale] ? conditions.delete(:i18n_locale).source.to_sym : nil
378
+ initialize_without_i18n_routing(app, conditions, defaults, name)
379
+ end
380
+
381
+ # Called for dynamic route generation
382
+ # If a @locale is present and if this locale is not the current one
383
+ # => return nil and refuse to generate the route
384
+ def generate_with_i18n_routing(method, params = {}, recall = {}, options = {})
385
+ return nil if @locale and @locale != I18n.locale.to_sym
386
+ generate_without_i18n_routing(method, params, recall, options)
387
+ end
388
+ end
389
+ end
390
+
391
+ ActionDispatch::Routing::Mapper.send :include, I18nRouting::Mapper
392
+ Rack::Mount::Route.send :include, I18nRouting::RackMountRoute
@@ -0,0 +1,388 @@
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 = resource_from_params(type, 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.blank? 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
+ resource = resource_from_params(type, r, opts.dup)
46
+
47
+ res = ["#{I18nRouting.locale_escaped(locale)}_#{r}".to_sym, opts]
48
+
49
+ constraints = opts[:constraints] ? opts[:constraints].dup : {}
50
+ constraints[:i18n_locale] = locale.to_s
51
+
52
+ scope(:constraints => constraints, :path_names => I18nRouting.path_names(resource.name, @scope)) do
53
+ localized_branch(locale) do
54
+ send(type, *res) do
55
+
56
+ # In the resource(s) block, we need to keep and restore some context :
57
+ if block
58
+ old_name = @scope[:i18n_real_resource_name]
59
+ old = @scope[:scope_level_resource]
60
+
61
+ @scope[:i18n_real_resource_name] = resource.name
62
+ @scope[:i18n_scope_level_resource] = old
63
+ @scope[:scope_level_resource] = resource
64
+
65
+ if type == :resource and @scope[:name_prefix]
66
+ # Need to fake name_prefix for singleton resource
67
+ @scope[:name_prefix] = @scope[:name_prefix].gsub(Regexp.new("#{old.name}$"), resource.name)
68
+ end
69
+
70
+ block.call if block
71
+
72
+ @scope[:scope_level_resource] = old
73
+ @scope[:i18n_real_resource_name] = old_name
74
+ end
75
+
76
+ @scope[:i18n_scope_level_resource] = nil
77
+
78
+ end
79
+ end
80
+ end
81
+
82
+ localizable_route = resource
83
+ end
84
+ end
85
+ end
86
+ return localizable_route
87
+ end
88
+
89
+ # Set if the next route created will be a localized route or not
90
+ # If yes, localizable is a name, or a Mapper::Resource
91
+ # Can take a block, if so, save the current context, set the new
92
+ # Call the block, then restore the old context and return the block return
93
+ def set_localizable_route(localizable)
94
+ if block_given?
95
+ old = @set.named_routes.localizable
96
+ @set.named_routes.set_localizable_route(localizable)
97
+ r = yield
98
+ @set.named_routes.set_localizable_route(old)
99
+ return r
100
+ else
101
+ @set.named_routes.set_localizable_route(localizable)
102
+ end
103
+ end
104
+
105
+ def localizable_route
106
+ @set.named_routes.localizable
107
+ end
108
+
109
+ # Return the aproximate deep in scope level
110
+ def nested_deep
111
+ (@scope and Array === @scope[:nested_deep] and @scope[:scope_level]) ? @scope[:nested_deep].size : 0
112
+ end
113
+
114
+ public
115
+
116
+ # On Routing::Mapper initialization (when doing Application.routes.draw do ...)
117
+ # prepare routing system to be i18n ready
118
+ def initialize(*args)
119
+ super
120
+
121
+ # Add i18n_locale as valid conditions for Rack::Mount / And add also :locale, as Rails 3.0.4 removed it ...
122
+ @valid_conditions = @set.instance_eval { @set }.instance_eval { @valid_conditions }
123
+ [:i18n_locale, :locale].each do |k|
124
+ @valid_conditions << k if !@valid_conditions.include?(k)
125
+ @set.valid_conditions << k if !@set.valid_conditions.include?(k)
126
+ end
127
+
128
+ # Extends the current RouteSet in order to define localized helper for named routes
129
+ # When calling define_url_helper, it calls define_localized_url_helper too.
130
+ if !@set.named_routes.respond_to?(:define_localized_url_helper)
131
+ @set.named_routes.class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
132
+ alias_method :localized_define_url_helper, :define_url_helper
133
+ def define_url_helper(route, name, kind, options)
134
+ localized_define_url_helper(route, name, kind, options)
135
+ define_localized_url_helper(route, name, kind, options)
136
+ end
137
+ END_EVAL
138
+
139
+ @set.named_routes.extend I18nRouting::NamedRouteCollection
140
+ end
141
+ end
142
+
143
+ # Rails 3 routing system
144
+ # Create a block for localized routes, in your routes.rb :
145
+ #
146
+ # localized do
147
+ # resources :users
148
+ # match 'about' => 'contents#about', :as => :about
149
+ # end
150
+ #
151
+ def localized(locales = I18n.available_locales, opts = {})
152
+ # Add if not added Rails.root/config/locales/*.yml in the I18n.load_path
153
+ if !@i18n_routing_path_set and defined?(Rails) and Rails.respond_to?(:root) and Rails.root
154
+ I18n.load_path = (I18n.load_path << Dir[Rails.root.join('config', 'locales', '*.yml')]).flatten.uniq
155
+ @i18n_routing_path_set = true
156
+ end
157
+
158
+ old_value = @locales
159
+ @locales = locales
160
+ @i18n_verbose ||= opts.delete(:verbose)
161
+ yield
162
+ ensure
163
+ @locales = old_value
164
+ end
165
+
166
+ # Create a branch for create routes in the specified locale
167
+ def localized_branch(locale)
168
+ set_localizable_route(nil) do
169
+ old = @localized_branch
170
+ @localized_branch = locale
171
+ localized([locale]) do
172
+ yield
173
+ end
174
+ @localized_branch = old
175
+ end
176
+ end
177
+
178
+ # Set we do not want to localize next resource
179
+ def skip_localization
180
+ old = @skip_localization
181
+ @skip_localization = @localized_branch ? nil : true
182
+ yield
183
+ @skip_localization = old
184
+ end
185
+
186
+ def match(*args)
187
+ # Localize simple match only if there is no resource scope.
188
+ if args.size == 1 and @locales and !parent_resource and args.last.is_a?(Hash) and args.first[:as]
189
+ options = Marshal.load(Marshal.dump(args.first)) # Dump is dirty but how to make deep cloning easily ? :/
190
+ path, to = options.find { |name, value| name.is_a?(String) }
191
+ options.merge!(:to => to).delete(path)
192
+ @locales.each do |locale|
193
+ mapping = LocalizedMapping.new(locale, @set, @scope, path, options)
194
+ if mapping.localizable?
195
+ puts("[I18n] > localize %-10s: %40s (%s) => %s" % ['route', args.first[:as], locale, mapping.path]) if @i18n_verbose
196
+ @set.add_route(*mapping.to_route)
197
+ end
198
+ end
199
+
200
+ # Now, create the real match :
201
+ return set_localizable_route(args.first[:as]) do
202
+ super
203
+ end
204
+ end
205
+
206
+ super
207
+ end
208
+
209
+ def create_globalized_resources(type, *resources, &block)
210
+ #puts "#{' ' * nested_deep}Call #{type} : #{resources.inspect} (#{@locales.inspect}) (#{@localized_branch}) (#{@skip_localization})"
211
+
212
+ @scope[:nested_deep] ||= []
213
+ @scope[:nested_deep] << 1
214
+
215
+ cur_scope = nil
216
+ if @locales
217
+ localized = localized_resources(type, *resources, &block) if !@skip_localization
218
+
219
+ ## We do not translate if we are in a translation branch :
220
+ return if localized and nested_deep > 0
221
+
222
+ # Set the current standard resource in order to customize url helper :
223
+ if !@localized_branch
224
+ r = resource_from_params(type, *resources)
225
+ cur_scope = (parent_resource and parent_resource.name == r.name) ? parent_resource : r
226
+ end
227
+ end
228
+
229
+ set_localizable_route(cur_scope) do
230
+ skip_localization do
231
+ #puts "#{' ' * nested_deep} \\- Call original #{type} : for #{resources.inspect}}"
232
+ send("#{type}_without_i18n_routing".to_sym, *resources, &block)
233
+ end
234
+ end
235
+
236
+ @scope[:nested_deep].pop
237
+ end
238
+
239
+ # Alias methods in order to handle i18n routes
240
+ def self.included(mod)
241
+ mod.send :alias_method_chain, :resource, :i18n_routing
242
+ mod.send :alias_method_chain, :resources, :i18n_routing
243
+
244
+ # Here we redefine some methods, in order to handle
245
+ # correct path_names translation on the fly
246
+ [:map_method, :member, :collection].each do |m|
247
+ rfname = "#{m}_without_i18n_routing".to_sym
248
+ mod.send :define_method, "#{m}_with_i18n_routing".to_sym do |*args, &block|
249
+ if @localized_branch and @scope[:i18n_scope_level_resource] and @scope[:i18n_real_resource_name]
250
+ o = @scope[:scope_level_resource]
251
+ @scope[:scope_level_resource] = @scope[:i18n_scope_level_resource]
252
+
253
+ pname = @scope[:path_names] || {}
254
+ i = 1
255
+ while i < args.size and (String === args[i] or Symbol === args[i])
256
+ pname[args[i]] = args[i]
257
+ i += 1
258
+ end
259
+ scope(:path_names => I18nRouting.path_names(@scope[:i18n_real_resource_name], {:path_names => pname})) do
260
+ send(rfname, *args, &block)
261
+ end
262
+ @scope[:scope_level_resource] = o
263
+ return
264
+ end
265
+
266
+ send(rfname, *args, &block)
267
+ end
268
+
269
+ mod.send :alias_method_chain, m, :i18n_routing
270
+ end
271
+ end
272
+
273
+ def resource_with_i18n_routing(*resources, &block)
274
+ create_globalized_resources(:resource, *resources, &block)
275
+ end
276
+
277
+ def resources_with_i18n_routing(*resources, &block)
278
+ create_globalized_resources(:resources, *resources, &block)
279
+ end
280
+ end
281
+
282
+ # Used for localize simple named routes
283
+ class LocalizedMapping < ActionDispatch::Routing::Mapper::Mapping
284
+ attr_reader :path
285
+
286
+ def initialize(locale, set, scope, path, options)
287
+ super(set, scope, path.clone, options ? options.clone : nil)
288
+
289
+ # try to get translated path :
290
+ I18n.locale = locale
291
+ ts = @path.gsub(/^\//, '')
292
+ ts.gsub!('(.:format)', '')
293
+
294
+ tp = @options[:as] && I18nRouting.translation_for(@options[:as], :named_routes) ||
295
+ !ts.blank? && I18nRouting.translation_for(ts, :named_routes_path) || ts
296
+
297
+ @localized_path = File.join((@scope[:path] || ''), tp).gsub(/\/$/, '')
298
+
299
+ # If a translated path exists, set localized infos
300
+ if !@localized_path.blank? and @localized_path != @path
301
+ #@options[:controller] ||= @options[:as]
302
+ @options[:as] = "#{I18nRouting.locale_escaped(locale)}_#{@options[:as]}"
303
+ @path = @localized_path
304
+ @options[:constraints] = @options[:constraints] ? @options[:constraints].dup : {}
305
+ @options[:constraints][:i18n_locale] = locale.to_s
306
+ @options[:anchor] = true
307
+ # Force the recomputation of the requirements with the new values
308
+ @requirements = nil
309
+ else
310
+ @localized_path = nil
311
+ end
312
+ end
313
+
314
+ # Return true if this route is localizable
315
+ def localizable?
316
+ @localized_path != nil
317
+ end
318
+ end
319
+
320
+ module NamedRouteCollection
321
+ attr_reader :localizable
322
+
323
+ def set_localizable_route(localizable)
324
+ @localizable = localizable
325
+ end
326
+
327
+ # Alias named route helper in order to check if a localized helper exists
328
+ # If not use the standard one.
329
+ def define_localized_url_helper(route, name, kind, options)
330
+ if n = localizable
331
+ selector = url_helper_name(name, kind)
332
+
333
+ rlang = if n.kind_of?(ActionDispatch::Routing::Mapper::Resources::Resource) and i = name.to_s.rindex("_#{n.plural}")
334
+ "#{selector.to_s[0, i]}_glang_#{n.plural}#{selector.to_s[i + "_#{n.plural}".size, selector.to_s.size]}"
335
+ elsif n.kind_of?(ActionDispatch::Routing::Mapper::Resources::Resource) and i = name.to_s.rindex("_#{n.singular}")
336
+ "#{selector.to_s[0, i]}_glang_#{n.singular}#{selector.to_s[i + "_#{n.singular}".size, selector.to_s.size]}"
337
+ else
338
+ "glang_#{selector}"
339
+ end
340
+
341
+ @module.module_eval <<-end_eval # We use module_eval to avoid leaks
342
+ alias_method :localized_#{selector}, :#{selector}
343
+
344
+ def #{selector}(*args)
345
+ selector_g = '#{rlang}'.gsub('glang', I18nRouting.locale_escaped(I18n.locale.to_s)).to_sym
346
+
347
+ #puts "Call routes : #{selector} => \#{selector_g} (\#{I18n.locale}) "
348
+ if respond_to? selector_g and selector_g != :#{selector}
349
+ send(selector_g, *args)
350
+ else
351
+ localized_#{selector}(*args)
352
+ end
353
+ end
354
+
355
+ end_eval
356
+
357
+ end
358
+ end
359
+ end
360
+
361
+ # Rack::Mount::Route module
362
+ # Exists in order to use apropriate localized route when using url_for
363
+ module RackMountRoute
364
+ # Alias methods in order to handle i18n routes
365
+ def self.included(mod)
366
+ mod.send :alias_method_chain, :generate, :i18n_routing
367
+ mod.send :alias_method_chain, :initialize, :i18n_routing
368
+ end
369
+
370
+ # During route initialization, if a condition i18n_locale is present
371
+ # Delete it, and store it in @locale
372
+ def initialize_with_i18n_routing(app, conditions, defaults, name)
373
+ @locale = conditions[:i18n_locale] ? conditions.delete(:i18n_locale).source.to_sym : nil
374
+ initialize_without_i18n_routing(app, conditions, defaults, name)
375
+ end
376
+
377
+ # Called for dynamic route generation
378
+ # If a @locale is present and if this locale is not the current one
379
+ # => return nil and refuse to generate the route
380
+ def generate_with_i18n_routing(method, params = {}, recall = {}, options = {})
381
+ return nil if @locale and @locale != I18n.locale.to_sym
382
+ generate_without_i18n_routing(method, params, recall, options)
383
+ end
384
+ end
385
+ end
386
+
387
+ ActionDispatch::Routing::Mapper.send :include, I18nRouting::Mapper
388
+ Rack::Mount::Route.send :include, I18nRouting::RackMountRoute