r18n-core 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +166 -0
- data/README.rdoc +136 -0
- data/Rakefile +93 -0
- data/base/en.yml +7 -0
- data/base/ru.yml +7 -0
- data/lib/r18n-core.rb +42 -0
- data/lib/r18n-core/i18n.rb +191 -0
- data/lib/r18n-core/locale.rb +171 -0
- data/lib/r18n-core/translated_string.rb +33 -0
- data/lib/r18n-core/translation.rb +203 -0
- data/locales/en.yml +34 -0
- data/locales/en_US.yml +5 -0
- data/locales/ru.yml +34 -0
- data/spec/i18n_spec.rb +78 -0
- data/spec/locale_spec.rb +89 -0
- data/spec/r18n_spec.rb +11 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/translation_spec.rb +90 -0
- data/spec/translations/extension/en.yml +2 -0
- data/spec/translations/extension/no_TR.yml +1 -0
- data/spec/translations/general/en.yml +24 -0
- data/spec/translations/general/no_LC.yml +6 -0
- data/spec/translations/general/ru.yml +4 -0
- metadata +79 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
=begin
|
2
|
+
I18n support.
|
3
|
+
|
4
|
+
Copyright (C) 2008 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
|
5
|
+
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
This program is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
=end
|
19
|
+
|
20
|
+
require 'date'
|
21
|
+
|
22
|
+
module R18n
|
23
|
+
# General class to i18n support in your application. It load Translation and
|
24
|
+
# Locale classes and create pretty way to use it.
|
25
|
+
#
|
26
|
+
# To get translation you can use same with Translation way – use method with
|
27
|
+
# translation’s name or <tt>[name]</tt> method. Translations will be also
|
28
|
+
# loaded for default locale, +sublocales+ from first in +locales+ and general
|
29
|
+
# languages for dialects (it will load +fr+ for +fr_CA+ too).
|
30
|
+
#
|
31
|
+
# See Translation and Locale documentation.
|
32
|
+
#
|
33
|
+
# == Usage
|
34
|
+
# translations/ru.yml
|
35
|
+
#
|
36
|
+
# one: Один
|
37
|
+
#
|
38
|
+
# translations/en.yml
|
39
|
+
#
|
40
|
+
# one: One
|
41
|
+
# two: Two
|
42
|
+
#
|
43
|
+
# example.rb
|
44
|
+
#
|
45
|
+
# i18n = R18n::I18n.new(['ru', 'en'], 'translations/')
|
46
|
+
#
|
47
|
+
# i18n.one #=> "Один"
|
48
|
+
# i18n.two #=> "Two"
|
49
|
+
#
|
50
|
+
# i18n.locale['title'] #=> "Русский"
|
51
|
+
# i18n.locale['code'] #=> "ru"
|
52
|
+
# i18n.locale['direction'] #=> "ltr"
|
53
|
+
#
|
54
|
+
# i18n.l -11000.5 #=> "−11 000,5"
|
55
|
+
# i18n.l Time.now #=> "Вск, 21 сен 2008, 22:10:10 MSD"
|
56
|
+
# i18n.l Time.now, :date #=> "21.09.2008"
|
57
|
+
# i18n.l Time.now, :time #=> "22:10"
|
58
|
+
# i18n.l Time.now, '%A' #=> "Воскресенье"
|
59
|
+
class I18n
|
60
|
+
@@default = 'en'
|
61
|
+
|
62
|
+
# Set default locale code to use when any user locales willn't be founded.
|
63
|
+
# It should has all translations and locale file.
|
64
|
+
def self.default=(locale)
|
65
|
+
@@default = locale
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get default locale code
|
69
|
+
def self.default
|
70
|
+
@@default
|
71
|
+
end
|
72
|
+
|
73
|
+
# Parse HTTP_ACCEPT_LANGUAGE and return array of user locales
|
74
|
+
def self.parse_http(str)
|
75
|
+
return [] if str.nil?
|
76
|
+
locales = str.split(',')
|
77
|
+
locales.map! do |locale|
|
78
|
+
locale = locale.split ';q='
|
79
|
+
if 1 == locale.size
|
80
|
+
[locale[0], 1.0]
|
81
|
+
else
|
82
|
+
[locale[0], locale[1].to_f]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
locales.sort! { |a, b| b[1] <=> a[1] }
|
86
|
+
locales.map! { |i| i[0] }
|
87
|
+
end
|
88
|
+
|
89
|
+
# User locales, ordered by priority
|
90
|
+
attr_reader :locales
|
91
|
+
|
92
|
+
# Dir with translations files
|
93
|
+
attr_reader :translations_dir
|
94
|
+
|
95
|
+
# First locale with locale file
|
96
|
+
attr_reader :locale
|
97
|
+
|
98
|
+
# Create i18n for +locales+ with translations from +translations_dir+ and
|
99
|
+
# locales data. Translations will be also loaded for default locale,
|
100
|
+
# +sublocales+ from first in +locales+ and general languages for dialects
|
101
|
+
# (it will load +fr+ for +fr_CA+ too).
|
102
|
+
#
|
103
|
+
# +Locales+ must be a locale code (RFC 3066) or array, ordered by priority.
|
104
|
+
def initialize(locales, translations_dir)
|
105
|
+
locales = locales.to_a if String == locales.class
|
106
|
+
|
107
|
+
@locales = locales.map do |locale|
|
108
|
+
if Locale.exists? locale
|
109
|
+
Locale.new locale
|
110
|
+
else
|
111
|
+
locale
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
locales << @@default
|
116
|
+
if Locale == @locales.first.class
|
117
|
+
locales += @locales.first['sublocales']
|
118
|
+
end
|
119
|
+
locales.each_with_index do |locale, i|
|
120
|
+
if "_" == locale[2..2]
|
121
|
+
locales.insert(i + 1, locale[0..1])
|
122
|
+
end
|
123
|
+
end
|
124
|
+
locales.uniq!
|
125
|
+
|
126
|
+
locales.each do |locale|
|
127
|
+
if Locale.exists? locale
|
128
|
+
@locale = Locale.new(locale)
|
129
|
+
break
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
@translations_dir = File.expand_path(translations_dir)
|
134
|
+
@translation = Translation.load(locales, @translations_dir)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Return Hash with titles (or code for translation without locale file) of
|
138
|
+
# available translations.
|
139
|
+
def translations
|
140
|
+
Translation.available(@translations_dir).inject({}) do |all, code|
|
141
|
+
all[code] = if Locale.exists? code
|
142
|
+
Locale.new(code)['title']
|
143
|
+
else
|
144
|
+
code
|
145
|
+
end
|
146
|
+
all
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Convert +object+ to String, according to the rules of the current locale.
|
151
|
+
# It support Fixnum, Bignum, Float, Time, Date and DateTime.
|
152
|
+
#
|
153
|
+
# For time classes you can set +format+ in standart +strftime+ form, or
|
154
|
+
# Symbol to use format from locale file (<tt>:time</tt>, <tt>:date</tt>,
|
155
|
+
# <tt>:short_data</tt>, <tt>:long_data</tt>, <tt>:datetime</tt>,
|
156
|
+
# <tt>:short_datetime</tt> or <tt>:long_datetime</tt>). Without format it
|
157
|
+
# use <tt>:datetime</tt> for Time and DateTime and <tt>:date</tt> for Date.
|
158
|
+
def localize(object, format = nil)
|
159
|
+
if Fixnum == object.class or Bignum == object.class
|
160
|
+
locale.format_number(object)
|
161
|
+
elsif Float == object.class
|
162
|
+
locale.format_float(object)
|
163
|
+
elsif Time == object.class or DateTime == object.class
|
164
|
+
format = :datetime if format.nil?
|
165
|
+
locale.strftime(object, format)
|
166
|
+
elsif Date == object.class
|
167
|
+
format = :date if format.nil?
|
168
|
+
locale.strftime(object, format)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
alias :l :localize
|
172
|
+
|
173
|
+
# Short and pretty way to get translation by method name. If translation
|
174
|
+
# has name like object methods (+new+, +to_s+, +methods+) use <tt>[]</tt>
|
175
|
+
# method to access.
|
176
|
+
#
|
177
|
+
# Translation can contain variable part. Just set is as <tt>%1</tt>,
|
178
|
+
# <tt>%2</tt>, etc in translations file and set values as methods params.
|
179
|
+
def method_missing(name, *params)
|
180
|
+
self[name.to_s, *params]
|
181
|
+
end
|
182
|
+
|
183
|
+
# Return translation with special +name+.
|
184
|
+
#
|
185
|
+
# Translation can contain variable part. Just set is as <tt>%1</tt>,
|
186
|
+
# <tt>%2</tt>, etc in translations file and set values in next +params+.
|
187
|
+
def [](name, *params)
|
188
|
+
@translation[name, *params]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
=begin
|
2
|
+
Locale to i18n support.
|
3
|
+
|
4
|
+
Copyright (C) 2008 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
|
5
|
+
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
This program is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
=end
|
19
|
+
|
20
|
+
require 'pathname'
|
21
|
+
require 'yaml'
|
22
|
+
|
23
|
+
module R18n
|
24
|
+
# Information about locale (language, country and other special variant
|
25
|
+
# preferences). Locale was named by RFC 3066. For example, locale for French
|
26
|
+
# speaking people in Canada will be +fr_CA+.
|
27
|
+
#
|
28
|
+
# Locale files is placed in <tt>locales/</tt> dir in YAML files.
|
29
|
+
#
|
30
|
+
# Each locale has +sublocales+ – often known languages for people from this
|
31
|
+
# locale. For example, many Belorussians know Russian and English. If there
|
32
|
+
# is’t translation for Belorussian, it will be searched in Russian and next in
|
33
|
+
# English translations.
|
34
|
+
#
|
35
|
+
# == Usage
|
36
|
+
#
|
37
|
+
# Get Russian locale and print it information
|
38
|
+
#
|
39
|
+
# ru = R18n::Locale.new('ru')
|
40
|
+
# ru['title'] #=> "Русский"
|
41
|
+
# ru['code'] #=> "ru"
|
42
|
+
# ru['direction'] #=> "ltr"
|
43
|
+
#
|
44
|
+
# == Available data
|
45
|
+
#
|
46
|
+
# * +code+: locale RFC 3066 code;
|
47
|
+
# * +title+: locale name on it language;
|
48
|
+
# * +direction+: writing direction, +ltr+ or +rtl+ (for Arabic and Hebrew);
|
49
|
+
# * +sublocales+: often known languages for people from this locale;
|
50
|
+
# * +pluralization+: function to get pluralization type for +n+ items;
|
51
|
+
# * +include+: locale code to include it data, optional.
|
52
|
+
#
|
53
|
+
# You can see more available data about locale in samples in
|
54
|
+
# <tt>locales/</tt> dir.
|
55
|
+
class Locale
|
56
|
+
LOCALES_DIR = Pathname(__FILE__).dirname.expand_path + '../../locales/'
|
57
|
+
|
58
|
+
# All available locales
|
59
|
+
def self.available
|
60
|
+
Dir.glob(LOCALES_DIR + '*.yml').map do |i|
|
61
|
+
File.basename(i, '.yml')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Is +locale+ has info file
|
66
|
+
def self.exists?(locale)
|
67
|
+
File.exists?(File.join(LOCALES_DIR, locale + '.yml'))
|
68
|
+
end
|
69
|
+
|
70
|
+
# Default pluralization rule to translation without locale file
|
71
|
+
def self.default_pluralize(n)
|
72
|
+
n == 0 ? 0 : n == 1 ? 1 : 'n'
|
73
|
+
end
|
74
|
+
|
75
|
+
# Load locale by RFC 3066 +code+
|
76
|
+
def initialize(code)
|
77
|
+
code.delete! '/'
|
78
|
+
code.delete! '\\'
|
79
|
+
|
80
|
+
@locale = {}
|
81
|
+
while code
|
82
|
+
file = LOCALES_DIR + "#{code}.yml"
|
83
|
+
raise "Locale #{code} isn't exists" if not File.exists? file
|
84
|
+
loaded = YAML.load_file(file)
|
85
|
+
@locale = loaded.merge @locale
|
86
|
+
code = loaded['include']
|
87
|
+
end
|
88
|
+
|
89
|
+
eval("def pluralize(n); #{@locale["pluralization"]}; end", binding)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get information about locale
|
93
|
+
def [](name)
|
94
|
+
@locale[name]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Is another locale has same code
|
98
|
+
def ==(locale)
|
99
|
+
@locale['code'] == locale['code']
|
100
|
+
end
|
101
|
+
|
102
|
+
# Human readable locale code and title
|
103
|
+
def inspect
|
104
|
+
"Locale #{@locale['code']} (#{@locale['title']})"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the integer in String form, according to the rules of the locale.
|
108
|
+
# It will also put real typographic minus.
|
109
|
+
def format_number(number)
|
110
|
+
str = number.to_s
|
111
|
+
str[0] = '−' if 0 > number # Real typographic minus
|
112
|
+
group = @locale['numbers']['group_delimiter']
|
113
|
+
|
114
|
+
if 'indian' == @locale['numbers']['separation']
|
115
|
+
str.gsub(/(\d)(?=((\d\d\d)(?!\d))|((\d\d)+(\d\d\d)(?!\d)))/) do |match|
|
116
|
+
match + group
|
117
|
+
end
|
118
|
+
else
|
119
|
+
str.gsub(/(\d)(?=(\d\d\d)+(?!\d))/) do |match|
|
120
|
+
match + group
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns the float in String form, according to the rules of the locale.
|
126
|
+
# It will also put real typographic minus.
|
127
|
+
def format_float(float)
|
128
|
+
decimal = @locale['numbers']['decimal_separator']
|
129
|
+
self.format_number(float.to_i) + decimal + float.to_s.split('.').last
|
130
|
+
end
|
131
|
+
|
132
|
+
# Same that <tt>Time.strftime</tt>, but translate months and week days
|
133
|
+
# names. In +time+ you can use Time, DateTime or Date object. In +format+
|
134
|
+
# you can use String with standart +strftime+ format (see
|
135
|
+
# <tt>Time.strftime</tt> docs) or Symbol with format from locale file
|
136
|
+
# (<tt>:time</tt>, <tt>:date</tt>, <tt>:short_data</tt>, <tt>:long_data</tt>,
|
137
|
+
# <tt>:datetime</tt>, <tt>:short_datetime</tt> or <tt>:long_datetime</tt>).
|
138
|
+
def strftime(time, format)
|
139
|
+
if Symbol == format.class
|
140
|
+
format = @locale['formats'][format.to_s]
|
141
|
+
end
|
142
|
+
|
143
|
+
translated = ''
|
144
|
+
format.scan(/%[EO]?.|./o) do |c|
|
145
|
+
case c.sub(/^%[EO]?(.)$/o, '%\\1')
|
146
|
+
when '%A'
|
147
|
+
translated << @locale['week']['days'][time.wday]
|
148
|
+
when '%a'
|
149
|
+
translated << @locale['week']['abbrs'][time.wday]
|
150
|
+
when '%B'
|
151
|
+
translated << @locale['months']['names'][time.month - 1]
|
152
|
+
when '%b'
|
153
|
+
translated << @locale['months']['abbrs'][time.month - 1]
|
154
|
+
when '%p'
|
155
|
+
translated << if time.hour < 12
|
156
|
+
@locale['time']['am']
|
157
|
+
else
|
158
|
+
@locale['time']['pm']
|
159
|
+
end
|
160
|
+
else
|
161
|
+
translated << c
|
162
|
+
end
|
163
|
+
end
|
164
|
+
time.strftime(translated)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Return pluralization type for +n+ items. It will be replacing by code
|
168
|
+
# from locale file.
|
169
|
+
def pluralize(n); end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
=begin
|
2
|
+
Translation string for i18n support.
|
3
|
+
|
4
|
+
Copyright (C) 2008 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
|
5
|
+
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
This program is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
=end
|
19
|
+
|
20
|
+
module R18n
|
21
|
+
# String, which is translated to some locale and loading from Translation.
|
22
|
+
class TranslatedString < String
|
23
|
+
# String locale
|
24
|
+
attr_reader :locale
|
25
|
+
|
26
|
+
# Returns a new string object containing a copy of +str+, which translated
|
27
|
+
# to +locale+
|
28
|
+
def initialize(str, locale)
|
29
|
+
super(str)
|
30
|
+
@locale = locale
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
=begin
|
2
|
+
Translation to i18n support.
|
3
|
+
|
4
|
+
Copyright (C) 2008 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
|
5
|
+
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
This program is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
=end
|
19
|
+
|
20
|
+
require 'pathname'
|
21
|
+
require 'yaml'
|
22
|
+
|
23
|
+
module R18n
|
24
|
+
# Translation for interface to i18n support. You can load several locales and
|
25
|
+
# if translation willn’t be found in first, r18n will be search it in next.
|
26
|
+
#
|
27
|
+
# Translation files use YAML format and has name like en.yml (English) or
|
28
|
+
# en_US.yml (USA English dialect) with language/country code (RFC 3066). In
|
29
|
+
# translation file you can use strings, numbers, floats (any YAML types),
|
30
|
+
# procedures (<tt>!!proc</tt>) and pluralizable values (<tt>!!pl</tt>). You
|
31
|
+
# can use params in string values, which you can replace in program. Just
|
32
|
+
# write <tt>%1</tt>, <tt>%2</tt>, etc and set it values as method arguments,
|
33
|
+
# when you will be get value.
|
34
|
+
#
|
35
|
+
# To get translation value use method with same name. If translation name
|
36
|
+
# is equal with Object methods (+new+, +to_s+, +methods+) use
|
37
|
+
# <tt>[name, params…]</tt>. If you want to get pluralizable value, just set
|
38
|
+
# value for pluralization in fisrt argument of method. See samples below.
|
39
|
+
#
|
40
|
+
# Translated strings will have +locale+ methods, which return Locale or it
|
41
|
+
# code, if locale file isn’t exists.
|
42
|
+
#
|
43
|
+
# R18n contain translations for common words (such as “OK”, “Cancel”, etc)
|
44
|
+
# for most supported locales. See <tt>base/</tt> dir.
|
45
|
+
#
|
46
|
+
# == Examples
|
47
|
+
# translations/ru.yml
|
48
|
+
#
|
49
|
+
# one: Один
|
50
|
+
#
|
51
|
+
# translations/en.yml
|
52
|
+
#
|
53
|
+
# one: One
|
54
|
+
# two: Two
|
55
|
+
#
|
56
|
+
# entry:
|
57
|
+
# between: Between %1 and %2
|
58
|
+
# methods: Is %1 method
|
59
|
+
#
|
60
|
+
# comments: !!pl
|
61
|
+
# 0: no comments
|
62
|
+
# 1: one comment
|
63
|
+
# n: %1 comments
|
64
|
+
#
|
65
|
+
# sum: !!proc |x, y| "is #{x + y}"
|
66
|
+
#
|
67
|
+
# example.rb
|
68
|
+
#
|
69
|
+
# i18n = R18n::Translation.load(['ru', 'en'], 'translations/')
|
70
|
+
# i18n.one #=> "Один"
|
71
|
+
# i18n.two #=> "Two"
|
72
|
+
#
|
73
|
+
# i18n.two.locale['code'] #=> "en"
|
74
|
+
# i18n.two.locale['direction'] #=> "ltr"
|
75
|
+
#
|
76
|
+
# i18n.entry.between(2, 3) #=> "between 2 and 3"
|
77
|
+
# i18n['methods', 'object'] #=> "Is object method"
|
78
|
+
#
|
79
|
+
# i18n.comments(0) #=> "no comments"
|
80
|
+
# i18n.comments(10) #=> "10 comments"
|
81
|
+
#
|
82
|
+
# i18n.sum(2, 3) #=> "is 5"
|
83
|
+
#
|
84
|
+
# i18n.yes #=> "Yes"
|
85
|
+
# i18n.ok #=> "OK"
|
86
|
+
# i18n.cancel #=> "Cancel"
|
87
|
+
#
|
88
|
+
# == Extension translations
|
89
|
+
# For r18n plugin you can add dir with translations, which will be used with
|
90
|
+
# application translations. For example, DB plugin may place translations for
|
91
|
+
# error messages in extension dir. R18n contain translations for base words as
|
92
|
+
# extension dir too.
|
93
|
+
class Translation
|
94
|
+
@@extension_translations = [
|
95
|
+
Pathname(__FILE__).dirname.expand_path + '../../base']
|
96
|
+
|
97
|
+
# Get dirs with extension translations. If application translations with
|
98
|
+
# same locale isn’t exists, extension file willn’t be used.
|
99
|
+
def self.extension_translations
|
100
|
+
@@extension_translations
|
101
|
+
end
|
102
|
+
|
103
|
+
# Return available translations in +translations_dir+
|
104
|
+
def self.available(translations_dir)
|
105
|
+
Dir.glob(File.join(translations_dir, '*.yml')).map do |i|
|
106
|
+
File.basename(i, '.yml')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Load all available translations for +locales+. +locales+ may be string
|
111
|
+
# with one user locale or array with many.
|
112
|
+
def self.load(locales, translations_dir)
|
113
|
+
locales = locales.to_a if Array != locales.class
|
114
|
+
|
115
|
+
locales &= self.available(translations_dir)
|
116
|
+
|
117
|
+
translations = []
|
118
|
+
locales.map! do |locale|
|
119
|
+
translation = {}
|
120
|
+
@@extension_translations.each do |dir|
|
121
|
+
file = File.join(dir, "#{locale}.yml")
|
122
|
+
translation.merge! YAML::load_file(file) if File.exists? file
|
123
|
+
end
|
124
|
+
file = File.join(translations_dir, "#{locale}.yml")
|
125
|
+
translation.merge! YAML::load_file(file)
|
126
|
+
translations << translation
|
127
|
+
|
128
|
+
if Locale.exists? locale
|
129
|
+
Locale.new(locale)
|
130
|
+
else
|
131
|
+
locale
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
self.new(locales, translations)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Create translation hash with messages in +translations+ for +locales+.
|
139
|
+
#
|
140
|
+
# This is internal a constructor. To load translation use
|
141
|
+
# <tt>R18n::Translation.load(locales, translations_dir)</tt>.
|
142
|
+
def initialize(locales, translations)
|
143
|
+
@locales = locales
|
144
|
+
@translations = translations
|
145
|
+
end
|
146
|
+
|
147
|
+
# Short and pretty way to get translation by method name. If translation
|
148
|
+
# has name like object methods (+new+, +to_s+, +methods+) use <tt>[]</tt>
|
149
|
+
# method to access.
|
150
|
+
#
|
151
|
+
# Translation can contain variable part. Just set is as <tt>%1</tt>,
|
152
|
+
# <tt>%2</tt>, etc in translations file and set values as methods params.
|
153
|
+
def method_missing(name, *params)
|
154
|
+
self[name.to_s, *params]
|
155
|
+
end
|
156
|
+
|
157
|
+
# Return translation with special +name+.
|
158
|
+
#
|
159
|
+
# Translation can contain variable part. Just set is as <tt>%1</tt>,
|
160
|
+
# <tt>%2</tt>, etc in translations file and set values in next +params+.
|
161
|
+
def [](name, *params)
|
162
|
+
@translations.each_with_index do |translation, i|
|
163
|
+
result = translation[name]
|
164
|
+
next if result.nil?
|
165
|
+
|
166
|
+
if YAML::PrivateType == result.class
|
167
|
+
case result.type_id
|
168
|
+
when 'proc'
|
169
|
+
return eval("proc {#{result.value}}").call(*params)
|
170
|
+
when 'pl'
|
171
|
+
locale = @locales[i]
|
172
|
+
|
173
|
+
type = if Locale == locale.class
|
174
|
+
locale.pluralize(params.first)
|
175
|
+
else
|
176
|
+
Locale.default_pluralize(params.first)
|
177
|
+
end
|
178
|
+
|
179
|
+
type = 'n' if not result.value.include? type
|
180
|
+
result = result.value[type]
|
181
|
+
else
|
182
|
+
return result
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
if String == result.class
|
187
|
+
params.each_with_index do |param, i|
|
188
|
+
result.gsub! "%#{i+1}", param.to_s
|
189
|
+
end
|
190
|
+
return TranslatedString.new(result, @locales[i])
|
191
|
+
elsif Hash == result.class
|
192
|
+
return self.class.new(@locales, @translations.map { |i|
|
193
|
+
i[name] or {}
|
194
|
+
})
|
195
|
+
else
|
196
|
+
return result
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
return nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|