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.
- 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
|