rack-i18n_best_langs 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.yardopts ADDED
@@ -0,0 +1,4 @@
1
+ -m markdown
2
+ --no-private
3
+ -
4
+ COPYING
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,176 @@
1
+ rack-i8n_best_langs: guess best language for content served over Rack
2
+ =====================================================================
3
+
4
+ rack-i18n_best_langs is a Rack middleware component that takes care of
5
+ understanding what are the best languages for a site visitor.
6
+
7
+ If you manage a site that has content many languages and also localized URLs,
8
+ you will find `rack-i18n_best_langs` very useful, especially when used in
9
+ conjunction with `rack-i18n_routes`.
10
+
11
+
12
+ Features
13
+ --------
14
+
15
+ Language discovery is done using three clues:
16
+
17
+ * the presences of language tags in paths (e.g. `/service/warranty/ita`),
18
+ * the content of the HTTP `Accept-Language` header,
19
+ * the content of the `rack.i18n_best_langs` cookie when set.
20
+
21
+ All these clues are taken into account and evaluated against the list
22
+ of languages available and their preferred order. It is possible to configure
23
+ which of these clues is the most important.
24
+
25
+ An additional clue is available when `AliasMapping` is used as the mapping
26
+ function: the language in which the path is written. For
27
+ example, `/articles/the-victory` is English, `/artículos/la-victoria`, is
28
+ Spanish, `/articles/la-victoire` is French.
29
+
30
+
31
+ Examples
32
+ --------
33
+
34
+ rack-i18n_best_langs works like any other Rack middleware component.
35
+
36
+ # in your server.ru rackup file
37
+ require 'rack/i18n_best_langs'
38
+
39
+ FAVORITE_LANGUAGES = %w(eng spa deu fra)
40
+
41
+ use Rack::I18nBestLangs, FAVORITE_LANGUAGES
42
+ run MyApp
43
+
44
+ In your application you will find the list of languages that should be used to
45
+ serve the content, arranged from the most favorite to the least in the
46
+ `rack.i18n_best_langs` Rack variable. It is then up to downstream application
47
+ to use this information in the best way.
48
+
49
+ ### See the guessed languages
50
+
51
+ This small application
52
+
53
+ # in your server.ru rackup file
54
+ require 'rack/i18n_best_langs'
55
+
56
+ FAVORITE_LANGUAGES = %w(eng spa deu)
57
+
58
+ use Rack::I18nBestLangs, FAVORITE_LANGUAGES
59
+
60
+ app = Proc.new do |env|
61
+ langs = env['rack.i18n_best_langs']
62
+ [200, {"Content-Type" => "text/plain"}, [langs.inspect] ]
63
+ end
64
+
65
+ run app
66
+
67
+ will produce the following results for these URLs.
68
+
69
+ # /foo =>
70
+ # [#<LocaleCode 'eng'>, #<LocaleCode 'spa'>, #<LocaleCode 'deu'>]
71
+
72
+ # /foo/spa =>
73
+ # [#<LocaleCode 'spa'>, #<LocaleCode 'eng'>, #<LocaleCode 'deu'>]
74
+
75
+ # /foo (with Accept-Language = it-IT, es-ES, fr-FR) =>
76
+ # [#<LocaleCode 'spa'>, #<LocaleCode 'eng'>, #<LocaleCode 'deu'>]
77
+
78
+ # /foo/deu (with Accept-Language = it-IT, es-ES) =>
79
+ # [#<LocaleCode 'deu'>, #<LocaleCode 'spa'>, #<LocaleCode 'eng'>]
80
+
81
+ # /foo (with cookie set to 'deu') =>
82
+ # [#<LocaleCode 'deu'>, #<LocaleCode 'eng'>, #<LocaleCode 'spa'>]
83
+
84
+ # /foo/spa (with cookie set to 'deu') =>
85
+ # [#<LocaleCode 'deu'>, #<LocaleCode 'spa'>, #<LocaleCode 'eng'>]
86
+
87
+
88
+ ### Changing the clues' weights
89
+
90
+ You can tune the weights of the clues to set which clue is the most important.
91
+
92
+ The default order of importance and weights are
93
+
94
+ * language set in cookie (`:cookie`): 3
95
+ * language present in tag (`:path`): 2
96
+ * language is in `Accept-Language` header (`:header`): 1
97
+
98
+ You can change these weight with the `:weights` option.
99
+
100
+ FAVORITE_LANGUAGES = %w(eng spa deu)
101
+ WEIGHTS = { :path => 3, :header => 2, :cookie = 1 }
102
+
103
+ use Rack::I18nBestLangs, FAVORITE_LANGUAGES, :weights => WEIGHTS
104
+
105
+
106
+ ### Using `AliasMapping`
107
+
108
+ If you want to use the content of the URI path as an additional clue to guess
109
+ the best languages, use an `AliasMapping` function as path mapping function.
110
+
111
+ # in your server.ru rackup file
112
+ require 'rack/i18n_best_langs'
113
+ require 'rack/i18n_routes/alias_mapping'
114
+
115
+ FAVORITE_LANGUAGES = %w(eng spa deu fra)
116
+
117
+ aliases = {
118
+ 'articles' => {
119
+ 'fra' => 'articles',
120
+ 'spa' => ['artículos', 'articulos']
121
+
122
+ :children => {
123
+ 'the-victory' => {
124
+ 'fra' => 'la-victoire',
125
+ 'spa' => 'la-victoria'
126
+ }
127
+ 'the-block' => {
128
+ 'fra' => 'le-bloc',
129
+ 'spa' => 'el-bloque'
130
+ }
131
+ }
132
+ }
133
+ }
134
+ MAPPING = Rack::I18nRoutes::AliasMapping.new(paths, :default => 'eng')
135
+
136
+ use Rack::I18nBestLangs, FAVORITE_LANGUAGES, :path_mapping_fn => MAPPING
137
+ run MyApp
138
+
139
+
140
+ Requirements
141
+ ------------
142
+
143
+ No requirements outside Ruby >= 1.8.7 and Rack.
144
+
145
+
146
+ Install
147
+ -------
148
+
149
+ gem install rack-i18n_best_langs
150
+
151
+
152
+ Author
153
+ ------
154
+
155
+ * Gioele Barabucci <http://svario.it/gioele> (initial author)
156
+
157
+ Development
158
+ -----------
159
+
160
+ Code
161
+ : <https://github.com/gioele/rack-i18n_best_langs>
162
+
163
+ Report issues
164
+ : <https://github.com/gioele/rack-i18n_best_langs/issues>
165
+
166
+ Documentation
167
+ : <http://rubydoc.info/gems/rack-i18n_best_langs>
168
+
169
+
170
+ License
171
+ -------
172
+
173
+ This is free software released into the public domain (CC0 license).
174
+
175
+ See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
176
+ for more details.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
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_best_langs'
15
+ authors 'Gioele Barabucci'
16
+ email 'gioele@svario.it'
17
+ url 'https://github.com/gioele/rack-i18n_best_langs'
18
+
19
+ version '0.2'
20
+
21
+ ignore_file '.gitignore'
22
+
23
+ depend_on 'rack'
24
+ depend_on 'rack-test', :development => true
25
+ depend_on 'rack-i18n_routes', :development => true
26
+ depend_on 'bones-rspec', :development => true
27
+ }
28
+
29
+ task :default => 'spec:run'
30
+ task 'gem:release' => 'spec:run'
@@ -0,0 +1,213 @@
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/language_tag.rb'
8
+
9
+ module Rack
10
+
11
+ class I18nBestLangs
12
+ RACK_VARIABLE = 'rack.i18n_best_langs'
13
+
14
+ # Create a new I18nBestLangs middleware component.
15
+ #
16
+ # @param [[String]] avail_languages
17
+ #
18
+ # @param [Hash] opts
19
+ # @option opts [Hash{Symbol => Integer}] :weights Weights for clues
20
+ # (the higher, the most
21
+ # important): `:header`,
22
+ # `:path`, `:cookie`, `:aliases_path`.
23
+ # @option opts [#map_with_langs] :path_mapping_fn A function that maps
24
+ # localized URI paths
25
+ # into normalized paths,
26
+ # should be a
27
+ # Rack::I18nRoutes::AliasMapping.
28
+
29
+ def initialize(app, avail_languages, opts = {})
30
+ @app = app
31
+
32
+ score_base = avail_languages.length
33
+
34
+ weights = opts[:weights] || {}
35
+ weight_header = weights[:header] || 1
36
+ weight_aliases_path = weights[:aliases_path] || 2
37
+ weight_path = weights[:path] || 3
38
+ weight_cookie = weights[:cookie] || 4
39
+
40
+ @score_for_header = score_base * (10 ** weight_header)
41
+ @score_for_aliases_path = score_base * (10 ** weight_aliases_path)
42
+ @score_for_path = score_base * (10 ** weight_path)
43
+ @score_for_cookie = score_base * (10 ** weight_cookie)
44
+
45
+ @avail_languages = {}
46
+ avail_languages.each_with_index do |lang, i|
47
+ code = LanguageTag.new(lang).freeze
48
+ score = score_base - i
49
+
50
+ @avail_languages[code] = score
51
+ end
52
+
53
+ @language_path_regex = regex_for_languages_in_path
54
+
55
+ @path_mapping_fn = opts[:path_mapping_fn]
56
+ end
57
+
58
+ def call(env)
59
+ lang_info = find_best_languages(env)
60
+ env[I18nBestLangs::RACK_VARIABLE] = lang_info[:languages]
61
+ env['PATH_INFO'] = lang_info[:path_info]
62
+
63
+ return @app.call(env)
64
+ end
65
+
66
+ def find_best_languages(env)
67
+ path = env['PATH_INFO']
68
+ accept_language_header = extract_language_header(env)
69
+ cookies = extract_language_cookie(env)
70
+
71
+ clean_path_info = remove_language_from_path(path)
72
+
73
+ langs = @avail_languages.dup
74
+ add_score_for_path(path, langs)
75
+ add_score_for_accept_language_header(accept_language_header, langs)
76
+ add_score_for_cookie(cookies, langs)
77
+ add_score_for_aliases_path(path, langs)
78
+
79
+ sorted_langs = langs.to_a.sort_by { |lang_info| -(lang_info[1]) }.map(&:first)
80
+
81
+ info = {
82
+ :languages => sorted_langs,
83
+ :path_info => clean_path_info,
84
+ }
85
+
86
+ return info
87
+ end
88
+
89
+ def extract_language_header(env)
90
+ header = env['HTTP_ACCEPT_LANGUAGE']
91
+
92
+ if (header =~ HEADER_FORMAT)
93
+ return header
94
+ else
95
+ # FIXME: env.warn
96
+ return ""
97
+ end
98
+ end
99
+
100
+ def extract_language_cookie(env)
101
+ return Rack::Request.new(env).cookies[I18nBestLangs::RACK_VARIABLE]
102
+ end
103
+
104
+ def remove_language_from_path(path)
105
+ return path.sub(@language_path_regex, '')
106
+ end
107
+
108
+ def add_score_for_path(path, langs)
109
+ path_match = path.match(@language_path_regex)
110
+
111
+ path_include_language = !path_match.nil?
112
+ if !path_include_language
113
+ return
114
+ end
115
+
116
+ lang_code = LanguageTag.new(path_match[1])
117
+ langs[lang_code] += @score_for_path
118
+ end
119
+
120
+ def add_score_for_accept_language_header(accept_language_header, langs)
121
+ if accept_language_header.nil? || !valid_language_header(accept_language_header)
122
+ return
123
+ end
124
+
125
+ header_langs = languages_in_accept_language(accept_language_header)
126
+
127
+ header_langs.each do |lang, q|
128
+ if !langs.include?(lang)
129
+ next
130
+ end
131
+
132
+ langs[lang] += @score_for_header * q
133
+ end
134
+ end
135
+
136
+ def add_score_for_cookie(cookie, langs)
137
+ if cookie.nil?
138
+ return
139
+ end
140
+
141
+ cookie_langs = cookie.split(',').map { |tag| LanguageTag.parse(tag) }
142
+
143
+ cookie_langs.reverse.each_with_index do |lang, idx|
144
+ if !langs.include?(lang)
145
+ next
146
+ end
147
+
148
+ importance = idx + 1
149
+ langs[lang] += @score_for_cookie * importance
150
+ end
151
+ end
152
+
153
+ def add_score_for_aliases_path(path, langs)
154
+ if !@path_mapping_fn.respond_to?(:map_with_langs)
155
+ return
156
+ end
157
+
158
+ ph, aliases_langs = @path_mapping_fn.map_with_langs(path)
159
+ aliases_langs.map! { |tag| LanguageTag.parse(tag) }
160
+
161
+ lang_uses = aliases_langs.inject(Hash.new(0)) {|freq, lang| freq[lang] += 1; freq }
162
+ lang_uses.sort_by { |lang, freq| -freq }.each do |lang, freq|
163
+ if !langs.include?(lang)
164
+ next
165
+ end
166
+
167
+ langs[lang] += @score_for_aliases_path * freq
168
+ end
169
+ end
170
+
171
+ def valid_language_header(accept_language_header)
172
+ return true # FIXME: check with regex
173
+ end
174
+
175
+ def languages_in_accept_language(accept_language_header)
176
+ raw_langs = accept_language_header.split(',')
177
+
178
+ langs = raw_langs.map { |l| l.sub('q=', '')}.
179
+ map { |l| l.split(';') }
180
+
181
+ langs.each_with_index do |l, i|
182
+ l[0] = LanguageTag.parse(l[0])
183
+ l[1] = (l[1] || 1).to_f
184
+
185
+ sorting_epsilon = (langs.size - i).to_f / 100
186
+ l[1] += sorting_epsilon # keep the original order when sorting
187
+ end
188
+
189
+ return langs
190
+ end
191
+
192
+ def regex_for_languages_in_path
193
+ all_languages = @avail_languages.keys.map(&:alpha3)
194
+
195
+ preamble = "/"
196
+ body = "(" + all_languages.join("|") + ")"
197
+ trail = "/?$"
198
+
199
+ return Regexp.new(preamble + body + trail)
200
+ end
201
+
202
+
203
+ def self.accept_language_format
204
+ lang = '[-_a-zA-Z]+'
205
+ qvalue = '(; ?q=[01]+(\.[0-9]{1,3})?)'
206
+
207
+ return Regexp.new("\\A#{lang}#{qvalue}?(, ?#{lang}#{qvalue}?)*\\Z")
208
+ end
209
+
210
+ HEADER_FORMAT = self.accept_language_format
211
+ end
212
+
213
+ end
@@ -0,0 +1,84 @@
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
+ # BCP 47
8
+
9
+ class LanguageTag
10
+ VERSION = "5646.0.1"
11
+
12
+ A3_TO_A2 = {
13
+ 'ara' => 'ar',
14
+ 'deu' => 'de',
15
+ 'eng' => 'en',
16
+ 'fra' => 'fr',
17
+ 'ita' => 'it',
18
+ }
19
+
20
+ def self.parse(raw_code)
21
+ iso_code = raw_code.split('-').flatten.first
22
+
23
+ if iso_code.nil?
24
+ raise "Unparseable language tag"
25
+ end
26
+
27
+ if !(A3_TO_A2.keys + A3_TO_A2.values).include?(iso_code)
28
+ raise "Unknown language tag #{iso_code}" #FIXME
29
+ end
30
+
31
+ return LanguageTag.new(iso_code)
32
+ end
33
+
34
+ def initialize(iso_code, extlang = nil, script = nil, region = nil, variant = [], extension = [], privateuse = nil)
35
+ case iso_code.length
36
+ when 3
37
+ @alpha3 = iso_code
38
+ when 2
39
+ @alpha2 = iso_code
40
+ end
41
+ end
42
+
43
+ def alpha2
44
+ @alpha2 ||= a3_to_a2(@alpha3)
45
+ end
46
+
47
+ def alpha3
48
+ @alpha3 ||= a2_to_a3(@alpha2)
49
+ end
50
+
51
+ def a3_to_a2(alpha3)
52
+ return A3_TO_A2[alpha3]
53
+ end
54
+
55
+ def a2_to_a3(alpha2)
56
+ return A3_TO_A2.invert[alpha2]
57
+ end
58
+
59
+ def complete
60
+ alpha3
61
+ end
62
+
63
+ def ==(other_code)
64
+ return self.complete == LanguageTag.new(other_code).complete
65
+ end
66
+
67
+ def hash
68
+ self.complete.hash
69
+ end
70
+
71
+ def eql?(other)
72
+ if self.equal?(other)
73
+ return true
74
+ elsif self.class != other.class
75
+ return false
76
+ end
77
+
78
+ return self.alpha3 == other.alpha3
79
+ end
80
+
81
+ def inspect
82
+ return "#<LocaleCode '#{complete}'>"
83
+ end
84
+ end
@@ -0,0 +1,132 @@
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
+ describe Rack::I18nBestLangs do
10
+ AVAIL_LANGUAGES = ['ita', 'fra', 'eng', 'ara']
11
+
12
+ def app(*opts)
13
+ builder = Rack::Builder.new do
14
+ use Rack::Lint
15
+ use Rack::I18nBestLangs, *opts
16
+ use Rack::Lint
17
+
18
+ run lambda { |env| [200, {"Content-Type" => "text/plain"}, [""]] }
19
+ end
20
+
21
+ return builder.to_app
22
+ end
23
+
24
+ def request_with(path, env_opts = {}, *i18n_opts)
25
+ if i18n_opts.empty?
26
+ extra_opts = {}
27
+
28
+ i18n_opts << AVAIL_LANGUAGES # known_languages
29
+ i18n_opts << extra_opts
30
+ end
31
+
32
+ session = Rack::Test::Session.new(Rack::MockSession.new(app(*i18n_opts)))
33
+ session.request(path, env_opts)
34
+
35
+ return session.last_request
36
+ end
37
+
38
+ def http_langs(*langs)
39
+ { 'HTTP_ACCEPT_LANGUAGE' => langs.flatten.join(', ') }
40
+ end
41
+
42
+ def cookie_langs(*langs)
43
+ { 'HTTP_COOKIE' => Rack::I18nBestLangs::RACK_VARIABLE + "=" + langs.flatten.join(',') }
44
+ end
45
+
46
+ def aliases_mapping(aliases, default)
47
+ mapping = Rack::I18nRoutes::AliasMapping.new(aliases, :default => default)
48
+
49
+ return { :path_mapping_fn => mapping }
50
+ end
51
+
52
+ context "with no external information" do
53
+ it "suggests exactly the list of languages" do
54
+ env = request_with('/').env
55
+ languages = env[Rack::I18nBestLangs::RACK_VARIABLE]
56
+
57
+ languages.should be_an Array
58
+ languages.should == AVAIL_LANGUAGES
59
+ end
60
+
61
+ it "is not confused by paths that look like languages" do
62
+ env = request_with('/francesca').env
63
+ languages = env[Rack::I18nBestLangs::RACK_VARIABLE]
64
+
65
+ languages.should == AVAIL_LANGUAGES
66
+ end
67
+ end
68
+
69
+ context "with language in path" do
70
+ it "places that language as best language when available" do
71
+ env = request_with('/fra/').env
72
+ languages = env[Rack::I18nBestLangs::RACK_VARIABLE]
73
+
74
+ languages.first.should eq('fra')
75
+ languages.should include(*AVAIL_LANGUAGES)
76
+ end
77
+
78
+ it "ignores that language when not available" do
79
+ env = request_with('/lat/').env
80
+ languages = env[Rack::I18nBestLangs::RACK_VARIABLE]
81
+
82
+ languages.should == AVAIL_LANGUAGES
83
+ end
84
+
85
+ it "selects the first path component" do
86
+ env = request_with('http://italia.example.org/foo/fra').env
87
+ languages = env[Rack::I18nBestLangs::RACK_VARIABLE]
88
+
89
+ languages.first.should eq('fra')
90
+ languages.should include(*AVAIL_LANGUAGES)
91
+ end
92
+
93
+ it "removes the language from the path" do
94
+ env = request_with('/foo/fra').env
95
+ languages = env[Rack::I18nBestLangs::RACK_VARIABLE]
96
+
97
+ languages.first.should eq('fra')
98
+ env['PATH_INFO'].should eq('/foo')
99
+ end
100
+ end
101
+
102
+ context "with language in headers" do
103
+ it "places that language as best language when available" do
104
+ env = request_with('/hello', http_langs('fr-FR')).env
105
+ languages = env[Rack::I18nBestLangs::RACK_VARIABLE]
106
+
107
+ languages.first.should == 'fra'
108
+ end
109
+ end
110
+
111
+ context "with language in cookie" do
112
+ it "places that language as best language when available" do
113
+ env = request_with('/hello', cookie_langs('eng')).env
114
+ languages = env[Rack::I18nBestLangs::RACK_VARIABLE]
115
+
116
+ languages.first.should == 'eng'
117
+ end
118
+ end
119
+
120
+ context "with language implied in path and AliasMapper" do
121
+ let(:aliases) { Hash['house' => { 'ita' => 'casa', 'fra' => 'maison' }] }
122
+ let(:default_lang) { 'unk' }
123
+
124
+ it "places the most common non-default language as best language" do
125
+ env = request_with('/maison', {}, AVAIL_LANGUAGES, aliases_mapping(aliases, default_lang)).env
126
+ languages = env[Rack::I18nBestLangs::RACK_VARIABLE]
127
+
128
+ languages.first.should == 'fra'
129
+ end
130
+ end
131
+ end
132
+
@@ -0,0 +1,18 @@
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_best_langs'
11
+ require 'rack/i18n_routes'
12
+ require 'rack/test'
13
+
14
+ include Rack::Test::Methods
15
+
16
+ RSpec.configure do |config|
17
+ end
18
+
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-i18n_best_langs
3
+ version: !ruby/object:Gem::Version
4
+ hash: 15
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ version: "0.2"
10
+ platform: ruby
11
+ authors:
12
+ - Gioele Barabucci
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-08-07 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: rack-i18n_routes
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 959707343
60
+ segments:
61
+ - 0
62
+ - 3
63
+ - dev
64
+ version: 0.3.dev
65
+ type: :development
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: bones-rspec
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 13
76
+ segments:
77
+ - 2
78
+ - 0
79
+ - 1
80
+ version: 2.0.1
81
+ type: :development
82
+ version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ name: bones
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 39
92
+ segments:
93
+ - 3
94
+ - 8
95
+ - 0
96
+ version: 3.8.0
97
+ type: :development
98
+ version_requirements: *id005
99
+ description: |-
100
+ rack-i18n_best_langs is a Rack middleware component that takes care of
101
+ understanding what are the best languages for a site visitor.
102
+ email: gioele@svario.it
103
+ executables: []
104
+
105
+ extensions: []
106
+
107
+ extra_rdoc_files: []
108
+
109
+ files:
110
+ - .yardopts
111
+ - COPYING
112
+ - README.md
113
+ - Rakefile
114
+ - lib/rack/i18n_best_langs.rb
115
+ - lib/rack/language_tag.rb
116
+ - spec/i18n-best-langs_spec.rb
117
+ - spec/spec_helper.rb
118
+ homepage: https://github.com/gioele/rack-i18n_best_langs
119
+ licenses: []
120
+
121
+ post_install_message:
122
+ rdoc_options:
123
+ - --main
124
+ - README.md
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ hash: 3
133
+ segments:
134
+ - 0
135
+ version: "0"
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ requirements: []
146
+
147
+ rubyforge_project: rack-i18n_best_langs
148
+ rubygems_version: 1.8.24
149
+ signing_key:
150
+ specification_version: 3
151
+ summary: rack-i18n_best_langs is a Rack middleware component that takes care of understanding what are the best languages for a site visitor.
152
+ test_files: []
153
+