rack-i18n_routes 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
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