fishman-i18n_routing 0.4.9

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.
@@ -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