interpret 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/README.md +488 -0
  2. data/app/controllers/interpret/search_controller.rb +3 -1
  3. data/app/controllers/interpret/tools_controller.rb +2 -1
  4. data/app/controllers/interpret/translations_controller.rb +16 -7
  5. data/app/models/interpret/expiration_observer.rb +29 -0
  6. data/app/models/interpret/translation.rb +9 -20
  7. data/app/views/interpret/tools/index.html.erb +16 -23
  8. data/app/views/interpret/translations/_listing.html.erb +1 -1
  9. data/app/views/interpret/translations/live_edit.html.erb +11 -0
  10. data/app/views/layouts/interpret.html.erb +6 -1
  11. data/app/views/layouts/interpret_base.html.erb +1 -1
  12. data/config/routes.rb +4 -0
  13. data/interpret.gemspec +0 -1
  14. data/lib/generators/interpret/setup_generator.rb +2 -1
  15. data/lib/interpret/controller_filter.rb +22 -0
  16. data/lib/interpret/engine.rb +17 -6
  17. data/lib/interpret/helpers.rb +39 -0
  18. data/lib/interpret/version.rb +1 -1
  19. data/lib/interpret.rb +4 -1
  20. data/lib/tasks/interpret.rake +4 -2
  21. data/public/javascripts/facebox-1.3/closelabel.png +0 -0
  22. data/public/javascripts/facebox-1.3/facebox.css +80 -0
  23. data/public/javascripts/facebox-1.3/facebox.js +309 -0
  24. data/public/javascripts/facebox-1.3/loading.gif +0 -0
  25. data/public/stylesheets/interpret_live_edit_style.css +38 -0
  26. data/public/stylesheets/interpret_style.css +18 -0
  27. data/spec/models/translation_spec.rb +33 -22
  28. data/spec/observers/expiration_observer_spec.rb +17 -0
  29. data/spec/spec_helper.rb +0 -1
  30. data/test_app/Gemfile +4 -2
  31. data/test_app/app/controllers/application_controller.rb +17 -1
  32. data/test_app/app/controllers/pages_controller.rb +0 -1
  33. data/test_app/app/models/my_sweeper.rb +5 -0
  34. data/test_app/app/views/layouts/application.html.erb +115 -5
  35. data/test_app/app/views/layouts/backoffice.html.erb +27 -0
  36. data/test_app/app/views/pages/archives.html.erb +3 -0
  37. data/test_app/app/views/pages/contact.html.erb +3 -0
  38. data/test_app/app/views/pages/index.html.erb +56 -0
  39. data/test_app/app/views/pages/links.html.erb +10 -0
  40. data/test_app/app/views/pages/resources.html.erb +5 -0
  41. data/test_app/config/application.rb +2 -2
  42. data/test_app/config/environments/production.rb +0 -5
  43. data/test_app/config/initializers/interpret.rb +3 -3
  44. data/test_app/config/locales/en.yml +55 -0
  45. data/test_app/config/locales/es.yml +3 -47
  46. data/test_app/config/routes.rb +8 -1
  47. data/test_app/public/images/a1.gif +0 -0
  48. data/test_app/public/images/a10.jpg +0 -0
  49. data/test_app/public/images/a16.gif +0 -0
  50. data/test_app/public/images/a18.gif +0 -0
  51. data/test_app/public/images/a22.gif +0 -0
  52. data/test_app/public/images/a26.gif +0 -0
  53. data/test_app/public/images/a33.gif +0 -0
  54. data/test_app/public/images/a36.gif +0 -0
  55. data/test_app/public/images/a38.gif +0 -0
  56. data/test_app/public/images/a41.gif +0 -0
  57. data/test_app/public/images/a47.gif +0 -0
  58. data/test_app/public/images/a50.gif +0 -0
  59. data/test_app/public/images/a8.gif +0 -0
  60. data/test_app/public/images/abg.gif +0 -0
  61. data/test_app/public/images/pic1.jpg +0 -0
  62. data/test_app/public/images/pic2.jpg +0 -0
  63. data/test_app/public/images/spacer.gif +0 -0
  64. data/test_app/public/images/upbg.gif +0 -0
  65. data/test_app/public/javascripts/facebox-1.3/closelabel.png +0 -0
  66. data/test_app/public/javascripts/facebox-1.3/facebox.css +80 -0
  67. data/test_app/public/javascripts/facebox-1.3/facebox.js +309 -0
  68. data/test_app/public/javascripts/facebox-1.3/loading.gif +0 -0
  69. data/test_app/public/stylesheets/default.css +361 -0
  70. data/test_app/public/stylesheets/interpret_live_edit_style.css +38 -0
  71. data/test_app/public/stylesheets/interpret_style.css +18 -0
  72. data/test_app/public/stylesheets/private.css +0 -0
  73. metadata +96 -29
  74. data/app/sweepers/interpret/base_sweeper.rb +0 -18
  75. data/app/sweepers/interpret/translation_sweeper.rb +0 -11
  76. data/public/javascripts/jquery.purr.js +0 -180
  77. data/test_app/app/sweepers/page_sweeper.rb +0 -12
  78. data/test_app/app/views/pages/contact.html.haml +0 -8
  79. data/test_app/app/views/pages/index.html.haml +0 -10
  80. data/test_app/config/locales/ca.yml +0 -6
  81. 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
- @translations = Interpret::Translation.locale(I18n.locale).where(t[:key].matches("%#{params[:key]}%").and(t[:value].matches("%#{params[:value]}%")) )
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(translations_url)}
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 { render :action => "edit" }
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 = session[:tree] ||= Interpret::Translation.get_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 datrabase may be corrupted."
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 in any
52
- # case it will delete an existing translation, it only overwrites the
53
- # ones you give in the file.
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
- # CAUTION: All existing data will be erased!
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