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 +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
|