rack-i18n_routes 0.2 → 0.3

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.
data/README.md CHANGED
@@ -6,7 +6,11 @@ that have been translated into untranslated or canonical URL.
6
6
 
7
7
  If you manage a site that has content many languages and also localized URLs,
8
8
  you will find `rack-i18n_routes` very useful, especially when used in
9
- conjunction with `rack-i18n_best_langs`.
9
+ conjunction with [`rack-i18n_best_langs`](https://github.com/gioele/rack-i18n_best_langs).
10
+
11
+ Differently from other similar Rack middleware components,
12
+ `rack-i18n_routes` focuses only on path translation/normalization and does not
13
+ require Rails or the `i18n` gem.
10
14
 
11
15
 
12
16
  Features
@@ -18,6 +22,8 @@ Path normalization rewrites the URI (actually the `PATH_INFO`) so that that the
18
22
  downstream applications will have to deal with the normalized path only, instead
19
23
  of a myriad of localized paths.
20
24
 
25
+ The original path is saved in the `rack.i18n_routes_orig_PATH_INFO` variable.
26
+
21
27
 
22
28
  Examples
23
29
  --------
@@ -55,12 +61,13 @@ rack-i18n_routes works like any other Rack middleware component:
55
61
  use Rack::I18nRoutes, MAPPING_FN
56
62
  run MyApp
57
63
 
58
- Request to `/articulos/el-bloque`, `/articles/le-bloc` and even
64
+ Requests to `/articulos/el-bloque`, `/articles/le-bloc` and even
59
65
  `/articulos/le-bloc` will all be sent to `/articles/the-block`.
60
66
 
61
67
  This component deals only with URL normalization. You can use
62
- `rack-i18n_best_langs` to automatically associate the translated URLs to their
63
- languages.
68
+ [`rack-i18n_best_langs`](https://github.com/gioele/rack-i18n_best_langs)
69
+ ([documentation](http://rubydoc.info/gems/rack-i18n_best_langs)) to
70
+ automatically associate the translated URLs to their languages.
64
71
 
65
72
 
66
73
  Requirements
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ Bones {
16
16
  email 'gioele@svario.it'
17
17
  url 'https://github.com/gioele/rack-i18n_routes'
18
18
 
19
- version '0.2'
19
+ version '0.3'
20
20
 
21
21
  ignore_file '.gitignore'
22
22
 
@@ -46,6 +46,8 @@ require 'rack'
46
46
 
47
47
  class Rack::I18nRoutes
48
48
 
49
+ ORIG_PATH_INFO_VARIABLE = 'rack.i18n_routes_orig_PATH_INFO'
50
+
49
51
  # Set up an i18n routing table.
50
52
  #
51
53
  # @overload initialize(app, url_mapper)
@@ -114,7 +116,7 @@ class Rack::I18nRoutes
114
116
  @path_lookup[path]
115
117
  end
116
118
 
117
- env['rack.i18n-routes_PATH_INFO'] = path
119
+ env[ORIG_PATH_INFO_VARIABLE] = path
118
120
  env['PATH_INFO'] = normalized_path
119
121
 
120
122
  return @app.call(env)
@@ -88,69 +88,107 @@ class Rack::I18nRoutes::AliasMapping
88
88
  # @return [String]
89
89
 
90
90
  def map(path)
91
- normalized_path, found_langs = map_with_langs(path)
91
+ normalized_pieces, translated_pieces, found_langs = path_analysis(path)
92
+
93
+ normalized_path = normalized_pieces.join('/')
92
94
 
93
95
  return normalized_path
94
96
  end
95
97
 
96
- # @return [(String, Array<Object>)]
98
+ # @return [String]
99
+
100
+ def translate_into(path, language)
101
+ normalized_pieces, translated_pieces, found_langs = path_analysis(path, language)
102
+
103
+ return translated_pieces.join('/')
104
+ end
97
105
 
98
- def map_with_langs(path)
106
+ # @return [(Array<String>, Array<String>, Array<Object>)]
107
+
108
+ def path_analysis(path, replacement_language = :default)
99
109
  orig_pieces = path.split('/')
100
110
 
101
111
  normalized_pieces = []
112
+ translated_pieces = []
102
113
  found_langs = []
103
114
 
104
- normalized_pieces << orig_pieces.shift
105
- found_langs << @default_lang
115
+ # PATH_INFO always starts with / in Rack, so we directly move
116
+ # the initial empty piece into the normalized ones
117
+
118
+ pre_slash = orig_pieces.shift
119
+ normalized_pieces << pre_slash
120
+ translated_pieces << pre_slash
106
121
 
107
122
  aliases = @aliases
108
123
 
109
124
  orig_pieces.each do |orig_piece|
110
- normalized, lang = normalization_for(orig_piece, aliases)
125
+ normalized, translation, lang = normalization_for(orig_piece, aliases, replacement_language)
111
126
  replacement = (normalized || orig_piece)
112
127
 
113
128
  normalized_pieces << replacement
129
+ translated_pieces << translation
114
130
  found_langs << lang
115
131
 
116
- if !aliases.nil?
117
- subaliases = aliases[replacement]
118
- aliases = subaliases[:children] unless subaliases.nil?
132
+ if !aliases.nil? && aliases.has_key?(replacement)
133
+ aliases = aliases[replacement][:children]
134
+ else
135
+ aliases = nil
119
136
  end
120
137
  end
121
138
 
122
139
  if path.end_with?('/')
123
140
  normalized_pieces << ""
124
- found_langs << @default_lang
141
+ translated_pieces << ""
125
142
  end
126
143
 
127
- normalized_path = normalized_pieces.join('/')
128
-
129
- return normalized_path, found_langs
144
+ return normalized_pieces, translated_pieces, found_langs
130
145
  end
131
146
 
132
147
  # @return [(String, Object)]
148
+ #
149
+ # @api private
133
150
 
134
- def normalization_for(piece, aliases)
151
+ def normalization_for(piece, aliases, replacement_language)
135
152
  if aliases.nil?
136
- return nil, @default_lang
153
+ return nil, piece, @default_lang
137
154
  end
138
155
 
139
156
  entities = aliases.keys
140
157
  entities.each do |entity|
141
158
  if piece == entity
142
- return entity, @default_lang
159
+ translated_piece = piece_translation(piece, aliases[entity], replacement_language)
160
+ return entity, translated_piece, @default_lang
143
161
  end
144
162
 
145
163
  subentities = aliases[entity].values.reject { |e| e.is_a? Hash }
146
164
  subentity = subentities.find { |s| Array(s).any? { |sube| piece == sube } }
147
165
  if !subentity.nil?
148
- return entity, aliases[entity].index(subentity)
166
+ lang = aliases[entity].index(subentity)
167
+ translated_piece = piece_translation(piece, aliases[entity], replacement_language)
168
+ return entity, translated_piece, lang
149
169
  end
150
170
  end
151
171
 
152
172
  # the piece is not present in the aliases
153
173
 
154
- return nil, @default_lang
174
+ return nil, piece, @default_lang
175
+ end
176
+
177
+ # @return [String]
178
+ #
179
+ # @api private
180
+
181
+ def piece_translation(piece, aliases, replacement_language)
182
+ if replacement_language == :default
183
+ return piece
184
+ end
185
+
186
+ translated_pieces = Array(aliases[replacement_language])
187
+
188
+ if translated_pieces.empty?
189
+ return piece
190
+ end
191
+
192
+ return translated_pieces.first
155
193
  end
156
194
  end
@@ -54,20 +54,42 @@ class Rack::I18nRoutes::AliasMappingUpdater
54
54
  @opts = opts
55
55
  end
56
56
 
57
- # @return [String]
57
+ # @see Rack::I18nRoutes::AliasMapping#map
58
+ #
59
+ # @return (see Rack::I18nRoutes::AliasMapping#map)
60
+ #
61
+ # @api private
58
62
 
59
63
  def map(path)
60
- normalized_path, found_langs = map_with_langs(path)
64
+ alias_mapping.map(path)
65
+ end
66
+
67
+ # @see Rack::I18nRoutes::AliasMapping#translate_into
68
+ #
69
+ # @return (see Rack::I18nRoutes::AliasMapping#translate_into)
70
+ #
71
+ # @api private
72
+
73
+ def translate_into(path, language)
74
+ alias_mapping.translated_into(path, language)
75
+ end
76
+
77
+ # @see Rack::I18nRoutes::AliasMapping#analysis
78
+ #
79
+ # @return (see Rack::I18nRoutes::AliasMapping#analysis)
80
+ #
81
+ # @api private
61
82
 
62
- return normalized_path
83
+ def path_analysis(path, replacement_language = :default)
84
+ alias_mapping.analysis(path, replacement_language)
63
85
  end
64
86
 
65
- # @return [(String, Array<Object>)]
87
+ # @api private
66
88
 
67
- def map_with_langs(path)
89
+ def alias_mapping
68
90
  aliases = @new_aliases_fn[]
69
- alias_mapping = Rack::I18nRoutes::AliasMapping.new(aliases, @opts)
91
+ mapping = Rack::I18nRoutes::AliasMapping.new(aliases, @opts)
70
92
 
71
- return alias_mapping.map_with_langs(path)
93
+ return mapping
72
94
  end
73
95
  end
@@ -28,7 +28,7 @@ TEST_ALIASES = {
28
28
 
29
29
  :children => {
30
30
  'gioconda' => {
31
- 'fra' => 'joconde',
31
+ 'fra' => ['joconde', 'la-joconde'],
32
32
  }
33
33
  }
34
34
  }
@@ -54,6 +54,24 @@ def request_with(path, mapping_fn)
54
54
  end
55
55
 
56
56
  describe Rack::I18nRoutes do
57
+ context "with a Proc mapping" do
58
+ let(:mapping) { Proc.new { |orig_path| orig_path + "_extra" } }
59
+
60
+ it "applies the mapping" do
61
+ env = request_with('/articles', mapping).env
62
+ path = env['PATH_INFO']
63
+
64
+ path.should == '/articles_extra'
65
+ end
66
+
67
+ it "saves the original path" do
68
+ env = request_with('/articles', mapping).env
69
+ orig_path = env[Rack::I18nRoutes::ORIG_PATH_INFO_VARIABLE]
70
+
71
+ orig_path.should == '/articles'
72
+ end
73
+ end
74
+
57
75
  context "with an AliasMapping" do
58
76
  let(:mapping) { Rack::I18nRoutes::AliasMapping.new(TEST_ALIASES) }
59
77
 
@@ -84,6 +102,13 @@ describe Rack::I18nRoutes do
84
102
 
85
103
  path.should == '/foobar'
86
104
  end
105
+
106
+ it "saves the original path" do
107
+ env = request_with('/articulos/le-bloc', mapping).env
108
+ orig_path = env[Rack::I18nRoutes::ORIG_PATH_INFO_VARIABLE]
109
+
110
+ orig_path.should == '/articulos/le-bloc'
111
+ end
87
112
  end
88
113
 
89
114
  context "with an AliasMappingUpdater" do
@@ -116,6 +141,13 @@ describe Rack::I18nRoutes do
116
141
 
117
142
  path.should == '/foobar'
118
143
  end
144
+
145
+ it "saves the original path" do
146
+ env = request_with('/articulos/le-bloc', mapping).env
147
+ orig_path = env[Rack::I18nRoutes::ORIG_PATH_INFO_VARIABLE]
148
+
149
+ orig_path.should == '/articulos/le-bloc'
150
+ end
119
151
  end
120
152
  end
121
153
 
@@ -124,22 +156,44 @@ describe Rack::I18nRoutes::AliasMapping do
124
156
  let(:mapping) { Rack::I18nRoutes::AliasMapping.new(TEST_ALIASES, :default => default_lang) }
125
157
 
126
158
  context "with a :default option set" do
127
- it "returns the default key for normalized paths" do
128
- ph, found_langs = mapping.map_with_langs('/paintings/gioconda/')
159
+ describe "#path_analysis" do
160
+ it "returns the default key for normalized paths" do
161
+ ph, trans, found_langs = mapping.path_analysis('/paintings/gioconda/')
129
162
 
130
- found_langs.should == [default_lang]*4
131
- end
163
+ found_langs.should == [default_lang, default_lang]
164
+ end
132
165
 
133
- it "returns the non-default key when set" do
134
- ph, found_langs = mapping.map_with_langs('/articulos/la-victoire/')
166
+ it "returns the non-default key when set" do
167
+ ph, trans, found_langs = mapping.path_analysis('/articulos/la-victoire/')
135
168
 
136
- found_langs.should == [default_lang, 'spa', 'fra', default_lang]
169
+ found_langs.should == ['spa', 'fra']
170
+ end
171
+
172
+ it "returns the default key for unknown paths" do
173
+ ph, trans, found_langs = mapping.path_analysis('/articulos/foobar/')
174
+
175
+ found_langs.should == ['spa', default_lang]
176
+ end
137
177
  end
138
178
 
139
- it "returns the default key for unknown paths" do
140
- ph, found_langs = mapping.map_with_langs('/articulos/fancy/')
179
+ describe "#translate_into" do
180
+ it "translates a path" do
181
+ ph = mapping.translate_into('/pinturas/gioconda', 'fra')
182
+
183
+ ph.should == '/peintures/joconde'
184
+ end
185
+
186
+ it "translates a path with untranslated pieces" do
187
+ ph = mapping.translate_into('/paintings/gioconda', 'spa')
188
+
189
+ ph.should == '/pinturas/gioconda'
190
+ end
191
+
192
+ it "translates a path with unknown pieces" do
193
+ ph = mapping.translate_into('/pinturas/foobar/quux', 'fra')
141
194
 
142
- found_langs.should == [default_lang, 'spa', default_lang, default_lang]
195
+ ph.should == '/peintures/foobar/quux'
196
+ end
143
197
  end
144
198
  end
145
199
  end
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-i18n_routes
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 13
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
9
- version: "0.2"
8
+ - 3
9
+ version: "0.3"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Gioele Barabucci
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2012-08-05 00:00:00 Z
17
+ date: 2012-08-09 00:00:00 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: rack