mongo_translatable 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. data/.document +5 -0
  2. data/.gitignore +26 -0
  3. data/.gitmodules +3 -0
  4. data/LICENSE +295 -0
  5. data/README.rdoc +131 -0
  6. data/Rakefile +55 -0
  7. data/VERSION +1 -0
  8. data/app/controllers/translations_controller.rb +150 -0
  9. data/app/helpers/translations_helper.rb +71 -0
  10. data/app/views/layouts/translations.html.erb +18 -0
  11. data/app/views/translations/_form.html.erb +50 -0
  12. data/app/views/translations/_translatable_value.html.erb +17 -0
  13. data/app/views/translations/_translation_field.html.erb +34 -0
  14. data/app/views/translations/_translation_field_input.html.erb +8 -0
  15. data/app/views/translations/edit.html.erb +8 -0
  16. data/app/views/translations/index.html.erb +22 -0
  17. data/app/views/translations/new.html.erb +7 -0
  18. data/app/views/translations/show.html.erb +12 -0
  19. data/config/locales/en.yml +33 -0
  20. data/config/locales/fr.yml +18 -0
  21. data/lib/mongo_translatable.rb +336 -0
  22. data/lib/mongo_translatable_configuration.rb +5 -0
  23. data/lib/translatables_helper.rb +227 -0
  24. data/lib/translations_controller_helpers.rb +37 -0
  25. data/rails/init.rb +3 -0
  26. data/test/full_2_3_5_app_with_tests/.gitignore +27 -0
  27. data/test/full_2_3_5_app_with_tests/README +1 -0
  28. data/test/full_2_3_5_app_with_tests/Rakefile +10 -0
  29. data/test/full_2_3_5_app_with_tests/app/controllers/application_controller.rb +26 -0
  30. data/test/full_2_3_5_app_with_tests/app/controllers/items_controller.rb +85 -0
  31. data/test/full_2_3_5_app_with_tests/app/helpers/application_helper.rb +3 -0
  32. data/test/full_2_3_5_app_with_tests/app/helpers/items_helper.rb +2 -0
  33. data/test/full_2_3_5_app_with_tests/app/models/comment.rb +3 -0
  34. data/test/full_2_3_5_app_with_tests/app/models/item.rb +5 -0
  35. data/test/full_2_3_5_app_with_tests/app/models/not_swapped_in_record.rb +3 -0
  36. data/test/full_2_3_5_app_with_tests/app/models/person.rb +3 -0
  37. data/test/full_2_3_5_app_with_tests/app/models/recipe.rb +21 -0
  38. data/test/full_2_3_5_app_with_tests/app/views/items/edit.html.erb +24 -0
  39. data/test/full_2_3_5_app_with_tests/app/views/items/index.html.erb +24 -0
  40. data/test/full_2_3_5_app_with_tests/app/views/items/new.html.erb +23 -0
  41. data/test/full_2_3_5_app_with_tests/app/views/items/show.html.erb +21 -0
  42. data/test/full_2_3_5_app_with_tests/app/views/layouts/items.html.erb +19 -0
  43. data/test/full_2_3_5_app_with_tests/config/boot.rb +110 -0
  44. data/test/full_2_3_5_app_with_tests/config/database.yml +22 -0
  45. data/test/full_2_3_5_app_with_tests/config/environment.rb +54 -0
  46. data/test/full_2_3_5_app_with_tests/config/environments/development.rb +17 -0
  47. data/test/full_2_3_5_app_with_tests/config/environments/production.rb +28 -0
  48. data/test/full_2_3_5_app_with_tests/config/environments/test.rb +28 -0
  49. data/test/full_2_3_5_app_with_tests/config/initializers/backtrace_silencers.rb +7 -0
  50. data/test/full_2_3_5_app_with_tests/config/initializers/inflections.rb +10 -0
  51. data/test/full_2_3_5_app_with_tests/config/initializers/mime_types.rb +5 -0
  52. data/test/full_2_3_5_app_with_tests/config/initializers/new_rails_defaults.rb +21 -0
  53. data/test/full_2_3_5_app_with_tests/config/initializers/session_store.rb +15 -0
  54. data/test/full_2_3_5_app_with_tests/config/locales.yml +5 -0
  55. data/test/full_2_3_5_app_with_tests/config/locales/en.yml +4 -0
  56. data/test/full_2_3_5_app_with_tests/config/locales/fr.yml +4 -0
  57. data/test/full_2_3_5_app_with_tests/config/locales/zh.yml +4 -0
  58. data/test/full_2_3_5_app_with_tests/config/routes.rb +4 -0
  59. data/test/full_2_3_5_app_with_tests/db/migrate/20100407010602_create_items.rb +14 -0
  60. data/test/full_2_3_5_app_with_tests/db/migrate/20100504234216_create_not_swapped_in_records.rb +15 -0
  61. data/test/full_2_3_5_app_with_tests/db/migrate/20100509011052_create_people.rb +13 -0
  62. data/test/full_2_3_5_app_with_tests/db/migrate/20100509042718_create_comments.rb +14 -0
  63. data/test/full_2_3_5_app_with_tests/db/migrate/20110112023516_create_recipes.rb +14 -0
  64. data/test/full_2_3_5_app_with_tests/db/schema.rb +56 -0
  65. data/test/full_2_3_5_app_with_tests/db/seeds.rb +7 -0
  66. data/test/full_2_3_5_app_with_tests/doc/README_FOR_APP +2 -0
  67. data/test/full_2_3_5_app_with_tests/public/404.html +30 -0
  68. data/test/full_2_3_5_app_with_tests/public/422.html +30 -0
  69. data/test/full_2_3_5_app_with_tests/public/500.html +30 -0
  70. data/test/full_2_3_5_app_with_tests/public/favicon.ico +0 -0
  71. data/test/full_2_3_5_app_with_tests/public/images/rails.png +0 -0
  72. data/test/full_2_3_5_app_with_tests/public/index.html +275 -0
  73. data/test/full_2_3_5_app_with_tests/public/javascripts/application.js +2 -0
  74. data/test/full_2_3_5_app_with_tests/public/javascripts/controls.js +963 -0
  75. data/test/full_2_3_5_app_with_tests/public/javascripts/dragdrop.js +973 -0
  76. data/test/full_2_3_5_app_with_tests/public/javascripts/effects.js +1128 -0
  77. data/test/full_2_3_5_app_with_tests/public/javascripts/prototype.js +4320 -0
  78. data/test/full_2_3_5_app_with_tests/public/robots.txt +5 -0
  79. data/test/full_2_3_5_app_with_tests/public/stylesheets/scaffold.css +54 -0
  80. data/test/full_2_3_5_app_with_tests/script/about +4 -0
  81. data/test/full_2_3_5_app_with_tests/script/console +3 -0
  82. data/test/full_2_3_5_app_with_tests/script/dbconsole +3 -0
  83. data/test/full_2_3_5_app_with_tests/script/destroy +3 -0
  84. data/test/full_2_3_5_app_with_tests/script/generate +3 -0
  85. data/test/full_2_3_5_app_with_tests/script/performance/benchmarker +3 -0
  86. data/test/full_2_3_5_app_with_tests/script/performance/profiler +3 -0
  87. data/test/full_2_3_5_app_with_tests/script/plugin +3 -0
  88. data/test/full_2_3_5_app_with_tests/script/runner +3 -0
  89. data/test/full_2_3_5_app_with_tests/script/server +3 -0
  90. data/test/full_2_3_5_app_with_tests/test/factories.rb +18 -0
  91. data/test/full_2_3_5_app_with_tests/test/functional/translations_controller_test.rb +66 -0
  92. data/test/full_2_3_5_app_with_tests/test/integration/translation_test.rb +72 -0
  93. data/test/full_2_3_5_app_with_tests/test/performance/browsing_test.rb +9 -0
  94. data/test/full_2_3_5_app_with_tests/test/selenium.rb +83 -0
  95. data/test/full_2_3_5_app_with_tests/test/test_helper.rb +86 -0
  96. data/test/full_2_3_5_app_with_tests/test/unit/helpers/translatables_helper_test.rb +37 -0
  97. data/test/full_2_3_5_app_with_tests/test/unit/helpers/translations_helper_test.rb +62 -0
  98. data/test/full_2_3_5_app_with_tests/test/unit/mongo_translatable_test.rb +342 -0
  99. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/init.rb +1 -0
  100. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter.rb +94 -0
  101. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/base.rb +31 -0
  102. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/force_extension.rb +57 -0
  103. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/locale.rb +70 -0
  104. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/pagination.rb +33 -0
  105. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/uuid_token.rb +78 -0
  106. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/force_extension_spec.rb +65 -0
  107. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/generation_spec.rb +367 -0
  108. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/pagination_extension_spec.rb +19 -0
  109. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/recognition_spec.rb +76 -0
  110. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/routing_filter_spec.rb +114 -0
  111. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/spec_helper.rb +108 -0
  112. metadata +309 -0
