r18n-core 0.1
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/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
|