rack-i18n_routes 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +4 -0
- data/COPYING +121 -0
- data/README.md +103 -0
- data/Rakefile +29 -0
- data/lib/rack/i18n_routes.rb +125 -0
- data/lib/rack/i18n_routes/alias_mapping.rb +130 -0
- data/lib/rack/i18n_routes/alias_mapping_updater.rb +60 -0
- data/spec/i18n_routes_spec.rb +115 -0
- data/spec/spec_helper.rb +17 -0
- metadata +138 -0
data/.yardopts
ADDED
data/COPYING
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
Creative Commons Legal Code
|
2
|
+
|
3
|
+
CC0 1.0 Universal
|
4
|
+
|
5
|
+
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
6
|
+
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
7
|
+
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
8
|
+
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
9
|
+
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
10
|
+
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
11
|
+
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
12
|
+
HEREUNDER.
|
13
|
+
|
14
|
+
Statement of Purpose
|
15
|
+
|
16
|
+
The laws of most jurisdictions throughout the world automatically confer
|
17
|
+
exclusive Copyright and Related Rights (defined below) upon the creator
|
18
|
+
and subsequent owner(s) (each and all, an "owner") of an original work of
|
19
|
+
authorship and/or a database (each, a "Work").
|
20
|
+
|
21
|
+
Certain owners wish to permanently relinquish those rights to a Work for
|
22
|
+
the purpose of contributing to a commons of creative, cultural and
|
23
|
+
scientific works ("Commons") that the public can reliably and without fear
|
24
|
+
of later claims of infringement build upon, modify, incorporate in other
|
25
|
+
works, reuse and redistribute as freely as possible in any form whatsoever
|
26
|
+
and for any purposes, including without limitation commercial purposes.
|
27
|
+
These owners may contribute to the Commons to promote the ideal of a free
|
28
|
+
culture and the further production of creative, cultural and scientific
|
29
|
+
works, or to gain reputation or greater distribution for their Work in
|
30
|
+
part through the use and efforts of others.
|
31
|
+
|
32
|
+
For these and/or other purposes and motivations, and without any
|
33
|
+
expectation of additional consideration or compensation, the person
|
34
|
+
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
35
|
+
is an owner of Copyright and Related Rights in the Work, voluntarily
|
36
|
+
elects to apply CC0 to the Work and publicly distribute the Work under its
|
37
|
+
terms, with knowledge of his or her Copyright and Related Rights in the
|
38
|
+
Work and the meaning and intended legal effect of CC0 on those rights.
|
39
|
+
|
40
|
+
1. Copyright and Related Rights. A Work made available under CC0 may be
|
41
|
+
protected by copyright and related or neighboring rights ("Copyright and
|
42
|
+
Related Rights"). Copyright and Related Rights include, but are not
|
43
|
+
limited to, the following:
|
44
|
+
|
45
|
+
i. the right to reproduce, adapt, distribute, perform, display,
|
46
|
+
communicate, and translate a Work;
|
47
|
+
ii. moral rights retained by the original author(s) and/or performer(s);
|
48
|
+
iii. publicity and privacy rights pertaining to a person's image or
|
49
|
+
likeness depicted in a Work;
|
50
|
+
iv. rights protecting against unfair competition in regards to a Work,
|
51
|
+
subject to the limitations in paragraph 4(a), below;
|
52
|
+
v. rights protecting the extraction, dissemination, use and reuse of data
|
53
|
+
in a Work;
|
54
|
+
vi. database rights (such as those arising under Directive 96/9/EC of the
|
55
|
+
European Parliament and of the Council of 11 March 1996 on the legal
|
56
|
+
protection of databases, and under any national implementation
|
57
|
+
thereof, including any amended or successor version of such
|
58
|
+
directive); and
|
59
|
+
vii. other similar, equivalent or corresponding rights throughout the
|
60
|
+
world based on applicable law or treaty, and any national
|
61
|
+
implementations thereof.
|
62
|
+
|
63
|
+
2. Waiver. To the greatest extent permitted by, but not in contravention
|
64
|
+
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
65
|
+
irrevocably and unconditionally waives, abandons, and surrenders all of
|
66
|
+
Affirmer's Copyright and Related Rights and associated claims and causes
|
67
|
+
of action, whether now known or unknown (including existing as well as
|
68
|
+
future claims and causes of action), in the Work (i) in all territories
|
69
|
+
worldwide, (ii) for the maximum duration provided by applicable law or
|
70
|
+
treaty (including future time extensions), (iii) in any current or future
|
71
|
+
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
72
|
+
including without limitation commercial, advertising or promotional
|
73
|
+
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
74
|
+
member of the public at large and to the detriment of Affirmer's heirs and
|
75
|
+
successors, fully intending that such Waiver shall not be subject to
|
76
|
+
revocation, rescission, cancellation, termination, or any other legal or
|
77
|
+
equitable action to disrupt the quiet enjoyment of the Work by the public
|
78
|
+
as contemplated by Affirmer's express Statement of Purpose.
|
79
|
+
|
80
|
+
3. Public License Fallback. Should any part of the Waiver for any reason
|
81
|
+
be judged legally invalid or ineffective under applicable law, then the
|
82
|
+
Waiver shall be preserved to the maximum extent permitted taking into
|
83
|
+
account Affirmer's express Statement of Purpose. In addition, to the
|
84
|
+
extent the Waiver is so judged Affirmer hereby grants to each affected
|
85
|
+
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
86
|
+
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
87
|
+
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
88
|
+
maximum duration provided by applicable law or treaty (including future
|
89
|
+
time extensions), (iii) in any current or future medium and for any number
|
90
|
+
of copies, and (iv) for any purpose whatsoever, including without
|
91
|
+
limitation commercial, advertising or promotional purposes (the
|
92
|
+
"License"). The License shall be deemed effective as of the date CC0 was
|
93
|
+
applied by Affirmer to the Work. Should any part of the License for any
|
94
|
+
reason be judged legally invalid or ineffective under applicable law, such
|
95
|
+
partial invalidity or ineffectiveness shall not invalidate the remainder
|
96
|
+
of the License, and in such case Affirmer hereby affirms that he or she
|
97
|
+
will not (i) exercise any of his or her remaining Copyright and Related
|
98
|
+
Rights in the Work or (ii) assert any associated claims and causes of
|
99
|
+
action with respect to the Work, in either case contrary to Affirmer's
|
100
|
+
express Statement of Purpose.
|
101
|
+
|
102
|
+
4. Limitations and Disclaimers.
|
103
|
+
|
104
|
+
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
105
|
+
surrendered, licensed or otherwise affected by this document.
|
106
|
+
b. Affirmer offers the Work as-is and makes no representations or
|
107
|
+
warranties of any kind concerning the Work, express, implied,
|
108
|
+
statutory or otherwise, including without limitation warranties of
|
109
|
+
title, merchantability, fitness for a particular purpose, non
|
110
|
+
infringement, or the absence of latent or other defects, accuracy, or
|
111
|
+
the present or absence of errors, whether or not discoverable, all to
|
112
|
+
the greatest extent permissible under applicable law.
|
113
|
+
c. Affirmer disclaims responsibility for clearing rights of other persons
|
114
|
+
that may apply to the Work or any use thereof, including without
|
115
|
+
limitation any person's Copyright and Related Rights in the Work.
|
116
|
+
Further, Affirmer disclaims responsibility for obtaining any necessary
|
117
|
+
consents, permissions or other rights required for any use of the
|
118
|
+
Work.
|
119
|
+
d. Affirmer understands and acknowledges that Creative Commons is not a
|
120
|
+
party to this document and has no duty or obligation with respect to
|
121
|
+
this CC0 or use of the Work.
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
rack-i8n_routes: route translated URLs to their canonical URLs
|
2
|
+
==============================================================
|
3
|
+
|
4
|
+
rack-i18n_routes is a Rack middleware component that internally re-routes URLS
|
5
|
+
that have been translated into untranslated or canonical URL.
|
6
|
+
|
7
|
+
If you manage a site that has content many languages and also localized URLs,
|
8
|
+
you will find `rack-i18n_routes` very useful, especially when used in
|
9
|
+
conjunction with `rack-i18n_best_langs`.
|
10
|
+
|
11
|
+
|
12
|
+
Features
|
13
|
+
--------
|
14
|
+
|
15
|
+
The main task of `rack-i18n_routes` is the normalization of request paths.
|
16
|
+
|
17
|
+
Path normalization rewrites the URI (actually the `PATH_INFO`) so that that the
|
18
|
+
downstream applications will have to deal with the normalized path only, instead
|
19
|
+
of a myriad of localized paths.
|
20
|
+
|
21
|
+
|
22
|
+
Examples
|
23
|
+
--------
|
24
|
+
|
25
|
+
rack-i18n_routes works like any other Rack middleware component:
|
26
|
+
|
27
|
+
# in your server.ru rackup file
|
28
|
+
require 'rack/i18n_routes'
|
29
|
+
|
30
|
+
aliases = {
|
31
|
+
'articles' => {
|
32
|
+
'fra' => 'articles',
|
33
|
+
'spa' => ['artículos', 'articulos'],
|
34
|
+
|
35
|
+
:children => {
|
36
|
+
'the-victory' => {
|
37
|
+
'fra' => 'la-victoire',
|
38
|
+
'spa' => 'la-victoria',
|
39
|
+
},
|
40
|
+
'the-block' => {
|
41
|
+
'fra' => 'le-bloc',
|
42
|
+
'spa' => 'el-bloque',
|
43
|
+
},
|
44
|
+
},
|
45
|
+
},
|
46
|
+
|
47
|
+
'paintings' => {
|
48
|
+
'fra' => 'peintures',
|
49
|
+
'spa' => 'pinturas',
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
MAPPING_FN = Rack::I18nRoutes::AliasMapping.new(aliases)
|
54
|
+
|
55
|
+
use Rack::I18nRoutes, MAPPING_FN
|
56
|
+
run MyApp
|
57
|
+
|
58
|
+
Request to `/articulos/el-bloque`, `/articles/le-bloc` and even
|
59
|
+
`/articulos/le-bloc` will all be sent to `/articles/the-block`.
|
60
|
+
|
61
|
+
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.
|
64
|
+
|
65
|
+
|
66
|
+
Requirements
|
67
|
+
------------
|
68
|
+
|
69
|
+
No requirements outside Ruby >= 1.8.7 and Rack.
|
70
|
+
|
71
|
+
|
72
|
+
Install
|
73
|
+
-------
|
74
|
+
|
75
|
+
gem install rack-i18n_routes
|
76
|
+
|
77
|
+
|
78
|
+
Author
|
79
|
+
------
|
80
|
+
|
81
|
+
* Gioele Barabucci <http://svario.it/gioele> (initial author)
|
82
|
+
|
83
|
+
|
84
|
+
Development
|
85
|
+
-----------
|
86
|
+
|
87
|
+
Code
|
88
|
+
: <https://github.com/gioele/rack-i18n_routes>
|
89
|
+
|
90
|
+
Report issues
|
91
|
+
: <https://github.com/gioele/rack-i18n_routes/issues>
|
92
|
+
|
93
|
+
Documentation
|
94
|
+
: <http://rubydoc.info/gems/rack-i18n_routes>
|
95
|
+
|
96
|
+
|
97
|
+
License
|
98
|
+
-------
|
99
|
+
|
100
|
+
This is free software released into the public domain (CC0 license).
|
101
|
+
|
102
|
+
See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
|
103
|
+
for more details.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# This is free software released into the public domain (CC0 license).
|
2
|
+
#
|
3
|
+
# See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
|
4
|
+
# for more details.
|
5
|
+
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'bones'
|
9
|
+
rescue LoadError
|
10
|
+
abort '### Please install the "bones" gem ###'
|
11
|
+
end
|
12
|
+
|
13
|
+
Bones {
|
14
|
+
name 'rack-i18n_routes'
|
15
|
+
authors 'Gioele Barabucci'
|
16
|
+
email 'gioele@svario.it'
|
17
|
+
url 'https://github.com/gioele/rack-i18n_routes'
|
18
|
+
|
19
|
+
version '0.1'
|
20
|
+
|
21
|
+
ignore_file '.gitignore'
|
22
|
+
|
23
|
+
depend_on 'rack'
|
24
|
+
depend_on 'rack-test', :development => true
|
25
|
+
depend_on 'bones-rspec', :development => true
|
26
|
+
}
|
27
|
+
|
28
|
+
task :default => 'spec:run'
|
29
|
+
task 'gem:release' => 'spec:run'
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# This is free software released into the public domain (CC0 license).
|
2
|
+
#
|
3
|
+
# See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
|
4
|
+
# for more details.
|
5
|
+
|
6
|
+
|
7
|
+
require 'rack'
|
8
|
+
|
9
|
+
# A middleware component to route translated URLs to their canonical URL.
|
10
|
+
#
|
11
|
+
# @example Basic setup with `AliasMapping`
|
12
|
+
#
|
13
|
+
# require 'rack/i18n_routes'
|
14
|
+
#
|
15
|
+
# aliases = {
|
16
|
+
# 'articles' => {
|
17
|
+
# 'fra' => 'articles',
|
18
|
+
# 'spa' => ['artículos', 'articulos'],
|
19
|
+
#
|
20
|
+
# :children => {
|
21
|
+
# 'the-victory' => {
|
22
|
+
# 'fra' => 'la-victoire',
|
23
|
+
# 'spa' => 'la-victoria',
|
24
|
+
# },
|
25
|
+
# 'the-block' => {
|
26
|
+
# 'fra' => 'le-bloc',
|
27
|
+
# 'spa' => 'el-bloque',
|
28
|
+
# },
|
29
|
+
# },
|
30
|
+
# },
|
31
|
+
#
|
32
|
+
# 'paintings' => {
|
33
|
+
# 'fra' => 'peintures',
|
34
|
+
# 'spa' => 'pinturas',
|
35
|
+
# }
|
36
|
+
# }
|
37
|
+
#
|
38
|
+
# MAPPING_FN = Rack::I18nRoutes::AliasMapping.new(aliases)
|
39
|
+
#
|
40
|
+
# use Rack::I18nRoutes, MAPPING_FN
|
41
|
+
# run MyApp
|
42
|
+
#
|
43
|
+
# # /articulos/el-bloque => /articles/the-block
|
44
|
+
# # /articles/le-bloc => /articles/the-block
|
45
|
+
# # /articulos/le-block => /articles/the-block
|
46
|
+
|
47
|
+
class Rack::I18nRoutes
|
48
|
+
|
49
|
+
# Set up an i18n routing table.
|
50
|
+
#
|
51
|
+
# @overload initialize(app, url_mapper)
|
52
|
+
#
|
53
|
+
# Uses the `#map` method of `url_mapper` to derive the normalized
|
54
|
+
# path.
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
# aliases = {
|
58
|
+
# 'articles' => {
|
59
|
+
# 'fra' => 'articles',
|
60
|
+
# 'spa' => ['artículos', 'articulos'],
|
61
|
+
#
|
62
|
+
# :children => {
|
63
|
+
# 'the-victory' => {
|
64
|
+
# 'fra' => 'la-victoire',
|
65
|
+
# 'spa' => 'la-victoria',
|
66
|
+
# },
|
67
|
+
# 'the-block' => {
|
68
|
+
# 'fra' => 'le-bloc',
|
69
|
+
# 'spa' => 'el-bloque',
|
70
|
+
# },
|
71
|
+
# },
|
72
|
+
# },
|
73
|
+
# }
|
74
|
+
#
|
75
|
+
# MAPPING = Rack::I18nRoutes::AliasMapping.new(aliases)
|
76
|
+
# use Rack::I18nRoutes, MAPPING
|
77
|
+
#
|
78
|
+
# @param app the downstream Rack application
|
79
|
+
# @param [#map] url_mapper the mapper that will perform the
|
80
|
+
# path normalization
|
81
|
+
#
|
82
|
+
# @see Rack::I18nRoutes::AliasMapping#initialize
|
83
|
+
# @see Rack::I18nRoutes::AliasMappingUpdater#initialize
|
84
|
+
#
|
85
|
+
# @overload initialize(app, url_mapping_fn)
|
86
|
+
#
|
87
|
+
# Uses the passed function to derive the normalized path.
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# MAPPING_FN = Proc.new do |orig_path|
|
91
|
+
# orig_path.sub(r{^/it/}, '/italian/')
|
92
|
+
# orig_path.sub('/ristorante/', '/restaurant/')
|
93
|
+
# orig_path.sub('/ostello/', '/hostell/')
|
94
|
+
# end
|
95
|
+
# use Rack::I18nRoutes, MAPPING_FN
|
96
|
+
#
|
97
|
+
#
|
98
|
+
# @param app the downstream Rack application
|
99
|
+
# @param [Proc|lambda] url_mapping_fn the function used to
|
100
|
+
# perform the path
|
101
|
+
# normalization
|
102
|
+
|
103
|
+
def initialize(app, path_lookup)
|
104
|
+
@app = app
|
105
|
+
|
106
|
+
@path_lookup = path_lookup
|
107
|
+
end
|
108
|
+
|
109
|
+
def call(env)
|
110
|
+
path = env['PATH_INFO']
|
111
|
+
normalized_path = if @path_lookup.respond_to?(:map)
|
112
|
+
@path_lookup.map(path)
|
113
|
+
else
|
114
|
+
@path_lookup[path]
|
115
|
+
end
|
116
|
+
|
117
|
+
env['rack.i18n-routes_PATH_INFO'] = path
|
118
|
+
env['PATH_INFO'] = normalized_path
|
119
|
+
|
120
|
+
return @app.call(env)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
require 'rack/i18n_routes/alias_mapping'
|
125
|
+
require 'rack/i18n_routes/alias_mapping_updater'
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# This is free software released into the public domain (CC0 license).
|
2
|
+
#
|
3
|
+
# See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
|
4
|
+
# for more details.
|
5
|
+
|
6
|
+
|
7
|
+
# Describe translated paths as aliases for the normalized ones.
|
8
|
+
#
|
9
|
+
# To be used as a mapping object for {Rack::I18nRoutes}.
|
10
|
+
#
|
11
|
+
# If the list of aliases is not known at buildtime, you can use a
|
12
|
+
# {Rack::I18nRoutes::AliasMappingUpdater}.
|
13
|
+
|
14
|
+
class Rack::I18nRoutes::AliasMapping
|
15
|
+
|
16
|
+
# Create a new alias-based Mapping object.
|
17
|
+
#
|
18
|
+
# The aliases as stored in a hash. Each keys of the hash contain
|
19
|
+
# a normalized path; its value contains another hash that associates
|
20
|
+
# _ids_ to one or more translations. The special id `:children` is
|
21
|
+
# used to specify the aliases of subpaths.
|
22
|
+
#
|
23
|
+
# @example A basic set of aliases
|
24
|
+
#
|
25
|
+
# # "articles" is the normalized path; there are three available
|
26
|
+
# # translations: an french translation ("articles") and two
|
27
|
+
# # spanish translations ("artículos" and "articulos").
|
28
|
+
#
|
29
|
+
# 'articles' => {
|
30
|
+
# 'fra' => 'articles',
|
31
|
+
# 'spa' => ['artículos', 'articulos'],
|
32
|
+
# }
|
33
|
+
#
|
34
|
+
# @example A set of aliases with subpaths
|
35
|
+
#
|
36
|
+
# # a special id `:children` is used to specify the aliases of
|
37
|
+
# # subpaths
|
38
|
+
#
|
39
|
+
# 'articles' => {
|
40
|
+
# 'fra' => 'articles',
|
41
|
+
# 'spa' => ['artículos', 'articulos'],
|
42
|
+
#
|
43
|
+
# :children => {
|
44
|
+
# 'the-victory' => {
|
45
|
+
# 'fra' => 'la-victoire',
|
46
|
+
# 'spa' => 'la-victoria',
|
47
|
+
# },
|
48
|
+
# 'the-block' => {
|
49
|
+
# 'fra' => 'le-bloc',
|
50
|
+
# 'spa' => 'el-bloque',
|
51
|
+
# },
|
52
|
+
# },
|
53
|
+
# }
|
54
|
+
#
|
55
|
+
# @example An AliasMapping as mapping object in I18nRoutes
|
56
|
+
#
|
57
|
+
# aliases = {
|
58
|
+
# 'articles' => {
|
59
|
+
# 'fra' => 'articles',
|
60
|
+
# 'spa' => ['artículos', 'articulos'],
|
61
|
+
#
|
62
|
+
# :children => {
|
63
|
+
# 'the-victory' => {
|
64
|
+
# 'fra' => 'la-victoire',
|
65
|
+
# 'spa' => 'la-victoria',
|
66
|
+
# },
|
67
|
+
# 'the-block' => {
|
68
|
+
# 'fra' => 'le-bloc',
|
69
|
+
# 'spa' => 'el-bloque',
|
70
|
+
# },
|
71
|
+
# },
|
72
|
+
# },
|
73
|
+
# }
|
74
|
+
#
|
75
|
+
# MAPPING = Rack::I18nRoutes::AliasMapping.new(aliases)
|
76
|
+
# use Rack::I18nRoutes, MAPPING
|
77
|
+
#
|
78
|
+
# @param [Hash] aliases the aliases
|
79
|
+
|
80
|
+
def initialize(aliases)
|
81
|
+
@aliases = aliases
|
82
|
+
end
|
83
|
+
|
84
|
+
def map(path)
|
85
|
+
orig_pieces = path.split('/')
|
86
|
+
normalized_pieces = []
|
87
|
+
|
88
|
+
normalized_pieces << orig_pieces.shift
|
89
|
+
|
90
|
+
aliases = @aliases
|
91
|
+
|
92
|
+
orig_pieces.each do |orig_piece|
|
93
|
+
normalized = normalization_for(orig_piece, aliases)
|
94
|
+
replacement = (normalized || orig_piece)
|
95
|
+
|
96
|
+
normalized_pieces << replacement
|
97
|
+
|
98
|
+
if !aliases.nil?
|
99
|
+
subaliases = aliases[replacement]
|
100
|
+
aliases = subaliases[:children] unless subaliases.nil?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if path.end_with?('/')
|
105
|
+
normalized_pieces << ""
|
106
|
+
end
|
107
|
+
|
108
|
+
return normalized_pieces.join('/')
|
109
|
+
end
|
110
|
+
|
111
|
+
def normalization_for(piece, aliases)
|
112
|
+
if aliases.nil?
|
113
|
+
return nil
|
114
|
+
end
|
115
|
+
|
116
|
+
entities = aliases.keys
|
117
|
+
entities.each do |entity|
|
118
|
+
if piece == entity
|
119
|
+
return entity
|
120
|
+
end
|
121
|
+
|
122
|
+
subentities = aliases[entity].values.reject { |e| e.is_a? Hash }
|
123
|
+
if subentities.any? { |subentity| Array(subentity).any? { |sube| piece == sube } }
|
124
|
+
return entity
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
return nil
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# This is free software released into the public domain (CC0 license).
|
2
|
+
#
|
3
|
+
# See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
|
4
|
+
# for more details.
|
5
|
+
|
6
|
+
|
7
|
+
require 'rack/i18n_routes/alias_mapping'
|
8
|
+
|
9
|
+
# An AliasMapping that updates on every `#map` call.
|
10
|
+
#
|
11
|
+
# To be used as a mapping object for {Rack::I18nRoutes} when the aliases
|
12
|
+
# needed by {Rack::I18nRoutes::AliasMapping} cannot be statically generated
|
13
|
+
# at middleware buildtime.
|
14
|
+
|
15
|
+
class Rack::I18nRoutes::AliasMappingUpdater
|
16
|
+
|
17
|
+
# Creates a new alias-based Mapping object that updates its aliases
|
18
|
+
# on every path normalization.
|
19
|
+
#
|
20
|
+
# @example Update aliases for modified user-generated content
|
21
|
+
#
|
22
|
+
# update_fn = Proc.new do
|
23
|
+
# aliases = {}
|
24
|
+
#
|
25
|
+
# aliases['articles'] => {
|
26
|
+
# 'ita' => 'articoli',
|
27
|
+
# 'spa' => 'articulos',
|
28
|
+
# }
|
29
|
+
#
|
30
|
+
# if @articles.any { |article| article.changed? }
|
31
|
+
# @cached_articles_aliases = all_article_aliases()
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# aliases['articles'][:children] = @cached_articles_aliases
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# MAPPING = Rack::I18nRoutes::AliasMappingUpdater.new(update_fn)
|
38
|
+
# use Rack::I18nRoutes, MAPPING
|
39
|
+
#
|
40
|
+
# @example Delegate the aliases generation to another class
|
41
|
+
#
|
42
|
+
# update_fn = Proc.new { @translation_mngr.updated_aliases }
|
43
|
+
#
|
44
|
+
# MAPPING = Rack::I18nRoutes::AliasMappingUpdater.new(update_fn)
|
45
|
+
# use Rack::I18nRoutes, MAPPING
|
46
|
+
#
|
47
|
+
# @param [Proc] new_aliases_fn a parameter-less function that returns
|
48
|
+
# the new aliases
|
49
|
+
|
50
|
+
def initialize(new_aliases_fn)
|
51
|
+
@new_aliases_fn = new_aliases_fn
|
52
|
+
end
|
53
|
+
|
54
|
+
def map(path)
|
55
|
+
aliases = @new_aliases_fn[]
|
56
|
+
alias_mapping = Rack::I18nRoutes::AliasMapping.new(aliases)
|
57
|
+
|
58
|
+
return alias_mapping.map(path)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# This is free software released into the public domain (CC0 license).
|
2
|
+
#
|
3
|
+
# See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
|
4
|
+
# for more details.
|
5
|
+
|
6
|
+
|
7
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
8
|
+
|
9
|
+
TEST_ALIASES = {
|
10
|
+
'articles' => {
|
11
|
+
'fra' => 'articles',
|
12
|
+
'spa' => ['artículos', 'articulos'],
|
13
|
+
|
14
|
+
:children => {
|
15
|
+
'the-victory' => {
|
16
|
+
'fra' => 'la-victoire',
|
17
|
+
'spa' => 'la-victoria',
|
18
|
+
},
|
19
|
+
'the-block' => {
|
20
|
+
'fra' => 'le-bloc',
|
21
|
+
'spa' => 'el-bloque',
|
22
|
+
},
|
23
|
+
},
|
24
|
+
},
|
25
|
+
'paintings' => {
|
26
|
+
'fra' => 'peintures',
|
27
|
+
'spa' => 'pinturas',
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
describe Rack::I18nRoutes do
|
32
|
+
def app(*opts)
|
33
|
+
builder = Rack::Builder.new do
|
34
|
+
use Rack::Lint
|
35
|
+
use Rack::I18nRoutes, *opts
|
36
|
+
use Rack::Lint
|
37
|
+
|
38
|
+
run lambda { |env| [200, {"Content-Type" => "text/plain"}, [""]] }
|
39
|
+
end
|
40
|
+
|
41
|
+
return builder.to_app
|
42
|
+
end
|
43
|
+
|
44
|
+
def request_with(path, mapping_fn)
|
45
|
+
session = Rack::Test::Session.new(Rack::MockSession.new(app(mapping_fn)))
|
46
|
+
session.request(path)
|
47
|
+
|
48
|
+
return session.last_request
|
49
|
+
end
|
50
|
+
|
51
|
+
context "with an AliasMapping" do
|
52
|
+
let(:mapping) { Rack::I18nRoutes::AliasMapping.new(TEST_ALIASES) }
|
53
|
+
|
54
|
+
it "keep the same path when already normalized" do
|
55
|
+
env = request_with('/articles', mapping).env
|
56
|
+
path = env['PATH_INFO']
|
57
|
+
|
58
|
+
path.should == '/articles'
|
59
|
+
end
|
60
|
+
|
61
|
+
it "normalizes a path with 2 components" do
|
62
|
+
env = request_with('/articles/le-bloc', mapping).env
|
63
|
+
path = env['PATH_INFO']
|
64
|
+
|
65
|
+
path.should == '/articles/the-block'
|
66
|
+
end
|
67
|
+
|
68
|
+
it "normalizes a path that ends with a slash" do
|
69
|
+
env = request_with('/articulos/le-bloc/', mapping).env
|
70
|
+
path = env['PATH_INFO']
|
71
|
+
|
72
|
+
path.should == '/articles/the-block/'
|
73
|
+
end
|
74
|
+
|
75
|
+
it "does not change unknown paths" do
|
76
|
+
env = request_with('/foobar', mapping).env
|
77
|
+
path = env['PATH_INFO']
|
78
|
+
|
79
|
+
path.should == '/foobar'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "with an AliasMappingUpdater" do
|
84
|
+
let(:mapping) { Rack::I18nRoutes::AliasMappingUpdater.new(Proc.new { TEST_ALIASES }) }
|
85
|
+
|
86
|
+
it "keep the same path when already normalized" do
|
87
|
+
env = request_with('/articles', mapping).env
|
88
|
+
path = env['PATH_INFO']
|
89
|
+
|
90
|
+
path.should == '/articles'
|
91
|
+
end
|
92
|
+
|
93
|
+
it "normalizes a path with 2 components" do
|
94
|
+
env = request_with('/articles/le-bloc', mapping).env
|
95
|
+
path = env['PATH_INFO']
|
96
|
+
|
97
|
+
path.should == '/articles/the-block'
|
98
|
+
end
|
99
|
+
|
100
|
+
it "normalizes a path that ends with a slash" do
|
101
|
+
env = request_with('/articulos/le-bloc/', mapping).env
|
102
|
+
path = env['PATH_INFO']
|
103
|
+
|
104
|
+
path.should == '/articles/the-block/'
|
105
|
+
end
|
106
|
+
|
107
|
+
it "does not change unknown paths" do
|
108
|
+
env = request_with('/foobar', mapping).env
|
109
|
+
path = env['PATH_INFO']
|
110
|
+
|
111
|
+
path.should == '/foobar'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This is free software released into the public domain (CC0 license).
|
2
|
+
#
|
3
|
+
# See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
|
4
|
+
# for more details.
|
5
|
+
|
6
|
+
|
7
|
+
LIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib]))
|
8
|
+
$LOAD_PATH.unshift(LIB_DIR) unless $LOAD_PATH.include?(LIB_DIR)
|
9
|
+
|
10
|
+
require 'rack/i18n_routes'
|
11
|
+
require 'rack/test'
|
12
|
+
|
13
|
+
include Rack::Test::Methods
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
end
|
17
|
+
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-i18n_routes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 9
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: "0.1"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Gioele Barabucci
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-08-05 00:00:00 Z
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: rack
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 5
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 4
|
31
|
+
- 1
|
32
|
+
version: 1.4.1
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rack-test
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 5
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
- 6
|
47
|
+
- 1
|
48
|
+
version: 0.6.1
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: bones-rspec
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 13
|
60
|
+
segments:
|
61
|
+
- 2
|
62
|
+
- 0
|
63
|
+
- 1
|
64
|
+
version: 2.0.1
|
65
|
+
type: :development
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: bones
|
69
|
+
prerelease: false
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 39
|
76
|
+
segments:
|
77
|
+
- 3
|
78
|
+
- 8
|
79
|
+
- 0
|
80
|
+
version: 3.8.0
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id004
|
83
|
+
description: |-
|
84
|
+
rack-i18n_routes is a Rack middleware component that internally re-routes URLS
|
85
|
+
that have been translated into untranslated or canonical URL.
|
86
|
+
email: gioele@svario.it
|
87
|
+
executables: []
|
88
|
+
|
89
|
+
extensions: []
|
90
|
+
|
91
|
+
extra_rdoc_files: []
|
92
|
+
|
93
|
+
files:
|
94
|
+
- .yardopts
|
95
|
+
- COPYING
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- lib/rack/i18n_routes.rb
|
99
|
+
- lib/rack/i18n_routes/alias_mapping.rb
|
100
|
+
- lib/rack/i18n_routes/alias_mapping_updater.rb
|
101
|
+
- spec/i18n_routes_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
homepage: https://github.com/gioele/rack-i18n_routes
|
104
|
+
licenses: []
|
105
|
+
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options:
|
108
|
+
- --main
|
109
|
+
- README.md
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
version: "0"
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
requirements: []
|
131
|
+
|
132
|
+
rubyforge_project: rack-i18n_routes
|
133
|
+
rubygems_version: 1.8.24
|
134
|
+
signing_key:
|
135
|
+
specification_version: 3
|
136
|
+
summary: rack-i18n_routes is a Rack middleware component that internally re-routes URLS that have been translated into untranslated or canonical URL.
|
137
|
+
test_files: []
|
138
|
+
|