@@ -0,0 +1,7 @@
1
+ <% @button_text = t("translations.new.create") -%>
2
+
3
+ <% form_for(@translation) do |f| %>
4
+
5
+ <%= render :partial => f -%>
6
+
7
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <ul>
2
+ <% translated_values_with_localized_labels.each do |translatable_value| %>
3
+ <li><%= translatable_value[:localized_label] %>
4
+ <% if translatable_value[:value] -%>
5
+ <ul><li><%= translatable_value[:value] -%></li></ul>
6
+ <% end %>
7
+ </li>
8
+ <% end %>
9
+ </ul>
10
+
11
+ <%= link_to(t('translations.show.edit'), { :id => h(@translation.locale), :action => :edit }) %> |
12
+ <%= link_to(t('translations.show.translations'), { :action => :index }) %>
@@ -0,0 +1,33 @@
1
+ en:
2
+ translations:
3
+ new:
4
+ create: "Create"
5
+ edit:
6
+ update: "Update"
7
+ form:
8
+ locale: "Choose language"
9
+ original_has_no_value: "This field cannot be translated, as the original does not have a value for it."
10
+ translate_from_and_to: "Translate from {{from}} to {{to}}"
11
+ helpers:
12
+ translate: "Translate"
13
+ are_you_sure: 'Are you sure?'
14
+ available_in: "Available in:"
15
+ close_box: "[close]"
16
+ delete: "delete"
17
+ edit: "edit"
18
+ manage: "manage"
19
+ needs_translating_to: "Needs translating to:"
20
+ index:
21
+ are_you_sure: 'Are you sure?'
22
+ back: "Return to {{type}}"
23
+ delete: "Delete"
24
+ edit: "Edit"
25
+ new: "New Translation"
26
+ title: "Translations for this {{type}}"
27
+ show:
28
+ edit: "Edit"
29
+ translations: "Translations"
30
+ controllers:
31
+ created: 'Translation was successfully created.'
32
+ deleted: 'Translation was successfully deleted.'
33
+ updated: 'Translation was successfully updated.'
@@ -0,0 +1,18 @@
1
+ fr:
2
+ translations:
3
+ new:
4
+ create: "Créer"
5
+ edit:
6
+ update: "Mettre à jour"
7
+ form:
8
+ locale: "Choisissez la langue"
9
+ translate_from_and_to: "Traduire de l' {{from}} à {{to}}"
10
+ helpers:
11
+ available_in: "Disponible en:"
12
+ close_box: "[Fermer]"
13
+ needs_translating_to: "Les besoins de traduction à:"
14
+ index:
15
+ new: "Translate"
16
+ show:
17
+ edit: "Éditer"
18
+ translations: "Traductions"
@@ -0,0 +1,336 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "active_record"
3
+ require "mongo_mapper"
4
+
5
+ ActionController::Base.send(:include, TranslationsControllerHelpers)
6
+ ActionController::Base.send(:helper, TranslatablesHelper)
7
+
8
+ # load our locales
9
+ I18n.load_path += Dir[ File.join(File.dirname(__FILE__), '..', 'config', 'locales', '*.{rb,yml}') ]
10
+
11
+ module MongoTranslatable #:nodoc:
12
+ # MongoTranslatable is for taking advantage of MongoDB for storing translations
13
+ # of ActiveRecord models. Here is how it works in practice:
14
+ # class Item < ActiveRecord::Base
15
+ # mongo_translate :label
16
+ # end
17
+ #
18
+ # I18n.locale = :en
19
+ #
20
+ # item = Item.create(:label => "a label")
21
+ # p item.locale
22
+ # "en"
23
+ #
24
+ # item = Item.find(1)
25
+ # p item.label
26
+ # "a label"
27
+ #
28
+ # item.translate(:label => "etiketissä", :locale => :fi).save
29
+ ## or you could have set I18n.locale = :fi in calling env and dropped locale from args
30
+ #
31
+ # I18n.locale = :fi
32
+ # item = Item.find(1)
33
+ # p item.label
34
+ # "etiketissä"
35
+ # p item.locale
36
+ # "fi"
37
+ #
38
+ # If
39
+ # The general approach is inspired by this code in globalize2:
40
+ # http://github.com/joshmh/globalize2/blob/master/lib/globalize/active_record.rb
41
+ # grab what the normal finder would return
42
+ # and look up corresponding translated version of the objects in question
43
+ # swap in corresponding translated attributes of object
44
+ # creates TranslatableClass::Translation when the declaration method is defined
45
+ # ala acts_as_versioned VersionedClass::Version
46
+ # every translatable class gets their own Translation class under it
47
+ #
48
+ # TODO: translations aren't real associations
49
+ # and the translations method is thus not chainable as you would expect
50
+ # currently investigating adding a plugin for mongo_mapper that will do associations declaired
51
+ # from activerecord models
52
+ # in the meantime, you will like want to use the "translation_for(locale)" method
53
+ module Translated
54
+ def self.included(base)
55
+ base.extend(ClassMethods)
56
+ end
57
+
58
+ module ClassMethods
59
+
60
+ def mongo_translate(*args)
61
+ # don't allow multiple calls
62
+ return if self.included_modules.include?(MongoTranslatable::Translated::InstanceMethods)
63
+
64
+ send :include, MongoTranslatable::Translated::InstanceMethods
65
+
66
+ options = args.last.is_a?(Hash) ? args.pop : Hash.new
67
+ redefine_find = !options[:redefine_find].nil? ? options[:redefine_find] : true
68
+
69
+ translatable_attributes = args.is_a?(Array) ? args : [args]
70
+
71
+ cattr_accessor :translatable_attributes, :as_foreign_key_sym
72
+
73
+ # expects a single attribute name symbol or array of attribute names as symbols
74
+ self.translatable_attributes = translatable_attributes
75
+ self.as_foreign_key_sym = self.name.foreign_key.to_sym
76
+
77
+ before_save :set_locale_if_necessary
78
+ before_destroy :destroy_translations
79
+
80
+ # create the dynamic translation model
81
+ const_set("Translation", Class.new).class_eval do
82
+ include MongoMapper::Document
83
+
84
+ cattr_accessor :translatable_class
85
+ self.translatable_class = self.name.split('::').first.constantize
86
+
87
+ def self.declare_key_from(spec)
88
+ key_type = String
89
+ key_name = spec
90
+
91
+ key_name, key_type = spec if spec.is_a?(Array)
92
+
93
+ unless keys.include?(key_name.to_s)
94
+ class_eval do
95
+ key key_name, key_type
96
+ end
97
+ end
98
+ end
99
+
100
+ self.translatable_class.translatable_attributes.each { |a| declare_key_from(a) }
101
+
102
+ key :locale, String, :required => true
103
+
104
+ before_save :locale_to_string
105
+
106
+ # for classes that have dynamic translatable_attributes
107
+ # add definition of keys for mongo_translatable Translation class, if not already defined
108
+ # add accessor methods, too, if not already defined
109
+ def self.update_keys_if_necessary_with(new_translatable_attributes)
110
+ attribute_type = String
111
+
112
+ new_translatable_attributes.each { |a| declare_key_from(a) }
113
+ end
114
+
115
+ # TODO: add validation for locale unique to translatable_class.as_foreign_key_sym scope
116
+ # not implemented in mongo mapper yet
117
+
118
+ def translatable
119
+ self.translatable_class.find(self.send(self.translatable_class.as_foreign_key_sym))
120
+ end
121
+
122
+ protected
123
+ # always store string version of locale (rather than symbol)
124
+ def locale_to_string
125
+ self.locale = self.locale.to_s
126
+ end
127
+ end
128
+
129
+ class_eval do
130
+
131
+ # dynamically define translation accessor methods
132
+ def self.define_translation_accessor_method_for(attribute_name)
133
+ # create the template code
134
+ code = Proc.new { |locale|
135
+ translation = translation_for(locale)
136
+ translation.send(attribute_name.to_sym) if translation
137
+ }
138
+
139
+ define_method(attribute_name.to_s + "_translation_for", &code)
140
+ end
141
+
142
+ # define convenience method for each translatable_attribute
143
+ # uses class method, see class method definitions
144
+ translatable_attributes.each do |attribute|
145
+ define_translation_accessor_method_for(attribute)
146
+ end
147
+
148
+ # take a collection of this class
149
+ # and process translations accordingly
150
+ def self.translatable_processing_of(results, *args)
151
+ # at least for now, we skip :select queries
152
+ # as we can't rely on having attributes available that we need
153
+ # to compare against
154
+ # we also return if results are nil
155
+ return results if (args.is_a?(Array) &&
156
+ args.last.is_a?(Hash) &&
157
+ args.last.keys.include?(:select) &&
158
+ !args.last[:select].nil?) || results.nil?
159
+
160
+ # handle single record
161
+ if results.is_a?(self)
162
+ result = results
163
+ results = swap_in_translation_for_single(result)
164
+ else
165
+ # handle multiple records
166
+ results = swap_in_translation_for_multiple(results)
167
+ end
168
+ end
169
+
170
+ # does the actual swapping of translatable_attributes for a result
171
+ def self.swap_in_translation_for_single(result)
172
+ # only look up translation if the required locale is not the default for the record
173
+ if result.present? && result.locale != I18n.locale.to_s
174
+ # look up translation and swap in its attributes for result
175
+ translated = result.translation_for(I18n.locale)
176
+ if translated.present?
177
+ result.translatable_attributes.each do |translated_attribute|
178
+ unless translated.attributes[translated_attribute].blank?
179
+ result.set_translation_for_this(translated_attribute, translated.attributes[translated_attribute])
180
+ end
181
+ end
182
+ result.locale = translated.locale
183
+ end
184
+ end
185
+
186
+ result
187
+ end
188
+
189
+ # does the actual swapping of translatable_attributes per result
190
+ # and returns new collection of translated results
191
+ def self.swap_in_translation_for_multiple(results)
192
+ # do second query of translations
193
+ # if item locale is different than current locale
194
+ # swap in attributse from translation for item that is current locale
195
+
196
+ # rather than rerun the full query, simply get ids and add locale
197
+ result_ids = results.collect(&:id)
198
+
199
+ conditions = {:locale => I18n.locale.to_s}
200
+ conditions[as_foreign_key_sym] = result_ids
201
+
202
+ translations = self::Translation.all(conditions)
203
+
204
+ translated_results = results
205
+ index = 0
206
+ results.each do |result|
207
+ unless result.locale == I18n.locale.to_s
208
+ matching_translation = translations.select { |t| t[as_foreign_key_sym] == result.id }.first
209
+
210
+ if matching_translation
211
+ result.translatable_attributes.each do |key|
212
+ unless matching_translation.attributes[key].blank?
213
+ result.set_translation_for_this(key, matching_translation.attributes[key])
214
+ end
215
+ end
216
+
217
+ result.locale = I18n.locale.to_s
218
+
219
+ translated_results[index] = result
220
+ end
221
+ end
222
+ index += 1
223
+ end
224
+ results = translated_results
225
+ end
226
+
227
+ end
228
+
229
+ if redefine_find
230
+
231
+ class_eval do
232
+ # override find, this is called by all the dynamic finders
233
+ # it isn't called by find_by_sql though (may be other exceptions)
234
+ def self.find(*args)
235
+ # get the standard results from find
236
+ # this will throw a RecordNotFound before executing our code
237
+ # if that is the response
238
+ results = super(*args)
239
+
240
+ results = translatable_processing_of(results, *args)
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ module InstanceMethods
247
+ # for classes that have dynamice translatable attributes, we may need to update
248
+ # accessor methods for a dynamic translatable attribute
249
+ def update_translation_for_methods_if_necessary_with(new_translatable_attributes)
250
+ new_translatable_attributes.each do |attribute|
251
+ unless respond_to?("#{attribute.to_s}_translation_for".to_sym)
252
+ self.class.define_translation_accessor_method_for(attribute)
253
+ end
254
+ end
255
+ end
256
+
257
+ # this will replace specified attribute with its translated value
258
+ # taks an attribute name as a string or symbol
259
+ def set_translation_for_this(attribute_name, translated_value)
260
+ send(attribute_name.to_s + "=", translated_value)
261
+ end
262
+
263
+ def translations
264
+ self.class::Translation.all(self.class.as_foreign_key_sym => id)
265
+ end
266
+
267
+ # sometimes all you need is only the locales of translations
268
+ def translations_locales
269
+ self.class::Translation.all(self.class.as_foreign_key_sym => id, :select => 'locale')
270
+ end
271
+
272
+ # get a list of locales as syms for all translations locales, plus object's original locale
273
+ def available_in_these_locales
274
+ [original_locale] + translations_locales.collect(&:locale)
275
+ end
276
+
277
+ # list of locales that haven't been translated yet
278
+ def needed_in_these_locales
279
+ TranslationsHelper::available_locales.keys - available_in_these_locales
280
+ end
281
+
282
+ # assumes unique locale
283
+ def translation_for(locale)
284
+ self.class::Translation.first(self.class.as_foreign_key_sym => id, :locale => locale)
285
+ end
286
+
287
+ # this will create a new translation (but won't save it)
288
+ # with either passed in options
289
+ # note that we don't save the changes to self
290
+ # only the new translation
291
+ # will return nothing if translate to locale
292
+ # is the same as the object to translate's original locale
293
+ def translate(options = {})
294
+ translation_locale = options[:locale] || I18n.locale
295
+
296
+ @translation = self.class::Translation.new({
297
+ :locale => translation_locale,
298
+ :translatable_locale => self.locale, # save original locale
299
+ self.class.as_foreign_key_sym => id
300
+ })
301
+
302
+ if translation_locale.to_s == original_locale.to_s
303
+ # TODO: locale's emptiness is the reported error
304
+ # when this is triggered, figure out why
305
+ # serving its purpose though to prevent a translation to be added for original_locale
306
+ @translation.errors.replace(:locale, "Cannot add translation the same as the original locale.")
307
+ else
308
+ # work through self and replace attributes
309
+ # with the passed in translations for defined translatable_attributes
310
+ translatable_attributes.each do |translated_attribute|
311
+ translated_value = options[translated_attribute]
312
+ @translation.send("#{translated_attribute.to_sym}=", translated_value) if translated_value.present?
313
+ end
314
+ end
315
+
316
+ @translation
317
+ end
318
+
319
+ protected
320
+ # always store string version of locale (rather than symbol)
321
+ # if none is specified, use environment's setting
322
+ def set_locale_if_necessary
323
+ self.locale = self.locale.present? ? self.locale : I18n.locale
324
+ self.locale = self.locale.to_s
325
+ self.original_locale = self.locale
326
+ end
327
+
328
+ def destroy_translations
329
+ translations = self.class::Translation.all(self.class.as_foreign_key_sym => id)
330
+ translations.each { |translation| translation.destroy }
331
+ end
332
+ end
333
+ end
334
+ end
335
+
336
+ ActiveRecord::Base.class_eval { include MongoTranslatable::Translated }
@@ -0,0 +1,5 @@
1
+ class MongoTranslatableConfiguration
2
+ @@provide_autotranslate = false
3
+
4
+ cattr_accessor :provide_autotranslate
5
+ end
@@ -0,0 +1,227 @@
1
+ module TranslatablesHelper
2
+ def available_in_locales_for(translatable, options = {})
3
+ return if TranslationsHelper.available_locales.size < 2
4
+
5
+ html = "<ul style='list-style:none; margin:0; padding:0;'>"
6
+ html += "<li style='float:left;'>#{I18n.t('translations.helpers.available_in')}</li>"
7
+ options[:params] ||= {}
8
+
9
+ translated_in_locales = Array.new
10
+ translatable.available_in_these_locales.each_with_index do |locale, index|
11
+ styles = "float: left; padding: 0 5px; #{'border-left: 1px solid #000' unless index == 0}"
12
+ onclick = 'update_translation_box(this); return false' if options[:lightbox]
13
+
14
+ html += content_tag(:li, :style => styles) do
15
+ link_to_unless_current(TranslationsHelper::available_locales[locale],
16
+ url_for(:locale => locale, :to_locale => (params[:to_locale] if defined?(params))),
17
+ { :onclick => onclick })
18
+ end
19
+ translated_in_locales << locale unless locale == translatable.original_locale
20
+ end
21
+
22
+ manage_links = manage_translations_links_for(translatable, translated_in_locales, options)
23
+
24
+ if translated_in_locales.size > 0
25
+ html += "<li style='float:left; padding-left: 25px;'>(#{manage_links})</li>"
26
+ end
27
+
28
+ html += '</ul>'
29
+ html += "<div style='clear:both;'></div>"
30
+
31
+ translatable_lightbox_js_and_css if options[:lightbox]
32
+ google_auto_translatable_js
33
+
34
+ html
35
+ end
36
+
37
+ def manage_translations_links_for(translatable, translated_in_locales, options)
38
+ manage_links = String.new
39
+
40
+ # we only have one locale beyond the original locale
41
+ # just give its edit and delete links
42
+ if translated_in_locales.size == 1
43
+ manage_links = link_to(I18n.t('translations.helpers.edit'), {
44
+ :controller => :translations,
45
+ :action => :edit,
46
+ :id => translated_in_locales.first,
47
+ translatable_key_from(translatable) => translatable
48
+ }.merge(options[:params]))
49
+ manage_links += " | " + link_to(I18n.t('translations.helpers.delete'), {
50
+ :controller => :translations,
51
+ :action => :destroy,
52
+ :id => translated_in_locales.first,
53
+ :return_to_translated => true,
54
+ translatable_key_from(translatable) => translatable
55
+ }.merge(options[:params]), {
56
+ :method => :delete,
57
+ :confirm => t('translations.helpers.are_you_sure') })
58
+ elsif translated_in_locales.size > 1
59
+ manage_links = link_to(I18n.t('translations.helpers.manage'), {
60
+ :controller => :translations,
61
+ :action => :index,
62
+ translatable_key_from(translatable) => translatable
63
+ })
64
+ end
65
+ end
66
+
67
+ def needed_in_locales_for(translatable, options = {})
68
+ return if TranslationsHelper.available_locales.size < 2
69
+
70
+ html = "<ul style='list-style:none; margin:0; padding:0;'>"
71
+ html += "<li style='float:left;'>#{I18n.t('translations.helpers.needs_translating_to')}</li>"
72
+
73
+ needed_locales_links = needed_in_locales_links(translatable, options)
74
+ return if needed_locales_links.blank?
75
+
76
+ needed_locales_links.each_with_index do |link, index|
77
+ styles = "float: left; padding: 0 10px; #{'border-left: 1px solid #000' unless index == 0}"
78
+ html += content_tag(:li, :style => styles) do
79
+ link
80
+ end
81
+ end
82
+
83
+ html += '</ul>'
84
+ html += "<div style='clear:both;'></div>"
85
+
86
+ translatable_lightbox_js_and_css if options[:lightbox]
87
+ google_auto_translatable_js
88
+
89
+ html
90
+ end
91
+
92
+ def needed_in_locales_links(translatable, options = {})
93
+ return if TranslationsHelper.available_locales.size < 2
94
+ needed_locales = translatable.needed_in_these_locales
95
+ return unless needed_locales.any?
96
+ options[:params] ||= {}
97
+ links = needed_locales.collect do |locale|
98
+ onclick = 'update_translation_box(this); return false' if options[:lightbox]
99
+ link_to(TranslationsHelper::available_locales[locale],
100
+ { :action => :new,
101
+ :controller => :translations,
102
+ translatable_key_from(translatable) => translatable,
103
+ :to_locale => locale }.merge(options[:params]), { :onclick => onclick })
104
+ end
105
+ links
106
+ end
107
+
108
+
109
+ #Links to all locales
110
+ def raw_locale_links(options = {})
111
+ links = TranslationsHelper::available_locales.keys.collect do |locale|
112
+ {:link => link_to_unless_current(TranslationsHelper::available_locales[locale],
113
+ url_for(:locale => locale, :to_locale => (params[:to_locale] if defined?(params)))
114
+ ), :locale => locale}
115
+ end
116
+ links
117
+ end
118
+
119
+ def raw_available_in_locales_links(translatable, options = {})
120
+ onclick = 'update_translation_box(this); return false' if options[:lightbox]
121
+ links = translatable.available_in_these_locales.collect do |locale|
122
+ if locale == I18n.locale
123
+ { :link => TranslationsHelper::available_locales[locale],
124
+ :locale => locale }
125
+ else
126
+ { :link => link_to(TranslationsHelper::available_locales[locale],
127
+ url_for(:locale => locale,
128
+ :to_locale => (params[:to_locale] if defined?(params))),
129
+ { :onclick => onclick }),
130
+ :locale => locale }
131
+ end
132
+ end
133
+ links
134
+ end
135
+
136
+ #Translate link for the given locale
137
+ def translate_link(translatable, options = {})
138
+ options[:params] ||= {}
139
+ options[:onclick] = 'update_translation_box(this); return false' if options[:lightbox]
140
+ options[:onclick] ||= ""
141
+
142
+ action = translatable.translation_for(params[:locale]) ? :edit : :new
143
+ action = options[:action].present? ? options[:action] : action
144
+
145
+ options[:params][:id] = params[:locale] if action == :edit
146
+ link_to(I18n.t('translations.helpers.translate'),
147
+ { :action => action,
148
+ :controller => :translations,
149
+ translatable_key_from(translatable) => translatable,
150
+ :to_locale => params[:locale] }.merge(options[:params]), :onclick => options[:onclick])
151
+ end
152
+
153
+ def translatable_key_from(translatable)
154
+ translatable.class.name.tableize.singularize + '_id'
155
+ end
156
+
157
+ def translatable_lightbox_js_and_css
158
+ return if @translatable_lightbox_js_and_css_inserted
159
+
160
+ js = javascript_tag("
161
+ function close_open_translation_box() {
162
+ if ($('translate_outer_box')) { $('translate_outer_box').remove(); }
163
+ if ($('translate_inner_box')) { $('translate_inner_box').remove(); }
164
+ }
165
+
166
+ function page_dimensions() {
167
+ var html_dimensions = $$('html').first().getDimensions();
168
+ var body_dimensions = document.body.getDimensions();
169
+
170
+ var dimensions = new Array();
171
+ dimensions['width'] = (html_dimensions['width'] > body_dimensions['width'] ? html_dimensions['width'] : body_dimensions['width']);
172
+ dimensions['height'] = (html_dimensions['height'] > body_dimensions['height'] ? html_dimensions['height'] : body_dimensions['height']);
173
+ return dimensions;
174
+ }
175
+
176
+ function update_translation_box(element) {
177
+ new Ajax.Request(element.href, {
178
+ method: 'get',
179
+ onComplete: function(transport) {
180
+ var outer_box = new Element('div', { 'id': 'translate_outer_box' }).setOpacity(0.8).setStyle({ width: page_dimensions()['width'] + 'px', height: page_dimensions()['height'] + 'px' });
181
+ var close_link = '<a href=\\'\\' name=\\'top\\' title=\\'Close\\' onclick=\\'close_open_translation_box(); return false;\\'>#{I18n.t('translations.helpers.close_box')}</a>';
182
+ var close_box = '<div id=\\'translate_close_box\\'>' + close_link + '</div>';
183
+ var inner_box = new Element('div', { 'id': 'translate_inner_box' }).update(close_box + transport.responseText);
184
+ close_open_translation_box();
185
+ document.body.appendChild(outer_box);
186
+ document.body.appendChild(inner_box);
187
+ window.location.hash = 'top'; // send us to the top of the translation box
188
+ window.onresize = function() {
189
+ $('translate_outer_box').setStyle({ width: page_dimensions()['width'] + 'px', height: page_dimensions()['height'] + 'px' });
190
+ }
191
+ }
192
+ });
193
+ }
194
+ ")
195
+
196
+ css = content_tag("style", :type => "text/css") do
197
+ <<-CSS
198
+ #translate_outer_box { position: absolute; top: 0; left: 0; width: 100%; min-height: 500px; background-color: #000; }
199
+ #translate_inner_box { position: absolute; top: 0; left: 15%; right: 15%; margin: 50px auto; padding: 20px;
200
+ background-color: #fff; -moz-border-radius: 1em; -webkit-border-radius: 1em; }
201
+ #translate_close_box { float: right; margin-top: -45px; margin-right: -15px; }
202
+ #translate_close_box a { color: #fff; }
203
+ CSS
204
+ end
205
+
206
+ @translatable_lightbox_js_and_css_inserted = true
207
+ content_for(:add_on_scripts_and_links) { js + css }
208
+ end
209
+
210
+ def google_auto_translatable_js
211
+ return unless MongoTranslatableConfiguration.provide_autotranslate
212
+ return if @google_auto_translatable_js_inserted
213
+
214
+ js = javascript_include_tag("http://www.google.com/jsapi?format=")
215
+ js += javascript_tag("
216
+ google.load('language', '1');
217
+ function getGoogleTranslation(field_id, text, from_language, to_language) {
218
+ google.language.translate(text, from_language, to_language, function(result) {
219
+ if (!result.error) { Form.Element.setValue(field_id, result.translation); }
220
+ });
221
+ }
222
+ ")
223
+
224
+ @google_auto_translatable_js_inserted = true
225
+ content_for(:add_on_scripts_and_links) { js }
226
+ end
227
+ end