mongo_translatable 0.1.0

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