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 +11 -4
- data/Rakefile +1 -1
- data/lib/rack/i18n_routes.rb +3 -1
- data/lib/rack/i18n_routes/alias_mapping.rb +56 -18
- data/lib/rack/i18n_routes/alias_mapping_updater.rb +29 -7
- data/spec/i18n_routes_spec.rb +65 -11
- metadata +4 -4
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
|
-
|
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`
|
63
|
-
|
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
data/lib/rack/i18n_routes.rb
CHANGED
@@ -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[
|
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
|
-
|
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 [
|
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
|
-
|
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
|
-
|
105
|
-
|
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
|
-
|
118
|
-
|
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
|
-
|
141
|
+
translated_pieces << ""
|
125
142
|
end
|
126
143
|
|
127
|
-
|
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
|
-
|
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
|
-
|
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
|
-
# @
|
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
|
-
|
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
|
-
|
83
|
+
def path_analysis(path, replacement_language = :default)
|
84
|
+
alias_mapping.analysis(path, replacement_language)
|
63
85
|
end
|
64
86
|
|
65
|
-
# @
|
87
|
+
# @api private
|
66
88
|
|
67
|
-
def
|
89
|
+
def alias_mapping
|
68
90
|
aliases = @new_aliases_fn[]
|
69
|
-
|
91
|
+
mapping = Rack::I18nRoutes::AliasMapping.new(aliases, @opts)
|
70
92
|
|
71
|
-
return
|
93
|
+
return mapping
|
72
94
|
end
|
73
95
|
end
|
data/spec/i18n_routes_spec.rb
CHANGED
@@ -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
|
-
|
128
|
-
|
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
|
-
|
131
|
-
|
163
|
+
found_langs.should == [default_lang, default_lang]
|
164
|
+
end
|
132
165
|
|
133
|
-
|
134
|
-
|
166
|
+
it "returns the non-default key when set" do
|
167
|
+
ph, trans, found_langs = mapping.path_analysis('/articulos/la-victoire/')
|
135
168
|
|
136
|
-
|
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
|
-
|
140
|
-
|
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
|
-
|
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:
|
4
|
+
hash: 13
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: "0.
|
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-
|
17
|
+
date: 2012-08-09 00:00:00 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: rack
|