r18n-core 0.4.14 → 1.0.0
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/.yardopts +1 -1
- data/ChangeLog +21 -0
- data/LICENSE +2 -2
- data/README.md +456 -0
- data/Rakefile +2 -1
- data/base/gl.yml +31 -0
- data/base/it.yml +1 -1
- data/lib/r18n-core/filter_list.rb +157 -0
- data/lib/r18n-core/filters.rb +40 -36
- data/lib/r18n-core/i18n.rb +17 -9
- data/lib/r18n-core/locale.rb +5 -2
- data/lib/r18n-core/translated.rb +18 -15
- data/lib/r18n-core/translated_string.rb +14 -0
- data/lib/r18n-core/translation.rb +19 -20
- data/lib/r18n-core/untranslated.rb +8 -5
- data/lib/r18n-core/utils.rb +5 -1
- data/lib/r18n-core/version.rb +1 -1
- data/lib/r18n-core/yaml_loader.rb +2 -2
- data/lib/r18n-core.rb +60 -13
- data/locales/ca.rb +1 -0
- data/locales/cs.rb +0 -1
- data/locales/en-au.rb +0 -1
- data/locales/en-gb.rb +0 -1
- data/locales/en-us.rb +0 -1
- data/locales/en.rb +1 -1
- data/locales/fr.rb +1 -1
- data/locales/gl.rb +21 -0
- data/locales/hu.rb +1 -2
- data/locales/nb-no.rb +2 -3
- data/locales/nl.rb +4 -2
- data/locales/pt-br.rb +0 -1
- data/locales/pt.rb +0 -1
- data/locales/sv-se.rb +2 -3
- data/locales/th.rb +1 -1
- data/locales/tr.rb +2 -4
- data/locales/zh-cn.rb +7 -0
- data/locales/zh-tw.rb +7 -0
- data/r18n-core.gemspec +10 -10
- data/spec/filters_spec.rb +38 -7
- data/spec/i18n_spec.rb +15 -18
- data/spec/locale_spec.rb +19 -14
- data/spec/locales/cs_spec.rb +1 -1
- data/spec/locales/pl_spec.rb +1 -1
- data/spec/locales/ru_spec.rb +1 -1
- data/spec/locales/sk_spec.rb +1 -1
- data/spec/r18n_spec.rb +64 -22
- data/spec/spec_helper.rb +2 -3
- data/spec/translated_spec.rb +18 -4
- data/spec/translation_spec.rb +17 -6
- data/spec/translations/general/en.yml +1 -0
- data/spec/yaml_loader_spec.rb +10 -10
- metadata +163 -141
- data/Gemfile +0 -5
- data/Gemfile.lock +0 -35
- data/README.rdoc +0 -482
- data/spec/translations/empty/en.yml +0 -0
data/README.rdoc
DELETED
@@ -1,482 +0,0 @@
|
|
1
|
-
= R18n
|
2
|
-
|
3
|
-
R18n is an i18n tool to translate your Ruby application into several languages.
|
4
|
-
|
5
|
-
Use <tt>r18n-rails</tt>, <tt>sinatra-r18n</tt> or teamon’s <tt>merb_i18n</tt> to
|
6
|
-
localize Web applications and <tt>r18n-desktop</tt> to localize desktop
|
7
|
-
application.
|
8
|
-
|
9
|
-
== Features
|
10
|
-
|
11
|
-
=== Ruby-style Syntax
|
12
|
-
R18n uses hierarchical, not English-centric, YAML format for translations by
|
13
|
-
default:
|
14
|
-
|
15
|
-
user:
|
16
|
-
edit: Edit user
|
17
|
-
name: User name is %1
|
18
|
-
count: !!pl
|
19
|
-
1: There is 1 user
|
20
|
-
n: There are %1 users
|
21
|
-
|
22
|
-
To access translation you can call methods with the same names:
|
23
|
-
|
24
|
-
t.user.edit #=> "Edit user"
|
25
|
-
t.user.name('John') #=> "User name is John"
|
26
|
-
t.user.count(5) #=> "There are 5 users"
|
27
|
-
|
28
|
-
t.not.exists | 'default' #=> "default"
|
29
|
-
t.not.exists.translated? #=> false
|
30
|
-
|
31
|
-
If the translation key is the name of an Object method you can access it via
|
32
|
-
hash index:
|
33
|
-
|
34
|
-
t[:methods] #=> "Methods"
|
35
|
-
|
36
|
-
=== Filters
|
37
|
-
|
38
|
-
You can add custom filters for YAML types or any translated string. Filters are
|
39
|
-
cascading and can communicate with each other.
|
40
|
-
|
41
|
-
R18n already has filters for HTML escaping, lambdas, Textile and Markdown:
|
42
|
-
|
43
|
-
hi: !!markdown |
|
44
|
-
**Hi**, people!
|
45
|
-
greater: !!escape
|
46
|
-
1 < 2 is true
|
47
|
-
|
48
|
-
t.hi #=> "<p><strong>Hi</strong>, people!</p>"
|
49
|
-
t.greater #=> "1 < 2 is true"
|
50
|
-
|
51
|
-
=== Flexibility
|
52
|
-
|
53
|
-
Translation variables and pluralization (“1 comment”, “5 comments”) are filters
|
54
|
-
too, so you can extend or replace them. For example, you can use the ‘named
|
55
|
-
variables filter’ from the <tt>r18n-rails-api</tt> gem:
|
56
|
-
|
57
|
-
greeting: "Hi, %{name}"
|
58
|
-
|
59
|
-
R18n::Filters.on(:named_variables)
|
60
|
-
t.greeting(name: 'John') #=> "Hi, John"
|
61
|
-
|
62
|
-
=== Flexible Locales
|
63
|
-
|
64
|
-
Locales extend the Locale class. For example, English locale extends the time
|
65
|
-
formatters:
|
66
|
-
|
67
|
-
l Date.now, :full #=> "30th of November, 2009"
|
68
|
-
|
69
|
-
Russian has built-in pluralization without any lambdas in YAML:
|
70
|
-
|
71
|
-
t.user.count(1) #=> "1 пользователь"
|
72
|
-
t.user.count(2) #=> "2 пользователя"
|
73
|
-
t.user.count(5) #=> "5 пользователей"
|
74
|
-
|
75
|
-
=== Loaders
|
76
|
-
|
77
|
-
R18n can load translations from anywhere, not just from YAML files. You just
|
78
|
-
need to create loader object with 2 methods: +available+ and +load+:
|
79
|
-
|
80
|
-
class DBLoader
|
81
|
-
def available
|
82
|
-
Translation.find(:all).map(&:locale)
|
83
|
-
end
|
84
|
-
def load(locale)
|
85
|
-
Translation.find(locale).to_hash
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
R18n.set(user_locales, DBLoader.new)
|
90
|
-
|
91
|
-
You can also set a list of different translation locations or set extension
|
92
|
-
locations which will be only used with application translation (useful for
|
93
|
-
plugins).
|
94
|
-
|
95
|
-
=== Object Translation
|
96
|
-
|
97
|
-
You can translate any class, including ORM models (ActiveRecord, DataMapper,
|
98
|
-
MongoMapper, Mongoid or others):
|
99
|
-
|
100
|
-
require 'r18n-core/translated'
|
101
|
-
|
102
|
-
class Product < ActiveRecord::Base
|
103
|
-
include R18n::Translated
|
104
|
-
# Model has two normal properties: title_en and title_ru
|
105
|
-
translations :title
|
106
|
-
end
|
107
|
-
|
108
|
-
# For English users
|
109
|
-
product.title #=> "Anthrax"
|
110
|
-
|
111
|
-
# For Russian users
|
112
|
-
product.title #=> "Сибирская язва"
|
113
|
-
|
114
|
-
=== Localization
|
115
|
-
|
116
|
-
R18n can localize numbers and time:
|
117
|
-
|
118
|
-
l -5000 #=> "−5,000"
|
119
|
-
l Time.now #=> "30/11/2009 14:36"
|
120
|
-
l Time.now, :full #=> "30th of November, 2009 14:37"
|
121
|
-
l Time.now - 60, :human #=> "1 minute ago"
|
122
|
-
|
123
|
-
=== Several User Languages
|
124
|
-
|
125
|
-
If a particular locale is requested but missing, R18n will automatically take
|
126
|
-
the next available language (according to the browser’s list of locales) and for
|
127
|
-
cultures with two official languages (e.g., exUSSR) it takes the second language
|
128
|
-
(e.g., if a translation isn’t available in Kazakh R18n will look for Russian):
|
129
|
-
|
130
|
-
i18n = R18n::I18n.new(['kk', 'de'], 'dir/with/translations')
|
131
|
-
|
132
|
-
i18n.locales #=> [Locale kk (Қазақша), Locale de (Deutsch),
|
133
|
-
# Locale ru (Русский), Locale en (English)]
|
134
|
-
|
135
|
-
i18n.kazakh #=> "Қазақша", main user language
|
136
|
-
i18n.deutsch #=> "Deutsch", not in Kazakh, use next user locale
|
137
|
-
i18n.russian #=> "Русский", not in kk and de, use Kazakh sublocale
|
138
|
-
i18n.english #=> "English", not in any user locales, use default
|
139
|
-
|
140
|
-
=== Agnostic
|
141
|
-
|
142
|
-
R18n has an agnostic core package and plugins with out-of-box support for
|
143
|
-
Sinatra, Merb and desktop applications.
|
144
|
-
|
145
|
-
== Usage
|
146
|
-
|
147
|
-
To add i18n support to your app, you can use the particular plugin for your
|
148
|
-
environment: <tt>r18n-rails</tt>, <tt>sinatra-r18n</tt> or
|
149
|
-
<tt>r18n-desktop</tt>.
|
150
|
-
|
151
|
-
If you develop you own plugin or want to use only core gem, you will need to
|
152
|
-
create an I18n object and by using <tt>R18n.set</tt> or, for the current thread,
|
153
|
-
by using <tt>R18n.thread_set</tt>:
|
154
|
-
|
155
|
-
R18n.set('en', 'path/to/translations')
|
156
|
-
|
157
|
-
You can add helpers to access the current R18n object:
|
158
|
-
|
159
|
-
include R18n::Helpers
|
160
|
-
|
161
|
-
t.yes #=> "Yes"
|
162
|
-
l Time.now, :human #=> "now"
|
163
|
-
r18n.locale.code #=> "en"
|
164
|
-
|
165
|
-
=== Translation
|
166
|
-
|
167
|
-
Translation files are in YAML format by default and have names like en.yml
|
168
|
-
(English) or en-us.yml (USA English dialect) with language/country code (RFC
|
169
|
-
3066).
|
170
|
-
|
171
|
-
In your translation files you can use:
|
172
|
-
* Strings
|
173
|
-
robot: This is a robot
|
174
|
-
percent: "Percent sign (%)"
|
175
|
-
* Numbers
|
176
|
-
number: 123
|
177
|
-
float: 12.45
|
178
|
-
* Pluralizable messages
|
179
|
-
robots: !!pl
|
180
|
-
0: No robots
|
181
|
-
1: One robot
|
182
|
-
n: %1 robots
|
183
|
-
* Filters
|
184
|
-
filtered: !!custom_type
|
185
|
-
This content will be processed by a filter
|
186
|
-
|
187
|
-
To get the translated string use a method with the key name or square brackets
|
188
|
-
[] for keys, which is the same with Object methods (+class+, +inspect+, etc):
|
189
|
-
|
190
|
-
t.robot #=> "This is a robot"
|
191
|
-
t[:robot] #=> "This is a robot"
|
192
|
-
|
193
|
-
Translation may be hierarchical:
|
194
|
-
|
195
|
-
t.post.add #=> "Add post"
|
196
|
-
t[:post][:add] #=> "Add post"
|
197
|
-
|
198
|
-
If the locale isn’t found in the user’s requested locale, R18n will search for
|
199
|
-
it in sublocales or in another locale, which the user also can accept:
|
200
|
-
|
201
|
-
t.not.in.english #=> "В английском нет"
|
202
|
-
|
203
|
-
The translated string has a +locale+ method for determining its locale (Locale
|
204
|
-
instance or code string if locale is’t supported in R18n):
|
205
|
-
|
206
|
-
i18n.not.in.english.locale #=> Locale ru (Русский)
|
207
|
-
|
208
|
-
You can include parameters in the translated string by specifying arguments:
|
209
|
-
|
210
|
-
name: "My name is %1"
|
211
|
-
|
212
|
-
t.name('John') #=> "My name is John"
|
213
|
-
|
214
|
-
Pluralizable messages get their item count from the first argument:
|
215
|
-
|
216
|
-
t.robots(0) #=> "No robots"
|
217
|
-
t.robots(1) #=> "One robot"
|
218
|
-
t.robots(50) #=> "50 robots"
|
219
|
-
|
220
|
-
If there isn’t a pluralization for a particular number, translation will be use
|
221
|
-
+n+. If there isn’t a locale file for translation, it will use the English
|
222
|
-
pluralization rule (0, 1 and +n+).
|
223
|
-
|
224
|
-
You can check if the key has a translation:
|
225
|
-
|
226
|
-
t.post.add.translated? #=> true
|
227
|
-
t.not.exists.translated? #=> false
|
228
|
-
|
229
|
-
You can set a default value for untranslated strings:
|
230
|
-
|
231
|
-
t.not.exists | 'default' #=> "default"
|
232
|
-
|
233
|
-
You can query the translation keys:
|
234
|
-
|
235
|
-
t.counties._keys.each do |county|
|
236
|
-
puts t.counties[county]
|
237
|
-
end
|
238
|
-
|
239
|
-
R18n already has translations for common words for most built in locales.
|
240
|
-
See <tt>base/</tt> the source.
|
241
|
-
|
242
|
-
t.yes #=> "Yes"
|
243
|
-
t.cancel #=> "Cancel"
|
244
|
-
t.delete #=> "Delete"
|
245
|
-
|
246
|
-
=== Filters
|
247
|
-
|
248
|
-
You can also add you own filters for translations: escape HTML entities, convert
|
249
|
-
from Markdown syntax, etc. Filters can be passive, only being processed when
|
250
|
-
loaded.
|
251
|
-
|
252
|
-
friendship: !!gender
|
253
|
-
f: She adds a friend
|
254
|
-
m: He adds a friend
|
255
|
-
|
256
|
-
R18n::Filters.add('gender', :user_gender) do |content, config, user|
|
257
|
-
if user.female?
|
258
|
-
content['f']
|
259
|
-
else
|
260
|
-
content['m']
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
t.friendship(anne) #=> "She adds a friend"
|
265
|
-
|
266
|
-
To create a filter you pass the following to <tt>R18n::Filters.add</tt>:
|
267
|
-
|
268
|
-
* Filter target. YAML type (<tt>!!type</tt>), <tt>String</tt> for all
|
269
|
-
translations of <tt>R18n::Untranslated</tt> for missing translations.
|
270
|
-
* Optional filter name, to disable, enable or delete it later by
|
271
|
-
<tt>R18n::Filters.off</tt>, <tt>R18n::Filters.on</tt> and
|
272
|
-
<tt>R18n::Filters.delete</tt>.
|
273
|
-
* Hash with options:
|
274
|
-
* <tt>:passive => true</tt> to filter translations only on load;
|
275
|
-
* <tt>:position</tt> within the list of current filters of this type
|
276
|
-
(by default a new filter will be inserted into last position).
|
277
|
-
|
278
|
-
The filter will receive at least two arguments:
|
279
|
-
* Translation (possibly already filtered by other filters for this type earlier
|
280
|
-
in the list).
|
281
|
-
* A Hash with translation +locale+ and +path+.
|
282
|
-
* Parameters from translation request will be in the remaining arguments.
|
283
|
-
|
284
|
-
==== HTML Escape
|
285
|
-
|
286
|
-
R18n contains 2 filters to escape HTML entities: by YAML type and global. If you
|
287
|
-
need to escape HTML in some translations, just set <tt>!!escape</tt> YAML type:
|
288
|
-
|
289
|
-
greater: !!escape
|
290
|
-
1 < 2 is true
|
291
|
-
|
292
|
-
t.greater #=> "1 < 2 is true"
|
293
|
-
|
294
|
-
If you develop web application and want to escape HTML in all translations, just
|
295
|
-
activate the global escape filter:
|
296
|
-
|
297
|
-
R18n::Filters.on(:global_escape_html)
|
298
|
-
|
299
|
-
If you enable global HTML escape, you may still use <tt>!!html</tt> YAML type to
|
300
|
-
disable escaping on some values:
|
301
|
-
|
302
|
-
warning: !!html
|
303
|
-
<b>Warning</b>
|
304
|
-
|
305
|
-
R18n::Filters.on(:global_escape_html)
|
306
|
-
t.warning #=> "<b>Warning</b>"
|
307
|
-
|
308
|
-
==== Markdown
|
309
|
-
|
310
|
-
To use Markdown in your translations you must install the Maruku gem:
|
311
|
-
|
312
|
-
hi: !!markdown
|
313
|
-
**Hi**, people!
|
314
|
-
|
315
|
-
t.hi #=> "<p><strong>Hi</strong>, people!</p>"
|
316
|
-
|
317
|
-
|
318
|
-
==== Textile
|
319
|
-
|
320
|
-
To use Textile in your translations you must install the RedCloth gem:
|
321
|
-
|
322
|
-
alarm: !!textile
|
323
|
-
It will delete _all_ users!
|
324
|
-
|
325
|
-
t.alarm #=> "<p>It will delete <em>all</em> users!</p>"
|
326
|
-
|
327
|
-
==== Lambdas
|
328
|
-
|
329
|
-
You can use lambdas in your translations.
|
330
|
-
|
331
|
-
sum: !!proc |x, y| x + y
|
332
|
-
|
333
|
-
t.sum(1, 2) #=> 3
|
334
|
-
|
335
|
-
If this is unsafe in your application (for example, user can change
|
336
|
-
translations), you can disable it:
|
337
|
-
|
338
|
-
R18n::Filters.off(:procedure)
|
339
|
-
|
340
|
-
=== Localization
|
341
|
-
|
342
|
-
You can print numbers and floats according to the rules of the user locale:
|
343
|
-
|
344
|
-
l -12000.5 #=> "−12,000.5"
|
345
|
-
|
346
|
-
Number and float formatters will also put real typographic minus and put
|
347
|
-
non-breakable thin spaces (for locale, which use it as digit separator).
|
348
|
-
|
349
|
-
You can translate months and week day names in Time, Date and DateTime by the
|
350
|
-
+strftime+ method:
|
351
|
-
|
352
|
-
l Time.now, '%B' #=> "September"
|
353
|
-
|
354
|
-
R18n has some built-in time formats for locales: <tt>:human</tt>, <tt>:full</tt>
|
355
|
-
and <tt>:standard</tt> (the default):
|
356
|
-
|
357
|
-
l Time.now, :human #=> "now"
|
358
|
-
l Time.now, :full #=> "August 9th, 2009 21:47"
|
359
|
-
l Time.now #=> "08/09/2009 21:41"
|
360
|
-
l Time.now.to_date #=> "08/09/2009"
|
361
|
-
|
362
|
-
=== Model
|
363
|
-
|
364
|
-
You can add i18n support to any classes, including ORM models (ActiveRecord,
|
365
|
-
DataMapper, MongoMapper, Mongoid or others):
|
366
|
-
|
367
|
-
require 'r18n-core/translated'
|
368
|
-
|
369
|
-
class Product
|
370
|
-
include DataMapper::Resource
|
371
|
-
property :title_ru, String
|
372
|
-
property :title_en, String
|
373
|
-
|
374
|
-
include R18n::Translated
|
375
|
-
translations :title
|
376
|
-
end
|
377
|
-
|
378
|
-
# For example, user only knows Russian
|
379
|
-
|
380
|
-
# Set English (default) title
|
381
|
-
product.title_en = "Anthrax"
|
382
|
-
product.title #=> "Anthrax"
|
383
|
-
|
384
|
-
# Set value for user locale (Russian)
|
385
|
-
product.title = "Сибирская язва"
|
386
|
-
product.title #=> "Сибирская язва"
|
387
|
-
|
388
|
-
product.title_en #=> "Anthrax"
|
389
|
-
product.title_ru #=> "Сибирская язва"
|
390
|
-
|
391
|
-
See R18n::Translated for documentation.
|
392
|
-
|
393
|
-
=== Locale
|
394
|
-
|
395
|
-
All supported locales are stored in R18n gem in +locales+ directory. If you want
|
396
|
-
to add your locale, please fork this project and send a pull request or email me
|
397
|
-
at andrey@sitnik.ru.
|
398
|
-
|
399
|
-
To get information about a locale create an R18n::Locale instance:
|
400
|
-
|
401
|
-
locale = R18n::Locale.load('en')
|
402
|
-
|
403
|
-
You can then get the following from the locale:
|
404
|
-
|
405
|
-
* Locale title and RFC 3066 code:
|
406
|
-
|
407
|
-
locale.title #=> "English"
|
408
|
-
locale.code #=> "en"
|
409
|
-
|
410
|
-
* Language direction (left to right, or right to left for Arabic and Hebrew):
|
411
|
-
|
412
|
-
locale.ltr? #=> true
|
413
|
-
|
414
|
-
* Week start day (+:monday+ or +:sunday+):
|
415
|
-
|
416
|
-
locale.week_start #=> :sunday
|
417
|
-
|
418
|
-
=== Loaders
|
419
|
-
|
420
|
-
You can load translations from anywhere, not just from YAML files. To load
|
421
|
-
translation you must create loader class with 2 methods:
|
422
|
-
|
423
|
-
* <tt>available</tt> – return array of locales of available translations;
|
424
|
-
* <tt>load(locale)</tt> – return Hash of translation.
|
425
|
-
|
426
|
-
Pass its instance to <tt>R18n::I18n.new</tt>:
|
427
|
-
|
428
|
-
R18n.set('en', MyLoader.new(loader_param))
|
429
|
-
|
430
|
-
You can set your default loader and pass it to <tt>R18n::I18n.new</tt> as the
|
431
|
-
only constructor argument:
|
432
|
-
|
433
|
-
R18n.default_loader = MyLoader
|
434
|
-
R18n.set('en', loader_param)
|
435
|
-
|
436
|
-
If you want to load a translation with some type for filter, use
|
437
|
-
<tt>R18n::Typed</tt> struct:
|
438
|
-
|
439
|
-
# Loader will return something like:
|
440
|
-
{ 'users' => R18n::Typed.new('pl', { 1 => '1 user', 'n' => '%1 users' }) }
|
441
|
-
|
442
|
-
# To use pluralization filter (+pl+ type):
|
443
|
-
t.users(5) #=> "5 users"
|
444
|
-
|
445
|
-
=== Extension Translations
|
446
|
-
|
447
|
-
For r18n plugin you can add loaders with translations, which will be used with
|
448
|
-
application translations. For example, DB plugin may place translations for
|
449
|
-
error messages in extension directory. R18n contain translations for base words
|
450
|
-
as extension directory too.
|
451
|
-
|
452
|
-
R18n.extension_places << R18n::Loader::YAML.new('./error_messages/')
|
453
|
-
|
454
|
-
== Add Locale
|
455
|
-
|
456
|
-
If R18n hasn’t got locale file for your language, please add it. It’s very
|
457
|
-
simple:
|
458
|
-
|
459
|
-
* Create the file _code_.rb in the locales/ directory for your language and
|
460
|
-
describe locale. Just copy from another locale and change the values.
|
461
|
-
* If your country has alternate languages (for example, in exUSSR countries
|
462
|
-
most people also know Russian), add
|
463
|
-
<tt>sublocales %{_another_locale_ en}</tt>.
|
464
|
-
* Create in base/ file _code_.yml for your language and translate the base
|
465
|
-
messages. Just copy file from language, which you know, and rewrite values.
|
466
|
-
* If your language needs some special logic (for example, different
|
467
|
-
pluralization or time formatters) you can extend Locale class methods.
|
468
|
-
* Send a pull request via GitHub (http://github.com/ai/r18n) or just write email
|
469
|
-
with the files to me (andrey@sitnik.ru).
|
470
|
-
|
471
|
-
_Code_ is RFC 3066 code for your language (for example, “en” for English and
|
472
|
-
“fr-CA” for Canadian French). Email me with any questions you may have, you will
|
473
|
-
find other contact addresses at http://sitnik.ru.
|
474
|
-
|
475
|
-
== License
|
476
|
-
|
477
|
-
R18n is licensed under the GNU Lesser General Public License version 3.
|
478
|
-
See the LICENSE file or http://www.gnu.org/licenses/lgpl.html.
|
479
|
-
|
480
|
-
== Author
|
481
|
-
|
482
|
-
Andrey “A.I.” Sitnik <andrey@sitnik.ru>
|
File without changes
|