babelfish-ruby 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1ed8121d4c95af80ac52863c5a56764ae14d401b
4
+ data.tar.gz: 1c2f02c33e27f7ef0cd9149b7887f208098316d8
5
+ SHA512:
6
+ metadata.gz: 307e241634ea1b3b76183e8ff5a15bdc80397ddcb127e723a1bea2070d233d21f186eeec7e1ca4dfc62ce128804f7fe4e55cded255f83011a704bb1bb14036ec
7
+ data.tar.gz: 5a3eb4545f188926ce0495ba435b7872321d9c05f1eb83cded96d01d956867ae4a9d5d12a0ad556ddf801e6932861fa45265b78f814d498adb1d65306b5616c1
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ pkg/
3
+ doc/
4
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org/"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ (The MIT License)
2
+
3
+ Copyright (C) 2014 by Akzhan Abdulin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,137 @@
1
+ BabelFish - human friendly i18n for Ruby
2
+ ========================================
3
+
4
+ Internationalisation with easy syntax for Ruby, Perl, node.js and browser. Classic solutions
5
+ use multiple phrases for plurals. But we define plurals inline - that's more
6
+ compact, and easy for programmers. Also, phrases are grouped into nested scopes,
7
+ like in Ruby.
8
+
9
+ We support all pluralisation rules from [unicode CLDR](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html),
10
+ version [2.0.1](http://cldr.unicode.org/index/downloads).
11
+
12
+ ### Installation
13
+
14
+ ```bash
15
+ gem ins babelfish
16
+
17
+ # or simply bundle it into your Gemfile
18
+ ```
19
+
20
+ ### Phrases Syntax
21
+
22
+ - `#{varname}` Echoes value of variable
23
+ - `((Singular|Plural1|Plural2)):count` Plural form
24
+
25
+ example:
26
+
27
+ - `А у меня в кармане #{nails_count} ((гвоздь|гвоздя|гвоздей)):nails_count`
28
+
29
+ You can also omit anchor variable for plurals, by default it will be `count`.
30
+ Thus following variants are equal:
31
+
32
+ - `I have #{count} ((nail|nails))`
33
+ - `I have #{count} ((nail|nails)):count`
34
+
35
+ Also you can use variables in plural parts:
36
+
37
+ - `I have ((#{count} nail|#{count} nails))`
38
+
39
+ Need special zero form or overwrite any specific value? No problems:
40
+
41
+ - `I have ((=0 no nails|#{count} nail|#{count} nails))`
42
+
43
+
44
+ ##### Escape chars
45
+
46
+ If you need `#{`, `((`, `|` or `))` somewhere in text, where it can be considered
47
+ as markup part - just escape them with `\`.
48
+
49
+
50
+ ##### Example with YAML
51
+
52
+ As BabelFish flatten scopes, it's really fun and nice to store translations in
53
+ YAML files:
54
+
55
+ ```yaml
56
+ ---
57
+ ru-RU:
58
+ profile: Профиль
59
+ forums: Форумы
60
+ apps:
61
+ forums:
62
+ new_topic: Новая тема
63
+ last_post:
64
+ title : Последнее сообщение
65
+ by : от
66
+ demo:
67
+ apples: На столе лежит #{count} ((яблоко|яблока|яблок))
68
+ ```
69
+
70
+ ### Usage
71
+
72
+ ```ruby
73
+ # Create new instance of Babelfish with default language/locale: 'en-GB'
74
+ require 'babelfish'
75
+ i18n = new Babelfish('en-GB');
76
+
77
+
78
+ # Fill in some phrases
79
+ i18n.addPhrase('en-GB', 'demo.hello', 'Hello, #{user.name}.');
80
+ i18n.addPhrase('en-GB', 'demo.conv.wazup', 'Whats up?');
81
+ i18n.addPhrase('en-GB', 'demo.conv.alright', 'Alright, man!');
82
+ i18n.addPhrase('en-GB', 'demo.coerce', 'Total: #{count}.');
83
+
84
+ i18n.addPhrase('ru-RU', 'demo.hello', 'Привет, #{user.name}.');
85
+ i18n.addPhrase('ru-RU', 'demo.conv.wazup', 'Как дела?');
86
+
87
+ i18n.addPhrase('uk-UA', 'demo.hello', 'Здоровенькі були, #{user.name}.');
88
+
89
+
90
+ # Set locale fallback to use the most appropriate translation when possible
91
+ i18n.setFallback('uk-UA', 'ru-RU');
92
+
93
+
94
+ # Translate
95
+ var params = {user: {name: 'ixti'}};
96
+
97
+ i18n.t('ru-RU', 'demo.hello', params); // -> 'Привет, ixti.'
98
+ i18n.t('ru-RU', 'demo.conv.wazup'); // -> 'Как дела?'
99
+ i18n.t('ru-RU', 'demo.conv.alright'); // -> 'Alright, man!'
100
+
101
+ i18n.t('uk-UA', 'demo.hello', params); // -> 'Здоровенькі були, ixti.'
102
+ i18n.t('uk-UA', 'demo.conv.wazup'); // -> 'Как дела?'
103
+ i18n.t('uk-UA', 'demo.conv.alright'); // -> 'Alright, man!'
104
+
105
+ # When params is number or strings, it will be coerced to
106
+ # `{ count: XXX, value: XXX }` - use any of those in phrase.
107
+ i18n.t('en-GB', 'demo.coerce', 5); // -> 'Total: 5.'
108
+
109
+
110
+ # You may wish to "dump" translations to load in browser later
111
+ # Dump will include all fallback translations and fallback rules
112
+ var locale_dump = i18n.stringify('ru-RU');
113
+
114
+ var i18n_new = require('babelfish')('en-GB'); // init without `new` also works
115
+ i18n_new.load(locale_dump);
116
+
117
+
118
+ # Use objects instead of strings (object/array/number/boolean) - can be
119
+ # useful to prepare bulk data for external libraries.
120
+ # Note, only JSON-supported types are ok (no date & regex)
121
+ i18n.addPhrase('en-GB', 'demo.boolean', true);
122
+ i18n.addPhrase('en-GB', 'demo.number', 123);
123
+ i18n.addPhrase('en-GB', 'demo.array', [1, 2, 3]);
124
+ # fourth param required for hashes (objects) to disable flattening,
125
+ # other types are autodetected
126
+ i18n.addPhrase('en-GB', 'demo.array', { foo:1, bar:"2" }, false);
127
+ ```
128
+
129
+
130
+ ### Implementations in other languages
131
+
132
+ - Perl - [Locale::Babelfish](https://metacpan.org/pod/Locale::Babelfish)
133
+
134
+
135
+ ### License
136
+
137
+ View the [LICENSE](https://github.com/regru/babelfish-ruby/blob/master/LICENSE) file (MIT).
@@ -0,0 +1,25 @@
1
+ require 'rake'
2
+
3
+ APP_ROOT = File.dirname(__FILE__).freeze
4
+
5
+ require 'bundler/setup'
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ require 'rspec/core/rake_task'
9
+
10
+ RSpec::Core::RakeTask.new
11
+
12
+ RSpec::Core::RakeTask.new(:rcov) do |t|
13
+ t.rcov = true
14
+ t.ruby_opts = '-w'
15
+ t.rcov_opts = %q[-Ilib --exclude "spec/*,gems/*"]
16
+ end
17
+
18
+ task :default => :spec
19
+
20
+ require 'yard'
21
+
22
+ YARD::Rake::YardocTask.new do |yard|
23
+ version = File.exists?('VERSION') ? IO.read('VERSION') : ""
24
+ yard.options << "--title='git-commit-notifier #{version}'"
25
+ end
@@ -0,0 +1,27 @@
1
+ GEM_ROOT = File.dirname(__FILE__).freeze unless defined?(GEM_ROOT)
2
+
3
+ lib_path = File.expand_path('lib', GEM_ROOT)
4
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include? lib_path
5
+
6
+ require 'babelfish/version'
7
+
8
+ Gem::Specification.new do |s|
9
+ s.name = "babelfish-ruby"
10
+ s.version = Babelfish::VERSION.dup
11
+ s.date = Time.now.strftime('%Y-%m-%d')
12
+ s.summary = "Babelfish syntax internationalization module."
13
+ s.email = "akzhan.abdulin@gmail.com"
14
+ s.homepage = "http://regru.github.io/babelfish-ruby/"
15
+ s.description = "Human friendly i18n in both JavaScript, Ruby, Perl whatever."
16
+ s.has_rdoc = true
17
+ s.author = "Akzhan Abdulin"
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
21
+ s.add_development_dependency "bundler", "~> 1.3"
22
+ s.add_development_dependency('rake', ['~> 0.8', '!= 0.9.0'])
23
+ s.add_development_dependency('yard', '~> 0.8.7')
24
+ s.add_development_dependency('redcarpet', '~> 3.0')
25
+ s.add_development_dependency(%q<rspec>, [">= 3.0"])
26
+ end
27
+
@@ -0,0 +1,2 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require_relative 'babelfish'
@@ -0,0 +1,289 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'find'
3
+ require 'yaml'
4
+
5
+ require 'babelfish/phrase/string_to_compile'
6
+ require 'babelfish/phrase/parser'
7
+ require 'babelfish/phrase/compiler'
8
+
9
+ class Babelfish
10
+ attr_accessor :dictionaries, :dirs, :suffix, :default_locale
11
+ attr_accessor :fallbacks, :fallback_cache
12
+ attr_accessor :file_filter, :watch, :watchers
13
+ attr_accessor :compiler, :parser
14
+ attr_accessor :_cfg
15
+
16
+ def _built_config(cfg)
17
+ {
18
+ dictionaries: {},
19
+ dirs: [ "./locales" ],
20
+ fallbacks: {},
21
+ fallback_cache: {},
22
+ suffix: cfg[:suffix] || 'yml',
23
+ default_locale: cfg[:default_locale] || 'en_US',
24
+ watch: cfg[:watch] || 0,
25
+ watchers: {},
26
+ }.merge( cfg || {} )
27
+ end
28
+
29
+ def initialize(cfg = {})
30
+ cfg = {
31
+ _cfg: cfg,
32
+ }.merge( _built_config(cfg) )
33
+
34
+ cfg.keys.each do |key|
35
+ send("#{key}=", cfg[key]) if respond_to?("#{key}=")
36
+ end
37
+
38
+ self.parser = Babelfish::Phrase::Parser.new
39
+ self.compiler = Babelfish::Phrase::Compiler.new
40
+
41
+ load_dictionaries( file_filter )
42
+ self.locale = default_locale
43
+ end
44
+
45
+ def locale
46
+ @locale
47
+ end
48
+
49
+ def locale=(new_locale)
50
+ @locale = detect_locale( new_locale )
51
+ end
52
+
53
+ def watch?
54
+ !! watch
55
+ end
56
+
57
+ def prepare_to_compile
58
+ dictionaries.each_pair do |locale, dic|
59
+ dic.each_pair do |key, value|
60
+ if phrase_need_compilation( value, key )
61
+ dic[key] = Babelfish::Phrase::StringToCompile.new(value) # lazy compile
62
+ #dic[key] = compiler.compile( parser.parse(value, locale) );
63
+ end
64
+ end
65
+ end
66
+ true
67
+ end
68
+
69
+
70
+ def detect_locale(locale)
71
+ return locale if dictionaries.has_key?(locale)
72
+ alt_locale = dictionaries.keys.find { |loc| loc =~ /^#{Regexp.escape(locale)}[\-_]/i }
73
+ if alt_locale && dictionaries.has_key?(alt_locale)
74
+ # Lets locale dictionary will refer to alt locale dictinary.
75
+ # This speeds up all subsequent calls of t/detect/exists on this locale.
76
+ dictionaries[locale] = dictionaries[alt_locale]
77
+
78
+ fallback_cache[locale] = fallback_cache[alt_locale] if fallback_cache.has_key?(alt_locale)
79
+
80
+ fallbacks[locale] = fallbacks[alt_locale] if fallbacks.has_key?(alt_locale)
81
+
82
+ return locale
83
+ end
84
+ return default_locale if dictionaries.has_key?(default_locale)
85
+ raise "bad locale: #{locale} and bad default_locale: #{default_locale}."
86
+ end
87
+
88
+
89
+ def load_dictionaries(filter)
90
+ dirs.each do |dir|
91
+ fdir = File.absolute_path( dir )
92
+ Find.find(dir) do |file|
93
+ file_path = File.absolute_path(file)
94
+ next unless FileTest.file? file_path
95
+ return if filter && !filter( file_path )
96
+ directories, base = File.split( file_path )
97
+ tmp = base.split('.')
98
+ cur_suffix = tmp.pop
99
+ return if cur_suffix != suffix
100
+ locale = tmp.pop
101
+ dictname = tmp.join('.')
102
+ subdir = directories
103
+ if subdir =~ /^#{Regexp.escape(fdir)}[\\\/](.+)$/
104
+ dictname = "#{$1}#{dictname}"
105
+ end
106
+ _load_dictionary( dictname, locale, file )
107
+ end
108
+ end
109
+ prepare_to_compile
110
+ end
111
+
112
+ def _load_dictionary( dictname, lang, file )
113
+ dictionaries[lang] ||= {}
114
+
115
+ yaml = YAML.load_file( file )
116
+ _flat_hash_keys( yaml, "#{dictname}.", dictionaries[lang] )
117
+
118
+ return unless watch?
119
+ watchers[file] = File.mtime(file)
120
+ end
121
+
122
+ def phrase_need_compilation( phrase, key )
123
+ raise "L10N: #{key} is undef" if phrase.nil?
124
+ return phrase.kind_of?(String) && phrase =~ /(?:\(\(|\#\{|\\\\)/
125
+ end
126
+
127
+ def on_watcher_change
128
+ _cfg.keys.each do |key|
129
+ send("#{key}=", nil) if respond_to?("#{key}=")
130
+ end
131
+
132
+ new_cfg = _built_config( _cfg )
133
+ new_cfg.keys.each do |key|
134
+ send("#{key}=", new_cfg[key]) if respond_to?("#{key}=")
135
+ end
136
+ load_dictionaries
137
+ self.locale = default_locale
138
+ end
139
+
140
+ def look_for_watchers
141
+ ok = true
142
+ watchers.each_pair do | file, mtime |
143
+ new_mtime = File.mtime(file)
144
+ if mtime.nil? || new_mtime.nil? || new_mtime != mtime
145
+ ok = false
146
+ break
147
+ end
148
+ end
149
+ return if ok;
150
+ on_watcher_change
151
+ end
152
+
153
+ def t_or_undef( dictname_key, params = nil, custom_locale = nil )
154
+ # disallow non-ASCII keys
155
+ raise("wrong dictname_key: #{dictname_key}") if dictname_key =~ /\P{ASCII}/;
156
+
157
+ look_for_watchers if watch?
158
+
159
+ _locale = custom_locale ? detect_locale( custom_locale ) : self.locale
160
+
161
+ r = dictionaries[_locale][dictname_key]
162
+
163
+ unless r.nil?
164
+ if r.kind_of?(Babelfish::Phrase::StringToCompile)
165
+ dictionaries[_locale][dictname_key] = r = compiler.compile(
166
+ parser.parse( r, _locale ),
167
+ )
168
+ end
169
+ # fallbacks
170
+ else
171
+ fallback_cache[_locale] ||= {}
172
+ # Cache can contain undef, as unexistent value.
173
+ if fallback_cache[_locale].has_key?(dictname_key)
174
+ r = fallback_cache[_locale][dictname_key]
175
+ else
176
+ fallback_locales = fallbacks[_locale] || []
177
+ fallback_locales.each do |fallback|
178
+ r = dictionaries[fallback][dictname_key]
179
+ unless r.nil?
180
+ if r.kind_of?(Babelfish::Phrase::StringToCompile)
181
+ dictionaries[fallback][dictname_key] = r = compiler.compile(
182
+ parser.parse( r, fallback ),
183
+ )
184
+ end
185
+ break
186
+ end
187
+ end
188
+ fallback_cache[_locale][dictname_key] = r;
189
+ end
190
+ end
191
+
192
+ if r.kind_of?(Proc)
193
+ flat_params = {}
194
+ # Convert parameters hash to flat form like "key.subkey"
195
+ unless params.nil?
196
+ # Scalar interpreted as { count => scalar, value => scalar }.
197
+ unless params.kind_of?(Hash)
198
+ flat_params = {
199
+ 'count' => params,
200
+ 'value' => params,
201
+ }
202
+ else
203
+ _flat_hash_keys( params, '', flat_params )
204
+ end
205
+ end
206
+
207
+ return r.call( flat_params );
208
+ end
209
+ return r
210
+ end
211
+
212
+ def t( dictname_key, params = nil, custom_locale = nil )
213
+ t_or_undef( dictname_key, params, custom_locale ) || "[#{dictname_key}]";
214
+ end
215
+
216
+ def has_any_value( dictname_key, custom_locale = nil )
217
+
218
+ # disallow non-ASCII keys
219
+ raise("wrong dictname_key: #{dictname_key}") if dictname_key =~ /\P{ASCII}/;
220
+
221
+ look_for_watchers if watch?
222
+
223
+ _locale = custom_locale ? detect_locale( custom_locale ) : self.locale
224
+
225
+ return true if dictionaries[_locale].has_key?(dictname_key)
226
+
227
+ fallback_cache[_locale] ||= {}
228
+ return ! fallback_cache[_locale][dictname_key].nil? if fallback_cache[_locale].has_key?(dictname_key)
229
+
230
+ fallback_locales = fallbacks[_locale] || []
231
+ return true if fallback_locales.find do |fallback|
232
+ ! dictionaries[fallback][dictname_key].nil?
233
+ end
234
+
235
+ return false
236
+ end
237
+
238
+ def set_fallback( locale, fallback_locales )
239
+ return unless fallback_locales && fallback_locales.size
240
+
241
+ _locale = detect_locale( locale )
242
+
243
+ fallbacks[_locale] = fallback_locales
244
+ fallback_cache.delete(_locale)
245
+
246
+ return true
247
+ end
248
+
249
+ def _flat_hash_keys( hash, prefix, store )
250
+ hash.each_pair do | key, value |
251
+ if value.kind_of?(Hash)
252
+ _flat_hash_keys( value, "#{prefix}#{key}.", store )
253
+ else
254
+ store["#{prefix}#{key}"] = value.kind_of?(Symbol) ? value.to_s : value
255
+ end
256
+ end
257
+ return true
258
+ end
259
+
260
+ def addPhrase( locale, phrase, translation, flatten_level = Float::INFINITY)
261
+ fl = Float::INFINITY
262
+ case flatten_level
263
+ when FalseClass
264
+ fl = 0
265
+ when TrueClass
266
+ fl = Float::INFINITY
267
+ when FixNum, Float
268
+ fl = flatten_level.to_i
269
+ fl = 0 if fl < 0
270
+ else
271
+ fl = Float::INFINITY;
272
+ end
273
+
274
+ if translation.kind_of?(Hash) && fl > 0
275
+ translation.each_pair do | key, val |
276
+ addPhrase( locale, "#{phrase}.#{key}", val, fl - 1 )
277
+ end
278
+ return
279
+ end
280
+
281
+ if phrase_need_compilation(translation)
282
+ dictionaries[locale][phrase] = Babelfish::Phrase::StringToCompile.new(translation)
283
+ else
284
+ dictionaries[locale][phrase] = translation
285
+ end
286
+
287
+ self.fallback_cache = {}
288
+ end
289
+ end