interpret 0.1.4 → 0.1.5
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/README.md +488 -0
- data/app/controllers/interpret/search_controller.rb +3 -1
- data/app/controllers/interpret/tools_controller.rb +2 -1
- data/app/controllers/interpret/translations_controller.rb +16 -7
- data/app/models/interpret/expiration_observer.rb +29 -0
- data/app/models/interpret/translation.rb +9 -20
- data/app/views/interpret/tools/index.html.erb +16 -23
- data/app/views/interpret/translations/_listing.html.erb +1 -1
- data/app/views/interpret/translations/live_edit.html.erb +11 -0
- data/app/views/layouts/interpret.html.erb +6 -1
- data/app/views/layouts/interpret_base.html.erb +1 -1
- data/config/routes.rb +4 -0
- data/interpret.gemspec +0 -1
- data/lib/generators/interpret/setup_generator.rb +2 -1
- data/lib/interpret/controller_filter.rb +22 -0
- data/lib/interpret/engine.rb +17 -6
- data/lib/interpret/helpers.rb +39 -0
- data/lib/interpret/version.rb +1 -1
- data/lib/interpret.rb +4 -1
- data/lib/tasks/interpret.rake +4 -2
- data/public/javascripts/facebox-1.3/closelabel.png +0 -0
- data/public/javascripts/facebox-1.3/facebox.css +80 -0
- data/public/javascripts/facebox-1.3/facebox.js +309 -0
- data/public/javascripts/facebox-1.3/loading.gif +0 -0
- data/public/stylesheets/interpret_live_edit_style.css +38 -0
- data/public/stylesheets/interpret_style.css +18 -0
- data/spec/models/translation_spec.rb +33 -22
- data/spec/observers/expiration_observer_spec.rb +17 -0
- data/spec/spec_helper.rb +0 -1
- data/test_app/Gemfile +4 -2
- data/test_app/app/controllers/application_controller.rb +17 -1
- data/test_app/app/controllers/pages_controller.rb +0 -1
- data/test_app/app/models/my_sweeper.rb +5 -0
- data/test_app/app/views/layouts/application.html.erb +115 -5
- data/test_app/app/views/layouts/backoffice.html.erb +27 -0
- data/test_app/app/views/pages/archives.html.erb +3 -0
- data/test_app/app/views/pages/contact.html.erb +3 -0
- data/test_app/app/views/pages/index.html.erb +56 -0
- data/test_app/app/views/pages/links.html.erb +10 -0
- data/test_app/app/views/pages/resources.html.erb +5 -0
- data/test_app/config/application.rb +2 -2
- data/test_app/config/environments/production.rb +0 -5
- data/test_app/config/initializers/interpret.rb +3 -3
- data/test_app/config/locales/en.yml +55 -0
- data/test_app/config/locales/es.yml +3 -47
- data/test_app/config/routes.rb +8 -1
- data/test_app/public/images/a1.gif +0 -0
- data/test_app/public/images/a10.jpg +0 -0
- data/test_app/public/images/a16.gif +0 -0
- data/test_app/public/images/a18.gif +0 -0
- data/test_app/public/images/a22.gif +0 -0
- data/test_app/public/images/a26.gif +0 -0
- data/test_app/public/images/a33.gif +0 -0
- data/test_app/public/images/a36.gif +0 -0
- data/test_app/public/images/a38.gif +0 -0
- data/test_app/public/images/a41.gif +0 -0
- data/test_app/public/images/a47.gif +0 -0
- data/test_app/public/images/a50.gif +0 -0
- data/test_app/public/images/a8.gif +0 -0
- data/test_app/public/images/abg.gif +0 -0
- data/test_app/public/images/pic1.jpg +0 -0
- data/test_app/public/images/pic2.jpg +0 -0
- data/test_app/public/images/spacer.gif +0 -0
- data/test_app/public/images/upbg.gif +0 -0
- data/test_app/public/javascripts/facebox-1.3/closelabel.png +0 -0
- data/test_app/public/javascripts/facebox-1.3/facebox.css +80 -0
- data/test_app/public/javascripts/facebox-1.3/facebox.js +309 -0
- data/test_app/public/javascripts/facebox-1.3/loading.gif +0 -0
- data/test_app/public/stylesheets/default.css +361 -0
- data/test_app/public/stylesheets/interpret_live_edit_style.css +38 -0
- data/test_app/public/stylesheets/interpret_style.css +18 -0
- data/test_app/public/stylesheets/private.css +0 -0
- metadata +96 -29
- data/app/sweepers/interpret/base_sweeper.rb +0 -18
- data/app/sweepers/interpret/translation_sweeper.rb +0 -11
- data/public/javascripts/jquery.purr.js +0 -180
- data/test_app/app/sweepers/page_sweeper.rb +0 -12
- data/test_app/app/views/pages/contact.html.haml +0 -8
- data/test_app/app/views/pages/index.html.haml +0 -10
- data/test_app/config/locales/ca.yml +0 -6
- data/test_app/public/javascripts/jquery.purr.js +0 -180
data/README.md
ADDED
@@ -0,0 +1,488 @@
|
|
1
|
+
Interpret
|
2
|
+
=========
|
3
|
+
|
4
|
+
Interpret is a rails 3 engine to help you manage your application
|
5
|
+
translations, also allowing you to have editable contents in live. In order to
|
6
|
+
do so it will register the I18n activerecord backend to be used for your
|
7
|
+
application. Interpret is pretty tied to it at the moment, but there are plans
|
8
|
+
to make it backend-agnostic.
|
9
|
+
We believe that key-value stores as I18n backends are pretty awesome and we
|
10
|
+
want to support them, although the activerecord backend with Memoize and
|
11
|
+
Flatten is very fast too, once you have loaded all the data.
|
12
|
+
|
13
|
+
Interpret is intented to allow live edition of your contents, making it very
|
14
|
+
easy for your client, your co-workers or yourself to edit them without a
|
15
|
+
deployment.
|
16
|
+
|
17
|
+
Caching techniques play a key role here in order to expire the page or the
|
18
|
+
fragment in which a certain content is displayed, and Interpret helps you do
|
19
|
+
this work with an observer, but you are the responsible to expire your caches.
|
20
|
+
See later on caching section.
|
21
|
+
|
22
|
+
If you want you can also use Interpret only as a translation tool, like
|
23
|
+
[Tolk](https://github.com/dhh/tolk) in which this gem was initially inspired.
|
24
|
+
See later the `registered envs` section on how to avoid the registration of
|
25
|
+
I18n activerecord backend. This way you can still edit your translations as
|
26
|
+
before, but in this case the modifications you make won't be directly
|
27
|
+
available in your application.
|
28
|
+
|
29
|
+
[SEE DEMO](http://interpretapp.heroku.com)
|
30
|
+
|
31
|
+
|
32
|
+
Installation
|
33
|
+
===========
|
34
|
+
|
35
|
+
Add the gem to your Gemfile:
|
36
|
+
|
37
|
+
gem 'interpret'
|
38
|
+
|
39
|
+
Then you must run the interpret setup generator in order to copy some asset
|
40
|
+
files required for the backoffice section, javascripts, css's and a couple of
|
41
|
+
images:
|
42
|
+
|
43
|
+
rails g interpret:setup
|
44
|
+
|
45
|
+
|
46
|
+
Finally you should also run this generator to create the 'translations' table
|
47
|
+
required by the I18n active-record backend:
|
48
|
+
|
49
|
+
rails g interpret:migration
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
Development considerations
|
54
|
+
==========================
|
55
|
+
|
56
|
+
If you have chosen to have dynamic contents, that is editable text in your
|
57
|
+
website, this means that all this text information is now stored in some
|
58
|
+
database. They no longer belongs to the application itself, they're now seed
|
59
|
+
data, but instead of _create_ it inside `seeds.rb` you edit an `en.yml` file
|
60
|
+
or something similar. This is the work flow Interpret expects you to follow,
|
61
|
+
develop your application using the standard `.yml` locale files and put in
|
62
|
+
there anything you need. Later, after a deployment, run the `update` rake task
|
63
|
+
to perform a synchronization between the production I18n database and your
|
64
|
+
modifications inside `.yml` files, not to update the _contents_ but to update
|
65
|
+
the **keys**.
|
66
|
+
|
67
|
+
The tool is different but the concept is the same. The contents you see and
|
68
|
+
edit in development ARE NOT the same contents you will see in production,
|
69
|
+
because they're dynamic and someone else may have changed them. However, the
|
70
|
+
page architecture does belongs to the application, I mean, the actual HTML
|
71
|
+
code you wrote. One `<h1>`, three paragraphs `<p>` and a list `<ul>` with five
|
72
|
+
elements `<li>`. This markup is there and it expects to have some text in it,
|
73
|
+
translated content, and it have to be there. So, that said, it's clear that
|
74
|
+
you can **edit** the contents, but not _create_ or _remove_ them.
|
75
|
+
|
76
|
+
|
77
|
+
The Update action
|
78
|
+
=================
|
79
|
+
|
80
|
+
The `update` action is the core of Interpret. It performs a synchronization
|
81
|
+
between your `*.yml` files and the I18n backend translations, and it is
|
82
|
+
expected to run after a deployment in order to update your production
|
83
|
+
translations.
|
84
|
+
|
85
|
+
- It will create any translation that exists in `.yml` files but not in the
|
86
|
+
database backend. When doing so, the value of the translation in yaml files
|
87
|
+
are preserved and copied into the database backend. The same happens if you
|
88
|
+
have created it in more than one language, it is copied for each locale.
|
89
|
+
|
90
|
+
- It will remove any translation that exists in the database backend but not
|
91
|
+
in the `.yml` files. Note that you can prevent Interpret to remove anything
|
92
|
+
setting the `soft` option described at the bottom of this document.
|
93
|
+
|
94
|
+
- For any key that exists in both `.yml` files and database backend it will not
|
95
|
+
do anything.
|
96
|
+
|
97
|
+
|
98
|
+
Main language
|
99
|
+
=============
|
100
|
+
|
101
|
+
The `I18n.default_locale` configured in your application will be the _master_
|
102
|
+
language Interpret will use. Keep in mind that rails lets you have a diferent
|
103
|
+
locale key hierarchy for each language in the `.yml` files, and this behaviour
|
104
|
+
is prohibited in Interpret. Here, the `I18n.default_locale` is the only
|
105
|
+
language that can be trusted to have all the required and correct locale keys
|
106
|
+
and it will be used to check for inconsitent translations into other
|
107
|
+
languages, knowing what you haven't translated yet.
|
108
|
+
|
109
|
+
This is also the locale used when an `update` action is performed. The
|
110
|
+
synchronization will only check for inconsistent keys between `.yml` files and
|
111
|
+
database backend within that **master** language.
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
Built-in Backoffice
|
116
|
+
===================
|
117
|
+
|
118
|
+
As an Engine, Interpret provides you with a set of _backoffice_ views in order
|
119
|
+
to manage your translations, and to perform some operations with them. You can
|
120
|
+
access it to the following path in your app (unless you define some `scope`,
|
121
|
+
see later):
|
122
|
+
|
123
|
+
http://localhost:3000/interpret
|
124
|
+
|
125
|
+
### Overview
|
126
|
+
|
127
|
+
This view shows all the translations organized by their keys, in a tree
|
128
|
+
structure as if they were folders and files. If you're used to the typical
|
129
|
+
filesystem architecture it's pretty simple.
|
130
|
+
|
131
|
+
Here you can edit your translations using
|
132
|
+
[best_in_place](https://github.com/bernat/best_in_place), such amazing
|
133
|
+
in-place edition tool by [bernat](https://github.com/bernat).
|
134
|
+
|
135
|
+
### Tools
|
136
|
+
|
137
|
+
Here you have some tools you can use to work with the translations:
|
138
|
+
|
139
|
+
- Export: Clicking the **download** link you will get a typical rails locale file
|
140
|
+
for the current language. It's generated with
|
141
|
+
[ya2yaml](https://github.com/kch/ya2yaml) so it _may_ be safe to
|
142
|
+
use with utf8.
|
143
|
+
|
144
|
+
- Import: With the **upload** option you can select a locale file from your
|
145
|
+
computer and it will be used to perform a massive translations update. To be
|
146
|
+
precise, for each translation you have in that file it will either:
|
147
|
+
1. Update that translation if it already exists, or
|
148
|
+
2. Create that translation if not
|
149
|
+
|
150
|
+
|
151
|
+
- Update: This action will perform an update from your `.yml` locale files.
|
152
|
+
It's described in an earlier section of this readme.
|
153
|
+
|
154
|
+
- Dump: Dump the contents of your `.yml` locale files into I18n backend. All
|
155
|
+
contents will be overwritten, so be cautious!
|
156
|
+
|
157
|
+
|
158
|
+
All of these operations can be very expensive if you have a large number of
|
159
|
+
translations, some optimization work is still required!
|
160
|
+
|
161
|
+
### Search
|
162
|
+
|
163
|
+
You can search by translation key or value, or both of them. The results will
|
164
|
+
be shown in the same way as in Overview, so you can also edit them from there.
|
165
|
+
|
166
|
+
|
167
|
+
|
168
|
+
Configuration
|
169
|
+
=============
|
170
|
+
|
171
|
+
To configure Interpret create an initializer file and put in there something
|
172
|
+
like this:
|
173
|
+
|
174
|
+
Interpret.configure do |config|
|
175
|
+
config.parent_controller = "admin/base_controller"
|
176
|
+
# Some other configuration options
|
177
|
+
end
|
178
|
+
|
179
|
+
The following sections describe in detail all the configuration options
|
180
|
+
available.
|
181
|
+
|
182
|
+
|
183
|
+
Registered environments
|
184
|
+
-----------------------
|
185
|
+
|
186
|
+
Interpret is intended to be used along with I18n active-record backend in
|
187
|
+
order to provide live edition capabilities for your translations. It will
|
188
|
+
automatically register the I18n.backend to the active-record one, with Memoize
|
189
|
+
and Flatten, if the current Rails environment is included in the
|
190
|
+
`registered_envs` list. By default i's initialized to the following:
|
191
|
+
|
192
|
+
Interpret.registered_envs = [:production, :staging]
|
193
|
+
|
194
|
+
|
195
|
+
You can override it in order to activate it also in development:
|
196
|
+
|
197
|
+
Interpret.configure do |config|
|
198
|
+
config.registered_envs << :development
|
199
|
+
# ...
|
200
|
+
end
|
201
|
+
|
202
|
+
Or to disable it if you only want to use Interpret as a translation tool:
|
203
|
+
|
204
|
+
Interpret.configure do |config|
|
205
|
+
config.registered_envs = []
|
206
|
+
# ...
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
|
211
|
+
Adding authorization and custom filters
|
212
|
+
----------------------------------------
|
213
|
+
|
214
|
+
If you want to add some authorization control over Interpret backoffice, or
|
215
|
+
any custom filters, you can use the `Interpret.parent_controller` option. This
|
216
|
+
will make all the Interpret controllers to inherit from it, so you can check
|
217
|
+
for user authentication or whatever:
|
218
|
+
|
219
|
+
Interpret.configure do |config|
|
220
|
+
config.parent_controller = "admin/base_controller"
|
221
|
+
# ...
|
222
|
+
end
|
223
|
+
|
224
|
+
For instance, the above code will make Interpret use `Admin::BaseController`
|
225
|
+
as a base class for all their controllers, and you can put in there any
|
226
|
+
`before_filter` you want to check for the current logged in user permissions.
|
227
|
+
It's likely you already have some controller like this to act as a base for
|
228
|
+
all your existing _admin_ or _backoffice_ controllers.
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
Custom layouts
|
233
|
+
--------------
|
234
|
+
|
235
|
+
In order to integrate the Interpret views into your existing backoffice, you
|
236
|
+
can define your own layout to be used by Interpret:
|
237
|
+
|
238
|
+
Interpret.configure do |config|
|
239
|
+
config.layout = "backoffice"
|
240
|
+
# ...
|
241
|
+
end
|
242
|
+
|
243
|
+
Then Interpret will use the `layouts/backoffice.html.<wathever>` layout.
|
244
|
+
|
245
|
+
If you want further customizations, you can edit the css file Interpret use,
|
246
|
+
it's in `public/stylesheets/interpret_style.css`. Be aware that this file will
|
247
|
+
be overwritten the next time you run a `rails g interpret:setup`.
|
248
|
+
|
249
|
+
For now there is no generator to copy all the views into your app, but you can
|
250
|
+
do it yourself by-hand if you want to also customize them.
|
251
|
+
|
252
|
+
Remember to load the Interpret stylesheet if you use your own layout:
|
253
|
+
|
254
|
+
= stylesheet_link_tag "interpret_style"
|
255
|
+
|
256
|
+
|
257
|
+
Routes scope
|
258
|
+
------------
|
259
|
+
|
260
|
+
You can make Interpret build their routes inside a scope of your choice:
|
261
|
+
|
262
|
+
Interpret.configure do |config|
|
263
|
+
config.scope = "(:locale)"
|
264
|
+
# ...
|
265
|
+
end
|
266
|
+
|
267
|
+
The above code for instance will produce better looking urls inside
|
268
|
+
interpret, as the current locale will be a prefix of the route instead of a
|
269
|
+
GET parameter.
|
270
|
+
|
271
|
+
|
272
|
+
|
273
|
+
Authentication
|
274
|
+
--------------
|
275
|
+
|
276
|
+
You can allow Interpret to know who is the current logged in user by setting
|
277
|
+
the following:
|
278
|
+
|
279
|
+
Interpret.configure do |config|
|
280
|
+
config.current_user = "current_user"
|
281
|
+
# ...
|
282
|
+
end
|
283
|
+
|
284
|
+
If the `Interpret.current_user` option is setted, Interpret will use it in
|
285
|
+
their controllers and views to retrieve the current user, and log their name
|
286
|
+
(or whatever string returned by calling `to_s` on it) into the log messages
|
287
|
+
every time a translation is modified.
|
288
|
+
|
289
|
+
|
290
|
+
|
291
|
+
Roles
|
292
|
+
-----
|
293
|
+
|
294
|
+
Once you have configured a `current_user` function, Interpret can work with
|
295
|
+
two different roles. Use the following configuration option:
|
296
|
+
|
297
|
+
Interpret.configure do |config|
|
298
|
+
config.current_user = "current_user"
|
299
|
+
config.admin = "interpret_admin?"
|
300
|
+
# ...
|
301
|
+
end
|
302
|
+
|
303
|
+
In this example, Interpret will call `current_user.interpret_admin?` to know
|
304
|
+
if the current logged in user is an interpret admin or not. Depending on the
|
305
|
+
result of this call Interpret allow more or less functionalities. If you
|
306
|
+
don't set any `admin` method, all users will be _admins_ inside Interpret. The
|
307
|
+
same happens if you don't set the `current_user` option. The following roles
|
308
|
+
are available:
|
309
|
+
|
310
|
+
### Editor
|
311
|
+
|
312
|
+
When the result is evaluated to false, the user is considered an **editor**.
|
313
|
+
This role is for an user who is intended to make translations, but no to
|
314
|
+
_administrate_ the site.
|
315
|
+
|
316
|
+
- It will be able to edit translations.
|
317
|
+
- It won't be able to use any of the **Tools**.
|
318
|
+
- It won't be able to modify any `protected` translation.
|
319
|
+
|
320
|
+
### Admin
|
321
|
+
|
322
|
+
When the result is evaluated to true, then the user is considered an
|
323
|
+
**admin**, so it can:
|
324
|
+
|
325
|
+
- Do everything described in the **Built-in Backoffice** section.
|
326
|
+
- Mark some translations as `protected`, which means only editable by
|
327
|
+
admins. This can be used to prevent non-technical people to mess up with
|
328
|
+
interpolated translations and things like this.
|
329
|
+
|
330
|
+
|
331
|
+
|
332
|
+
Live translation edition
|
333
|
+
------------------------
|
334
|
+
|
335
|
+
This feature will let you edit your translations and contents directly from
|
336
|
+
your application views. This way the edition work is much more user-friendly,
|
337
|
+
since you're changing what your are seeing. To do so, you will need to do two
|
338
|
+
things:
|
339
|
+
|
340
|
+
1. Let Interpret know about who is logged in, setting the `current_user`
|
341
|
+
option.
|
342
|
+
|
343
|
+
2. Also set an `admin` option, to discriminate which users are _interpret
|
344
|
+
admins_.
|
345
|
+
|
346
|
+
3. Use the following helper in your main layout (or all layouts your
|
347
|
+
application use):
|
348
|
+
|
349
|
+
`= interpret_live_edition`
|
350
|
+
|
351
|
+
You should use it at the bottom of your `body` block.
|
352
|
+
|
353
|
+
4. Set the `Interpret.live_edit` variable to **true**, to enable live edition.
|
354
|
+
|
355
|
+
From there, if the current logged in user is an admin, he will be able to
|
356
|
+
translate contents in live. Note that this is NOT per user, it's a global
|
357
|
+
setting. Also note that only `admins` can use it. We know about this
|
358
|
+
limitations and we will improve this functionality in the future for sure.
|
359
|
+
|
360
|
+
You also need to take care about caching, obviously this will not work with
|
361
|
+
cached views.
|
362
|
+
|
363
|
+
|
364
|
+
Caching
|
365
|
+
-------
|
366
|
+
|
367
|
+
Interpret register the I18n activerecord backend with Flatten and Memoize, so
|
368
|
+
it takes care to reload the I18n backend every time a translation is edited,
|
369
|
+
created or destroyed. Unfortunately I18n only provides a global method
|
370
|
+
`.reload!` to expire the cache, so we can't be more precise about what exactly
|
371
|
+
translation we want to expire, without patching I18n itself at least.
|
372
|
+
|
373
|
+
Besides that, if you're using any kind of caching technique you should use the
|
374
|
+
following:
|
375
|
+
|
376
|
+
Interpret.configure do |config|
|
377
|
+
config.sweeper = "my_sweeper"
|
378
|
+
# ...
|
379
|
+
end
|
380
|
+
|
381
|
+
Using the above code you tell Interpret to register the `MySweeper` class as
|
382
|
+
an observer to `Interpret::Translation`, so you will be able to run expiration
|
383
|
+
logic when a translation changes. With this, you sweeper is the entirely
|
384
|
+
responsible to expire caching, and it's responsible to run an
|
385
|
+
`I18n.backend.reload!` too, unless you inherit from the given
|
386
|
+
`Interpret::ExpirationObserver` class.
|
387
|
+
|
388
|
+
If you want some help with that, the recommended way to run custom expiration
|
389
|
+
logic is to build your sweeper class like the following:
|
390
|
+
|
391
|
+
class MySweeper < Interpret::ExpirationObserver
|
392
|
+
def expire_cache(key)
|
393
|
+
# run your expiration logic
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
|
398
|
+
One parameter will be passed to your `expire_cache` method, a string
|
399
|
+
containing the key of the affected translation. It's your business to find
|
400
|
+
out which page or fragment you have to expire from here.
|
401
|
+
|
402
|
+
Also take note that your _sweeper_ class is in fact an observer, not a
|
403
|
+
Rails sweeper. I've initially implemented this using real sweepers, but I
|
404
|
+
simply don't like the idea to bind the expiration logic to the controllers.
|
405
|
+
And Interpret can't afford it since it needs to expire the cache from a rake
|
406
|
+
task, for example, to run an `update` after a deployment.
|
407
|
+
|
408
|
+
So, you won't be able to use the default expire methods rails provides you,
|
409
|
+
since they are only available from within a controller. You will need to find
|
410
|
+
out a more **raw** way to expire your cache.
|
411
|
+
|
412
|
+
|
413
|
+
Rake tasks
|
414
|
+
----------
|
415
|
+
|
416
|
+
Interpret comes with two rake tasks, which are simply interfaces to run the
|
417
|
+
same `update` and `dump` actions you can run from the **Tools** section of the
|
418
|
+
backoffice.
|
419
|
+
|
420
|
+
rake interpret:update
|
421
|
+
rake interpret:dump
|
422
|
+
|
423
|
+
The `update` task is what you may want to run after a deployment, for what
|
424
|
+
Interpret already has a capistrano recipe...
|
425
|
+
|
426
|
+
|
427
|
+
Capistrano recipe
|
428
|
+
-----------------
|
429
|
+
|
430
|
+
Interpret also have a capistrano recipe to run the `update` rake task after
|
431
|
+
updating code. You only need to require this file in your `deploy.rb`:
|
432
|
+
|
433
|
+
require 'interpret/capistrano'
|
434
|
+
|
435
|
+
|
436
|
+
Soft behavior
|
437
|
+
--------------
|
438
|
+
|
439
|
+
Using this option you choose between give a full control to Interpret over
|
440
|
+
the I18n translations or not. It defaults to `false` and you can change it
|
441
|
+
with:
|
442
|
+
|
443
|
+
Interpret.configure do |config|
|
444
|
+
config.soft = true
|
445
|
+
# ...
|
446
|
+
end
|
447
|
+
|
448
|
+
- When `soft` is set to false: Then Interpret is the _owner_ of all I18n
|
449
|
+
translations, in the sense that it hasn't to be worried about creating or
|
450
|
+
deleting translations. This way, if you remove a key from the `.yml` locale
|
451
|
+
file Interpret will remove that translation from the I18n backend when you
|
452
|
+
run an `update`.
|
453
|
+
|
454
|
+
- When `soft` is set to true: Then Interpret will be more cautious with your
|
455
|
+
translations, and won't remove any of them even though if you have removed
|
456
|
+
the key in the `.yml` file. This is intented to be used when you have a
|
457
|
+
situation where your I18n translations are used by some _other means_.
|
458
|
+
Initially I've implemented this to make Interpret compatible with
|
459
|
+
[Armot](https://github.com/rogercampos/armot), a tool for handle model
|
460
|
+
translations directly with I18n activerecord backend.
|
461
|
+
|
462
|
+
In this case, if some translation exists in the I18n backend but not in `.yml`
|
463
|
+
files, Interpret has no way to know if it's because you have removed them or
|
464
|
+
because it's a translation handled outside Interpret. So, you will end up
|
465
|
+
with unused translations in your database.
|
466
|
+
|
467
|
+
|
468
|
+
Logger
|
469
|
+
------
|
470
|
+
|
471
|
+
Updating, removing or creating a translation will result in a new entry in the
|
472
|
+
log file `log/interpret.log`. The user who made the modification will be also
|
473
|
+
registered in the log entry, if `current_user` is available.
|
474
|
+
|
475
|
+
This can be used as a sort-of backup system, to restore the old contents of a
|
476
|
+
certain translation. It won't be very difficult to write some script to do
|
477
|
+
this, but by now it's not included in Interpret.
|
478
|
+
|
479
|
+
|
480
|
+
Final notes
|
481
|
+
===========
|
482
|
+
|
483
|
+
Thanks to [NodeThirtyThree](http://nodethirtythree.com) for their website
|
484
|
+
templates released under CreativeCommons 3.0 license, one of which is used
|
485
|
+
here.
|
486
|
+
|
487
|
+
This piece of software is on a very early stage of development, so use it at your
|
488
|
+
own risk!
|
@@ -2,6 +2,8 @@ class Interpret::SearchController < Interpret::BaseController
|
|
2
2
|
|
3
3
|
def perform
|
4
4
|
t = Interpret::Translation.arel_table
|
5
|
-
|
5
|
+
search_key = params[:key].split(" ").map{|x| "%#{CGI.escape(x)}%"}
|
6
|
+
search_value = params[:value].split(" ").map{|x| "%#{CGI.escape(x)}%"}
|
7
|
+
@translations = Interpret::Translation.locale(I18n.locale).where(t[:key].matches_all(search_key).or(t[:value].matches_all(search_value)) )
|
6
8
|
end
|
7
9
|
end
|
@@ -34,7 +34,8 @@ class Interpret::ToolsController < Interpret::BaseController
|
|
34
34
|
begin
|
35
35
|
Interpret::Translation.import(params[:file])
|
36
36
|
rescue Exception => e
|
37
|
-
redirect_to interpret_tools_url, :alert => e
|
37
|
+
redirect_to interpret_tools_url, :alert => "Error when importing: #{e.message}"
|
38
|
+
return
|
38
39
|
end
|
39
40
|
|
40
41
|
session.delete(:tree)
|
@@ -1,15 +1,13 @@
|
|
1
1
|
class Interpret::TranslationsController < Interpret::BaseController
|
2
|
-
cache_sweeper eval(Interpret.sweeper.to_s.classify) if Interpret.sweeper
|
3
|
-
cache_sweeper Interpret::TranslationSweeper
|
4
2
|
before_filter :get_tree, :only => :index
|
5
3
|
|
6
4
|
def index
|
7
5
|
key = params[:key]
|
8
6
|
t = Interpret::Translation.arel_table
|
9
7
|
if key
|
10
|
-
@translations = Interpret::Translation.locale(I18n.locale).where(t[:key].matches("#{key}.%"))
|
8
|
+
@translations = Interpret::Translation.locale(I18n.locale).where(t[:key].matches("#{CGI.escape(key)}.%"))
|
11
9
|
if I18n.locale != I18n.default_locale
|
12
|
-
@references = Interpret::Translation.locale(I18n.default_locale).where(t[:key].matches("#{key}.%"))
|
10
|
+
@references = Interpret::Translation.locale(I18n.default_locale).where(t[:key].matches("#{CGI.escape(key)}.%"))
|
13
11
|
end
|
14
12
|
else
|
15
13
|
@translations = Interpret::Translation.locale(I18n.locale).where(t[:key].does_not_match("%.%"))
|
@@ -46,11 +44,11 @@ class Interpret::TranslationsController < Interpret::BaseController
|
|
46
44
|
msg << "Locale: [#{@translation.locale}], key: [#{@translation.key}]. The translation has been changed from [#{old_value}] to [#{@translation.value}]"
|
47
45
|
Interpret.logger.info msg
|
48
46
|
|
49
|
-
format.html { redirect_to(
|
47
|
+
format.html { redirect_to(request.env["HTTP_REFERER"]) }
|
50
48
|
format.xml { head :ok }
|
51
49
|
format.json { head :ok }
|
52
50
|
else
|
53
|
-
format.html {
|
51
|
+
format.html { redirect_to(request.env["HTTP_REFERER"]) }
|
54
52
|
format.xml { render :xml => @translation.errors, :status => :unprocessable_entity }
|
55
53
|
format.json { render :status => :unprocessable_entity }
|
56
54
|
end
|
@@ -81,9 +79,20 @@ class Interpret::TranslationsController < Interpret::BaseController
|
|
81
79
|
end
|
82
80
|
end
|
83
81
|
|
82
|
+
def live_edit
|
83
|
+
blobs = params[:key].split(".")
|
84
|
+
locale = blobs.first
|
85
|
+
key = blobs[1..-1].join(".")
|
86
|
+
@translation = Interpret::Translation.locale(locale).find_by_key(key)
|
87
|
+
|
88
|
+
respond_to do |format|
|
89
|
+
format.html { render :layout => false }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
84
93
|
private
|
85
94
|
def get_tree
|
86
|
-
@tree
|
95
|
+
@tree ||= Interpret::Translation.get_tree
|
87
96
|
end
|
88
97
|
|
89
98
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Interpret
|
2
|
+
|
3
|
+
class ExpirationObserver < ActiveRecord::Observer
|
4
|
+
observe Interpret::Translation
|
5
|
+
|
6
|
+
def after_update(record)
|
7
|
+
run_expiration(record) if record.value_changed?
|
8
|
+
end
|
9
|
+
|
10
|
+
def after_create(record)
|
11
|
+
run_expiration(record)
|
12
|
+
end
|
13
|
+
|
14
|
+
def after_destroy(record)
|
15
|
+
run_expiration(record)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
# expiration logic for your app
|
20
|
+
def expire_cache(key)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def run_expiration(record)
|
25
|
+
Interpret.backend.reload! if Interpret.backend
|
26
|
+
expire_cache(record.key)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,16 +1,8 @@
|
|
1
|
-
require 'i18n/backend/active_record/translation'
|
2
|
-
|
3
|
-
|
4
1
|
module Interpret
|
5
2
|
|
6
|
-
class TableDoesNotExists < ActiveRecord::ActiveRecordError; end
|
7
|
-
|
8
|
-
unless I18n::Backend::ActiveRecord::Translation.table_exists?
|
9
|
-
raise TableDoesNotExists, "You must setup a translations table first"
|
10
|
-
end
|
11
|
-
|
12
3
|
class Translation < I18n::Backend::ActiveRecord::Translation
|
13
4
|
default_scope order('locale ASC')
|
5
|
+
validates_presence_of :value
|
14
6
|
|
15
7
|
class << self
|
16
8
|
# Generates a hash representing the tree structure of the translations
|
@@ -41,24 +33,21 @@ module Interpret
|
|
41
33
|
LazyHash.add(res, "#{e.locale}.#{e.key}", e.value)
|
42
34
|
end
|
43
35
|
if res.keys.size != 1
|
44
|
-
raise IndexError, "Generated hash must have only one root key. Your translation data in
|
36
|
+
raise IndexError, "Generated hash must have only one root key. Your translation data in database may be corrupted."
|
45
37
|
end
|
46
38
|
res
|
47
39
|
end
|
48
40
|
|
49
41
|
# Import the contents of the given .yml locale file into the database
|
50
42
|
# backend. If a given key already exists in database, it will be
|
51
|
-
# overwritten, otherwise it won't be touched. This means that
|
52
|
-
#
|
53
|
-
#
|
43
|
+
# overwritten, otherwise it won't be touched. This means that it won't
|
44
|
+
# delete any existing translation, it only overwrites the ones you give
|
45
|
+
# in the file.
|
54
46
|
# If the given file has new translations, these will be ignored.
|
55
47
|
#
|
56
48
|
# The language will be obtained from the first unique key of the yml
|
57
49
|
# file.
|
58
50
|
def import(file)
|
59
|
-
if file.content_type && file.content_type.match(/^text\/.*/).nil?
|
60
|
-
raise ArgumentError, "Invalid file content type"
|
61
|
-
end
|
62
51
|
hash = YAML.load file
|
63
52
|
raise ArgumentError, "the YAML file must contain an unique first key representing the locale" unless hash.keys.count == 1
|
64
53
|
|
@@ -76,11 +65,11 @@ module Interpret
|
|
76
65
|
end
|
77
66
|
|
78
67
|
# Dump all contents from *.yml locale files into the database.
|
79
|
-
#
|
80
|
-
#
|
68
|
+
# If Interpret.soft is set to false, all existing translations will be
|
69
|
+
# removed
|
81
70
|
def dump
|
82
71
|
files = Dir[Rails.root.join("config", "locales", "*.yml").to_s]
|
83
|
-
delete_all
|
72
|
+
delete_all unless Interpret.soft
|
84
73
|
|
85
74
|
records = []
|
86
75
|
files.each do |f|
|
@@ -195,7 +184,7 @@ module Interpret
|
|
195
184
|
end
|
196
185
|
end
|
197
186
|
|
198
|
-
if prefix.blank?
|
187
|
+
if prefix.blank? && !Interpret.soft
|
199
188
|
remove_unused_keys(existing)
|
200
189
|
end
|
201
190
|
end
|