zlocalize 6.1.1 → 7.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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +20 -0
- data/README.md +584 -0
- data/lib/zlocalize/source_processor.rb +67 -64
- metadata +14 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 87f72c65f42afb37957fea37b99f9ff4ed4f1b5c80158644b5e333213796f551
|
|
4
|
+
data.tar.gz: c2f63f596d3f50a34c5672337124a196106b7246c1e2cb013eb0d46a5e12a5f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4f13ccb6f5dbdc0b0fb166f3ad36f948e72fa2097bdab4a47c67f5d83de9331c68597c02079705c63c3e8b7502218e31d4efb6b49ccbc86ba47791dff84321ef
|
|
7
|
+
data.tar.gz: d2572db0055d54d5f7c10bae01b5eb54e517a5bc810aa0f2ac3ad580a71bf145bc803978e2598437bbf1da2c57aaa2e3c751dddfb9587285cb0b66bfbe733c2a
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2008-2020 Charles Bedard (zzeligg@gmail.com)
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
# ZLocalize - A translation engine for Rails 7.1+ applications
|
|
2
|
+
|
|
3
|
+
`ZLocalize` provides string translation through YAML-defined dictionaries.
|
|
4
|
+
|
|
5
|
+
What it does:
|
|
6
|
+
|
|
7
|
+
* It uses strings in the source code as keys for lookups with interpolation
|
|
8
|
+
of values.
|
|
9
|
+
* It features a harvester rake task which scans the application source code
|
|
10
|
+
(controllers, models, helpers and views) and to extract all the strings used
|
|
11
|
+
in calls to translation methods.
|
|
12
|
+
* The harvester also *manages* the translation entries, e.g. each time it
|
|
13
|
+
runs, it will update the translation dictionaries without destroying
|
|
14
|
+
existing entries or creating duplicate entries.
|
|
15
|
+
* Translation dictionaries are stored as YAML files, containing metadata (such
|
|
16
|
+
as unique entry ID's and a list of references where an entry is used, much
|
|
17
|
+
like gettext .po files).
|
|
18
|
+
* Provides a simple way to manage used-generated content in multiple languages
|
|
19
|
+
by including modules an ActiveRecord.
|
|
20
|
+
* Provides conversion of locale-formatted decimal values assigned to
|
|
21
|
+
ActiveRecord attributes by users (through forms, console, etc.).
|
|
22
|
+
|
|
23
|
+
What it does not do:
|
|
24
|
+
|
|
25
|
+
* Localization of date/time or other regional-dependent data (except for
|
|
26
|
+
input of decimal values). For localization, you must use `I18n#localize`.
|
|
27
|
+
* Provide an interface to edit and manage translations. You have to edit the
|
|
28
|
+
YAML files by hand.
|
|
29
|
+
|
|
30
|
+
Within a Rails application, `ZLocalize` does not replace the `I18n` backend. Its
|
|
31
|
+
operation is (almost) entirely independent from `I18n`. The only tie to `I18n`
|
|
32
|
+
is the internal use of the `I18n#locale` and `I18n#default_locale` methods to
|
|
33
|
+
get/set its own active and default locales. Setting the active locale through
|
|
34
|
+
`ZLocalize` will set `I18n`'s locale and vice-versa.
|
|
35
|
+
|
|
36
|
+
Other than that, `ZLocalize` keeps its translation data isolated from the
|
|
37
|
+
`I18n` translation data.
|
|
38
|
+
|
|
39
|
+
The reason for this architecture is that the Rails framework itself contains a
|
|
40
|
+
finite set of strings, and as such `I18n` is already doing a fine job with
|
|
41
|
+
translation and localization of the framework internal strings and helpers,
|
|
42
|
+
using a hierarchical data store with Ruby symbols as keys.
|
|
43
|
+
|
|
44
|
+
One of the main reasons `ZLocalize` was created was to avoid having to deal
|
|
45
|
+
with symbols as keys while developing. It is much easier and straightforward
|
|
46
|
+
to start coding your application using a base locale, and then run the
|
|
47
|
+
harvester to collect all the strings used in the translation calls throughout
|
|
48
|
+
the source code.
|
|
49
|
+
|
|
50
|
+
## Requirements
|
|
51
|
+
|
|
52
|
+
* Ruby 3.4 or later
|
|
53
|
+
* Rails 7.1 or later
|
|
54
|
+
|
|
55
|
+
The harvester parses application source with [Prism](https://github.com/ruby/prism),
|
|
56
|
+
which ships in Ruby's standard library from Ruby 3.4 onwards. Earlier Ruby and
|
|
57
|
+
Rails releases are no longer supported: the `parser` gem used by ZLocalize 6.x has
|
|
58
|
+
no Ruby 4 target, and Rails 5.2/6.x are end-of-life.
|
|
59
|
+
|
|
60
|
+
If you really must use this gem with a previous version of Ruby (< 2.0) or Rails (< 6.0),
|
|
61
|
+
install [ZLocalize 4.2.3](https://github.com/zzeligg/zlocalize/releases/tag/4.2.3).
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
Add to your Gemfile:
|
|
66
|
+
|
|
67
|
+
gem 'zlocalize'
|
|
68
|
+
|
|
69
|
+
and run +bundle install+
|
|
70
|
+
|
|
71
|
+
## Basic configuration
|
|
72
|
+
|
|
73
|
+
1. Create an initializer:
|
|
74
|
+
|
|
75
|
+
bin/rails generate zlocalize:initializer
|
|
76
|
+
|
|
77
|
+
This will create `config/initializers/zlocalize.rb` with some default
|
|
78
|
+
configuration values.
|
|
79
|
+
|
|
80
|
+
You can configure I18n and ZLocalize in each their own initializer files,
|
|
81
|
+
or you can configure both in the same initializer.
|
|
82
|
+
|
|
83
|
+
First, set the default locale:
|
|
84
|
+
|
|
85
|
+
I18n.default_locale = :en
|
|
86
|
+
|
|
87
|
+
Calling `I18n.default_locale` and `I18n.locale` is the same as calling
|
|
88
|
+
`ZLocalize.default_locale` and `ZLocalize.locale`. The two are bound together.
|
|
89
|
+
|
|
90
|
+
2. Edit the `ZLocalize.config.locales` structure, by specifying a Hash for
|
|
91
|
+
each locale you want to support. Each locale definition is a key/value pair.
|
|
92
|
+
For example, the French locale definition could be:
|
|
93
|
+
|
|
94
|
+
ZLocalize.config.locales = {
|
|
95
|
+
:fr => {
|
|
96
|
+
:plural_select => -> (n) { n <= 0 ? 0 : (n > 1 ? 2 : 1) },
|
|
97
|
+
:translations => File.join(Rails.root,'config/translations/fr.strings.yml'),
|
|
98
|
+
:titleize => -> (s) { s.capitalize.to_s },
|
|
99
|
+
:convert_float => -> (s) { s.to_s.gsub(' ','').gsub(',','.') }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
* `:plural_select` is a proc evaluating the value of `n` for
|
|
104
|
+
translation of plural strings (in calls to `ZLocalize#pluralize` or `n_`). It returns
|
|
105
|
+
the index of the Array element to use as the translation for plural
|
|
106
|
+
expressions.
|
|
107
|
+
|
|
108
|
+
* `:translations` is an Array (or a single entry) of file names where the
|
|
109
|
+
translations are stored.
|
|
110
|
+
|
|
111
|
+
* `:titleize` is a proc to be used instead of ActiveSupport's `titleize`
|
|
112
|
+
helper. Call `ZLocalize.titleize` instead of the builtin titleize (helper or
|
|
113
|
+
`ActiveSupport::Inflector` method) in your application.
|
|
114
|
+
|
|
115
|
+
* `:convert_float` is a proc to be used to convert ActiveRecord
|
|
116
|
+
attributes containing decimal values. For example, in French, if the String
|
|
117
|
+
`"1 234,23"` is assigned to a AR instance, it will be converted to `"1234.23"`
|
|
118
|
+
so that Rails can then correctly convert it to `BigDecimal` internally.
|
|
119
|
+
|
|
120
|
+
Note that you can also call `ZLocalize#convert_float` anywhere in your
|
|
121
|
+
application.
|
|
122
|
+
|
|
123
|
+
3. Set the other configuration values:
|
|
124
|
+
|
|
125
|
+
* `define_gettext_methods` : define `_()` and `n_()` (on `Object` class).
|
|
126
|
+
Defaults to `true`. If you do not define the gettext methods, you will
|
|
127
|
+
need to call `ZLocalize.translate` and `ZLocalize.pluralize` in your
|
|
128
|
+
application.
|
|
129
|
+
|
|
130
|
+
* `return_source_on_missing`: Hash with a key/value pair for each of your
|
|
131
|
+
application environments. This indicates if missing translations in a
|
|
132
|
+
given locale should raise a `ZLocalize::MissingTranslationDataError`
|
|
133
|
+
exception. Defaults to `{ :development => true, :test => false,
|
|
134
|
+
:production => false, :staging => false }`
|
|
135
|
+
|
|
136
|
+
* `harvest_paths` : Array of path patterns (same as `Dir.glob`) relative to
|
|
137
|
+
`Rails.root` that the ZLocalizer Harvester (see Harvester section below) will
|
|
138
|
+
scan to collect all calls to `_()`, `n_()`, `ZLocalize.translate` and
|
|
139
|
+
`ZLocalize.pluralize`. Defaults to
|
|
140
|
+
`["app/channels/**/*.rb", "app/controllers/**/*.rb", "app/helpers/**/*.rb",
|
|
141
|
+
"app/models/**/*.rb", "app/views/**/*.erb", "app/mailers/**/*.rb",
|
|
142
|
+
"app/jobs/**/*.rb", "lib/**/*.rb" ]`
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## In your application (Controllers, Helpers, Views, Models, etc.)
|
|
146
|
+
|
|
147
|
+
The idea is to simply start coding your application without thinking too much
|
|
148
|
+
about how to translate it. The only thing to worry about is wrapping any String
|
|
149
|
+
you will eventually want translated in a call to `_()` or `n_()` (or
|
|
150
|
+
`ZLocalize.translate` and `ZLocalize.pluralize`). Make sure you use parenthesis
|
|
151
|
+
for the parameter list to those methods (see Harvester section below for an
|
|
152
|
+
explanation).
|
|
153
|
+
|
|
154
|
+
For example, in a view:
|
|
155
|
+
|
|
156
|
+
<%= _("Dear user") %>
|
|
157
|
+
<%= n_(["No messages","One message", "{{count}} messages"], @user.messages.count) %>
|
|
158
|
+
|
|
159
|
+
Or in a controller:
|
|
160
|
+
|
|
161
|
+
def create
|
|
162
|
+
@post = Post.new(params[:post])
|
|
163
|
+
if @post.save
|
|
164
|
+
flash[:notice] = _("Your post has been added.")
|
|
165
|
+
redirect_to [@post] and return
|
|
166
|
+
else
|
|
167
|
+
flash[:error] = _("There was an error adding your post")
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
There are 2 methods to translate content:
|
|
172
|
+
|
|
173
|
+
* `ZLocalize.translate(key, options = {})` (and its gettext-style alias `_(key, options = {})`).
|
|
174
|
+
This method looks up the String `key` with the following `options`:
|
|
175
|
+
|
|
176
|
+
* `:locale` : Lookup key in this locale. Defaults to `I18n.current_locale`.
|
|
177
|
+
* `:default` : Return this String if key is not found.
|
|
178
|
+
* `:return_source_on_missing` : Override the global `ZLocalize.config.return_source_on_missing` value.
|
|
179
|
+
|
|
180
|
+
Any other option key passed is meant to be an interpolated named value. See Interpolation section below.
|
|
181
|
+
|
|
182
|
+
* `ZLocalize.pluralize(key, count, options = {})` (and its gettext-style alias `n_(key, count, options = {})`).
|
|
183
|
+
This methods looks up the Array `key` (which is an Array of String), and computes the index of the String to
|
|
184
|
+
return based on the value of `count`. The index is computed using the `:plural_select` Proc from
|
|
185
|
+
`ZLocalize.config` (see Basic Configuration section above).
|
|
186
|
+
|
|
187
|
+
The `options` are the same as the `translate` method. Also, the `count` parameter is also automatically
|
|
188
|
+
treated as a value to be interpolated (when `{{count}}` token is present in translation string).
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
## Interpolation
|
|
192
|
+
|
|
193
|
+
ZLocalize supports interpolation of values in source strings. Simply enclose
|
|
194
|
+
the values in double curly braces (`{{` and `}}`), and pass the actual values
|
|
195
|
+
in a Hash as the 2nd parameter to `ZLocalize.translate` and
|
|
196
|
+
`ZLocalize.pluralize`. For example:
|
|
197
|
+
|
|
198
|
+
<%= _("Hello, {{username}}! How are you {{moment}}?",
|
|
199
|
+
:username => @user.name,
|
|
200
|
+
:moment => Time.now.hour > 18 ? _("tonight") : _("today") ) %>
|
|
201
|
+
|
|
202
|
+
If you need to output actual double curly braces, simply double-escape them:
|
|
203
|
+
|
|
204
|
+
<%= _("Hello, \\{{username}}") %>
|
|
205
|
+
|
|
206
|
+
The above will return "Hello, {{username}}" without interpolation of
|
|
207
|
+
`{{username}}`.
|
|
208
|
+
|
|
209
|
+
## Namespacing Source Strings
|
|
210
|
+
|
|
211
|
+
It is possible to namespace (to give a scope to) a source string by simply
|
|
212
|
+
prefixing it with a name that ends with `::` . The namespace is used
|
|
213
|
+
when looking up the key in the current locale, but the prefix will always be
|
|
214
|
+
stripped in any output (even in the default locale).
|
|
215
|
+
|
|
216
|
+
Let's say your default locale is 'en':
|
|
217
|
+
|
|
218
|
+
<%= _('btn::Edit') %>
|
|
219
|
+
|
|
220
|
+
The above returns "Edit" if the current locale is the base locale (en in this
|
|
221
|
+
case), and will lookup `"btn::Edit"` (e.g. including namespace) for all other
|
|
222
|
+
locales and return the corresponding translation. The translation may include a
|
|
223
|
+
scope, but it will be removed too.
|
|
224
|
+
|
|
225
|
+
The idea behind such scopes is that some languages have different spelling or
|
|
226
|
+
even words for a given word in another locale. For example, "Update" in English
|
|
227
|
+
can be used both as a noun and a verb. But it is not always the case in other
|
|
228
|
+
languages. By adding scopes such as `"btn::Update"` or `"title::Update"`, we
|
|
229
|
+
infer context to the string, so other languages would translate differently
|
|
230
|
+
depending on the context (scope). And in the base locale, only the part
|
|
231
|
+
after `::` would be shown (the scope would be stripped).
|
|
232
|
+
|
|
233
|
+
Should you need to output the scope delimiter `'::'` as is, escape it with a
|
|
234
|
+
backslash:
|
|
235
|
+
|
|
236
|
+
<%= _("MyWebSite.com\\::HomePage") %>
|
|
237
|
+
|
|
238
|
+
The above will lookup `"MyWebSite.com\\::HomePage"`, but will not treat it as a
|
|
239
|
+
namespace prefix and will return the string with only the backslash stripped
|
|
240
|
+
(i.e. `"MyWebSite.com::HomePage"`).
|
|
241
|
+
|
|
242
|
+
## Harvester
|
|
243
|
+
|
|
244
|
+
Once you're ready to translate any work you've done:
|
|
245
|
+
|
|
246
|
+
bin/rake zlocalize:harvest output=config/translations/fr.strings.yml
|
|
247
|
+
|
|
248
|
+
The harvester will scan your application (models, controllers, helpers, views
|
|
249
|
+
and any other path you might add). This will create a YAML file containing a
|
|
250
|
+
list of entries corresponding to all the strings used in the calls to
|
|
251
|
+
`_()` and `n_()` in your source code.
|
|
252
|
+
|
|
253
|
+
However, you have complete control over the location of your `ZLocalize`
|
|
254
|
+
translation files and can store them in any subdirectory of your Rails
|
|
255
|
+
application. See Basic Configuration section above.
|
|
256
|
+
|
|
257
|
+
Use `rake -D zlocalize:harvest` for a list of options.
|
|
258
|
+
|
|
259
|
+
#### HARVESTER LIMITATIONS
|
|
260
|
+
|
|
261
|
+
The parser used by the harvester module does have some limitations:
|
|
262
|
+
|
|
263
|
+
1. You *must* use parenthesis in the calls to `_()` and `n_()` (or
|
|
264
|
+
`ZLocalize.translate` and `ZLocalize.pluralize`). While the Ruby language
|
|
265
|
+
allows to omit parenthesis for method parameters, the Harvester parser does
|
|
266
|
+
require them. The rule is simple: the parameter list to a translation
|
|
267
|
+
method (and the parameters of any nested calls) must use parenthesis.
|
|
268
|
+
|
|
269
|
+
This will not be harvested:
|
|
270
|
+
|
|
271
|
+
_ "Hello"
|
|
272
|
+
|
|
273
|
+
_ "Hello, {{name}}", :name => get_user_name @user
|
|
274
|
+
|
|
275
|
+
But this will:
|
|
276
|
+
|
|
277
|
+
_("Hello")
|
|
278
|
+
|
|
279
|
+
_("Hello, {{name}}", :name => get_user_name(@user))
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
2. Translation calls inside interpolated double-quoted strings are not
|
|
283
|
+
supported.
|
|
284
|
+
|
|
285
|
+
While you can do something like:
|
|
286
|
+
|
|
287
|
+
"This is #{_('embedded')} in a string"
|
|
288
|
+
|
|
289
|
+
The Harvester parser will not detect the call. Avoid doing so.
|
|
290
|
+
|
|
291
|
+
## Translation file format
|
|
292
|
+
|
|
293
|
+
Entries in the YAML file have the following format:
|
|
294
|
+
|
|
295
|
+
entry_00001:
|
|
296
|
+
id: 1
|
|
297
|
+
plural: false
|
|
298
|
+
ignore: false
|
|
299
|
+
references:
|
|
300
|
+
- /app/views/users/show.html.erb:12
|
|
301
|
+
source: "Dear user"
|
|
302
|
+
translation: "Cher utilisateur"
|
|
303
|
+
|
|
304
|
+
entry_00002:
|
|
305
|
+
id: 2
|
|
306
|
+
plural: true
|
|
307
|
+
ignore: false
|
|
308
|
+
references:
|
|
309
|
+
- /app/views/users/show.html.erb:21
|
|
310
|
+
source:
|
|
311
|
+
- "No messages"
|
|
312
|
+
- "One message"
|
|
313
|
+
- "{{count}} messages"
|
|
314
|
+
translation:
|
|
315
|
+
- "Aucun message"
|
|
316
|
+
- "Un message"
|
|
317
|
+
- "{{count}} messages"
|
|
318
|
+
|
|
319
|
+
Note that for plural entries, the source is the Array of strings passed to
|
|
320
|
+
`n_()` (or `ZLocalize.pluralize()` ) in the source file. It can have any number
|
|
321
|
+
of elements, as required by your base locale. The translation is also an Array,
|
|
322
|
+
and it too can have any number of elements as required by the target locale.
|
|
323
|
+
It is up to the `:plural_selector` proc to compute the correct index to use
|
|
324
|
+
in the translation array.
|
|
325
|
+
|
|
326
|
+
## Updating translations when the application changes
|
|
327
|
+
|
|
328
|
+
Simply run `rake zlocalize:harvest` again, specifying as `output` the file that
|
|
329
|
+
already contains for the target language.
|
|
330
|
+
|
|
331
|
+
The Harvester will scan the source code and add/remove/modify the references to
|
|
332
|
+
all strings already present (if a string is not used anymore, it will be removed
|
|
333
|
+
only if you also add the `purge=true` parameter). It will of course also add any
|
|
334
|
+
missing string to the existing translation file.
|
|
335
|
+
|
|
336
|
+
## Translation of user-generated content (ActiveRecord)
|
|
337
|
+
|
|
338
|
+
Any ActiveRecord model can be made to support multiple languages for its
|
|
339
|
+
attributes. `ZLocalize` provides 2 mechanisms to achieve this:
|
|
340
|
+
|
|
341
|
+
### 1. Attached Translations
|
|
342
|
+
|
|
343
|
+
This method stores the translation of values in a separate model (judiciously)
|
|
344
|
+
called `Translation`.
|
|
345
|
+
|
|
346
|
+
Any model can have multiple translated values in multiple locales attached to
|
|
347
|
+
it. These values essentially become attributes of the model.
|
|
348
|
+
|
|
349
|
+
To use attached translations for a given model, declare `has_translations` for
|
|
350
|
+
that model. For example:
|
|
351
|
+
|
|
352
|
+
class Page < ActiveRecord::Base
|
|
353
|
+
...
|
|
354
|
+
has_translations
|
|
355
|
+
...
|
|
356
|
+
validates :title, translation: { required_locales: [:fr, :en] }
|
|
357
|
+
# `required_locales` can be a Symbol, in which case it refers to a method on the instance (no parameters)
|
|
358
|
+
# that will be called when the validation is performed.
|
|
359
|
+
# It can also be a Proc or lambda to be called, with (record, attribute, value) as parameters
|
|
360
|
+
# validates :title, translation: { required_locales: :get_required_locales }
|
|
361
|
+
# validates :title, translation: { required_locales: -> (record, attribute, value) { [:es, :fr] } }
|
|
362
|
+
...
|
|
363
|
+
# return an Array of locale identifiers
|
|
364
|
+
def get_required_locales
|
|
365
|
+
[ :de, :en ]
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
From then on, any instance of Page will have the following methods:
|
|
370
|
+
|
|
371
|
+
* `#translate(attr_name,locale = nil)`
|
|
372
|
+
* `#add_translation(attr_name,locale,value)`
|
|
373
|
+
* `#insert_translations(locales = {})`
|
|
374
|
+
|
|
375
|
+
So, a typical use with our example would be:
|
|
376
|
+
|
|
377
|
+
In the controller:
|
|
378
|
+
|
|
379
|
+
def create
|
|
380
|
+
@page = Page.new(params[:page])
|
|
381
|
+
@page.add_translation('title',:fr,'Mon titre')
|
|
382
|
+
@page.save
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
And in a view (where the current locale is +:fr+):
|
|
386
|
+
|
|
387
|
+
<h3><%= @page.translate(title) %></h3> # => "Mon titre"
|
|
388
|
+
|
|
389
|
+
If you have some kind of administration interface, you can also mass-assign the
|
|
390
|
+
translations for a number of attributes and locales with the `insert_translations`
|
|
391
|
+
method.
|
|
392
|
+
|
|
393
|
+
`#insert_translations` accepts a Hash containing the translations for multiple
|
|
394
|
+
columns and locales. The Hash must have the locales as keys and its value is
|
|
395
|
+
another Hash of name-value pairs. As in:
|
|
396
|
+
|
|
397
|
+
@article.insert_translations(
|
|
398
|
+
{ 'en' => { 'title' => "What's new this week",
|
|
399
|
+
'synopsis' => "Learn what has happened this week"},
|
|
400
|
+
'fr' => { 'title' => "Quoi de neuf cette semaine",
|
|
401
|
+
'synopsis' => "Tout sur ce qui s'est passé cette semaine" }
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
Alternatively, you can use ActiveRecord's builtin `accepts_nested_attributes_for`
|
|
405
|
+
mechanism to mass-assign translations:
|
|
406
|
+
|
|
407
|
+
class Article < ActiveRecord::Base
|
|
408
|
+
has_translations
|
|
409
|
+
accepts_nested_attributes_for :translations, :allow_destroy => true
|
|
410
|
+
...
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
And use `form.fields_for :translations` inside a view to have the translations
|
|
414
|
+
assigned directly...
|
|
415
|
+
|
|
416
|
+
The Translation model is loaded by `ZLocalize` itself, whenever a model
|
|
417
|
+
declares `has_translations`. The only thing missing is the translations table,
|
|
418
|
+
which you can generate with:
|
|
419
|
+
|
|
420
|
+
rails generate zlocalize:translations_migration
|
|
421
|
+
|
|
422
|
+
And run `rake db:migrate`.
|
|
423
|
+
|
|
424
|
+
Attached translations are very flexible, because the values are not stored in
|
|
425
|
+
the model itself (as opposed to translated columns, explained in the next
|
|
426
|
+
section below). This allows to easily add new locales with minimal changes
|
|
427
|
+
to the models that use them. All this at the cost of having an association with
|
|
428
|
+
the `Translation` model.
|
|
429
|
+
|
|
430
|
+
### Validation of attached translations
|
|
431
|
+
|
|
432
|
+
`ZLocalize` also adds a Validator to ActiveRecord::Base, to check the presence
|
|
433
|
+
of attached validations:
|
|
434
|
+
|
|
435
|
+
class Page < ActiveRecord::Base
|
|
436
|
+
|
|
437
|
+
has_translations
|
|
438
|
+
validates_translation_of :content, :required_locales => [:fr, :en]
|
|
439
|
+
# or #
|
|
440
|
+
validates :content, :translation => { :required_locales => [:fr, :en] }
|
|
441
|
+
# or #
|
|
442
|
+
validates :content, :translation => { :required_locales => :get_required_locales }
|
|
443
|
+
...
|
|
444
|
+
|
|
445
|
+
def get_required_locales
|
|
446
|
+
[:es, :en]
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
This will generate an error message for each missing translation:
|
|
452
|
+
|
|
453
|
+
p = @page.create
|
|
454
|
+
|
|
455
|
+
p.errors[:content] # => ["content is missing its translation in fr, en"]
|
|
456
|
+
|
|
457
|
+
The `required_locales` option can also be a Proc/lambda:
|
|
458
|
+
|
|
459
|
+
validates_translation_of [:title, :content], :required_locales => -> (record,attribute,value) {
|
|
460
|
+
if attribute == 'content'
|
|
461
|
+
[:fr,:en]
|
|
462
|
+
else
|
|
463
|
+
[:fr]
|
|
464
|
+
end }
|
|
465
|
+
|
|
466
|
+
The other standard validation options, such as `:message`, `:on`, `:if` and
|
|
467
|
+
`:unless` are also supported. `:message` defaults to `:missing_translations` and this value should be
|
|
468
|
+
added to the I18n translations for Rails, with the other ActiveRecord validation messages:
|
|
469
|
+
|
|
470
|
+
en:
|
|
471
|
+
errors:
|
|
472
|
+
messages: &errors_messages
|
|
473
|
+
inclusion: "n'est pas inclus(e) dans la liste"
|
|
474
|
+
exclusion: "n'est pas disponible"
|
|
475
|
+
...
|
|
476
|
+
missing_translations: "doit être traduit en %{locales}"
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
and the `%{locales}` token will be interpolated to the actual missing locales.
|
|
480
|
+
|
|
481
|
+
### 2. Translated columns
|
|
482
|
+
|
|
483
|
+
This second attribute translation mechanism works a bit differently. It is
|
|
484
|
+
basically a wrapper around column names, allowing to use different attribute
|
|
485
|
+
values for each locale.
|
|
486
|
+
|
|
487
|
+
First, you need to create the columns in the model table. You need one column
|
|
488
|
+
for each attribute and locale pair. For example, a `title` attribute with
|
|
489
|
+
values in French and English would require 2 columns:
|
|
490
|
+
|
|
491
|
+
create_table 'pages' do |t|
|
|
492
|
+
t.string :title_en
|
|
493
|
+
t.string :title_fr
|
|
494
|
+
...
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
In your model:
|
|
498
|
+
|
|
499
|
+
class Page < ActiveRecord::Base
|
|
500
|
+
translates_columns [:title]
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
Then whenever you want to access the value of +title+ in the current locale:
|
|
504
|
+
|
|
505
|
+
<%= @page.title %>
|
|
506
|
+
|
|
507
|
+
The wrapper method can take a Hash of options:
|
|
508
|
+
|
|
509
|
+
* `:locale` to force a given locale
|
|
510
|
+
* `:fetch_default` (`true|false`) to retrieve the the value of the attribute
|
|
511
|
+
in the `default_locale`. Defaults to `true`
|
|
512
|
+
|
|
513
|
+
For example:
|
|
514
|
+
|
|
515
|
+
<%= @page.title(:locale => :fr, :fetch_default => false) %>
|
|
516
|
+
|
|
517
|
+
This will automatically call the wrapper method to read the value of the column
|
|
518
|
+
in the `:fr` locale (the value of `title_fr`). If `title_fr` is nil,
|
|
519
|
+
then the value in the `default_locale` will not be fetched and `nil` will be
|
|
520
|
+
returned. If `fetch_default` is `true`, then the value of `title_en` (given
|
|
521
|
+
that the `default_locale` is `:en`) would be fetched.
|
|
522
|
+
|
|
523
|
+
Translated columns are more efficient in terms of fetching translated content,
|
|
524
|
+
because they are always part of the underlying table columns. If you know in
|
|
525
|
+
advance the exact locales you are going support in your application, and that
|
|
526
|
+
only 2-3 locales will be present, then having multiple columns for each
|
|
527
|
+
attribute is probably the way to go. If you have many locales and/or the
|
|
528
|
+
attributes to be translated are large (text columns and such), then maybe
|
|
529
|
+
Attached Translations would be a better strategy.
|
|
530
|
+
|
|
531
|
+
## Localized Decimal Attributes
|
|
532
|
+
|
|
533
|
+
`ZLocalize` provides a way to declare some ActiveRecord attributes as being
|
|
534
|
+
localized decimal values. This ensures the assignment of decimal values from
|
|
535
|
+
their string-based representation will not fail. For example, in French, the
|
|
536
|
+
decimal separator is a period ('.') and the thousands separator is a space.
|
|
537
|
+
Assigning the string `"1 234,56"` to a decimal attribute will cause the value
|
|
538
|
+
to become 1.0 (the `BigDecimal` class will parse the string, but stop at the
|
|
539
|
+
space).
|
|
540
|
+
|
|
541
|
+
To enable correct decimal attribute assignment for different locales, declare
|
|
542
|
+
the attributes as such:
|
|
543
|
+
|
|
544
|
+
class Account < ActiveRecord::Base
|
|
545
|
+
localize_decimal_attributes [:balance, :variation]
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
Then, you can safely assign a string representing a decimal value in the
|
|
549
|
+
current locale:
|
|
550
|
+
|
|
551
|
+
Zlocalize.locale = :fr
|
|
552
|
+
@account.balance = "8 765,43"
|
|
553
|
+
=> 8765.43
|
|
554
|
+
|
|
555
|
+
Conversion from a locale-specific decimal value to `BigDecimal` is done by
|
|
556
|
+
the `:convert_float` Proc declared in `ZLocalize.locales` (in the
|
|
557
|
+
initializer). For example, in English:
|
|
558
|
+
|
|
559
|
+
ZLocalize.config.locales = {
|
|
560
|
+
:en => {
|
|
561
|
+
:plural_select => -> (n) { n <= 0 ? 0 : (n > 1 ? 2 : 1) },
|
|
562
|
+
:convert_float => -> (s) { s.to_s.gsub(',','') }
|
|
563
|
+
}
|
|
564
|
+
...
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
## License
|
|
568
|
+
|
|
569
|
+
`ZLocalize` is released under the MIT license.
|
|
570
|
+
|
|
571
|
+
## Support
|
|
572
|
+
|
|
573
|
+
Source code, documentation, bug reports, feature requests or anything else is
|
|
574
|
+
at
|
|
575
|
+
|
|
576
|
+
* http://github.com/zzeligg/zlocalize
|
|
577
|
+
|
|
578
|
+
## Credits
|
|
579
|
+
|
|
580
|
+
This plugin is based on original work by Thomas Fuchs (A Rails 1.X plugin named
|
|
581
|
+
Localization), but it has been extended in many ways to make it answer our needs.
|
|
582
|
+
|
|
583
|
+
Many thanks to Stephane Volet (https://github.com/schmlblk) for his contributions and ideas
|
|
584
|
+
to this gem.
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'prism'
|
|
2
2
|
# NOTE: This prevents Rails test environment to complain about this:
|
|
3
3
|
# `ActionView::Template::Error: undefined method 'new' for module Erubi`
|
|
4
4
|
# somehow Bundler or the autoload mechanism gets confused about which Erubi
|
|
5
5
|
# implementation is defined
|
|
6
|
-
|
|
7
|
-
require 'action_view/template/handlers/erb/erubi'
|
|
8
|
-
end
|
|
6
|
+
require 'action_view'
|
|
9
7
|
require 'action_view/template/handlers/erb/erubi'
|
|
10
8
|
require File.join(File.dirname(__FILE__),'translation_file')
|
|
11
9
|
require File.join(File.dirname(__FILE__),'harvester')
|
|
@@ -14,102 +12,101 @@ module ZLocalize
|
|
|
14
12
|
|
|
15
13
|
class SourceProcessor
|
|
16
14
|
|
|
15
|
+
# Methods recognized as translation calls when called without an explicit
|
|
16
|
+
# receiver (bare calls), mapped to whether they yield plural entries.
|
|
17
|
+
BARE_TRANSLATE_METHODS = { '_' => false, 'n_' => true }.freeze
|
|
18
|
+
|
|
19
|
+
# Methods recognized as translation calls when called on the ZLocalize
|
|
20
|
+
# constant, mapped to whether they yield plural entries.
|
|
21
|
+
QUALIFIED_TRANSLATE_METHODS = { 'translate' => false, 'pluralize' => true }.freeze
|
|
22
|
+
|
|
17
23
|
def initialize(filename, root, is_erb = false)
|
|
18
24
|
@in_hash = 0
|
|
19
25
|
@translate_calls = []
|
|
20
26
|
@root = File.join(File.expand_path(root).downcase,'/') # add a trailing /
|
|
21
27
|
@filename = File.expand_path(filename).downcase
|
|
22
28
|
@relative_filename = @filename.gsub(@root,'')
|
|
29
|
+
@line_offset = 0
|
|
23
30
|
content = File.open(filename, "r") { |f| f.read }
|
|
24
31
|
if is_erb
|
|
25
32
|
content = ActionView::Template::Handlers::ERB::Erubi.new(content, escape: true, trim: true).src
|
|
33
|
+
# Erubi compiles ERB to bare Ruby with top-level `yield`, which Prism
|
|
34
|
+
# rejects (yield is only valid inside a method). Wrap in a method and
|
|
35
|
+
# track the offset so reported line numbers still match the template.
|
|
36
|
+
content = "def __zlocalize_erb__\n#{content}\nend"
|
|
37
|
+
@line_offset = 1
|
|
26
38
|
end
|
|
27
|
-
@parser = create_parser_for_ruby_version
|
|
28
39
|
begin
|
|
29
|
-
|
|
30
|
-
process(@stree)
|
|
40
|
+
process(content)
|
|
31
41
|
rescue ArgumentError => ae
|
|
32
42
|
raise ArgumentError.new("In #{filename} #{ae.message}")
|
|
33
43
|
end
|
|
34
44
|
end
|
|
35
45
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
rescue
|
|
41
|
-
raise "Unsupported Ruby version #{RUBY_VERSION}"
|
|
42
|
-
end
|
|
46
|
+
def process(content)
|
|
47
|
+
parse_result = Prism.parse(content)
|
|
48
|
+
raise ArgumentError.new("Syntax error: #{format_parse_errors(parse_result)}") unless parse_result.success?
|
|
49
|
+
walk(parse_result.value)
|
|
43
50
|
end
|
|
44
51
|
|
|
45
|
-
def
|
|
46
|
-
return unless node.is_a?(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
def walk(node)
|
|
53
|
+
return unless node.is_a?(Prism::Node)
|
|
54
|
+
|
|
55
|
+
if node.is_a?(Prism::CallNode)
|
|
56
|
+
if node.receiver.nil?
|
|
57
|
+
plural = BARE_TRANSLATE_METHODS[node.name.to_s]
|
|
58
|
+
if !plural.nil? && node.arguments && node.arguments.arguments.size >= 1
|
|
59
|
+
record_call(node, plural) and return
|
|
53
60
|
end
|
|
54
|
-
elsif
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
process_pluralize_call(node) and return
|
|
61
|
+
elsif zlocalize_const?(node.receiver)
|
|
62
|
+
plural = QUALIFIED_TRANSLATE_METHODS[node.name.to_s]
|
|
63
|
+
if !plural.nil? && node.arguments && node.arguments.arguments.size >= 1
|
|
64
|
+
record_call(node, plural) and return
|
|
59
65
|
end
|
|
60
66
|
end
|
|
61
67
|
end
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
end
|
|
68
|
+
|
|
69
|
+
node.compact_child_nodes.each { |child| walk(child) }
|
|
65
70
|
end
|
|
66
71
|
|
|
67
|
-
def
|
|
68
|
-
|
|
72
|
+
def record_call(node, plural)
|
|
73
|
+
argument_nodes = node.arguments.arguments
|
|
74
|
+
source = plural ? get_string_array_node_value(argument_nodes[0]) : get_string_node_value(argument_nodes[0])
|
|
75
|
+
@translate_calls << { name: node.name.to_s,
|
|
76
|
+
line_no: node.message_loc.start_line - @line_offset,
|
|
77
|
+
char_no: node.message_loc.start_column + 1,
|
|
78
|
+
parameter: source }
|
|
79
|
+
# keep harvesting inside any nested arguments (e.g. options hashes containing
|
|
80
|
+
# further translation calls)
|
|
81
|
+
argument_nodes[1..].each { |child| walk(child) } if argument_nodes.size > 1
|
|
69
82
|
end
|
|
70
83
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
raise ArgumentError.new("On line #{node.loc.selector.line} at column #{node.loc.selector.column+1} : String Expected but got: #{node.inspect}")
|
|
74
|
-
end
|
|
75
|
-
return node.children[0]
|
|
84
|
+
def zlocalize_const?(node)
|
|
85
|
+
node.is_a?(Prism::ConstantReadNode) && node.name.to_s == 'ZLocalize'
|
|
76
86
|
end
|
|
77
87
|
|
|
78
|
-
def
|
|
79
|
-
unless
|
|
80
|
-
raise ArgumentError.new("On line #{node.
|
|
81
|
-
end
|
|
82
|
-
a = []
|
|
83
|
-
for i in 0..node.children.size - 1
|
|
84
|
-
a << get_string_node_value(node.children[i])
|
|
88
|
+
def get_string_node_value(node)
|
|
89
|
+
unless string_node?(node)
|
|
90
|
+
raise ArgumentError.new("On line #{node.location.start_line} at column #{node.location.start_column+1} : String Expected but got: #{node.inspect}")
|
|
85
91
|
end
|
|
86
|
-
|
|
92
|
+
return node.unescaped
|
|
87
93
|
end
|
|
88
94
|
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
char_no: node.loc.selector.column+1,
|
|
93
|
-
parameter: get_string_node_value(node.children[2]) }
|
|
94
|
-
for i in 3..node.children.size-1
|
|
95
|
-
process(node.children[i])
|
|
95
|
+
def get_string_array_node_value(node)
|
|
96
|
+
unless node.is_a?(Prism::ArrayNode)
|
|
97
|
+
raise ArgumentError.new("On line #{node.location.start_line} at column #{node.location.start_column+1} : Array expected but got: #{node.inspect}")
|
|
96
98
|
end
|
|
99
|
+
node.elements.map { |element| get_string_node_value(element) }
|
|
97
100
|
end
|
|
98
101
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
line_no: node.loc.selector.line,
|
|
102
|
-
char_no: node.loc.selector.column+1,
|
|
103
|
-
parameter: get_string_array_node_value(node.children[2]) }
|
|
104
|
-
for i in 3..node.children.size-1
|
|
105
|
-
process(node.children[i])
|
|
106
|
-
end
|
|
102
|
+
def string_node?(node)
|
|
103
|
+
node.is_a?(Prism::StringNode)
|
|
107
104
|
end
|
|
108
105
|
|
|
109
|
-
def
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
def format_parse_errors(parse_result)
|
|
107
|
+
parse_result.errors.map do |error|
|
|
108
|
+
"line #{error.location.start_line}: #{error.message}"
|
|
109
|
+
end.join("\n")
|
|
113
110
|
end
|
|
114
111
|
|
|
115
112
|
# return a Hash of all translation entries we collected
|
|
@@ -126,6 +123,12 @@ module ZLocalize
|
|
|
126
123
|
entries
|
|
127
124
|
end
|
|
128
125
|
|
|
126
|
+
def make_translation_entry(h)
|
|
127
|
+
TranslationEntry.new('plural' => h[:name] == 'n_' || h[:name] == 'pluralize',
|
|
128
|
+
'source' => h[:parameter],
|
|
129
|
+
'references' => [ "#{@relative_filename}:#{h[:line_no]}" ])
|
|
130
|
+
end
|
|
131
|
+
|
|
129
132
|
end
|
|
130
133
|
|
|
131
134
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zlocalize
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 7.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Charles Bedard
|
|
8
8
|
- Stephane Volet
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -16,42 +16,42 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '7.1'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '7.1'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: activesupport
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '7.1'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
40
|
+
version: '7.1'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: actionpack
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '7.1'
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '7.1'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: i18n
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -73,19 +73,19 @@ dependencies:
|
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
74
|
version: '2'
|
|
75
75
|
- !ruby/object:Gem::Dependency
|
|
76
|
-
name:
|
|
76
|
+
name: prism
|
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: '2
|
|
81
|
+
version: '1.2'
|
|
82
82
|
type: :runtime
|
|
83
83
|
prerelease: false
|
|
84
84
|
version_requirements: !ruby/object:Gem::Requirement
|
|
85
85
|
requirements:
|
|
86
86
|
- - ">="
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '2
|
|
88
|
+
version: '1.2'
|
|
89
89
|
email:
|
|
90
90
|
- zzeligg@gmail.com
|
|
91
91
|
- steph@zboing.ca
|
|
@@ -93,6 +93,8 @@ executables: []
|
|
|
93
93
|
extensions: []
|
|
94
94
|
extra_rdoc_files: []
|
|
95
95
|
files:
|
|
96
|
+
- MIT-LICENSE
|
|
97
|
+
- README.md
|
|
96
98
|
- lib/zlocalize.rb
|
|
97
99
|
- lib/zlocalize/backend.rb
|
|
98
100
|
- lib/zlocalize/config.rb
|
|
@@ -123,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
123
125
|
requirements:
|
|
124
126
|
- - ">="
|
|
125
127
|
- !ruby/object:Gem::Version
|
|
126
|
-
version:
|
|
128
|
+
version: 3.4.0
|
|
127
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
130
|
requirements:
|
|
129
131
|
- - ">="
|