Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 390
- Log:
Initial import of Canvass, a donations-based poll application.
- Author:
- rool
- Date:
- Mon Mar 21 14:58:04 +0000 2011
- Size:
- 9791 Bytes
1 | ######################################################################## |
2 | # File:: translation.rb |
3 | # (C):: Hipposoft 2009, 2010, 2011 |
4 | # |
5 | # Purpose:: Manage model-like aspects of translations, although this is |
6 | # not an ActiveRecord sub-class as it has no representation |
7 | # in the database. |
8 | # ---------------------------------------------------------------------- |
9 | # 16-Feb-2011 (ADH): Imported from Artisan. |
10 | ######################################################################## |
11 | |
12 | class Translation |
13 | |
14 | # =========================================================================== |
15 | # CHARACTERISTICS |
16 | # =========================================================================== |
17 | |
18 | # None (this is not an ActiveRecord sub-class). |
19 | |
20 | # =========================================================================== |
21 | # TRANSLATION |
22 | # =========================================================================== |
23 | |
24 | # If Model.respond_to?( :columns_for_translation ) then call with the name |
25 | # of a column to get a locale-specific version returned (will be the same |
26 | # name if the column name isn't translated, or will have a locale suffix). |
27 | # |
28 | def self.translated_column( model, name ) |
29 | if ( model.columns_for_translation.include?( name.to_s ) ) |
30 | model.column_name_localized( name.to_s ) |
31 | else |
32 | name |
33 | end |
34 | end |
35 | |
36 | # Reverse of "translated_column". Slow! Do not call frequently. |
37 | # |
38 | def self.untranslated_column( model, name_with_locale ) |
39 | for name in model.columns_for_translation() |
40 | names_with_locales = model.available_translatable_columns_of( name ) |
41 | return name if names_with_locales.include?( name_with_locale.to_s ) |
42 | end |
43 | |
44 | return name_with_locale |
45 | end |
46 | |
47 | # =========================================================================== |
48 | # PERMISSIONS |
49 | # =========================================================================== |
50 | |
51 | # Agents (and administrators too, since "is_agent?" returns "true" for both) |
52 | # are allowed to modify translations. |
53 | # |
54 | def self.can_modify?( user, ignored ) |
55 | user.try( :is_agent? ) |
56 | end |
57 | |
58 | # =========================================================================== |
59 | # GENERAL |
60 | # =========================================================================== |
61 | |
62 | # Given an RFC 4646 compliant language code return the best match of actually |
63 | # available language code that we can find. You can pass the code as a string, |
64 | # a symbol, or an array of strings and/or symbols (mixtures of both are OK). |
65 | # If using an array, the codes are assumed to be given in preference order, |
66 | # most preferred first. A single, valid, currently available locale code |
67 | # (according to the Rails I18n module) is always returned, as a Ruby Symbol. |
68 | # |
69 | # See class function 'set_best_locale' if you want to actually apply this |
70 | # code to use as the current locale. |
71 | # |
72 | # http://www.ietf.org/rfc/rfc4646.txt |
73 | # http://www.w3.org/International/articles/language-tags/ |
74 | # http://guides.rubyonrails.org/i18n.html |
75 | # |
76 | # The matching strategy of RFC 4647 indicates that more specific language |
77 | # tag should not match a less specific one. "de" matches "de-CH", say, but |
78 | # "de-CH" should not match "de". Meanwhile, "zh-Hant" matches "zh-Hant-TW", |
79 | # but not, say, "zh-Hans" or "zh-TW". That is, the script seems to be more |
80 | # important than the region; importance decreases from left to right. |
81 | # |
82 | # http://www.ietf.org/rfc/rfc4647.txt |
83 | # http://www.w3.org/International/articles/bcp47/ |
84 | # |
85 | # Artisan needs to run the matching algorithm backwards. It tries to find a |
86 | # language that matches exactly then looks to more and more general solutions. |
87 | # We make an intelligent guess at the best approach given the RFC guidance. |
88 | # |
89 | # * Note that all matches should be case insensitive. |
90 | # * If we get an exact match, use it immediately. |
91 | # * Use the Locale gem to parse the language tag using proper REGEXPs (see |
92 | # below for URLs related to the Locale gem). |
93 | # * Reconstruct a tag using just the language, script and region. This strips |
94 | # out any other data which we might not have recognised. |
95 | # * If we get a match of this, use it (implies variant(s) etc. not matched) |
96 | # * Try getting rid of the region. Match? Use it. |
97 | # * We're left with language and script. If the user has a matching language |
98 | # and region but in a different script, there's a strong chance they can't |
99 | # read it and dropping back to the default locale is preferable. |
100 | # |
101 | # http://www.yotabanana.com/hiki/ruby-locale-rails.html |
102 | # http://rubyforge.org/projects/locale/ |
103 | # |
104 | # When presented with an array of codes, the function first tries an exact |
105 | # match for each code in preference order, then tries the above generalised |
106 | # matching algortihm for each code. If all attempts to match fail, it returns |
107 | # the default locale. |
108 | # |
109 | # When calling this function, give thought to the source of the code or codes |
110 | # passed in. These may be derived from a relevant location or, for example, |
111 | # from the HTTP accept-language header (see the http_accept_language plugin, |
112 | # which is installed and available within Artisan). |
113 | # |
114 | # http://github.com/iain/http_accept_language/tree/master |
115 | # |
116 | # Note that behaviour is undefined if the function is given an empty array, |
117 | # string, or any other unspecified input type or value. |
118 | # |
119 | # Further reading: |
120 | # |
121 | # http://www.iana.org/assignments/language-subtag-registry |
122 | # http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes |
123 | # http://en.wikipedia.org/wiki/List_of_official_languages |
124 | # http://en.wikipedia.org/wiki/List_of_countries_by_native_names |
125 | # http://blog.grayproductions.net/articles/understanding_m17n |
126 | # |
127 | def self.select_best_locale( code_or_codes ) |
128 | available_locales = I18n.available_locales() # All symbols, no strings |
129 | default_locale = I18n.default_locale() # A symbol, not a string |
130 | downcase_locales = available_locales.map() { | l | l.to_s.downcase } |
131 | |
132 | # Coerce code_or_codes to a flat array and make all entries into strings. |
133 | |
134 | codes = [ code_or_codes ].flatten |
135 | codes.map!() { | code | code.to_s } |
136 | |
137 | # Try for an *exact* match from the preference-ordered array of codes. |
138 | # Since the matches must be case-insensitive but the returned code must |
139 | # be of correct case to obtain the right language file, we can't just use |
140 | # array intersection (the '&' operator) - instead, have to step through |
141 | # each item slowly. |
142 | |
143 | for code in codes |
144 | found_at = downcase_locales.index( code.to_s.downcase ) |
145 | return available_locales[ found_at ] unless found_at.nil? |
146 | end |
147 | |
148 | # Define a small procedure which builds a locale tag using the given |
149 | # language, script and region - the last two parameters may be 'nil'. |
150 | # The string equivalent of this new tag is case-insensitive compared |
151 | # to the available locales and if a match is found, the correct case |
152 | # locale symbol will be returned. In any other condition, returns nil. |
153 | |
154 | match = lambda() do | language, script, region | |
155 | common_tag = Locale::Tag::Common.new( language, script, region ) |
156 | |
157 | unless common_tag.nil? |
158 | found_at = downcase_locales.index( common_tag.to_rfc.to_s.downcase ) |
159 | |
160 | unless found_at.nil? |
161 | return available_locales[ found_at ] |
162 | end |
163 | end |
164 | |
165 | return nil |
166 | end |
167 | |
168 | # Use the above block of code to try matches with tags simplified in the |
169 | # various ways described by comments below. |
170 | |
171 | for code in codes |
172 | tag = Locale::Tag::Rfc.parse( code ) |
173 | next if tag.nil? |
174 | |
175 | # Try with just the language, script and region (no variants, etc.). |
176 | |
177 | found = match.call( tag.language, tag.script, tag.region ) |
178 | return found unless found.nil? |
179 | |
180 | # Try without the region, but keep the script. Must not remove this |
181 | # because in some languages even though the language (say, "zh") may |
182 | # be understood when spoken, the user can only *read* it if presented |
183 | # in a specific script - so stripping out the script specifier might |
184 | # well be worse than just giving up and going for the default locale. |
185 | |
186 | found = match.call( tag.language, tag.script, nil ) |
187 | return found unless found.nil? |
188 | |
189 | end # 'for code in codes' |
190 | |
191 | # Give up! |
192 | |
193 | return default_locale |
194 | end |
195 | |
196 | # Decide which locale to use from the choice of the user's configured |
197 | # languge, the primary language set for the user's configured location, |
198 | # or the HTTP accept-language header, in that order. |
199 | # |
200 | # Pass a request object instance and a user object instance. Returns the |
201 | # chosen language code as a symbol. Always returns a valid value from the |
202 | # available locales according to the I18n module. See class function |
203 | # 'set_best_locale' if you want to actually apply this code to use as the |
204 | # current locale. |
205 | # |
206 | # If the given user value is "nil", only the HTTP header is considered. |
207 | # |
208 | def self.reconcile_user_data_with_http_request_language( request, user ) |
209 | list = [] |
210 | |
211 | # Canvass doesn't use Locations like Artisan. |
212 | # |
213 | # unless ( user.nil? ) |
214 | # list.push( user.language.code ) unless ( user.language.nil? ) |
215 | # list.push( user.location.language.code ) unless ( user.location.nil? || user.location.language.nil? ) |
216 | # end |
217 | |
218 | codes_from_header = request.user_preferred_languages() |
219 | list.push( codes_from_header ) unless ( codes_from_header.nil? ) |
220 | |
221 | return select_best_locale( list.flatten ) |
222 | end |
223 | |
224 | # Set the Rails locale to the best match for the given code. The code is |
225 | # assumed to match an available translation. For ways of getting at such |
226 | # codes, see class functions 'select_best_locale' and |
227 | # 'reconcile_user_data_with_http_request_language'. |
228 | # |
229 | def self.set_best_locale( code ) |
230 | I18n.locale = code |
231 | end |
232 | end |