interpret 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|