phrasing 3.2.10 → 4.0.0rc1
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.
- checksums.yaml +4 -4
- data/.travis.yml +8 -11
- data/CHANGELOG.md +0 -20
- data/Gemfile +4 -1
- data/README-3.md +134 -0
- data/README.md +10 -14
- data/Release_notes_version_4.md +26 -0
- data/app/assets/javascripts/phrasing.js.erb +37 -17
- data/app/assets/stylesheets/phrasing_edit_mode_bubble.css.scss +3 -3
- data/app/controllers/phrasing_phrase_versions_controller.rb +4 -4
- data/app/controllers/phrasing_phrases_controller.rb +42 -75
- data/app/helpers/inline_helper.rb +24 -31
- data/app/models/phrasing_phrase.rb +29 -23
- data/app/views/phrasing/_production_warning.html.haml +0 -2
- data/app/views/phrasing_phrases/edit.html.haml +2 -2
- data/app/views/phrasing_phrases/import_export.html.haml +0 -5
- data/config/routes.rb +11 -9
- data/lib/generators/phrasing/phrasing_generator.rb +26 -0
- data/lib/generators/phrasing/templates/app/helpers/phrasing_helper.rb +12 -0
- data/lib/generators/phrasing/templates/config/initializers/phrasing.rb +11 -0
- data/{db/migrate/20131010101010_create_phrasing_phrase_versions.rb → lib/generators/phrasing/templates/db/migrate/create_phrasing_phrase_versions.rb} +2 -2
- data/{db/migrate/20120313191745_create_phrasing_phrases.rb → lib/generators/phrasing/templates/db/migrate/create_phrasing_phrases.rb} +2 -2
- data/lib/phrasing.rb +11 -37
- data/lib/phrasing/version.rb +2 -2
- data/phrasing.gemspec +1 -3
- data/spec/features/dummy_spec.rb +27 -24
- data/spec/features/phrasing_spec.rb +128 -84
- data/spec/lib/phrasing_spec.rb +50 -50
- metadata +14 -26
- data/4.0.0_changes.md +0 -1
- data/lib/phrasing/implementation.rb +0 -21
- data/lib/phrasing/simple.rb +0 -3
- data/lib/tasks/phrasing_tasks.rake +0 -69
@@ -1,9 +1,9 @@
|
|
1
|
-
class PhrasingPhraseVersionsController <
|
1
|
+
class PhrasingPhraseVersionsController < ActionController::Base
|
2
2
|
|
3
3
|
def destroy
|
4
|
-
|
5
|
-
|
6
|
-
redirect_to edit_phrasing_phrase_path(
|
4
|
+
phrase_version = PhrasingPhraseVersion.find(params[:id])
|
5
|
+
phrase_version.destroy
|
6
|
+
redirect_to edit_phrasing_phrase_path(phrase_version.phrasing_phrase.id)
|
7
7
|
end
|
8
8
|
|
9
9
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class PhrasingPhrasesController <
|
1
|
+
class PhrasingPhrasesController < ActionController::Base
|
2
2
|
|
3
3
|
layout 'phrasing'
|
4
4
|
|
@@ -8,18 +8,21 @@ class PhrasingPhrasesController < Phrasing.parent_controller.constantize
|
|
8
8
|
|
9
9
|
before_filter :authorize_editor
|
10
10
|
|
11
|
+
def import_export; end
|
12
|
+
def help; end
|
13
|
+
|
11
14
|
def index
|
12
15
|
params[:locale] ||= I18n.default_locale
|
13
|
-
query = PhrasingPhrase
|
14
|
-
query = query.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@phrasing_phrases = query.where(key_like.or(value_like))
|
16
|
+
query = PhrasingPhrase.order("phrasing_phrases.key")
|
17
|
+
query = query.where(locale: params[:locale]) if params[:locale].present?
|
18
|
+
|
19
|
+
@phrasing_phrases = if params[:search].present?
|
20
|
+
key_like = PhrasingPhrase.arel_table[:key].matches("%#{params[:search]}%")
|
21
|
+
value_like = PhrasingPhrase.arel_table[:value].matches("%#{params[:search]}%")
|
22
|
+
query.where(key_like.or(value_like))
|
21
23
|
else
|
22
|
-
|
24
|
+
# because we want to have non nil values first.
|
25
|
+
query.where("value is not null") + query.where("value is null")
|
23
26
|
end
|
24
27
|
|
25
28
|
@locale_names = PhrasingPhrase.uniq.pluck(:locale)
|
@@ -30,24 +33,13 @@ class PhrasingPhrasesController < Phrasing.parent_controller.constantize
|
|
30
33
|
end
|
31
34
|
|
32
35
|
def update
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
respond_to do |format|
|
38
|
-
format.html do
|
39
|
-
redirect_to phrasing_phrases_path, notice: "#{@phrasing_phrase.key} updated!"
|
40
|
-
end
|
41
|
-
|
42
|
-
format.js do
|
43
|
-
render json: @phrasing_phrase
|
44
|
-
end
|
36
|
+
if request.xhr?
|
37
|
+
xhr_phrase_update
|
38
|
+
else
|
39
|
+
phrase_update
|
45
40
|
end
|
46
41
|
end
|
47
42
|
|
48
|
-
def import_export
|
49
|
-
end
|
50
|
-
|
51
43
|
def download
|
52
44
|
app_name = Rails.application.class.to_s.split("::").first
|
53
45
|
app_env = Rails.env
|
@@ -58,7 +50,7 @@ class PhrasingPhrasesController < Phrasing.parent_controller.constantize
|
|
58
50
|
def upload
|
59
51
|
number_of_changes = Phrasing::Serializer.import_yaml(params["file"].tempfile)
|
60
52
|
redirect_to phrasing_phrases_path, notice: "YAML file uploaded successfully! Number of phrases changed: #{number_of_changes}."
|
61
|
-
rescue
|
53
|
+
rescue => e
|
62
54
|
logger.info "\n#{e.class}\n#{e.message}"
|
63
55
|
message = if params[:file].nil?
|
64
56
|
"Please choose a file."
|
@@ -71,65 +63,40 @@ class PhrasingPhrasesController < Phrasing.parent_controller.constantize
|
|
71
63
|
end
|
72
64
|
|
73
65
|
def destroy
|
74
|
-
|
75
|
-
|
76
|
-
redirect_to phrasing_phrases_path, notice: "#{
|
66
|
+
phrasing_phrase = PhrasingPhrase.find(params[:id])
|
67
|
+
phrasing_phrase.destroy
|
68
|
+
redirect_to phrasing_phrases_path, notice: "#{phrasing_phrase.key} deleted!"
|
77
69
|
end
|
78
70
|
|
79
|
-
|
80
|
-
end
|
71
|
+
private
|
81
72
|
|
82
|
-
|
83
|
-
|
84
|
-
redirect_to :back, alert: "You didn't set your source server"
|
85
|
-
else
|
86
|
-
yaml = read_remote_yaml(Phrasing.staging_server_endpoint)
|
87
|
-
|
88
|
-
if yaml
|
89
|
-
Phrasing::Serializer.import_yaml(yaml)
|
90
|
-
redirect_to :back, notice: "Translations synced from source server"
|
91
|
-
else
|
92
|
-
redirect_to :back
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def remote_update_phrase
|
99
|
-
klass, attribute = params[:klass], params[:attribute]
|
100
|
-
|
101
|
-
if Phrasing.is_whitelisted?(klass, attribute)
|
102
|
-
class_object = klass.classify.constantize
|
103
|
-
@object = class_object.where(id: params[:id]).first
|
104
|
-
@object.send("#{attribute}=",params[:new_value])
|
105
|
-
@object.save!
|
106
|
-
render json: @object
|
107
|
-
else
|
108
|
-
render status: 403, text: "Attribute not whitelisted!"
|
73
|
+
def authorize_editor
|
74
|
+
redirect_to root_path unless can_edit_phrases?
|
109
75
|
end
|
110
76
|
|
111
|
-
|
112
|
-
|
113
|
-
end
|
77
|
+
def xhr_phrase_update
|
78
|
+
klass, attribute = params[:klass], params[:attribute]
|
114
79
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
rescue Exception => e
|
124
|
-
logger.fatal e
|
125
|
-
flash[:alert] = "Syncing failed: #{e}"
|
80
|
+
if Phrasing.is_whitelisted?(klass, attribute)
|
81
|
+
class_object = klass.classify.constantize
|
82
|
+
@object = class_object.where(id: params[:id]).first
|
83
|
+
@object.send("#{attribute}=",params[:new_value])
|
84
|
+
@object.save!
|
85
|
+
render json: @object
|
86
|
+
else
|
87
|
+
render status: 403, text: "Attribute not whitelisted!"
|
126
88
|
end
|
127
|
-
|
89
|
+
|
90
|
+
rescue ActiveRecord::RecordInvalid => e
|
91
|
+
render status: 403, text: e
|
128
92
|
end
|
129
93
|
|
94
|
+
def phrase_update
|
95
|
+
phrase = PhrasingPhrase.find(params[:id])
|
96
|
+
phrase.value = params[:phrasing_phrase][:value]
|
97
|
+
phrase.save!
|
130
98
|
|
131
|
-
|
132
|
-
redirect_to root_path unless can_edit_phrases?
|
99
|
+
redirect_to phrasing_phrases_path, notice: "#{phrase.key} updated!"
|
133
100
|
end
|
134
101
|
|
135
102
|
end
|
@@ -3,58 +3,51 @@ module InlineHelper
|
|
3
3
|
# phrase("headline", url: www.infinum.co/yabadaba, inverse: true, interpolation: {min: 15, max: 20}, scope: "models.errors")
|
4
4
|
|
5
5
|
# Data model phrase
|
6
|
-
# phrase(
|
6
|
+
# phrase(record, :title, inverse: true, class: phrase-record-title)
|
7
7
|
|
8
8
|
def phrase(*args)
|
9
9
|
if args[0].class == String or args[0].class == Symbol
|
10
10
|
key, options = args[0].to_s, args[1]
|
11
11
|
phrasing_phrase(key,options || {})
|
12
12
|
else
|
13
|
-
record,
|
14
|
-
inline(record,
|
13
|
+
record, attribute, options = args[0], args[1], args[2]
|
14
|
+
inline(record, attribute, options || {})
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
return record.send(field_name).to_s.html_safe unless can_edit_phrases?
|
18
|
+
private
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
klass += ' inverse' if options[:inverse]
|
24
|
-
klass += options[:class] if options[:class]
|
20
|
+
def inline(record, attribute, options={})
|
21
|
+
return uneditable_phrase(record, attribute, options) unless can_edit_phrases?
|
25
22
|
|
26
|
-
|
23
|
+
klass = 'phrasable phrasable_on'
|
24
|
+
klass += ' inverse' if options[:inverse]
|
25
|
+
klass += options[:class] if options[:class]
|
27
26
|
|
28
|
-
|
29
|
-
(record.send(field_name) || record.try(:key)).to_s.html_safe
|
30
|
-
end
|
31
|
-
end
|
27
|
+
url = phrasing_polymorphic_url(record, attribute)
|
32
28
|
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
content_tag(:span, { class: klass, contenteditable: true, spellcheck: false, "data-url" => url }) do
|
30
|
+
(record.send(attribute) || record.try(:key)).to_s.html_safe
|
31
|
+
end
|
32
|
+
end
|
36
33
|
|
37
34
|
def phrasing_phrase(key, options = {})
|
38
|
-
key = options[:scope] ? "#{options[:scope]}.#{key}" : key
|
39
|
-
|
40
|
-
|
41
|
-
inline(@record, :value, options)
|
42
|
-
else
|
43
|
-
options.try(:[], :interpolation) ? t(key, options[:interpolation]).html_safe : t(key).html_safe
|
44
|
-
end
|
35
|
+
key = options[:scope] ? "#{options[:scope]}.#{key}" : key
|
36
|
+
record = PhrasingPhrase.find_phrase(key)
|
37
|
+
inline(record, :value, options)
|
45
38
|
end
|
46
39
|
|
47
|
-
def
|
48
|
-
if
|
49
|
-
|
50
|
-
true
|
40
|
+
def uneditable_phrase(record, attribute, options={})
|
41
|
+
record_value = if options[:interpolation]
|
42
|
+
I18n.interpolate(record.send(attribute), options[:interpolation])
|
51
43
|
else
|
52
|
-
|
44
|
+
record.send(attribute)
|
53
45
|
end
|
46
|
+
record_value.to_s.html_safe
|
54
47
|
end
|
55
48
|
|
56
49
|
def phrasing_polymorphic_url(record, attribute)
|
57
|
-
|
50
|
+
phrasing_phrase_path(klass: record.class.to_s, id: record.id, attribute: attribute)
|
58
51
|
end
|
59
52
|
|
60
|
-
end
|
53
|
+
end
|
@@ -1,39 +1,45 @@
|
|
1
1
|
class PhrasingPhrase < ActiveRecord::Base
|
2
2
|
|
3
3
|
validates_presence_of :key, :locale
|
4
|
+
# validate :uniqueness_of_key_on_locale_scope, on: :create
|
5
|
+
validates_uniqueness_of :key, scope: [:locale]
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
has_many :versions, dependent: :destroy, class_name: "PhrasingPhraseVersion"
|
7
|
+
has_many :versions, dependent: :destroy, class_name: PhrasingPhraseVersion
|
8
8
|
|
9
9
|
after_update :version_it
|
10
10
|
|
11
|
-
def self.
|
12
|
-
|
13
|
-
value = I18n.t key, raise: true
|
14
|
-
PhrasingPhrase.where(key: key, locale: I18n.locale).first
|
15
|
-
rescue I18n::MissingTranslationData
|
16
|
-
create_phrase(key)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.create_phrase key, value = nil
|
21
|
-
phrasing_phrase = PhrasingPhrase.new
|
22
|
-
phrasing_phrase.locale = I18n.locale.to_s
|
23
|
-
phrasing_phrase.key = key.to_s
|
24
|
-
phrasing_phrase.value = value || key.to_s
|
25
|
-
phrasing_phrase.save
|
26
|
-
phrasing_phrase
|
11
|
+
def self.find_phrase(key)
|
12
|
+
where(key: key, locale: I18n.locale.to_s).first || search_i18n_and_create_phrase(key)
|
27
13
|
end
|
28
14
|
|
29
15
|
private
|
30
16
|
|
17
|
+
def self.search_i18n_and_create_phrase(key)
|
18
|
+
begin
|
19
|
+
value = I18n.t(key, raise: true)
|
20
|
+
create_phrase(key, value)
|
21
|
+
rescue I18n::MissingTranslationData
|
22
|
+
create_phrase(key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create_phrase(key, value=nil)
|
27
|
+
phrasing_phrase = PhrasingPhrase.new
|
28
|
+
phrasing_phrase.locale = I18n.locale.to_s
|
29
|
+
phrasing_phrase.key = key.to_s
|
30
|
+
phrasing_phrase.value = value || key.to_s
|
31
|
+
phrasing_phrase.save
|
32
|
+
phrasing_phrase
|
33
|
+
end
|
34
|
+
|
31
35
|
def uniqueness_of_key_on_locale_scope
|
32
|
-
|
36
|
+
if PhrasingPhrase.where(key: key, locale: locale).any?
|
37
|
+
errors.add(:key, "Duplicate entry #{key} for locale #{locale}")
|
38
|
+
end
|
33
39
|
end
|
34
40
|
|
35
41
|
def version_it
|
36
|
-
PhrasingPhraseVersion.create_version(id,
|
42
|
+
PhrasingPhraseVersion.create_version(id, value_was) if value_was != value
|
37
43
|
end
|
38
|
-
|
39
|
-
end
|
44
|
+
|
45
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
.edit
|
2
2
|
%h2= @phrasing_phrase.key
|
3
|
-
= link_to "Delete Phrase", phrasing_phrase_path(@phrasing_phrase), class: "btn btn-danger delete-phrase", method: :delete,
|
3
|
+
= link_to "Delete Phrase", phrasing_phrase_path(@phrasing_phrase), class: "btn btn-danger delete-phrase", method: :delete, onclick: "return confirm('Are you sure?');"
|
4
4
|
= form_for @phrasing_phrase, url: { action: "update" } do |f|
|
5
5
|
= f.text_area :value, rows: 12
|
6
6
|
= f.submit "Update", class: "btn btn-success submit-edit-phrase"
|
@@ -20,4 +20,4 @@
|
|
20
20
|
%td.phrasing-version-value= version.value.html_safe
|
21
21
|
%td.phrasing-version-created_at= version.created_at.strftime("%d-%m-%Y %H:%M:%S")
|
22
22
|
%td= link_to "Revert", phrasing_phrase_path(@phrasing_phrase.id, phrasing_phrase: {value: version.value}), class: "btn btn-success", method: :put
|
23
|
-
%td= link_to "Delete", phrasing_phrase_version_path(version.id), class: "btn btn-danger", method: :delete,
|
23
|
+
%td= link_to "Delete", phrasing_phrase_version_path(version.id), class: "btn btn-danger", method: :delete, onclick: "return confirm('Are you sure?');"
|
@@ -8,11 +8,6 @@
|
|
8
8
|
= file_field_tag "file"
|
9
9
|
= submit_tag "Upload"
|
10
10
|
|
11
|
-
- unless Phrasing.staging_server_endpoint.nil? || Rails.env.staging?
|
12
|
-
%h2 Staging syncing
|
13
|
-
%div
|
14
|
-
= link_to "Sync from staging server", sync_phrasing_phrases_path
|
15
|
-
|
16
11
|
%h2 Export
|
17
12
|
%div
|
18
13
|
= link_to "Download as YAML", download_phrasing_phrases_path
|
data/config/routes.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
-
resources Phrasing.route,
|
2
|
+
resources Phrasing.route,
|
3
|
+
as: :phrasing_phrases,
|
4
|
+
controller: :phrasing_phrases,
|
5
|
+
only: [:index, :edit, :update, :destroy] do
|
3
6
|
collection do
|
4
|
-
get
|
5
|
-
get
|
6
|
-
get
|
7
|
-
|
8
|
-
post 'upload'
|
9
|
-
put 'remote_update_phrase'
|
7
|
+
get :help
|
8
|
+
get :import_export
|
9
|
+
get :download
|
10
|
+
post :upload
|
10
11
|
end
|
11
12
|
end
|
12
|
-
|
13
|
-
|
13
|
+
|
14
|
+
resources :phrasing_phrase_versions, only: :destroy
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class PhrasingGenerator < Rails::Generators::Base
|
2
|
+
include Rails::Generators::Migration
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def create_initializer_file
|
6
|
+
initializer_location = "config/initializers/phrasing.rb"
|
7
|
+
copy_file initializer_location, initializer_location
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_helper_file
|
11
|
+
helper_location = "app/helpers/phrasing_helper.rb"
|
12
|
+
copy_file helper_location, helper_location
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_migrations
|
16
|
+
phrasing_phrase_migration = "db/migrate/create_phrasing_phrases.rb"
|
17
|
+
migration_template phrasing_phrase_migration, phrasing_phrase_migration
|
18
|
+
phrase_versions_migration = "db/migrate/create_phrasing_phrase_versions.rb"
|
19
|
+
migration_template phrase_versions_migration, phrase_versions_migration
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.next_migration_number(path)
|
23
|
+
sleep 1 # migration numbers should differentiate
|
24
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module PhrasingHelper
|
2
|
+
# You must implement the can_edit_phrases? method.
|
3
|
+
# Example:
|
4
|
+
#
|
5
|
+
# def can_edit_phrases?
|
6
|
+
# current_user.is_admin?
|
7
|
+
# end
|
8
|
+
|
9
|
+
def can_edit_phrases?
|
10
|
+
raise NotImplementedError.new("You must implement the can_edit_phrases? method")
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Phrasing.setup do |config|
|
2
|
+
config.route = 'phrasing'
|
3
|
+
|
4
|
+
# List all the model attributes you wish to edit with Phrasing, example:
|
5
|
+
# config.whitelist = ["Post.title", "Post.description"]
|
6
|
+
config.whitelist = []
|
7
|
+
|
8
|
+
# You can whitelist all models, but it's not recommended.
|
9
|
+
# Read here: https://github.com/infinum/phrasing#security
|
10
|
+
config.allow_update_on_all_models_and_attributes = false
|
11
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
class CreatePhrasingPhraseVersions < ActiveRecord::Migration
|
2
2
|
def change
|
3
|
-
create_table :phrasing_phrase_versions do |t|
|
3
|
+
create_table :phrasing_phrase_versions do |t|
|
4
4
|
t.integer :phrasing_phrase_id
|
5
5
|
t.text :value
|
6
6
|
t.timestamps
|
7
7
|
end
|
8
8
|
add_index :phrasing_phrase_versions, :phrasing_phrase_id
|
9
9
|
end
|
10
|
-
end
|
10
|
+
end
|