mongo_translatable 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +26 -0
- data/.gitmodules +3 -0
- data/LICENSE +295 -0
- data/README.rdoc +131 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/app/controllers/translations_controller.rb +150 -0
- data/app/helpers/translations_helper.rb +71 -0
- data/app/views/layouts/translations.html.erb +18 -0
- data/app/views/translations/_form.html.erb +50 -0
- data/app/views/translations/_translatable_value.html.erb +17 -0
- data/app/views/translations/_translation_field.html.erb +34 -0
- data/app/views/translations/_translation_field_input.html.erb +8 -0
- data/app/views/translations/edit.html.erb +8 -0
- data/app/views/translations/index.html.erb +22 -0
- data/app/views/translations/new.html.erb +7 -0
- data/app/views/translations/show.html.erb +12 -0
- data/config/locales/en.yml +33 -0
- data/config/locales/fr.yml +18 -0
- data/lib/mongo_translatable.rb +336 -0
- data/lib/mongo_translatable_configuration.rb +5 -0
- data/lib/translatables_helper.rb +227 -0
- data/lib/translations_controller_helpers.rb +37 -0
- data/rails/init.rb +3 -0
- data/test/full_2_3_5_app_with_tests/.gitignore +27 -0
- data/test/full_2_3_5_app_with_tests/README +1 -0
- data/test/full_2_3_5_app_with_tests/Rakefile +10 -0
- data/test/full_2_3_5_app_with_tests/app/controllers/application_controller.rb +26 -0
- data/test/full_2_3_5_app_with_tests/app/controllers/items_controller.rb +85 -0
- data/test/full_2_3_5_app_with_tests/app/helpers/application_helper.rb +3 -0
- data/test/full_2_3_5_app_with_tests/app/helpers/items_helper.rb +2 -0
- data/test/full_2_3_5_app_with_tests/app/models/comment.rb +3 -0
- data/test/full_2_3_5_app_with_tests/app/models/item.rb +5 -0
- data/test/full_2_3_5_app_with_tests/app/models/not_swapped_in_record.rb +3 -0
- data/test/full_2_3_5_app_with_tests/app/models/person.rb +3 -0
- data/test/full_2_3_5_app_with_tests/app/models/recipe.rb +21 -0
- data/test/full_2_3_5_app_with_tests/app/views/items/edit.html.erb +24 -0
- data/test/full_2_3_5_app_with_tests/app/views/items/index.html.erb +24 -0
- data/test/full_2_3_5_app_with_tests/app/views/items/new.html.erb +23 -0
- data/test/full_2_3_5_app_with_tests/app/views/items/show.html.erb +21 -0
- data/test/full_2_3_5_app_with_tests/app/views/layouts/items.html.erb +19 -0
- data/test/full_2_3_5_app_with_tests/config/boot.rb +110 -0
- data/test/full_2_3_5_app_with_tests/config/database.yml +22 -0
- data/test/full_2_3_5_app_with_tests/config/environment.rb +54 -0
- data/test/full_2_3_5_app_with_tests/config/environments/development.rb +17 -0
- data/test/full_2_3_5_app_with_tests/config/environments/production.rb +28 -0
- data/test/full_2_3_5_app_with_tests/config/environments/test.rb +28 -0
- data/test/full_2_3_5_app_with_tests/config/initializers/backtrace_silencers.rb +7 -0
- data/test/full_2_3_5_app_with_tests/config/initializers/inflections.rb +10 -0
- data/test/full_2_3_5_app_with_tests/config/initializers/mime_types.rb +5 -0
- data/test/full_2_3_5_app_with_tests/config/initializers/new_rails_defaults.rb +21 -0
- data/test/full_2_3_5_app_with_tests/config/initializers/session_store.rb +15 -0
- data/test/full_2_3_5_app_with_tests/config/locales.yml +5 -0
- data/test/full_2_3_5_app_with_tests/config/locales/en.yml +4 -0
- data/test/full_2_3_5_app_with_tests/config/locales/fr.yml +4 -0
- data/test/full_2_3_5_app_with_tests/config/locales/zh.yml +4 -0
- data/test/full_2_3_5_app_with_tests/config/routes.rb +4 -0
- data/test/full_2_3_5_app_with_tests/db/migrate/20100407010602_create_items.rb +14 -0
- data/test/full_2_3_5_app_with_tests/db/migrate/20100504234216_create_not_swapped_in_records.rb +15 -0
- data/test/full_2_3_5_app_with_tests/db/migrate/20100509011052_create_people.rb +13 -0
- data/test/full_2_3_5_app_with_tests/db/migrate/20100509042718_create_comments.rb +14 -0
- data/test/full_2_3_5_app_with_tests/db/migrate/20110112023516_create_recipes.rb +14 -0
- data/test/full_2_3_5_app_with_tests/db/schema.rb +56 -0
- data/test/full_2_3_5_app_with_tests/db/seeds.rb +7 -0
- data/test/full_2_3_5_app_with_tests/doc/README_FOR_APP +2 -0
- data/test/full_2_3_5_app_with_tests/public/404.html +30 -0
- data/test/full_2_3_5_app_with_tests/public/422.html +30 -0
- data/test/full_2_3_5_app_with_tests/public/500.html +30 -0
- data/test/full_2_3_5_app_with_tests/public/favicon.ico +0 -0
- data/test/full_2_3_5_app_with_tests/public/images/rails.png +0 -0
- data/test/full_2_3_5_app_with_tests/public/index.html +275 -0
- data/test/full_2_3_5_app_with_tests/public/javascripts/application.js +2 -0
- data/test/full_2_3_5_app_with_tests/public/javascripts/controls.js +963 -0
- data/test/full_2_3_5_app_with_tests/public/javascripts/dragdrop.js +973 -0
- data/test/full_2_3_5_app_with_tests/public/javascripts/effects.js +1128 -0
- data/test/full_2_3_5_app_with_tests/public/javascripts/prototype.js +4320 -0
- data/test/full_2_3_5_app_with_tests/public/robots.txt +5 -0
- data/test/full_2_3_5_app_with_tests/public/stylesheets/scaffold.css +54 -0
- data/test/full_2_3_5_app_with_tests/script/about +4 -0
- data/test/full_2_3_5_app_with_tests/script/console +3 -0
- data/test/full_2_3_5_app_with_tests/script/dbconsole +3 -0
- data/test/full_2_3_5_app_with_tests/script/destroy +3 -0
- data/test/full_2_3_5_app_with_tests/script/generate +3 -0
- data/test/full_2_3_5_app_with_tests/script/performance/benchmarker +3 -0
- data/test/full_2_3_5_app_with_tests/script/performance/profiler +3 -0
- data/test/full_2_3_5_app_with_tests/script/plugin +3 -0
- data/test/full_2_3_5_app_with_tests/script/runner +3 -0
- data/test/full_2_3_5_app_with_tests/script/server +3 -0
- data/test/full_2_3_5_app_with_tests/test/factories.rb +18 -0
- data/test/full_2_3_5_app_with_tests/test/functional/translations_controller_test.rb +66 -0
- data/test/full_2_3_5_app_with_tests/test/integration/translation_test.rb +72 -0
- data/test/full_2_3_5_app_with_tests/test/performance/browsing_test.rb +9 -0
- data/test/full_2_3_5_app_with_tests/test/selenium.rb +83 -0
- data/test/full_2_3_5_app_with_tests/test/test_helper.rb +86 -0
- data/test/full_2_3_5_app_with_tests/test/unit/helpers/translatables_helper_test.rb +37 -0
- data/test/full_2_3_5_app_with_tests/test/unit/helpers/translations_helper_test.rb +62 -0
- data/test/full_2_3_5_app_with_tests/test/unit/mongo_translatable_test.rb +342 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/init.rb +1 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter.rb +94 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/base.rb +31 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/force_extension.rb +57 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/locale.rb +70 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/pagination.rb +33 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/uuid_token.rb +78 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/force_extension_spec.rb +65 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/generation_spec.rb +367 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/pagination_extension_spec.rb +19 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/recognition_spec.rb +76 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/routing_filter_spec.rb +114 -0
- data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/spec_helper.rb +108 -0
- metadata +309 -0
@@ -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,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
|